Benutzer-Werkzeuge

Webseiten-Werkzeuge


scripting:tutorials:level2:npcs

NPCs

Grundsätzlich sind alle Nichtspielercharaktere (Non-Player-Characters) NPCs. Im Kontext von Siedler 5 sind allerdings speziell jene Charaktere gemeint, die dem Spieler über Helden Interaktionen anbieten, gekennzeichnet durch ein großes Ausrufezeichen über dem Kopf.

Theoretisch kann bei der Interaktion mit einem NPC jede beliebige Funktion ausgelöst werden. In der Regel ist das aber ein Briefing, weshalb wir diesen Fall zuerst anschauen.

NPCs werden mit der Funktion CreateNPC(_NpcDescription) erstellt. Die _NpcDescription ist ein Table, das einige notwendige Informationen enthalten muss und optionale Einstellungen darüber hinaus erlaubt. In der untenstehenden Tabelle sind alle Keys, die man angeben kann, aufgelistet. Im Anschluss werden wir sie nacheinander erklären.

Key Bedeutung Erforderlich?
name Skriptname des NPCs (kann im Editor gesetzt werden) Ja
callback Funktion, die bei der Interaktion mit dem NPC aufgerufen wird Ja
briefing Briefing, das bei der Interaktion mit dem NPC gestartet wird Nur, wenn kein callback angegeben wurde
heroName Wenn der NPC nur mit einem bestimmten Helden interagieren soll, wird hier dessen Skriptname eingetragen
(Standardmäßig kann mit allen Helden interagiert werden)
Nein
wrongHeroMessage Nachricht, die angezeigt wird, wenn der NPC mit dem falschen Helden angesprochen wird
(alle außer derjenige, der mit heroName vorgeschrieben wird)
Nein, sollte bei der Angabe von heroName aber ebenfalls angegeben werden
follow Kann entweder true oder ein Skriptname sein. Der NPC folgt bei true dem nächsten Helden, sonst der angegebenen Entity Nein
vanishPos Nach der Interaktion geht der NPC zur angegebenen Position und verschwindet, sobald er aus der Sichtweite des Spielers gerät Nein

Tatsächlich kann man einer _NpcDescription beliebige Key-Value-Paare geben. In der callback-Funktion kann dann darauf zugegriffen werden (siehe auch das Beispiel unten).

Hinweis: Wir werden im Folgenden nur mit der callback-Option arbeiten und die briefing-Angabe außen vor lassen. Das hat den Grund, dass der Weg über das callback deutlich übersichtlicher und flexibler ist.

Achtung: Die Entity, die bei name angegeben wird, muss eine Siedler-Einheit sein (also Präfix PU oder CU)! Andernfalls wird das Spiel abstürzen.

Der einfachste Fall ist ein NPC, der einfach nur ein Gespräch über ein Briefing anbietet:

-- Wir schreiben jeweils eine Funktion für die Erstellung des NPCs und eine für das Callback, in dem
-- Fall den Start des Briefings
function CreateNpcScout()
    local Npc = {
        -- Auf der Karte muss eine Einheit (am besten ein Kundschafter) mit dem Namen "Scout" existieren
        name = "Scout",
        -- Der Callback ist einfach nur der Name der Funktion
        callback = CreateBriefingScout
    }
 
    CreateNPC(Npc)
end
 
function CreateBriefingScout()
    local Briefing = {
        {
            title = "Kundschafter",
            text = "Alles ruhig hier an den Grenzen, mein Herr!",
            position = GetPosition("Scout")
        }
    }
 
    StartBriefing(Briefing)
end


Interaktion mit einem bestimmten Helden

Möglicherweise wollen wir den Kundschafter nur mit Dario sprechen lassen. Dafür muss die Dario-Entity den Skriptnamen „Dario“ besitzen:

-- Wir schreiben jeweils eine Funktion für die Erstellung des NPCs und eine für das Callback, in dem
-- Fall den Start des Briefings
function CreateNpcScout()
    -- Zur Anschauung die äquivalente, verkürzte Schreibweise
    CreateNPC{
        name = "Scout",
        callback = CreateBriefingScout,
        heroName = "Dario",
        wrongHeroMessage = "Meine Informationen übergebe ich nur Dario persönlich!"
    }
end
 
