Benutzer-Werkzeuge

Webseiten-Werkzeuge


scripting:tutorials:level1:variable_scope

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 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
scripting/tutorials/level1/variable_scope.txt · Zuletzt geändert: 2023/05/27 09:43 von fritz_98