Noch nie gescriptet? Der Guide Scripting für Einsteiger auf visionaire-studio.net zeigt an einem kleinen Beispiel, wann sich Lua lohnt und wie du deine ersten Skripte schreibst.

Überblick

Willkommen zu AFRLmes Einführung in Lua, für Einsteiger und alle, die ihr Wissen auffrischen wollen. Hier lernst du die Grundlagen von Lua mit Beispielen und Bildern kennen. Wie du Lua zusammen mit Visionaire Studio einsetzt, erfährst du in Lua in Visionaire Studio, in den Datenstrukturen, in den globalen Befehlen, im Skriptverzeichnis und in der offiziellen Lua-Dokumentation von Visionaire Studio.

Bevor du loslegst, lies kurz die Hinweise zum Ausprobieren der Beispiele weiter unten. Oder spring direkt zur Übersicht und fang an.

Inhalt

# Kapitel Beschreibung
0 Überblick Diese Seite
1 Einführung Kurzer Überblick über Lua mit ein paar zusätzlichen Hinweisen.
2 Grundlagen Die grundlegenden Bausteine von Lua.
3 Operatoren Mit Operatoren rechnest du, vergleichst Werte und verknüpfst Bedingungen.
4 Typen Lua kennt Zahlen, Zeichenketten, Tabellen, Wahrheitswerte und sogar Funktionen als Typen.
5 Tabellen Tabellen gehören zu den wichtigsten Bausteinen von Lua.
6 SchleifenSchleifen wiederholen Code für jeden Eintrag einer Tabelle oder solange eine Bedingung erfüllt ist.
Funktionen Funktionen verarbeiten Daten und geben Ergebnisse zurück. Sie sparen Arbeit, weil du sie immer wieder verwenden kannst.

Beispiele ausprobieren

Alle Beispiele dieser Anleitung kannst du ohne zusätzliche Software ausprobieren: Lege im Skriptbereich von Visionaire Studio ein Skript an, füge den Code ein und starte das Spiel. Ausgaben von print erscheinen in der Logdatei und in der Konsole des Players (im Testmodus mit der Taste Tab ein- und ausblenden).

Wenn du Lua lieber außerhalb von Visionaire testen möchtest, führt die Online-Demo auf lua.org reines Lua direkt im Browser aus. Code-Editoren wie Visual Studio Code mit einer Lua-Erweiterung helfen zusätzlich mit Syntaxhervorhebung und Fehlerhinweisen. Beachte, dass Visionaire-Befehle wie getObject nur innerhalb der Engine funktionieren.

Einführung

Was ist Lua?

Lua ist eine schlanke, plattformunabhängige Skriptsprache. Ursprünglich wurde sie entwickelt, um bestehende Programmiersprachen zu erweitern. Weil sie leicht zu lernen ist und sich einfache Abläufe, Funktionen und Tabellen damit schnell umsetzen lassen, ist sie heute in vielen Game Engines die erste oder zweite Skriptsprache.

Mehr Informationen findest du auf lua.org und bei Wikipedia.

Was bedeutet der Name Lua?

Richtig geschrieben heißt es nichtLUA, sondern Lua. Der Name ist keine Abkürzung, sondern einfach das portugiesische Wort für Mond.

Welchen Sprachen ähnelt Lua?

Lua ähnelt Programmiersprachen wie C, C#, C++ oder Java, ist aber deutlich einfacher. Zum Beispiel musst du beim Anlegen einer Variable keinen Typ angeben.

Warum Lua lernen?

Warum solltest du überhaupt Lua lernen? Visionaire Studio bringt so viele fertige Aktionen und Abfragen mit, dass du damit fast ein komplettes kommerzielles Spiel bauen kannst. Wie gut es wird, liegt an dir und deinem Team.

Die Antwort ist einfach: Warum nicht? Mit Lua erweiterst du die vorhandenen Aktionen und Abfragen oder baust ganz neue Funktionen, die Visionaire Studio von Haus aus nicht mitbringt.

Lua ist leicht zu lernen. Wer schon ein wenig programmiert hat, und sei es nur einfaches HTML, findet sich schnell zurecht. Für manche Dinge brauchst du Lua sogar, zum Beispiel wenn Spieler Screenshots erstellen sollen, wenn dein Spiel die gewählten Optionen beim nächsten Start wieder kennen soll oder wenn du über die eingebauten Möglichkeiten der Engine hinausgehen willst.

