====== Lokale und Globale Variablen ======
Bisher sind wir von folgendem ausgegangen: Alle Variablen, die wir definieren, sind vom Zeitpunkt der Definition an immer verfügbar. Für die Variablen in den gegebenen Beispielen stimmt das auch.
Der Grund dafür ist, dass alle Variablen, die man ohne Weiteres in Lua definiert, **global** sind. Das bedeutet, man kann an jeder Stelle im Code ihren Inhalt abfragen oder verändern. Das hat einige Nachteile:
* Mehr Speicherverbrauch: Jede globale Variable wird immer im Speicher bleiben, weil sie jederzeit abrufbar sein muss. Wenn man ein Zwischenergebnis in einer globalen Variable speichert, die man danach nie wieder verwendet, hat man Speicherplatz verschwendet
* Schlechtere Übersicht: Für jede globale Variable musst du dir merken, was du darin abgespeichert hast. Wenn du viele Variablen brauchst, bleiben unter Umständen keine sinnvollen Namen mehr übrig, die du ihnen geben kannst
* Langsamere Laufzeit: Der Zugriff auf globale Variablen ist in Lua langsamer als auf Variablen, die nicht global sind
* Schwer auffindbare Skriptfehler: Bei vielen globalen Variablen kann es irgendwann passieren, dass du den Überblick verlierst. Unter Umständen überschreibst du dann versehentlich eine Variable, obwohl du den Inhalt darin noch brauchst
Zum letzten der genannten Punkte wollen wir ein Beispiel geben. Angenommen, du hast dir eine Funktion geschrieben, die einen String produziert, der dem Spieler mitteilen kann, welchen Schwierigkeitsgrad er ausgewählt hat. Die Funktion kann so aussehen:
function GetDifficultyString(_Difficulty)
Information = "Du hast die "
if _Difficulty == 1 then
Information = Information.."leichte "
elseif _Difficulty == 2 then
Information = Information.."mittlere "
elseif _Difficulty == 3 then
Information = Information.."schwierige "
end
Information = Information.."Variante gewählt!"
return Information
end
Diese Funktion würde für sich genommen einwandfrei funktionieren. Gehen wir nun weiterhin davon aus, dass du einen String definiert hast, der dem Spieler irgendwann angezeigt werden soll und der einen Hinweis zum Aufenthaltsort einer Schatztruhe beinhaltet.
Information = "Der Bürgermeister erwähnte, dass es in der Nähe der alten Sägemühle vor kurzem "..
"einen Erdrutsch gab. Vielleicht habt Ihr dort Glück."
Auch an diesem Text gibt es für sich genommen nichts auszusetzen. Werden diese beiden allerdings in einer bestimmten Reihenfolge kombiniert, kann das zu unerwünschtem Verhalten führen:
Information = "Der Bürgermeister erwähnte, dass es in der Nähe der alten Sägemühle vor kurzem "..
"einen Erdrutsch gab. Vielleicht habt Ihr dort Glück."
-- hier könnte dein Skript stehen
-- Bis hier her ist "Information" nicht verändert worden. Der Hinweis zur Schatztruhe besteht also noch.
-- Mit folgendem Aufruf allerdings verändert sich der "Information"-String. Es wird aus dem Aufruf der
-- Funktion nicht ersichtlich, dass er das tut, was diese Art Fehler schwer zu finden macht
DifficultyString = GetDifficultyString(2)
-- Ab hier enthält "Information" keinen Hinweis mehr, sondern die Beschreibung des Schwierigkeitsgrades
Dass der Name ''Information'', wenn er global definiert ist, "verbraucht" wird (also nicht für andere Zwecke benutzt werden kann, ohne überschrieben zu werden), beschränkt sich nicht nur auf andere Strings. Angenommen, du hast außerdem eine Funktion definiert, die ebenfalls ''Information'' heißt:
function Information()
-- mit der Funktion "Message" können im Spiel Texte im linken oberen Eck eingeblendet werden, die nach einiger
-- Zeit von selbst wieder verschwinden
Message("Du spielst gerade meine Karte in der Version 1.06!")
end
-- hier könnte dein Skript stehen
-- Bis hier her ist "Information" nicht verändert worden. Die Funktion kann also problemlos aufgerufen werden.
-- Mit dem folgenden Aufruf wird die Funktion allerdings mit einem String überschrieben:
DifficultyString = GetDifficultyString(2)
-- der folgende Aufruf wird nun zu einem Fehler führen, da "Information" nicht länger eine Funktion ist:
Information()
In beiden Beispielen entsteht das Problem dadurch, dass die Variable ''Information'' in der Funktion ''GetDifficultyString'' nur zur Berechnung eines Ergebnisses gebraucht wird, danach aber nicht mehr. Da sie global definiert ist, überschreibt sie aber andere, wichtige Variablen. Um das zu verhindern, wollen wir **lokale** Variablen verwenden. Lokale Variablen sind nur in einem bestimmten Bereich im Skript gültig und werden dann automatisch verworfen. Um zu verstehen, in welchen Bereichen eine lokale Variable gültig ist, wollen wir zunächst beschreiben, was **Blöcke** sind.
----
**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 ]]
Um dabei bei der Ausgabe ein Ergebnis zu sehen, kann man die Funktion ''print'' verwenden. Will man beispielsweise die Variable ''Information'' ansehen, kann man
print(Information)
schreiben. Wenn die Variable einen anderen Namen hat, muss man diesen dementsprechend austauschen.
----
===== Blöcke =====
Ein Block sind eine oder mehrere zusammengehörige Instruktionen. "Zusammengehörig" bedeutet dabei nicht einfach nur, dass sie zusammenstehen, sondern dass sie zusammen ausgeführt werden. Bei einer Verzweigung beispielsweise bilden der Fall, dass die Bedingung erfüllt ist und der Fall dass sie nicht erfüllt ist, jeweils einen Block.
if Text == "Ma-i-a hi" then
-- Anfang Block 1
print(Text)
print("Ma-i-a hu")
print("Ma-i-a ho")
print("Ma-i-a ha-ha")
-- Ende Block 1
elseif Text == "nu mă" then
-- Anfang Block 2
print(Text)
print("nu mă iei")
print("Nu mă, nu mă iei")
print("nu mă, nu mă, nu mă iei")
-- Ende Block 2
end
-- Der Befehl print hat in Siedler 5 keine Funktion
-- Der Code muss also in der lua demo, die oben verlinkt ist, ausgeführt werden
-- Alternativ können alle print-Aufrufe auch durch Message ersetzt werden. Dann wird Text im Spiel angezeigt
Blöcke können Problemlos auch ineinander geschachtelt werden, wie die Minimumssuche [[ scripting:tutorials:level1:branches | hier ]] gut zeigt.
Im Allgemeinen ist ein Block immer zwischen einem Schlüsselwort wie ''then'', ''else'', ''function'' (im nächsten Kapitel auch ''do'') und dem ''end'', das einen Block abschließt. Würde man den Code oben in eine Funktion setzen, würde die gesamte Verzweigung einen äußeren Block bilden:
function DragosteaDinTei(_Text)
-- Anfang äußerer Block
if _Text == "Ma-i-a hi" then
-- Anfang innerer Block 1
print(_Text)
print("Ma-i-a hu")
print("Ma-i-a ho")
print("Ma-i-a ha-ha")
-- Ende innerer Block 1
elseif _Text == "nu mă" then
-- Anfang innerer Block 2
print(_Text)
print("nu mă iei")
print("Nu mă, nu mă iei")
print("nu mă, nu mă, nu mă iei")
-- Ende innerer Block 2
end
-- Ende äußerer Block
end
----
===== Lokale Variablen =====
In lokalen Variablen können Zwischenergebnisse und andere Daten, die später verworfen werden sollen, gespeichert werden. Eine lokale Variable wird mit dem vorangehenden Schlüsselwort ''local'' deklariert:
local Result = 42
Eine lokale Variable ist nur im gleichen Block gültig, in dem sie definiert wurde. Dies schließt alle darin eingeschlossenen Unterblöcke mit ein.
function TestScope()
local Variable = 5
print(Variable)
if Variable == 5 then
Variable = 42
end
print(Variable)
end
TestScope() -- zeigt zuerst 5, dann 42 an
**Achtung**: Wenn du nach der Definition einer lokalen Variable diese überschreiben willst, darf **nicht** das Schlüsselwort ''local'' verwendet werden. Jedes ''local'' führt eine **neue** lokale Variable im aktuellen Block ein:
function TestScope()
local Variable = 5
if Variable == 5 then
local Variable = 42
print(Variable) -- hier wird wie erwartet 42 angezeigt
end
print(Variable)
-- hier wird 5 angezeigt, da die lokale Variable aus dem if-Block nicht mehr existiert
-- (if-Block ist zuende)
end
TestScope() -- zeigt zuerst 42, dann 5 an
**Achtung**: Zudem musst du darauf achten, dass eine lokale Variable, die du benutzen willst, wirklich im genau dem Block definiert ist, in dem du sie benutzen willst. Zwei Beispiele:
function TestScope()
-- mit 'do' kann man ohne zusätzliche Bedingungen einen neuen Block starten
do
local Variable = 5
end
print(Variable)
end
TestScope() --zeigt 'nil' an
In diesem Beispiel wird nicht 5, sondern ''nil'' ausgegeben, da die Variable zum Zeitpunkt des ''print''-Befehls nicht mehr existiert. Der Block, in dem sie definiert wurde, ist mit ''end'' zu Ende, sodass man sich danach nicht mehr darauf beziehen kann.
Anderes Beispiel:
function TestScope()
do
local Variable = 5
end
Variable = 42
print(Variable)
end
TestScope() -- zeigt 42 an. Allerdings wurde eine globale Variable erschaffen/überschrieben
Hier wird zwar die erwartete 42 ausgegeben, allerdings wurde eine **globale** Variable erschaffen. Da wie im vorigen Beispiel die lokale Variable unterhalb von ''end'' nicht mehr existiert, kann man sich dort nicht mehr darauf beziehen. Ohne ''local'' entsteht demnach eine globale Variable bzw. eine bereits existierende globale Variable wird überschrieben.
Um diesen Abschnitt abzuschließen, wollen wir unser Beispiel oben "bereinigen". Dazu definieren wir wieder die Funktion ''GetDifficultyString'', allerdings unter Zuhilfenahme einer lokalen Variable:
function GetDifficultyString(_Difficulty)
local Information = "Du hast die "
if _Difficulty == 1 then
Information = Information.."leichte "
elseif _Difficulty == 2 then
Information = Information.."mittlere "
elseif _Difficulty == 3 then
Information = Information.."schwierige "
end
Information = Information.."Variante gewählt!"
return Information
end
Dies wird mit keiner globalen Variable mehr kollidieren. Lokale Variablen haben in dem Block, in dem sie definiert wurden, immer "Vorrang" gegenüber gleichnamigen globalen Variablen. Wenn also wie im Beispiel oben ein globaler String ''Information'' existiert, wird innerhalb der Funktion trotzdem die korrekte lokale Variable benutzt und anschließend wieder verworfen. Damit der generierte String nicht verloren geht, kann er in einer globalen Variable gespeichert werden:
DifficultyString = GetDifficultyString(2)
Es ist sehr sinnvoll, sich die Zeit zu nehmen, zu überlegen, wo genau in diesem Beispiel welche Variablen existieren.
----
===== Verwendungszwecke =====
Zum Schluss wollen wir noch einige Beispiele geben, für welche Zwecke globale und lokale Variablen sinnvoll sind.
^ Verwendungszweck ^ Global oder lokal? ^
| Zähle die Anzahl der vom Spieler gefundenen Schatzkisten | Global, da man jederzeit wissen möchte, wie viele Kisten gefunden wurden |
| Zwischenergebnisse in einer Funktion | Lokal, da lediglich das Ergebnis zählt und mit einem ''return'' an eine Ergebnisvariable gegeben werden kann |
| Funktionen | Funktionen sind in der Regel immer global |
| Parameter | Parameter, die einer Funktion übergeben werden, sind zwangsweise **immer** lokal und können deshalb nur innerhalb der Funktion benutzt werden |
----
Um mit wenig Schreibarbeit große Rechenvorschriften aufzuschreiben, werden im nächsten Kapitel Schleifen eingeführt.
[[ scripting:tutorials:level1:functions_blocks | Voriges Kapitel: Funktionen ]]\\
[[ scripting:tutorials:level1:loops | Nächstes Kapitel: Schleifen ]]\\
[[ scripting:tutorials:level1:variable_scope | Zurück nach oben ]]