function CreateBriefingScout()
    local Briefing = {
        {
            title = "Kundschafter",
            text = "König Dario, hier ist immer noch alles ruhig.",
            position = GetPosition("Scout")
        }
    }
 
    StartBriefing(Briefing)
end


Zusätzliche Parameter der Callback-Funktion

Im vorigen Beispiel haben wir gesehen, dass es ganz schön sein kann, wenn der NPC den Helden, mit dem er angesprochen wird, direkt beim Namen nennen kann bzw. generell der Heldenname im Callback verfügbar ist.

Tatsächlich werden sowohl die _NpcDescription als auch die Entity-Id des interagierenden Helden an das Callback übergeben. Über die Entity-Id wiederum lässt sich der Skriptname des Helden ermitteln. Nimmt man wie in den obigen Beispielen diese Parameter in der Funktion nicht an, werden sie einfach ignoriert.

In diesem Beispiel haben wir zuvor auf der Karte Dario mit dem Skriptnamen „Dario“ und Ari mit dem Skriptnamen „Ari“ auf der Karte platziert.

-- An der Erstellung des NPCs ändert sich nichts
function CreateNpcScout()
    CreateNPC{
        name = "Scout",
        callback = CreateBriefingScout
    }
end
 
function CreateBriefingScout(_NpcDescription, _HeroId)
    -- Mit der Funktion GetEntityName kann der Skriptname einer Entity anhand der Entity-Id
    -- ermittelt werden
    -- Weil der Skriptname genau dem Anzeigenamen der Helden entspricht, bekommen wir so den
    -- korrekten Namen für jeden Helden
    -- Hätten wir Dario den Skriptnamen "Karl" gegeben, würden wir hier stattdessen auch "Karl" erhalten
    local HeroName = GetEntityName(_HeroId)
 
    -- Mit dieser Information können wir nun festlegen, wie der Kundschafter den Helden anspricht
    local Address = "Herr"
    if HeroName == "Ari" then
        Address = "Herrin"
    end
 
    local Briefing = {
        {
            title = "Kundschafter",
            text = Address..", hier ist weiterhin alles ruhig.",
            position = GetPosition("Scout")
        },
 
        {
            -- Der Name des Helden kann hier benutzt werden, um den Titel und die Position der 
            -- Briefingseite korrekt zu setzen
            title = HeroName,
            text = "Sehr gut, weitermachen!",
            position = GetPosition(HeroName)
        }
    }
 
    StartBriefing(Briefing)
end


Weitere Parameter

Im nächsten Beispiel wollen wir weitere Parameter für NPCs demonstrieren und zeigen, dass auf eine NPC-Interaktion nicht notwendigerweise ein Briefing folgen muss. Außerdem zeigen wir, dass einem NPC noch weitere, frei wählbare Werte gegeben werden können, auf die in der NPC-Interaktion zugegriffen werden kann.

Hierfür haben wir Drake (Skriptname „Drake“) und vier Schafe (Skriptnamen „Sheep1“ bis „Sheep4“) auf der Karte platziert.

function SetupSheep()
    -- Alle 4 Schafe sollen zum NPC werden
    for i = 1, 4 do
        CreateNpcSheep(i)
    end
end
 
function CreateNpcSheep(_Number)
    local Npc = {
        -- Weil wir die Namen der Schafe durchnummeriert haben, können wir hier an das Präfix "Sheep" einfach
        -- die Zahl anhängen
        name = "Sheep".._Number,
        -- Zusätzlicher Wert: Wir merken uns die Nummer des Schafs. Das wird im Callback wichtig
        number = _Number,
        -- Alle Schafe benutzen das gleiche Callback!
        callback = SheepInteraction,
        -- Alle Schafe sollen Drake folgen
        follow = "Drake"
    }
 
    CreateNPC(Npc)
end
 
function SheepInteraction(_NpcDescription, _HeroId)
    -- Sobald Drake mit einem der Schafe interagiert, soll es einfach nur "Määh" machen
    Sound.PlayGUISound(Sounds.AmbientSounds_Sheep_rnd_1)
    -- Damit das "angesprochene" Schaf Drake weiterhin folgt, muss der Schaf-NPC erneut erstellt werden
    -- Dazu benutzen wir die oben definierte Funktion CreateNpcSheep und geben als Parameter die Nummer
    -- an, die wir uns beim ersten Erstellen des Schaf-NPCs gemerkt haben
    CreateNpcSheep(_NpcDescription.number)