Lern zumindest die Grundlagen. Was du hier lernst, hilft dir auch in anderen Skript- und Programmiersprachen. Vielleicht entdeckst du dabei sogar Spaß am Programmieren und wagst dich später an Sprachen wie C# oder C++, die in Engines wie Unity oder Unreal Engine zum Einsatz kommen.


Grundlagen

Hier geht es um einige Grundbausteine von Lua, die keine eigene Seite brauchen. Du begegnest ihnen in den Beispielen dieser Anleitung immer wieder.

Print

Mit print schreibst du Meldungen in die Logdatei oder in die Lua-Konsole. Das hilft dir beim Testen und bei der Fehlersuche in deinen Skripten.

print("hello world") -- prints "hello world" into the log/console (ah yes how cliché of me, but it had to be done)

Variablen

Variablen sind Namen, unter denen du Daten speicherst und wieder abrufst. In Lua können Variablen lokal oder global sein. Anders als in vielen anderen Sprachen gibst du beim Anlegen keinen Typ an, und du kannst eine Variable jederzeit mit neuen Daten überschreiben.

a = 1

Verkettung

Verkettung bedeutet, zwei oder mehr Zeichenketten aneinanderzuhängen. Dafür schreibst du zwei Punkte: ..

a = "hello"
b = "world"

print(a .. " " .. b) -- prints "hello world" - we had to add " " to create a space between the 2 words

Return

Mit return gibst du Daten aus einem Skript oder einer Funktion zurück, damit ein anderes Skript oder eine andere Funktion sie weiterverwenden kann.

function hello()
 return "hello world!"
end

print( hello() ) -- prints "hello world!"

Global oder lokal

Global und lokal betrifft Variablen, Tabellen und Funktionen gleichermaßen. Lokales gilt nur innerhalb des Skripts oder der Funktion, in der es angelegt wurde. Globales kannst du in allen Skripten verwenden.

Global verwendest du meist für Funktionen, Tabellen und Variablen, auf die mehrere Skripte oder verschiedene Stellen zugreifen müssen.

local a = 1 -- only accessible in this script

a = 1 -- can be accessed from anywhere

function return_n(n) 
 local a = n -- only valid inside of this function but can be returned outside of function
 return a 
end
Hinweis: Achte bei globalen Namen darauf, dass du denselben Namen nicht in einem anderen Skript erneut vergibst. Sonst überschreibst du die vorhandenen Daten der Variable oder Funktion, und dein Skript funktioniert nicht mehr.

Kommentare

Einzeilige Kommentare

Stehen entweder in einer eigenen Zeile oder am Ende einer Zeile.

-- this is a single line comment
i = 10 -- comment placed after working code
-- comment place before code; i = 10 -- notice how the code is also commented out?

Mehrzeilige Kommentare

Können überall im Skript stehen und über mehrere Zeilen gehen.

--[[
this is a multiple line comment
I can go on & on & on
& on & on
& on.
]]

local i = 2 * --[[ this is a comment ]] 2
print(i) -- would print 4 to the log (2x2)

Verschachtelte mehrzeilige Kommentare

Damit kommentierst du ganze Codeblöcke aus, auch wenn sie bereits mehrzeilige Kommentare enthalten. Die vorhandenen Kommentare beenden den äußeren Kommentar dann nicht vorzeitig.

--[=[
this is a nested comment
see how --[[ we can include ]]
a multi-line comment inside of it?
]=]

--[[
this is what would happen if we were to use
a regular multi-line --[[ comment ]]
i = 10
]]

Typen

Lua kennt mehrere Typen. Welchen Typ eine Variable, Funktion oder Tabelle hat, ergibt sich automatisch aus ihrem Wert.

Typen

boolean Ein Wahrheitswert: true (wahr) oder false (falsch).
function Eine Funktion enthält meist eine Folge von Anweisungen und kann Werte berechnen und zurückgeben.
nil Kommt kein Wert zurück, ist das Abgefragte leer oder existiert nicht. Es ist dann nil.
number Eine ganze Zahl oder eine Kommazahl.
string Eine Zeichenkette, also Text oder Zahlen in "Anführungszeichen" oder 'Hochkommas'.
table Eine Liste oder mehrere verschachtelte Listen mit Daten unterschiedlicher Typen.

