======Weiteres zu Funktionen======
Für ein lauffähiges Skript ist der korrekte Umgang mit Funktionen essentiell. Einige Funktionen, die Siedler bereitstellt, gehen über die Vorstellung, die wir in [[ scripting:tutorials:level1:functions_blocks |Ebene 1]] erstanden haben, hinaus. In diesem Kapitel werden deshalb einige nützliche Möglichkeiten zur Verwendung von Funktionen präsentiert und Beispiele anhand der Siedler-API gegeben.
----
**Hinweis**: Unsere Beispiele in diesem und den folgenden Kapiteln können besser nachvollzogen werden, wenn man die Programmzeilen nebenbei ausprobieren kann. Dazu kann man diese Webseite hier verwenden:
[[ https://www.lua.org/demo.html ]]
----
=====Verkürzte Funktionsaufrufe=====
Wenn Funktionen nur einen einzigen Parameter haben und dieser Parameter entweder ein [[ scripting:tutorials:level1:variables#strings |String]] oder ein [[ scripting:tutorials:level1:tables |Table]] ist, kann man den Funktionsaufruf mit diesem einen Parameter verkürzen.
Beispielsweise übergibt man an die Funktion ''print'' in der Regel nur einen einzigen String-Parameter. In so einem Fall können die runden Klammern weggelassen werden. Folgende Funktionsaufrufe sind äquivalent:
-- Zeigt "Hallo Welt" an
print("Hallo Welt")
-- Zeigt auch "Hallo Welt" an
print"Hallo Welt"
Es handelt sich dabei nur um eine alternative Schreibweise, die Funktionalität bleibt die gleiche.
Das gleiche Prinzip funktioniert auch, wenn ein Table als einziger Parameter erwartet wird. Wir definieren eine Funktion, die eine Person etwas sagen lässt und belassen es Optional, ob der Sprecher oder die Aussage angegeben werden. Dazu benutzen wir ein Table als Paramter und wenden einen kleinen Trick an: Um herauszufinden, welche Informationen gegeben wurden, fragen wir (indirekt), ob sie **nil** sind. In Lua ist jede Variable, bevor sie definiert wird, **nil**. Demzufolge sind alle undefinierten Variablen **nil**.
-- _WhoAndWhat ist ein Table, das eine Person und einen Text enthalten kann, aber nicht muss
-- Wenn eine Angabe fehlt, wird ein Standardwert gesetzt
function SaySomething(_WhoAndWhat)
-- Wenn die Quelle der Aussage fehlt (also nil ist), wird "Ein Unbekannter" als Quelle angenommen
-- nil wird von if wie false interpretiert
if not _WhoAndWhat.Person then
_WhoAndWhat.Person = "Ein Unbekannter"
end
-- Das gleiche Prinzip für den Text
if not _WhoAndWhat.Text then
_WhoAndWhat.Text = "Je kälter, desto draußen"
end
print(_WhoAndWhat.Person.." sagt: ".._WhoAndWhat.Text)
end
So eine Funktion kann auf verschiedene Arten aufgerufen werden:
-- Variante 1: Man definiert ein (lokales) Table im Voraus und gibt es an die Funktion weiter
local WhoAndWhat = {
Person = "Konfuzius",
Text = "Der Weg ist das Ziel"
}
SaySomething(WhoAndWhat)
-- Variante 2: Man erstellt das Table im Funktionsaufruf und benutzt die Standardschreibweise
SaySomething({Person = "Kerberos"})
-- Variante 3: Der verkürzte Funktionsaufruf, bei dem ebenfalls das Table beim Funktionsaufruf erstellt wird
-- In der verkürzten Variante können die Klammern weggelassen werden
SaySomething{Person = "Niemand", Text = "Nutella schmeckt ohne Butter besser"}
-- Die Funktion ist so formuliert, dass auch ein leeres Table übergeben werden kann
-- Der verkürzte Funktionsaufruf sähe damit so aus:
SaySomething{}
----
====Beispiele im Siedler-Skripting====
Wir haben in Ebene 1 bereits Funktionen kennengelernt, die entweder einen einzelnen String oder ein einzelnes Table als Parameter haben.
Beispielsweise haben wir im Artikel zu [[ scripting:tutorials:level1:simple_job |Sieg- und Niederlagebedingungen]] die Funktion ''IsDead'' benutzt, um zu prüfen, ob eine Entität noch lebt oder nicht. Folgende Schreibweisen sind demnach äquivalent:
-- Variante 1
if IsDead("Player2") then
-- Reaktion auf Ereignis
end
-- Variante 2
if IsDead"Player2" then
-- Reaktion auf Ereignis
end
**Hinweis**: Du wirst die verkürzte Schreibweise für Strings in Mapskripten sehr selten finden, da sie weder besonders nützlich noch hübsch anzusehen ist. Wir wollten sie der Vollständigkeit halber trotzdem zeigen.
Im Artikel zu [[ scripting:tutorials:level1:briefings |Briefings]] haben wir die Funktion ''StartBriefing'' vorgestellt, mit der ein Briefing nach einer Beschreibung in einem Table gestartet werden kann. Da diese Funktion nur das Table als Parameter nimmt, kommt dafür die alternative Schreibweise infrage:
-- Im verlinkten Artikel haben wir uns der Übersicht zuliebe für Variante 1 entschieden
local Briefing = {
{
title = "Dario",
text = "Dies ist ein sehr kurzes Briefing. Ich grüße meine Mama, meinen Papa und Falki.",
position = GetPosition("Dario")
}
}
StartBriefing(Briefing)
-- Die Variante mit verkürzter Schreibweise sähe so aus:
StartBriefing{
{
title = "Dario",
text = "Dies ist ein sehr kurzes Briefing. Ich grüße meine Mama, meinen Papa und Falki.",
position = GetPosition("Dario")
}
}
**Hinweis**: Je größer ein Table wird, desto unübersichtlicher wird der verkürzte Aufruf, weil weniger schnell ersichtlich wird, auf welche Funktion sich gerade bezogen wird. Speziell bei Briefings nimmt dir das außerdem die Möglichkeit, einzelne Seiten als globale Variable abzuspeichern, um sie ''ResolveBriefing'' zu übergeben. Die verkürzte Schreibweise ist also **nicht automatisch die beste**!\\
Wir werden im [[ scripting:tutorials:level2:npcs |Artikel zu NPCs]] die Funktion ''CreateNPC'' kennenlernen, die sich für die verkürzte Schreibweise sehr eignet.
----
=====Mehrere Rückgabewerte=====
Funktionen in Lua können mehr als einen Wert mittels **return** zurückgeben. Die einzelnen Werte müssen dafür durch ein Komma getrennt sein.
Zur Anschauung definieren wir eine Funktion, die die größte Zahl in einem Table findet. Außerdem soll die Funktion den Index dieser Zahl zurückgeben.
function Argmax(_Table)
local MaxIndex = 1
local Max = _Table[MaxIndex]
for i, Value in ipairs(_Table) do
if _Table[i] > Max then
Max = _Table[i]
MaxIndex = i
end
end
return Max, MaxIndex
end
Für beide Rückgabewerte müssen eigene Variablen definiert werden, ebenfalls durch ein Komma getrennt:
Max, MaxIndex = Argmax{3, 25, 14, 9, 11, 17, 36}
Falls wir den ''MaxIndex'' doch mal nicht brauchen, können wir ihn einfach weglassen:
-- Das Maximum wird weiterhin in die Variable Max geschrieben
Max = Argmax{3, 25, 14, 9, 11, 17, 36}
Falls wir **nur** den ''MaxIndex'' brauchen, müssen wir den ersten Rückgabewert dennoch "auffangen", um im Skript zu signalisieren, dass wir am zweiten Rückgabewert interessiert sind. Im Beispiel darüber haben wir gesehen, dass sonst automatisch der erste Rückgabewert als der gewünschte angenommen wird.
-- Technisch gesehen ist _ auch nur eine Variable. Sie sollte allerdings nicht benutzt werden, sondern
-- nur einen Platzhalter für nicht benötigte Variablen darstellen
_, MaxIndex = Argmax{3, 25, 14, 9, 11, 17, 36}
Abschließend können wir auch alle Rückgabewerte automatisch in ein Table fassen, indem wir geschweifte Klammern um den Funktionsaufruf setzen:
MaxResults = { Argmax{3, 25, 14, 9, 11, 17, 36} }
Der Wert ''Max'' findet sich in ''MaxResults[1]'' und der Wert ''MaxIndex'' in ''MaxResults[2]''.
**Wichtig**: Wenn die Anzahl der Rückgabewerte einer Funktion bekannt ist, sollte man immer die Variante über die Definition der einzelnen Variablen wählen. Sie ist performanter und macht es für einen Leser direkt ersichtlich, welche und wie viele Variablen entstehen. Der Umweg über das Table ist vor allem nützlich, wenn man die Anzahl der Rückgabewerte **nicht kennt**. \\
Ein Beispiel für den letzteren Fall wird im Abschnitt zum Siedler-Skripting beleuchtet.
Lua selbst stellt auch Funktionen mit mehreren Rückgabewerten zur Verfügung. Die Funktion ''string.find'' durchsucht einen String nach dem ersten Vorkommen eines gegebenen Substrings. Zurückgegeben werden der erste und der letzte Index des gefundenen Stringabschnitts:
FoundStart, FoundEnd = string.find("HelloWorld", "oWo")
''FoundStart'' ist ''5'' und FoundEnd ''7'', da der Teil des Strings, auf den die Suche zutrifft, am fünften Buchstaben beginnt und am siebten endet.
----
====Beispiele im Siedler-Skripting====
Eine Funktion, die beim Siedler-Skripten häufig verwendet wird, ermittelt die Gebäude oder Einheiten eines bestimmten Typs des Spielers. ''Logic.GetPlayerEntities(_PlayerId, _EntityType, _Amount)'' gibt höchstens ''_Amount'' [[ scripting:tutorials:level1:place_entities#exkursentity-id_vs_skriptname |Entity-Ids]] von Spieler ''_PlayerId'' vom Typ ''_EntityType'' zurück. Wenn der Spieler weniger als ''_Amount'' Entities des gesuchten Typs besitzt, werden entsprechend weniger EnitityIds zurückgegeben. Wir haben hier also genau den Fall, dass eine Funktion viele Rückgabewerte hat, deren Anzahl aber nicht immer gleich ist.
Darüber hinaus ist der erste Rückgabewert keine EntityId, sondern die Anzahl der gefundenen Ids!
In diesem Beispiel wollen wir 5 kleine Wohnhäuser von Spieler 1 zum Brennen bringen:
-- Maximal 5, potentiell 0 Entities im Table
local Player1Residences = { Logic.GetPlayerEntitites(1, Entities.PB_Residence1, 5) }
-- Die Iteration soll bei Index 2 beginnen, da an Index 1 keine EntityId, sondern die Anzahl der
-- gefundenen Häuser enthält
-- Die Iteration endet bei Player1Residences[1] + 1, da die Anzahl der gefundenen Häuser plus
-- jede einzelne EntityId zusammen die Länge der Liste bestimmen
-- Wenn keine Häuser gefunden wurden, ist Player1Residences[1] + 1 = 0 + 1 = 1
-- Das bedeutet, dass mit i = 2, 1 der Block in der Schleife nie betreten wird
for i = 2, Player1Residences[1] + 1 do
-- Setze die Lebenspunkte herab, sodass das Haus zu brennen beginnt
SetHealth(Player1Residences[i], 25)
end
----
===== Zu viele und zu wenige Parameter=====
Möglicherweise ist es dir schon passiert, dass du einer Funktion die Parameter in der falschen Reihenfolge oder zu viele oder zu wenige Parameter angegeben hast. Falls du mit anderen Programmiersprachen vertraut bist, würdest du erwarten, dass solche Fälle Fehlermeldungen nach sich ziehen. Lua allerdings wird das erlauben und auch teilweise ausnutzen. In diesem Abschnitt wollen wir beleuchten, was passiert, wenn die Anzahl an gegebenen Argumenten nicht der erwarteten entspricht.
Der einfachere Fall ist der mit zu vielen Argumenten. Wir definieren eine Funktion, die drei Zahlen addiert:
function SumOfThree(_A, _B, _C)
return _A + _B + _C
end
Wenn diese Funktion mit zu vielen Argumenten aufgerufen wird
SumOfThree(3, 25, 14, 9, 11)
werden nur die ersten drei beachtet und addiert, der Rest verworfen. Die Funktion hat keine Möglichkeit, auf die Argumente zuzugreifen. Allerdings wird **kein** Fehler angezeigt! Darauf zu achten, einer Funktion nicht zu viele Argumente zu übergeben, wird dem Programmierer überlassen.
Der Nutzen, den man daraus ziehen kann, ist eingeschränkt. Im Artikel zur [[ scripting:tutorials:level2:lua_library |Lua-Standardbibliothek]] wird die Funktion ''unpack'' beschrieben, mit der ein numerisches Table ([[ scripting:tutorials:level1:tables#tables_als_listen |Liste]]) "ausgepackt" wird. Die Funktion gibt jeden Eintrag im Table einzeln als Rückgabewert aus (siehe auch den Abschnitt [[ scripting:tutorials:level2:functions#mehrere_rueckgabewerte |oben]] über mehrere Rückgabewerte). ''unpack'' kann also dazu benutzt werden, die Inhalte eines numerischen Tables einzeln als Argumente zu übergeben. Mit der Funktion ''SumOfThree'' können so die ersten drei Zahlen in so einem Table addiert werden:
local Numbers = {3, 25, 14, 9, 11, 17, 36}
print(SumOfThree(unpack(Numbers)))
Dieser Use Case lässt sich aber auch anders (und deutlich besser lesbar) umsetzen und soll hier nur als Beispiel dienen. \\
**Hinweis**: In der Lua-Demo kann die Funktion ''unpack'' nicht benutzt werden und muss durch ''table.unpack'' ersetzt werden.
Der interessantere Fall ist der, bei dem zu wenig Argumente übergeben wurden. Jeder Parameter einer Funktion, der nicht mit einer Eingabe belegt wird, ist innerhalb der Funktion **nil**:
function SumOfThree(_A, _B, _C)
print(_A)
print(_B)
print(_C)
return _A + _B + _C
end
SumOfThree(3)
Dieser Aufruf wird ''3 nil nil'' anzeigen und dann einen Fehler, weil eine Zahl nicht mit **nil** verrechnet werden kann. Zu wenige übergebene Argumente können also Fehler verursachen. Diese Fehler entstehen allerdings erst, wenn die Funktion mit **nil** nicht arbeiten kann.
Das kann man ausnutzen, um Parameter optional zu machen. Wir modifizieren die Funktion ''SumOfThree'' so, dass weniger als 3 Argumente übergeben werden können.
function SumOfThree(_A, _B, _C)
-- nil wird von if wie false interpretiert
-- Wenn _A nil ist, also nicht angegeben wurde, wird es auf 0 gesetzt
if not _A then
_A = 0
end
-- Das selbe Prinzip wenden wir bei _B an
if not _B then
_B = 0
end
-- Und auch bei _C
if not _C then
_C = 0
end
-- So können wir sicherstellen, dass diese Berechnung immer gelingt
return _A + _B + _C
end
-- Gibt nun ohne Fehler das korrekte Ergebnis aus
print(SumOfThree(3))
print(SumOfThree(3, 39))
Auch hier ist wieder zu beachten, dass zu wenige Argumente nicht per se als Fehler gelten und als Problemursache häufig versteckt auftreten. In unserem Beispiel mit der Summe können damit aber alle Parameter optional gemacht werden, sodass die nicht zwingend besetzt werden müssen.
**Achtung**: Auf diese Weise solltest du höchstens **einen** Parameter als optional definieren. Bedenke, dass in unserem Beispiel für die Angabe von ''_C'' **zwingend** die Angabe von ''_B'' notwendig ist. Es lassen sich demnach keine Parameter "überspringen". Wenn du mehrere optionale Parameter anbieten willst, solltest du den Parameter als Table definieren (siehe auch das Beispiel ''SaySomething'' aus dem Abschnitt [[ scripting:tutorials:level2:functions#verkuerzte_funktionsaufrufe |oben]] über verkürzte Funktionsaufrufe). Dadurch bekommt jeder Parameter einen Namen und kann unabhängig von allen anderen angegeben werden. \\
Diesem Prinzip folgt beispielsweise die aus [[ scripting:tutorials:level1:enemy_ai#setupplayerai |Ebene 1 bekannte Funktion]] ''SetupPlayerAi'', die viele Optionen für die Definition eines KI-Spielers bietet, die unabhängig voneinander gewählt werden können.
----
====Beispiele im Siedler-Skripting====
Einige Funktionen, die du schon kennst, machen sich optionale Parameter zunutze. Eine davon ist ''ForbidTechnology(_Technology, _PlayerId)''. Tatsächlich kannst du die Angabe von ''_PlayerId'' weglassen. Dann wird für den Spieler die Id des lokalen menschlichen Spielers angenommen. Im Singleplayer ist das immer Spieler **1**. Folgende Aufrufe sind **nur** im Singleplayer also äquivalent:
-- Verbietet den Ausbau zum Kanonenturm für Spieler 1
ForbidTechnology(Technologies.UP2_Tower, 1)
-- Verbietet ebenfalls den Ausbau zum Kanonenturm für Spieler 1
ForbidTechnology(Technologies.UP2_Tower)
Wenig überraschend ist ''ForbidTechnology'' folgendermaßen definiert:
function ForbidTechnology(_Technology, _PlayerId)
if not _PlayerId then
-- Gibt die Spieler-Id des lokalen menschlichen Spielers zurück (auch im Multiplayer)
-- Dadurch kann im Multiplayer mit einem einzigen Aufruf von ForbidTechnology
-- allen Spielern eine bestimmte Technologie verboten werden
_PlayerId = GUI.GetPlayerID()
end
Logic.SetTechnologyState(_PlayerId, _Technology, 0)
end
Analog sind auch ''AllowTechnology'' und ''ResearchTechnology'' definiert.
Ein etwas anderes Beispiel ist die Funktion ''AddGold(_PlayerId, _Amount)''. Auch da ist die ''_PlayerId'' optional, obwohl sie im Gegensatz zu ''ForbidTechnology'' vorne steht! Das liegt daran, dass intern beim Fehlen des zweiten Arguments die Parameter vertauscht werden:
function AddGold(_PlayerId, _Amount)
if not _Amount then
_Amount = _PlayerId
_PlayerId = GUI.GetPlayerID()
end
Logic.AddToPlayersGlobalResource(_PlayerId, ResourceType.Gold, _Amount)
end
Bei allen anderen Ressourcen ist das ebenfalls so gelöst.
----
=====Beliebig viele Parameter=====
In manchen Situationen ist es nützlich, eine Funktion mit beliebig vielen Parametern aufrufen zu können. Die Funktion soll dann einfach mit allen Parametern, die sie bekommt, arbeiten.
Ein Beispiel dafür kann eine Funktion sein, die aus allen Zahlen, die sie als Argumente erhält, eine Summe bildet. Auf herkömmlichem Weg kann man nur eine endliche Menge an Parametern definieren, die jederzeit überschritten werden kann. Zwangsläufig muss der Weg über Tables führen, die beliebig groß sein können:
function Sum(_Numbers)
local Result = 0
for _, Number in ipairs(_Numbers) do
Result = Result + Number
end
return Result
end
print(Sum{3, 25, 14, 9, 11, 17, 36})
Es gibt in Lua aber auch eine Schreibweise, die automatisch alle Argumente, die der Funktion übergeben werden, in ein Table gibt. Dazu schreibt man statt dem Argument drei Punkte (''...''). Alle an diesem Platzhalter gegebenen Argumente sind innerhalb der Funktion in einem Table namens ''arg'' verfügbar:
function Sum(...)
local Result = 0
for _, Number in ipairs(arg) do
Result = Result + Number
end
return Result
end
-- Beachte den Unterschied zu oben: Hier wird kein Table als Argument überreicht, sondern einfach alle Zahlen
-- hintereinander!
print(Sum(3, 25, 14, 9, 11, 17, 36))
**Hinweis**: In der Lua-Demo muss ''arg'' durch ''{...}'' getauscht werden, da Siedler noch mit einer alten Lua-Version läuft.
Das lässt sich auch mit festen Parametern kombinieren. In der folgenden Beispielfunktion wollen wir in ein bestehendes Table alle angegebenen Werte einfügen:
MyTable = {3, 25, 14}
function MultiInsert(_Table, ...)
for _, Value in ipairs(arg) do
table.insert(_Table, Value)
end
end
MultiInsert(MyTable, 7, "t", 0, "Spinat", 9999)
Nach dem Aufruf hat ''MyTable'' 8 Einträge, wurde also um alle Werte, die der Funktion übergeben wurden, erweitert.
**Achtung**: Die Parameter können in diesem Fall nicht umgekehrt angegeben werden (also nicht ''MultiInsert(..., _Table)''! Die Funktion kann beim Aufruf nicht wissen, welche Argumente zu ''...'' und welche zu ''_Table'' gehören. Deshalb besteht generell die Regel, dass zuerst die festen/notwendigen Parameter angegeben werden müssen, bevor man die Funktion für beliebig viele weitere Parameter mit ''...'' "öffnet".
----
Im nächsten Kapitel bleiben wir bei Funktionen und versuchen, mit Rekursionen zu arbeiten.
[[ scripting:tutorials:level2:loops| Voriges Kapitel: Weiteres zu Schleifen ]] \\
[[ scripting:tutorials:level2:recursion | Nächstes Kapitel: Rekursionen ]] \\
[[ scripting:tutorials:level2:functions | Zurück nach oben ]]