end

Funktionen für NPCs

Es gibt einige Funktionen, die für NPCs benutzt werden können, die im Folgenden kurz erklärt sind.

Einen NPC deaktivieren

Mit der Funktion DestroyNPC(_NpcDescription) kann ein zuvor erstellter NPC wieder „zerstört“ werden. Dabei wird nicht die Entity gelöscht, sondern nur die Interaktivität deaktiviert (also das Ausrufezeichen entfernt).

Technisch gesehen arbeitet DestroyNPC mit einer _NpcDescription. Für die Funktion relevant ist allerdings nur der Name, sodass man die NPC-Beschreibung verkürzt angeben kann. Will man beispielsweise Drake aus seiner persönlichen Schaf-Hölle befreien, reicht der Aufruf

for i = 1, 4 do
    DestroyNPC{name = "Sheep"..i}
end


Wegpunkte für NPCs

Wir können NPCs wie den Kundschafter oben zwischen verschiedenen Wegpunkten umherwandern lassen. Dazu müssen auf der Karte Entities platziert werden, deren Skriptnamen einem durchnummerierten Schema folgen.

Zum Beispiel können wir dem Kundschafter oben Wegpunkte geben, indem wir einige XD_ScriptEntitys mit den Skriptnamen „ScoutWaypoint1“ bis „ScoutWaypoint5“ auf der Karte platzieren. Es kann zwar prinzipiell jede Entity als Wegpunkt dienen, zerstörte Entities wie gefällte Bäume oder eingerissene Gebäude können aber für Fehler sorgen. Die Nummerierung muss bei 1 beginnen und darf keine Lücken haben.

Zuerst muss der NPC mit CreateNPC erstellt werden. Dann kann mit

SetNPCWaypoints("Scout", "ScoutWaypoint", 30)

alle Wegpunkte, deren Namen mit „ScoutWaypoint“ beginnt als Wegpunkte für den Kundschafter festgelegt werden. Die 30 ist die Zeit in Sekunden die gewartet wird, bevor der NPC einen weiteren Wegpunkt ansteuert. Wird die Zeit zu gering gewählt, wird der NPC oft einen der Wegpunkte nicht erreichen, sondern auf halbem Weg abbrechen und einen neuen Wegpunkt ansteuern. Ist die Zeit sehr hoch gewählt, wird der NPC bei jedem Wegpunkt eine zeitlang verweilen.

Die Wegpunkte werden nicht der Reihe nach angesteuert. Es wird (in diesem Fall alle 30 Sekunden) ein Wegpunkt ausgewählt, zu dem der NPC hinläuft. Bei dieser Zufallsauswahl kann der nächste Wegpunkt auch der sein, an dem er sich bereits befindet. Der NPC wird also an diesem Punkt etwas länger stehenbleiben.

Wird ein Zielpunkt während des Spiels zerstört, so wird er nicht automatisch aus der Liste entfernt. Die so entstandene „Lücke“ in den durchnummerierten Wegpunkten sorgt dafür, dass alle Wegpunkte, deren Nummerierung nach der der zerstörten Entity kommt, nicht mehr besucht werden können.

Dem NPC kann zu jedem Zeitpunkt neue Wegpunkte zuweisen. Außerdem verweilt der NPC automatisch an einem Wegpunkt, wenn ein Held in der Nähe ist, was das Ansprechen erleichtert. Er bleibt allerdings nicht automatisch stehen, wenn er einen Held auf seinem Weg trifft.


NPC-Status abfragen

Um zu prüfen, ob mit einem NPC bereits gesprochen wurde, wird die Funktion TalkedToNPC(_NpcDescription) benutzt. Wie auch DestroyNPC arbeitet diese Funktion nur mit dem Namen der NPC-Beschreibung. Deshalb ist folgender Aufruf ausreichend:

TalkedToScout = TalkedToNPC{name = "Scout"}

NPCs können nun mit Helden sprechen und ihnen Aufträge erteilen. Wie die Erfüllung dieser Aufträge ermittelt wird, wird im nächsten Kapitel gezeigt.

Nächstes Kapitel: Orte erreichen, Gebäude bauen, Gegner besiegen
Zurück nach oben

scripting/tutorials/level2/npcs.txt · Zuletzt geändert: 2023/11/11 11:53 von fritz_98