Lua Beginner's Guide - Part One - by ComodoFox alias Scarecrow
Vorwort:
Ich habe mir letztens (Jahr 2007) Garry's Mod 10 gekauft und wollte natürlich direkt
mit Lua beginnen, also suchte ich nach Tutorials... und suchte... und suchte...
und fand nichts, außer einfachen Basics von Lua. Über den Einstieg in Garry's Mod
Programmierung fand ich leider so gut wie nichts. Also begab ich mich auf den
eisernen Weg des "Learning By Doing" und will euch meine Erkenntnisse nicht
vorenthalten. Das heisst, dass ich selbst noch ein Neuling bin (auch wenn ich mich
mit C++ beschäftige). Verbesserungsvorschläge sind also immer drin ;-)
PS: Mitllwerweile kann ich es exellent, das Ganze Geschwafle oben ist ein Jahr alt
Viel Spaß beim Scripten!
Basics:
Bevor ihr euch dieses Tutorial durchlest, solltet ihr schon einmal die Basics von
Lua drauf haben. Die könnt ihr euch z.B. hier durchlesen:
http://lua.gts-stolberg.de/
Ansonsten auch mal im Facepunch vorbeischauen :-)
Something new:
Generell ist die Scriptsprache Lua eine einfache Sprache. Jedoch machen die
Implementationen mit Games und Programmen aus Lua einen fetten Schinken,
bei dem der Eine schnell den Überblick verlieren kann. So gibt es insbesondere hier
einiges zu erwähnen:
Funktionen - Jeder kennt sie (siehe Basics), die Funktionen, und wenn man schon
daran denkt, dass
Code:
_PlayerSetHealt(player, health)
den Health-Wert auf einen Anderen setzt, dann "fühlt man sich zuhause". Aber
neuerdings heisst der Befehl
Code:
Player:SetHealth( health )
Jetzt stellt sich die Frage: Was ist das mit dem Doppelpunkt?
Ganz einfach: Alles vor dem Doppelpunkt, also Player, nennt man (glaube ich)
Objekt. Ein Objekt ist damit also wie eine Variable. Ich habe mir sagen
lassen, dass Garry dieses wegen der leichten Schreibweise eingebaut hat.
So kann man z.B. mit einem Befehl, der einen Spieler ausgibt folgendes bauen:
Code:
spieler = FunktionGibtSpielerAus()
spieler:SetHealth( 200 )
Die Variable spieler wird zu dem, was FunktionGibtSpielerAus() ausgibt, z.B. "ComodoFox".
Dann wird mit spieler:SetHealth( 200 ) dem Player "ComodoFox" ein Healthwert von
200 gegeben, weil wir Player durch einen Namen eines Spielers ersetzt haben, nämlich
durch spieler und weil diese Variable den Inhalt trägt, den FunktionGibtSpielerAus() ausgegeben hat,
sieht das im Endeffekt so aus:
Code:
ComodoFox:SetHealth( 200 )
Aber das macht man nicht, deshalb definiert man ein Objekt, weil der Inhalt dynamisch, veränderbar, wird ;-)
Hooks - Wie der name schon vermuten lässt, sind Hooks einfach nur Haken, die an etwas
ziehen. Jetzt mal genauer: Ein Hook ist eine bestimmte Situation im Spiel, die dann
eine Funktion aktiviert. Z.B. ist mit dem Hook GM:PlayerConnect die Situation eines
Connects verbunden. Wenn jemand connected, dann wird jede Funktion ausgeführt, die
mit GM:PlayerConnect in Verbindung gebracht wurde.
Diese Verbindung entsteht durch den Befehl
Code:
hook.Add(name, unique_name, function)
wenn wir uns die Funktion GibDemAffenZucker() gebaut haben und sie mit dem Connecten eines
Players verbinden wollen, dann lautet der Befehl:
Code:
hook.Add("PlayerConnect", "GibDemAffenZuckerHook", GibDemAffenZucker)
name ist der Name des Hooks, den wir in Verbindung setzen wollen, also mit GM:PlayerConnect(gm weglassen).
unique_name ist der einzigartige Name unseres neuerstellten Hooks. Ich habe ihn liebevoll
GibDemAffenZuckerHook genannt.
Und function ist die Funktion, die wir auslösen wollen, wenn der Hook zutrifft.
Wenn ein Spieler connected, wird automatisch die Funktion GibDemAffenZucker() ausgeführt.
TIPP: Funktionen und Hooks findet ihr im englischsprachigen Gmod.Wiki, leider ist mir der Link
entfallen, obwohl ich da jeden Tag drauf bin, mein guter, alter Freund Goo Gell (www.google.de) kann euch
aber helfen =D
Server vs. Client - Früher war alles einfach: Luascript fertig gemacht, auf Server gepackt, funktioniert.
Aber es gab einen entscheidenden Nachteil: Alles, was im Script ist, und damit auch die Berechnung von
Sachen wie Farben, Modellen, etc, musste auf dem Server berechnet werden.
In Gmod10 gibt es Serverside Scripts und Clientside Scripts. Es gibt kein Muss, aber wenn ihr
Sachen, die berechnet werden müssen (Farben, Modelle, Effekte etc.) dem Clienten überlasst,
dann muss der Server nicht so viel berechnen (Nur Kollisionen, Standorte etc.) und hat damit
einen besseren Ping. In Zukunft werdet ihr also ein wenig darauf achten, aber alles zu seiner
Zeit ;-)
Jetzt wird es Zeit für ein echtes, kleines Script...
Scripting Example No. 1:
Jetzt schreiben wir unser eigenes Script, welches euch kaum was bringt und nicht mal mit
SteamIDs arbeitet, aber die Basis für beispielweise RP-Scripts ist.
Stellt euch vor, ihr habt einen eigenen Server, und ihr möchtet erfahren, welche Spieler
auf eurem Server bereits wahren. Die Theorie ist also folgende:
Wenn ein Spieler euren Server betritt, wird geprüft, ob er schonmal hier war, seit dem
der Server gestartet wurde. War er schon mal da, passiert nichts, war er noch nicht da,
dann wird er in eine Liste eingetragen, die vorübergehend existiert. Mit einem
Konsolenkommando, welches nur der Admin benutzen kann, wird die Liste in der Konsole ausgegeben.
Jetzt die Umsetzung:
Zunächst einmal benötigen wir die Funktion, die überprüft, ob der Spieler, der connected ist,
schon einmal da war. Wenn nicht, soll sie ihn eintragen:
Code:
playertab = {}
function PlayerConnects( ply, adr, steam )
local vorhanden = 0
//Wenn es den Player in der Liste gibt, dann wird er nachher nicht eingetragen
for i,v in pairs(playertab) do
if i == ply then
vorhanden = 1
return
end
end
//Gibt es ihn nicht, wird er in die Liste eingetragen
if vorhanden == 0 then
playertab[ply] = 1
end
end
playertab = {} definiert einen Table (siehe Basics), den wir später brauchen.
Dann wird eine Funktion namens PlayerConnects definiert, die 3 Argumente hat. ply ist der Spieler,
adr die Adresse (brauchen wir überhaupt nicht) und steam die SteamID (ich hatte bei SteamIDs
letztens einen Fehler). Wichtig ist hier ply, denn dort wird später der Name des Spielers gespeichert.
Wahrscheinlich fragt ihr euch, warum ich die Argumente in meine Funktion einbaue, obwohl ich sie
garnicht brauche? Einfach deshalb, weil der Hook diese Argumente beim Aktivieren ausgibt, und die
müssen sozusagen auch in der Funktion untergebracht sein.
local vorhanden = 0 definiert eine lokale Variable, die später für etwas nützlich wird.
Und jetzt kommt eine for-Schleife (siehe Basics). Mit ihr wird in i der Tablename(Nummer der Tables), z.B.
"ComodoFox" gespeichert und in v der Inhalt, der eigentlich egal ist.
Dann wird geprüft, ob i dem Namen des gerade angekommenen Spielers, ply, entspricht.
Dass wird jetzt durch die For-Schleife mit jedem Namen in playertab getan.
Wenn es einen Namen gibt, der dem gerade connectendem Spieler entspricht, wird der Variable
vorhanden der Wert 1 zugewiesen und durch return wird die Schleife beendet.
Jetzt geht die Funktion weiter, indem geprüft wird, ob vorhanden gleich eins ist.
Wir erinnern uns daran, dass hier 1 für vorhanden und 0 für nicht vorhanden steht.
Der Befehl playertab[ply] = 1, der einem Table den Namen unseres Players gibt und den Wert
eins zuweist (der Wert ist unwichtig, der Name zählt), wird nur ausgeführt, wenn vorhanden
auf null gesetzt ist. Es bedeutet, dass vorhin in der Liste kein Spieler mit diesem Namen
gefunden wurde.
Jetzt setzen wir die Funktion mit einem Hook in verbindung (schreibt das unter die Funktion):
Code:
hook.Add("PlayerConnect", "PlayerConnectsServer", PlayerConnects)
"PlayerConnect" ist der Hook, vergesst die Anführungszeichen nicht ;-)
"PlayerConnectsServer" ist mein selbst definierter Name für den eigenen Hook.
PlayerConnects ist einfach nur der Name unserer Funktion, aber ohne ().
Das speichert ihr nun in:
Code:
steamapps\[STEAMACCOUNTNAME]\garrysmod\garrysmod\lua\autorun\server\spieler_check.lua
Alle Scripts in diesem Ordner werden automatisch auf dem Server ausgeführt, wenn er gestartet wird.
Wir sind aber noch lange nicht fertig, denn uns fehlt ja noch das versprochene Konsolenkommando:
Code:
function GiveOutPlayerTab( ply, command, args )
if !ply:IsAdmin() then return end
Msg("\n\n---Players in list---\n")
Msg("------------------------------\n\n")
for i,v in pairs(playertab) do
Msg(i .. "\n")
end
Msg("\n------------------------------\n")
end
Ich definiere hier die Funktion GiveOutPlayerTab mit drei Argumenten. ply ist der Spieler, der das
Konsolenkommando auslöst. command habe ich vergessen (im Gmod.Wiki unter concommand nachschauen,
wenn es wieder online ist) und args sind die Argumente, die beim Konsolenkommando übergeben werden.
Z.B. sv_cheats 1 - eins wäre ein Argument (tolles Beispiel =D).
Diese drei Argumente sind vorausbestimmt, denn wenn wir die Funktion mit einem Konsolenkommando in
Verbindung bringen, dann werden diese Argumente der Funktion übergeben, ähnlich wie bei den Hooks.
Dann wird durch die IF-Anweisung geprüft, ob der Spieler ply, der dieses Kommando aufruft, überhaupt
ein Admin ist. Der Befehl
gibt true aus, wenn Player, bei uns ply, ein Admin ist und false, wenn nicht.
Code:
if ply:IsAdmin() then return end
würde heissen: "Wenn ply ein Admin ist, dann Beenden." Aber ich habe dort stehen:
Code:
if !ply:IsAdmin() then return end
Das Ausrufezeichen macht den Befehl negativ, also geht von ungleich eins aus, denn ihr
müsst wissen, dass true für 1 und false für 0 steht. Mit dem Ausrufezeichen geht man
also auf das Gegenteil von true ein, hiese also: "Wenn ply kein Admin ist, dann Beenden."
Man könnte also auch
Code:
if ply:IsAdmin() == 0 then return end
schreiben oder 0 gegen false ersetzen, aber mit dem Ausrufezeichen geht alles einfacher.
Die nächsten zwei Befehle sind Msg("string"). Diese geben etwas in der Konsole aus, nützlich
für Debugging. Und vergesst das Newline-Zeichen \n nicht, oder wollt ihr alles in einer
einzigen Zeile haben? ;-)
Wichtig ist dort wieder die for-Schleife. Sie geht wie immer durch alle Inhalte des genannten
Tables playertab und führt ihre Befehle im Block aus:
i steht für den derzeitigen Tablenamen, z.B. "ComodoFox" (playertab["ComodoFox"]) und gibt ihn aus.
Danach folgt ein Newline-Zeichen für den nächsten Namen. Die zwei Punkte sind zur Abgrenzung von
Variablen und Strings da bzw. für alle Abgrenzungen. Beispiel:
Code:
Msg("Ich bin " .. i .. " Jahre alt, und du bist nur " .. j .. " Jahre alt.\n")
Das würde dann folgendes ausgeben (wenn i 50 und j 30 sind):
Code:
Ich bin 50 Jahre alt, und du bist nur 30 Jahre alt.
Fehlt nur noch die Vereinbarung zu einem Konsolenkommando:
Code:
concommand.Add( "check_player_tab", GiveOutPlayerTab )
Das erste Argument ist der String, wie unsere Funktion nachher aufgerufen wird.
Das Zweite ist dann die Funktion, ohne "". Ein drittes Argument gibt es auch, die
Autovervollständigung. Wenn man das Kommando ohne Argumente eingibt, dann werden
die einzugebenden Argumente dargestellt. Aber das ist nur optional und total unnütz,
wenn euer Kommando keine Argumente übergeben soll.
Das ist dann das fertige Script:
Code:
// Lernscript von ComodoFox alias Scarecrow (im Gmod.de-forum)
// Gebraucht: 2 Funktionen (Connect und Disconnect) mit 2 Hooks verbunden
//Wichtige Variablen
playertab = {}
//Für den Hook bei Connecten eines Players
function PlayerConnects( ply, adr, steam )
local vorhanden = 0
//Wenn es den Player in der Liste gibt, dann wird er nachher nicht eingetragen
for i,v in pairs(playertab) do
if i == ply then
vorhanden = 1
return
end
end
//Gibt es ihn nicht, wird er in die Liste eingetragen
if vorhanden == 0 then
playertab[ply] = 1
end
end
//Der entgültige Hook
hook.Add("PlayerConnect", "PlayerConnectsServer", PlayerConnects)
function GiveOutPlayerTab( ply, command, args )
if !ply:IsAdmin() then return end
Msg("\n\n---Players in list---\n")
Msg("------------------------------\n\n")
for i,v in pairs(playertab) do
Msg(i .. "\n")
end
Msg("\n------------------------------\n")
end
concommand.Add( "check_player_tab", GiveOutPlayerTab )
Schön abspeichern und testen: Macht ein Singleplayer-Spiel (oder Multplayer) auf und gebt in die
Konsole check_player_tab ein. Euch wird, wenn alles funktioniert, eine Liste mit allen Spielern
angezeigt, die seit dem ihr euren neuen Server gestartet habt, connected sind. Bei mir kam das heraus:
Code:
---Players in list---
------------------------------
ComodoFox
------------------------------
TIPPS:
- Ihr könnt auf diese Art und Weise auch Roleplay Scripts bauen, dann aber mit SteamID
in eine Datei speichern.
- Statt dem Namen könnt ihr auch die SteamID nutzen, aber auf LAN-Servern bringt euch das
wenig.
- Hooks, die mit PlayerConnect verbunden werden, sind gefährlich. Denn wenn ihr einen Listenserver
erstellt, connected ihr, wenn noch nicht einmal alle Funktionen geladen wurden. Deshalb erhaltet
ihr vielleicht NIL VALUEs in der Konsole. In diesem beispiel habe ich deshalb Konsolenkommandos
genutzt, weil sie erst nach dem Connecten benutzbar sind.
- Vergesst nicht, zu kommentieren. Wenn ihr viel kommentieren müsst, lasst es sein. Schreibt lieber
das Script einfacher ;-)
- Benutzt das englische Gmod.Wiki. Dort steht alles, was ihr braucht.
Wenn ihr Fragen habt, dann fragt!
Bis zum nächsten Teil ;-)