Die Inhalte in diesem Wiki wurden seit längerem nicht mehr aktualisiert.

Alternativ gibt es unter https://gmod.de ein deutschsprachiges Forum zu Garry's Mod.

LUA für Beginner Teil 1

Aus GMod Wiki (DE)
Wechseln zu: Navigation, Suche

Vorwort

Ich habe mir letztens 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 heißt, dass ich selbst noch ein Neuling bin (auch wenn ich mich mit C++ beschäftige). Verbesserungsvorschläge sind also immer drin.

Viel Spaß beim Skripten!

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:

lua.gts-stolberg.de

Ansonsten auch mal im Facepunch vorbeischauen.


Something new

Generell ist die Skriptsprache 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

_PlayerSetHealt(player, health)

den Health-Wert auf einen Anderen setzt, dann "fühlt man sich zuhause". Aber neuerdings heißt der Befehl

Player:SetHealth( health )

Jetzt stellt sich die Frage: Was ist das mit dem Doppelpunkt? Ganz einfach: Alles vor dem Doppelpunkt, also Player, nennt man Metatag, und sobald es ein Metatag mit Inhalt ist, ein 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:

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:

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

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:

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.

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 #1

Jetzt schreiben wir unser eigenes Script, welches euch kaum was bringt und nicht mal mit SteamIDs arbeitet, aber die Basis für beispielsweise 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 schon mal 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:

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): 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:

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:

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

Player:IsAdmin()

gibt true aus, wenn Player, bei uns ply, ein Admin ist und false, wenn nicht.

if ply:IsAdmin() then return end

würde heissen: "Wenn "ply" ein Admin ist, dann Beenden." Aber ich habe dort stehen:

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 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: Msg(i .. "\n")

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: 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): Ich bin 50 Jahre alt, und du bist nur 30 Jahre alt.


Fehlt nur noch die Vereinbarung zu einem Konsolenkommando: 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:

// 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: ---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.