Inhaltsverzeichnis

Häufige Fehler

Wahrscheinlich wird dein Skript in seiner ersten Version Fehler enthalten. Damit du diese Fehler schneller einordnen und beheben kannst, sind hier Fehler gelistet, die häufig entstehen. Bei jedem Fehlertyp steht auch beschrieben, wodurch er verursacht werden könnte und wie man ihn behebt.

Syntaxfehler

Syntaxfehler sind Fehler, bei denen dein Skript gegen die „Grammatik“ der Skriptsprache verstößt. Beispielsweise ist das Schreiben einen öffnenden Klammer und Auslassen einer schließenden Klammer ein Syntaxfehler. Die Fehlermeldungen im Debugger geben dir an, welche Grammatikregel nicht eingehalten wurde und in welcher Zeile der Fehler passiert ist. Auch werden Syntaxfehler in Visual Studio Code rot markiert, sodass sie leicht zu finden sind.

Einige Beispiele:

Das schließende Anführungszeichen fehlt

Message("Hallo Welt!)

Die Fehlermeldung:

unfinished string near `"Hallo Welt!)'

Falsch verwendete Schlüsselwörter

if IsDead("Player2") do
    Victory()
end

Die Fehlermeldung:

`then' expected near `do'

Komma im Table vergessen

local Briefing = {
    {
        title = "Mentor",
        text = "Willkommen, mächtiger Anführer. Euer Mentor bin ich.",
        position = GetPosition("Dario")
    }
 
    {
        title = "Mentor",
        text = "Herr, mir scheint, Ihr habt da ein Komma vergessen!",
        position = GetPosition("Dario")
    }
}

Die Fehlermeldung:

expected (to close `{' at line 833) near `{'

Achte auf die Zeilenangabe im Debugger und die roten Markierungen in VSC!


Groß-/Kleinschreibung

Für Variablennamen wird zwischen Groß- und Kleinschreibung unterschieden. Sich da zu vertippen, wird Fehler verursachen. Zum Beispiel:

function FirstMapAction()
    MyCounter = 1
    IncreaseMyCounter()
end
 
function IncreaseMyCounter()
    MyCounter = Mycounter + 1
end

Die Fehlermeldung sieht dabei so aus:

attempt to perform arithmetic on global `Mycounter' (a nil value)

Sie drückt damit aus, dass versucht wurde mit einer globalen Variable namens Mycounter zu rechnen, die nicht existiert (also den Wert nil hat).

Etwas Ähnliches kann dir auch mit Funktionsnamen passieren:

function FirstMapAction()
    MyCounter = 1
    IncreaseMyCounter()
end
 
function InCreaseMyCounter()
    MyCounter = MyCounter + 1
end

Die Fehlermeldung:

attempt to call global `IncreaseMyCounter' (a nil value)

Also wurde in dem Fall versucht, eine nicht existierende Funktion aufzurufen.

Derartige Fehler werden in Visual Studio Code gelb unterstrichen. Wenn du mit der Maus über eine gelb hervorgehobene Variable fährst, sollte Undefined global angezeigt werden.

Spezialfall: SimpleJobs

In SimpleJobs, wie beispielsweise für Sieg- und Niederlagebedingungen, wird der Funktionsname als String angegeben. Wenn angegebener Job-Name und Funktionsname nicht gleich sind, ist das Resultat, dass der Job ohne Fehlermeldung nicht ausgeführt wird! Achte also insbesondere bei SimpleJobs darauf, dass die angegebenen Namen übereinstimmen, weil weder VSC noch der Debugger dich darauf hinweisen werden.


Copy & Paste

Fehler, die durch Copy & Paste entstehen, kommen selbst bei erfahrenen Programmierern immer wieder vor. Sie haben dabei nicht notwendigerweise eine Fehlermeldung zur Folge, sondern resultieren in unerwartetem Verhalten des Spiels. Oft vergisst man, notwendige Änderungen am kopierten Code vorzunehmen.

Angenommen, du hast wie im Tutorial beschrieben, eine VictoryCondition definiert:

function VictoryCondition()
    if IsDead("Player2") then
        Victory()
        return true
    end
end

Weil die DefeatCondition dazu sehr ähnlich ist, kopierst du die Funktion und machst entsprechende Änderungen:

function DefeatCondition()
    if IsDead("Player2") then
        Defeat()
        return true
    end
end

Im Spiel hätte das zur Folge, dass der Spieler nicht verliert, wenn sein Haupthaus fällt. Außerdem kann es passieren, dass die Niederlagemeldung angezeigt wird, wenn er das feindliche Haupthaus zerstört hat.

Anderes Beispiel: Du möchtest zwei gleich starke KI-Spieler für Spieler 2 und Spieler 3 definieren:

MapEditor_SetupAI(2, 2, 32000, 3, "Player2", 2, 0)
MapEditor_SetupAI(2, 2, 32000, 3, "Player3", 2, 0)

In diesem Beispiel wird Spieler 3 nichts machen, weil vergessen wurde, den ersten Parameter mit der Spieler-Id zu ändern. Die Funktion prüft, ob an der angegebenen Position ein Gebäude des angegebenen Spielers steht. Ist das nicht der Fall, bricht die Funktion ab.

Fehler durch Copy & Paste können vermieden werden, indem weniger Code dupliziert wird. Überlege dir, wie du dein Skript formulieren kannst, ohne dich allzu oft zu wiederholen.


Variablennamen und Strings

Es kann zu Beginn schwer fallen, Variablennamen und Strings zu unterscheiden, weil beide als Zeichenkette geschrieben werden. Die Unterscheidung ist aber wichtig, um Fehler zu vermeiden und Code von Anfang an korrekt zu strukturieren.

Beispielsweise kannst du für jeden einzelnen KI-Spieler eine Funktion definieren, in der die Parameter für diesen KI-Spieler definiert werden:

function CreatePlayer2()
    -- ...
end
function CreatePlayer3()
    -- ...
end
function CreatePlayer4()
    -- ...
end

So aufgebaut ist es können die Funktionen nicht in einer Schleife aufgerufen werden, also z.B:

for i = 2, 4 do
    CreatePlayer .. i()
end

Die CreatePlayer-Funktionsnamen sind Variablennamen und keine Strings, weshalb auch keine String-Operationen auf ihnen durchgeführt werden können. Der Beispielcode oben resultiert in einem Syntaxfehler.


Funktionsname und Funktionsaufruf

An verschiedenen Stellen ist es notwendig, Funktionen anzugeben, die beispielsweise als Reaktion auf ein bestimmtes Ereignis ausgeführt werden sollen. Ein häufig auftretendes Anwendungsbeispiel hierfür ist der finished Parameter in einem Briefing. Die angegebene Funktion wird am Ende des Briefings aufgerufen.

Die Zuweisung einer solchen Funktion darf nicht verwechselt werden mit dem Aufruf einer Funktion, der über sich öffnende und schließende Klammern definiert ist. Ein Beispiel:

-- Wir definieren eine Funktion, die eine Entity mit dem Skriptnamen "Dario" zu
-- einer Entity mit dem Skriptnamen "Player1" laufen lässt. Sinnvollerweise soll bei
-- diesem Beispiel "Dario" ein PU_Hero1c und "Player1" ein PB_Headquarters1 sein
function MoveDarioToHQ()
    Move("Dario", GetPosition("Player1"))
end

Vergleiche nun die folgenden beiden Briefings:

-- Variante 1
function CreateBriefingMentor()
    local Briefing = {
        {
            title = "Mentor",
            text = "Dario, bitte begebt Euch umgehend zu Eurem Haupthaus!",
            position = GetPosition("Dario")
        },
 
        finished = MoveDarioToHQ
    }
    StartBriefing(Briefing)
end

und

-- Variante 2
function CreateBriefingMentor()
    local Briefing = {
        {
            title = "Mentor",
            text = "Dario, bitte begebt Euch umgehend zu Eurem Haupthaus!",
            position = GetPosition("Dario")
        },
 
        finished = MoveDarioToHQ()
    }
    StartBriefing(Briefing)
end

In Variante 1 wird Dario nach dem Briefing zu seinem Haupthaus gehen. In Variante 2 wird Dario direkt zu Beginn des Briefings zum Haupthaus gehen. Das liegt daran, dass im finished-Parameter die Funktion MoveDarioToHQ aufgerufen wird. Der Rückgabewert der Funktion wird dann in finished eingetragen. Da in diesem Fall die Funktion keinen Wert zurückgibt, ist finished nil.


Funktion nicht aufgerufen

Achte darauf, dass du definierte Funktionen auch an der korrekten Stelle aufrufst. Andernfalls kann der Questablauf unterbrochen werden, weil dafür notwendige Aufrufe nicht stattgefunden haben. Unbenutzte Funktionen werden in VSC nicht angezeigt.

Tipp: Unbenutzte lokale Variablen werden in VSC blasser dargestellt und sind daher leicht zu finden.


SimpleJob nicht beendet

SimpleJobs werden ab ihrem Start so lange jede Sekunde ausgeführt, bis sie beendet werden. Meistens bewerkstelligt das man mit return true in der SimpleJob-Funktion, sobald eine Bedingung erfüllt ist. Lässt man das Beenden des Jobs aus, kann das zu unerwünschtem Verhalten führen.

Ein Beispiel: Sobald Dario sich seiner Burg nähert, soll ein Briefing abgespielt werden:

function FirstMapAction()
    StartSimpleJob("IsDarioNearHQ")
end
 
-- Das Setup hier ist das gleiche wie beim Beispiel oben zu Funktionsnamen und Funktionsaufrufen
function IsDarioNearHQ()
    -- Die Funktion IsNear prüft, ob die beiden Entities höchstens die angegebene Distanz zueinander haben
    -- Der SimpleJob fragt also jede Sekunde, ob "Dario" höchstens einen Abstand von 800 Scm zu "Player1" hat
    if IsNear("Dario", "Player1", 800) then
        -- Ist das der Fall, soll ein Briefing gestartet werden
        CreateBriefingDario()
    end
end

In diesem Beispiel wird das Briefing gestartet, sobald sich Dario der Burg nähert. Dadurch, dass der Job nie mit return true beendet wird, wird das Briefing jede Sekunde gestartet, in der Dario in der Nähe der Burg ist. Da man während Briefings keine Einheiten steuern kann, wird das dazu führen, dass das Spiel an dieser Stelle feststeckt.


Parameter verwechselt

Jedes mal, wenn du Argumente an eine Funktion übergibst, solltest du dir klar machen, in welcher Reihenfolge diese zu übergeben sind. Wenn du Visual Studio mit der S5-Doku korrekt aufgesetzt hast, wird für die meisten Funktionen ein Hilfetext angezeigt.

Leider sind die Parameter in Siedler 5 - Funktionen nicht konsistent definiert. So gibt es zum Beispiel die Funktionen

-- Gib Spieler _PlayerId _Amount Gold-Einheiten
AddGold(_PlayerId, _Amount)
 
-- Verbiete Spieler _PlayerId die Technologie _Technology
ForbidTechnology(_Technology, _PlayerId)

Bei der einen Funktion steht die _PlayerId vorne, bei der anderen hinten. Das kann leicht zu Verwechslungen führen. So ist ein fehlerhafter Aufruf wie dieser hier denkbar:

AddGold(500, 2)

Das drückt aus, dass du Spieler mit der Id 500 (existiert nicht im Spiel) 2 Gold geben möchtest. Da das nicht möglich ist, wird der Funktionsaufruf wirkungslos bleiben.

Leicht falsch zu bedienen sind Funktionen, die viele Parameter anbieten, wie MapEditor_SetupAI:

-- Geplant ist, eine KI der Stärke 2 mit Technologiestufe 0 zu erstellen
MapEditor_SetupAI(2, 0, 32000, 2, "Player2", 2, 0)

Hier wurden die Argumente für _Strength und _Techlevel vertauscht, sodass die KI inaktiv bleibt, da _Strength = 0 bedeutet, dass die KI ausgeschaltet ist.


Logikfehler

Logikfehler bedeuten, dass du zwar aus programmiertechnischer Sicht alles richtig gemacht hast, im Spiel aber nicht das passiert, was du möchtest. Diese Fehler sind schwieriger zu beheben, da man sich zuvor einen Überblick über das gesamte Skript und den dazugehörigen Plan für die Karte verschaffen muss.

Beim Ausmerzen von Logikfehlern zahlt es sich aus, im Voraus einen möglichst konkreten Plan ausgearbeitet und gut lesbaren Code geschrieben zu haben. Der Code lässt sich so schneller mit dem Plan abgleichen, um festzustellen, wo die beiden voneinander abweichen.


Fehler im Mapdesign

Fehler können auch im Mapdesign geschehen. Vor allem Blocking an der falschen Stelle kann den Mapablauf empfindlich stören, beispielsweise:

Für solche Fehler, die nur im Editor behoben werden können, ist das Grid (Shortcut Ctrl + G) das wichtigste Werkzeug. Mit aktiviertem Grid wird blockiertes Terrain rot dargestellt. Halte Ausschau nach Terrainhöhen, Texturen, Objekten oder unsauberen Wasserflächen, die unerwünschtes Blocking verursachen.


Zu guter Letzt beschäftigen wir uns im nächsten Kapitel mit dem Balancing einer Karte.

Voriges Kapitel: Fehler finden und beheben
Nächstes Kapitel: Balancing
Zurück nach oben