Boolean

gibt true aus
local a = true

print( a, type(a) )

Function

gibt 7 aus
function a(i)
 return i
end

print( a(7), type(a) )

Nil

gibt nil, nil aus
local a = nil

print( a, "  type = " .. type(a) )
print( b, "  type = " .. type(b) )

Number

gibt 7, 7.25 aus
local a = 7
local b = 7.25

print( a, type(a) )
print( b, type(b) )

String

gibt hello world aus
local a = "hello world"

print( a, type(a) )

Table

gibt 1, 2, 3, 4, 5, 6, 7, 8 aus
local a = {1,1+1,1*3,2*2,20/4,"6",3.5*2,10-2}

print( a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], type(a) )

Operatoren

Mit Operatoren rechnest du, vergleichst Werte und verknüpfst Bedingungen.

Bedingungen

if Prüft, ob eine Bedingung erfüllt ist.
else Führt etwas anderes aus, wenn die Bedingung nicht erfüllt ist.
elseif Prüft eine weitere Bedingung, wenn die vorherige nicht erfüllt ist.
end Schließt Abfragen und Funktionen ab. Zu jedem if gehört genau ein end. Ein elseif braucht kein eigenes end.

if

wenn die Bedingung wahr ist (Variante 1)
local a = true

if a then
  print("a = true")
end

wenn die Bedingung wahr ist (Variante 2)
local a = true

if a == true then
  print("a = true")
end

wenn die Bedingung falsch ist
local a = false

if a == false then
  print("a = false")
end

else

wenn die Bedingung nicht erfüllt ist, dann else
local a = false

if a then
  print("a = true")
else
  print("a = false")
end

elseif

wenn die Bedingung nicht erfüllt ist, dann elseif
local a = false

if a then
  print("a = true")
elseif a == false then
  print("a = false")
end

Logische Operatoren

and Prüft mehrere Bedingungen in einer einzigen Abfrage.
or Fügt einer Abfrage eine alternative Bedingung hinzu.
not Prüft, ob eine Bedingung falsch ist.

and

wenn Bedingung a wahr und Bedingung b falsch ist
local a = true
local b = false

if a == true and b == false then
  print("a = true", "b = false")
end

or

wenn die eine oder die andere Bedingung wahr ist
local a = true

if a or a == true then
 print("a = true")
end

not

wenn die Bedingung nicht wahr ist
local a = false

if not a then
 print("a = false")
end

Vergleichsoperatoren

== Gleich.
~= Ungleich.
>= Größer oder gleich.
> Größer.
<= Kleiner oder gleich.
< Kleiner.

gleich

wenn a gleich 1 ist
local a = 1

if a == 1 then
 print("a = 1")
end

ungleich

wenn a ungleich 2 ist
local a = 1

if a ~= 2 then
 print("a = " .. a)
end

größer oder gleich

wenn a größer oder gleich 2 ist
local a = 5

if a >= 2 then
 print("a = " .. a)
end

größer

wenn a größer als 2 ist
local a = 3

if a > 2 then
 print("a = " .. a)
end

kleiner oder gleich

wenn a kleiner oder gleich 2 ist
local a = 2

if a <=  2 then
 print("a = " .. a)
end

kleiner

wenn a kleiner als 2 ist
local a = 0

if a <  2 then
 print("a = " .. a)
end

Rechenoperatoren

+ Addieren
- Subtrahieren oder Vorzeichen umkehren
* Multiplizieren
/ Dividieren
^ Potenz
% Rest einer Division

addieren

1 + 2 = 3
local a = 1
local b = 2
local result = a + b

print( result )

subtrahieren

3 - 2 = 1
local a = 3
local b = 2
local result = a - b

print( result )

Vorzeichen umkehren

Umkehrung von 3 = -3
local a = 3
local result = -a

print( "original value: " .. a, "inversed value: " .. result )

multiplizieren

2 x 2 = 4
local a = 2
local result = a * a

print( result )

dividieren

10 ÷ 5 = 2
local a = 10
local b = 5
local result = a / b

print( result )

Potenz

Basis 2 hoch 8 (2ⁿ) = 256 (2x2=4x2=8x2=16x2=32x2=64x2=128x2=256)
local a, b = 2, 8
local result = a ^ b -- generate answer

-- break down power function into string result (ignore this code)
function power(i1, i2)
 local val = "" -- clear string
 --+--
 for i = 1, i2 do -- generate the longhand math formula
  if i < i2 then val = val..i1.."*" else val = val..i1 end
 end
 return "which is the equivalent of " .. val
end

print("power value of "..a.." & "..b.." = " .. result, power(a,b))

Rest

Rest von 25 ÷ 4 = 1 (25 ÷ 4 = 6,25; 6 x 4 = 24; 25 minus 24 = 1)
local a = 25
local b = 4
local result = a / b -- 25 ÷ 4 = 6.25
local result_decimal = math.fmod(result, 1) -- 6.25 - 6 = 0.25
local result_rounded = math.modf(result) -- 6.25 - 0.25 = 6
local remainder = a % b -- 0.25 * 4 = 1

print("result: " .. result .. ", decimal: " .. result_decimal .. ", rounded: " .. result_rounded .. ", remainder: " .. remainder)

Tabellen

Tabellen machen Lua so flexibel und einfach: Du legst sie mühelos an und kannst Daten einfügen, entfernen und sortieren. Eine Tabelle besteht meist aus Einträgen mit einem Schlüssel oder einer Indexnummer und einem Wert. Auf die Einträge kannst du auf verschiedene Arten zugreifen.

Tabellen anlegen

Hier siehst du, wie du verschiedene Arten von Tabellen anlegst.

Indextabellen

Variante 1: Indexwerte werden automatisch vergeben
local t = {1, 2, 3, 4, 5, "six", 7, "ate", 9} -- automatically assigns an index number to each value starting from 1
local str = "" -- empty string

for i = 1, #t do -- for 1 to table total, print value of index number
 str = str .. t[i] -- add current table index value to existing text data in the str variable
 if i < #t then str = str .. ", " end -- if i is less than table entry total then insert ", " into the str variable
end

print("table data: " .. str) -- print the table data
print( "table entry total: " .. #(t) ) -- print total of table entries

Variante 2: Indexwerte von Hand vergeben
local t = {

[1] = 1,
[2] = "two",
[3] = 3,
[4] = 2 * 2,
[5] = "five",

}

local str = "" -- empty string

for i = 1, #t do -- for 1 to table total, print value of index number
 str = str .. t[i] -- add current table index value to existing text data in the str variable
 if i < #t then str = str .. ", " end -- if i is less than table entry total then insert ", " into the str variable
end

print("table data: " .. str) -- print the table data
print( "table entry total: " .. #(t) ) -- print total of table entries

Schlüsseltabellen

Schlüssel und Werte
local t = {

["hello world"] = "hello world",
example = "this also works",
camelCaseExample = "this will also work",
snake_case_example = "as will this"

}

for k, v in pairs(t) do
  print(k ..": " .. v)
end

print("\n...but as you can see it does not necessarily return them in the order they were created")

Indexwerte mit Untertabellen

automatisch vergebene Indexeinträge mit Untertabellen, die Schlüssel enthalten
local t = {

{x = 100, y = 300},
{x = 200, y = 111}

}

print("x = " .. t[1].x .. ", y = " .. t[1].y)

Schlüssel mit Untertabellen

Schlüsseltabellen mit indexbasierten Untertabellen, die Schlüssel enthalten
local t_texts = {

["English"] = {
    {txt = "hello world!"},
    {txt = "goodbye world!"}
},

["German"] = {
  {txt = "hallo welt!"},
  {txt = "auf wiedersehen welt"}
}

}

print( t_texts["English"][1].txt )

Weitere Methoden und Tabellenfunktionen

Hier findest du Tabellenfunktionen und fortgeschrittene Methoden für die Arbeit mit Tabellen.

Tabelleneinträge verketten

Einträge ohne Trennzeichen verketten (nur Indextabellen)
local t = {1, 2, 3, 4}

print( table.concat(t) )

Einträge mit Trennzeichen verketten (nur Indextabellen)
local t = {1, 2, 3, 4}

print( table.concat(t, ", ") )

Einträge mit Trennzeichen und festgelegtem Indexbereich verketten (nur Indextabellen)
local t = {1, 2, 3, 4}

print( table.concat(t, ", ", 2, 4) )

Einträge einfügen

einen Wert an Indexposition 2 der Tabelle einfügen (nur Indextabellen)
local t = {"one", "three"}

print("before: " .. table.concat(t, ", ") )

table.insert(t, 2, "two") -- insert "two" into index position 2

print("after: " .. table.concat(t, ", ") )

einen Wert am Ende der Tabelle anhängen (nur Indextabellen)
local t = {"one", "two", "three"}

print("before: " .. table.concat(t, ", ") )

table.insert(t, "four") -- insert "four" at the end of the table

print("after: " .. table.concat(t, ", ") )

einen Schlüssel mit Wert in eine Tabelle einfügen
local t = {

["one"] = 1,
["three"] = 3

}

local str = "" -- init str variable
for k, v in pairs(t) do str = (str .. v .. " ") end -- concatenate data
print("before: " .. str)

t["two"] = 2 -- insert key "two" along with a value into the t table

str = "" -- reset str
for k, v in pairs(t) do str = (str .. v .. " ") end -- concatenate data
print("after: " .. str)

Hinweis: Technisch kannst du Schlüssel und Indexwerte in einer Tabelle mischen. Übersichtlicher ist es aber, wenn du es nicht tust. Indexwerte in Untertabellen einer Schlüsseltabelle oder Schlüssel in Untertabellen einer Indextabelle sind dagegen kein Problem.

Einträge entfernen

einen Eintrag aus der Tabelle entfernen (nur Indextabellen)
local t = {1, 2, 3, 4}

print("before: " .. table.concat(t, ", ") )

table.remove(t, 3) -- remove index value 3 from the table

print("after: " .. table.concat(t, ", ") )

Hinweis: Wenn du in eine Indextabelle einen Eintrag einfügst oder einen entfernst, passt Lua die Indexwerte aller folgenden Einträge automatisch an.

einen Schlüsseleintrag aus der Tabelle entfernen
local t = {

["one"] = 1,
["two"] = 2,
["three"] = 3

}

local str = ""
for k, v in pairs(t) do str = (str .. v .. " ") end -- concatenate data
print("before: " .. str)

t["two"] = nil -- remove keyword from table by setting it as nil

str = ""
for k, v in pairs(t) do str = (str .. v .. " ") end -- concatenate data
print("after: " ..str)

Tabellen sortieren

Indextabelle alphabetisch sortieren (aufsteigend)
local t = {"seven", "ate", "nine", "and", "one", "ran away" }

print("before: " .. table.concat(t, ", ") )

table.sort(t) -- sort alphabetically (ascending a, b, c, etc)

print("after: " .. table.concat(t, ", ") )

Hinweis: Tabellen mit gemischten Daten führen bei table.sort zu einem Fehler. Die Tabelle muss entweder nur Zeichenketten oder nur Zahlen enthalten. Als Ausweg kannst du Zahlen als Zeichenketten einfügen.

Indextabelle alphabetisch sortieren (absteigend)
local t = {"7", "ate", "9", "and", "1", "ran away" }

print("before: " .. table.concat(t, ", ") )

table.sort(t, function(a, b) return a > b end) -- sort alphabetically (descending c, b, a, etc)

print("after: " .. table.concat(t, ", ") )

Einträge zählen

Anzahl der Einträge einer Indextabelle (aktuelle Ebene)
local t = {1, 2, 3, 4}

print( #t )

Anzahl der Einträge einer Schlüsseltabelle (aktuelle Ebene)
local t = {

["one"] = 1,
["two"] = 4/2,
["three"] = 3

}

function countKeys(tbl)
  local keys = 0 -- init keys variable
  for k, v in pairs(tbl) do -- iterate through table
    keys = keys + 1 -- increment key value by 1 per key found
  end
  return keys -- return the total amount of keys found
end

print( countKeys(t) )

Schleifen

Daten in einer Schleife zu durchlaufen, um sie zu lesen oder zu ändern, spart beim Skripten enorm viel Zeit. Lua bietet dafür Zählschleifen mit for, for-Schleifen über Tabellen sowie while- und repeat-Schleifen.

Hinweis: Eine Schleife läuft komplett durch, bevor die nächste Zeile danach ausgeführt wird. In Visionaire heißt das auch: Solange eine Schleife läuft, kann das Spiel kein neues Bild zeichnen. Verwende deshalb nie eine Schleife, um auf etwas zu warten (einen Wert, einen Klick, einen Timer). Nimm dafür einen Event-Handler für die Hauptschleife, setDelay oder das Aktionsteil „Pause“.

Zählschleife

Zählt von einem Startwert bis zu einem Endwert. Ein optionaler dritter Wert legt die Schrittweite fest.

local t = {"Hi,", "my", "name", "is", "Barry."}
local str = ""

for i = 1, #t do -- for variable equals 1 to table total entries do
  str = str .. t[i] -- insert string from table based on current index value into the str variable
  if i < #t then str = str .. " " end -- if i is less than table total then insert white space after each word
end

print(str) -- prints: Hi, my name is Barry.

for i = 10, 1, -2 do -- count down from 10 to 1 in steps of 2
  print(i) -- prints 10, 8, 6, 4, 2
end

ipairs: Indextabellen

ipairs durchläuft eine Indextabelle der Reihe nach ab Index 1 und stoppt beim ersten leeren Eintrag.

local fruit = {"apple", "banana", "cherry"}

for i, name in ipairs(fruit) do
  print(i .. ": " .. name) -- prints 1: apple, 2: banana, 3: cherry
end

pairs: Schlüsseltabellen

pairs durchläuft alle Einträge einer Tabelle, auch die mit Schlüsseln. Die Reihenfolge ist dabei nicht festgelegt.

local player = {name = "Barry", coins = 12, hasKey = true}

for key, value in pairs(player) do
  print(key .. " = " .. tostring(value))
end

while

Wiederholt einen Block, solange eine Bedingung wahr ist. Die Bedingung wird vor jedem Durchlauf geprüft.

local n = 1

while n < 100 do
  n = n * 2
end

print(n) -- prints 128

repeat … until

Wie while, nur wird die Bedingung nach jedem Durchlauf geprüft. Der Block läuft also mindestens einmal.

local roll

repeat
  roll = math.random(1, 6)
until roll == 6

print("finally a six")

break

break beendet die aktuelle Schleife sofort.

local t = {3, 8, 15, 4}

for i = 1, #t do
  if t[i] > 10 then
    print("first value above 10 is at index " .. i) -- prints 3
    break
  end
end

Schleifen über Visionaire-Objekte

Listen aus der Datenstruktur, zum Beispiel die Objekte einer Szene oder die Items eines Charakters, kannst du wie eine Indextabelle durchlaufen.

local objects = game.CurrentScene.Objects

for i = 1, #objects do
  print(objects[i]:getName()) -- prints the name of every object of the current scene
end

for i, item in ipairs(game.CurrentCharacter.Items) do
  print(item:getName()) -- prints every item the current character carries
end

Funktionen

Funktionen sind sehr nützlich: Sie verarbeiten oder berechnen große Datenmengen und geben das Ergebnis zurück. Mit Eingabeparametern kannst du dieselbe Funktion immer wieder verwenden. Das spart Arbeit und macht deine Skripte übersichtlicher.

Funktionen anlegen

Hier siehst du, wie du eigene Funktionen anlegst.

Klassische Funktion

gibt hello world! aus
function hello()
 print("hello world!")
end

hello()

Variable als Funktion

gibt hello world! aus
hello = function()
 print("hello world!")
end

hello()

Funktion mit Parametern

gibt 6 aus
function plus(a, b)
 return a + b
end

print( plus(2, 4) )

Parameter mit Standardwert

gibt 0.5, 5 aus
function divideAndConquer(a, b, c)
  c = c or 1 -- if c equals nil then c equals 1
  return (a / b) * c
end

print( divideAndConquer(2, 4) ) -- 2 ÷ 4 x 1 = 0.5
print( divideAndConquer(2, 4, 10) ) -- 2 ÷ 4 x 10 = 5