Hey!
In diesem Thread möchte ich jenen helfen, die entweder schon in Garry's Mod scripten oder damit anfangen wollen, aber nicht wissen wie.
Ich bitte auch jeden auch selber Eigen-Recherche zu betreiben, da ich nicht alles hier in diesem Guide schreibe(n kann).
Um anzufangen:
GMod verwendet die Scriptsprache Lua und die Library noch um viele Funktionen erweitert. Diese findet man alle hier: http://wiki.garrysmod.com/page/Main_Page.
Das GMod-Wiki ist eine der wichtigsten Seiten, die man braucht, wenn man in Garry's Mod scriptet. Dort stehen alle Funktionen, die euch Garry's Mod zur Verfügung stellt.
Über mich noch ein bisschen:
Ich bin Airfox aka. Konsti. Bin 16 (Stand: 9.2.18) und besuche eine IGS in der 11. Klasse. Ich scripte/programmiere seit über einem Jahr und bin seit ungefähr August 2017 als Dev hier bei EGM tätig. Obwohl ich schon selbst paar Dinge gemacht habe vor EGM, hab ich sehr sehr sehr sehr sehr sehr sehr sehr viel von dem lieben P4sca1 gelernt, der auch mir vieles beigebracht hat. Durch diesen Wissenzuwachs konnte ich auch viel mehr mache und hab auch dadurch selbstständig viel gelernt. Da hat auch das Internet, Google, geholfen.
Zur Struktur des Guides:
Ich werde einzelne Abteile, die ich auch immer wieder updaten werde, in Spoiler packen, damit der am Ende nicht zu lang ist. Es wird auch vorkommen, dass der Main-Beitrag zu groß ist und ich deswegen eine Antwort hier drunter verfassen muss, damit das hier reinpasst. In diesem Fall hinterlass ich einfach ein Link dazu.
Es werden auch immer mehr Abteile kommen.
Zweck des Guides:
Ich möchte Leuten helfen, die Probleme haben mit dem Scripten in Garry's Mod anzufangen oder dabei sind, aber nicht vorankommen. Dieser Guide richtet sich auch an einige Leute hier aus der Community, die schon für Server Scripte erstellen, jedoch immer wieder Probleme haben oder durch einige Tipps Dinge unnötig kompliziert machen.
Also.. lange Rede, kurzer Sinn. Fangen wir mal an:
Programmieroberfläche
Um richtig programmieren zu können, empfehle ich auf jeden Fall eine gute Oberfläche. Dies gilt nicht nur für Lua, sondern auch für alle anderen Sprachen.
Ich persönlich nutze Sublime Text 3, jedoch ist eben so Notepad++ o.Ä. möglich.
Sobald ihr das Programm eurer Wahl installiert habt, solltet ihr den GLua-Syntax bei euch installieren. (https://github.com/FPtje/Sublime-GLua-Highlight für Sublime oder auch via Package Control (STRG+SHIFT+P) -> Install Package -> "GLua" -> "SublimerLinter-contrib-glua")
Sobald ihr das getan habt, können wir zum nächsten Schritt kommen. Sollte es Probleme hier geben, schreibt mich einfach im TS³ an, aber zuerst bitte selbst googeln.
Syntax
Der Syntax einer Sprache ist nichts anderes, als die Rechtschreibung, Grammatik und Satzbau dieser, wohingegen die Semantik die Bedeutung der Ausdrücke ist.
Kommentare
Um strukturiert zu arbeiten, verwendet man Kommentare um den geschriebenen Code zu kommentieren um im späteren Verlauf noch zu wissen, was diese Sequenz eigentlich macht.
In Lua gibt es 2 Arten von Kommentaren:
- einzeilige Kommentare
- mehrzeilige Kommentare
Wie sieht das Ganze in der Praxis aus?
Einzeilige Kommentare:
Sie beginnen mit -- am Anfang des Kommentars und werden nicht beendet. Alles hinter diesen Zeichen wird als Kommentar gewertet.
-- Ich bin ein Kommentar
function() -- Ich bin auch ein Kommentar. Ich kann also auch hinter einer Sequenz stehen.
Mehrzeilige Kommentare:
Dieser Kommentar beginnt mit --[[ und geht solange, bis dieser von ]] beendet wird. Alles dazwischen ist
Variablen und Datentypen
Wie in jeder Programmiersprache, gibt es in Lua ebenso Datentypen. Dadurch, dass aber Lua eine Scriptsprache ist, ist keine Datentypdeklaration nötig.
Dies bedeutet, dass man nicht explizit sagen muss, dass die Variablen nicht eindeutig zu einem Datentypen zuordnen muss. Dies gibt auch einige Freiheiten, später dazu mehr.
Die Frage ist jetzt: Was sind Variablen?
Variablen sind, wie in Mathe, Platzhalter für eine Information. Also eine Variable kann alles sein, soweit in der Theorie. Natürlich gibt es da Begrenzungen, z.B. bei Zahlen oder, dass man 2 unterschiedliche Datentypen nicht vereinen kann. Es sind viele Tricks möglich, die ich auch euch im Laufe des Guides zeigen werde. c:
Was für Datentypen gibt es denn?
- Integer (int) -> ganze Zahlen, wie: 1, 2, 3, 1337
- Float / Double -> Gleitkommazahlen, wie: 1.234, 9.81 (hier ist zu beachten, dass in der Programmierung Kommazahlen mit . angeben werden, also anstatt 9,81 -> 9.81)
- String -> Text, wie: "Lua ist nice", "I bims 1 Dev" (Strings werden immer mit " definiert. Der String fängt mit " an und wird auch damit beendet. Alles dazwischen zählt dazu.)
- Boolean -> Wahrheitswert -> da gibt es nur nur true oder false
- Table (auch Array genannt) -> Ansammlung von mehreren Werten/ Variablen in einer Tabelle
Soviel zur Theorie. Wie sieht das jetzt aber in Lua aus?
Bevor ich das euch zeige, muss ich euch noch erklären, was lokale und globale Variablen sind.
Lokale Variablen werden mit "local" voran definiert und sind nur in der Datei / Sequenz vorhanden. Also, wenn man z.B. in einer neuen Datei eine lokale Variable definiert, ist sie nur in dieser Datei zu finden. Wenn man jedoch diese Variable z.B. in einer Funktion definiert, dann kann sie nur darin, aber nicht ausserhalb, der Funktion genutzt werden.
-- Diese lokale Variable kann jetzt mit einem Wert definiert und abgefragt werden.
local variable
Globale Variablen sind jedoch, wie der Name schon sagt, überall verfügbar. Dies hat zur Folge, dass man ziemlich aufpassen muss, wie man diese nennt. Wenn es vorkommen sollte, dass ein anderes Script diese Variablen überschreibt, ansonsten kann alles kaputt gehen. :x Wie man diese definiert ist schon etwas fortgeschrittener. Ich werd dies hier trotzdem zeigen, aber als Anfänger braucht man das erstmal nicht so sehr, wie lokale Variablen.
-- Haupttable, indem alle anderen Werte / Sektionen gespeichert werden.
-- Config or {} ist dafür gedacht, dass wenn Config schon existiert, das nicht nochmal erstellt werden muss -> auch genannt "caching"
Config = Config or {}
-- Eine weitere Sektion in der Config.
Config.Server = Config.Server or {}
-- Im Table Server in der globalen Table Config wird der Wert "Tollen Namen hier einfügen" mit dem Key "Name" gespeichert
Config.Server.name = "Tollen Namen hier einfügen"
-- Im Table Config wird der Wert mit dem Key "enabled" gespeichert.
Config.enabled = true
So.. jetzt! Wie deklariert man Variablen? Naja.. so spektakulär ist das nicht...
-- Integervariable
local int = 1
-- Float / Double-Variable
local double = 1.23456
-- Stringvariable
local string = "Hallo"
-- Booleanvariable
local bool = true
-- Tablevariable
local table = {1, 2, 3, "String", true}
-- Wenn man oben die Variable schon als local definiert hat, braucht man kein weiteres local um die Variable neu zu definieren!
int = 2
Alles anzeigen
Auswahl - if-Abfragen
Diese Art von Sequenz benutzen wir um Abfragen zu machen. Z.B. ob die Variable int gleich 2 ist. Und wenn ja, dann soll er was machen.
Bevor wir das aber machen können, müssen wir uns was dazu anschauen:
Logische Operatoren:
Diese Operatoren werden in if-Abfragen genutzt um zu bestimmen, ob Dinge wahr oder falsch sind. Dies bedeutet also, dass bei solchen Abfragen immer ein Booleanwert rauskommt, also entweder true oder false.
Logischer Operator - Name | Quellcode in Lua | Beschreibung / Funktion |
Logisches und | and | Um 2 Abfragen miteinander zu verbinden -> beide sind voneinander abhängig wenn ein der Abfragen false ist, dann ist das Endergebnis false (siehe Tabelle darunter) |
Logisches oder | or | Um 2 Abfragen miteinander zu verbinden -> wenn eines der beiden Abfragen true ist, ist die ganze Abfrage true (siehe Tabelle darunter) |
Logisches nicht / Negierung | not | Um den Ausgangswert einer Abfrage umzukehren -> (not true) -> false / (not false) -> true |
Logisches gleich | == | Um Werte abzugleichen (1 == 1) -> true |
größer als | > | Um Werte abzugleichen (2 > 1) -> true |
kleiner als | < | Um Werte abzugleichen (1 < 2) -> true |
größer gleich | >= | Um Werte abzugleichen (2 >= 1 and 2 >= 2) -> true |
kleiner gleich | <= | Um Werte abzugleichen (4 <= 4 and 4 <= 10) -> true |
Logisches ungleich | ~= / != | Um Werte abzugleichen (1 ~= 2 and 1 != 4) -> true |
Logische Und und Oder:
Die nächste Tabelle gilt für die Abfrage: v1 and v2
Diese Tabellen sind dafür da, um zu zeigen, wie das Endergebnis bei dem logischen Und und Oder sich bei verändernden Ausgangsvariablen verändert.
Variable 1 - v1 | Variable 2 - v2 | Endergebnis |
true | true | true |
true | false | false |
false | true | false |
false | false | false |
Die nächste Tabelle gilt für die Abfrage: v1 or v2
Variable 1 - v1 | Variable 2 - v2 | Endergebnis |
true | true | true |
true | false | true |
false | true | true |
false | false | false |
Nachdem wir uns nun die Vergleichsmöglichkeiten angeschaut haben, kommen wir dazu, wie if-Abfragen aufgebaut sind.
Es gibt 2 Arten der Auswahl:
- einseitige Auswahl
- mehrseitige Auswahl
Einseitige Auswahl:
Hier gibt es nur einen "wenn"-Teil. Dies bedeutet, wenn die Bedingung true ist, dann führe den "wenn"-Teil aus. Hier ein Beispiel:
local int1 = 1
local int2 = 10
if int1 < int2 then
print("Die Zahl 1 ist kleiner als die 2. Zahl!")
end
Diese Abfrage würde true ergeben, da 1 kleiner als 10 ist.
Und wie ihr schon gesehen habt, besteht eine einseitige if-Abfrage aus 3 Wörtern:
- if
- then
- end
Das if startet die if-Abfrage. Nach diesem if kommt die Abfrage, die true sein muss, damit der Teil nach dem then kommt.
Alles, was nach dem then bis zum end geht, wird ausgeführt, wenn die Abfrage true ist.
Mehrseitige Auswahl:
In dem Fall, dass man ein "sonst"-Teil braucht, nutzt man die mehrseitige Auswahl. Hier wird aber nochmal unterschieden zwischen "ansonsten falls" und "ansonsten".
Der Unterschied ist einfach, dass bei "ansonsten falls" ebenso eine Abfrage gemacht wird, wenn die erste Abfrage false ist. Der "ansonsten"-Teil ist ohne eine weitere Abfrage und wird ausgeführt, wenn alle anderen Abfragen zuvor false ergeben haben. So sieht das im Code aus:
local int1 = 1
local int2 = 20
local int3 = 10
-- Abfrage
if int1 > int2 and int1 > int3 then
-- "wenn"-Teil
print("Zahl 1 ist die größte Zahl!")
-- "ansonsten falls"
elseif int2 > int3 then
print("Zahl 2 ist die größte Zahl!")
-- "ansonsten"
else
print("Zahl 3 ist die größte Zahl!")
end
Alles anzeigen
Hier kommen 2 neue Wörter dazu:
- elseif
- else
Hier ist noch eine Sache zu beachten: Nach einem elseif kommt die weitere Abfrage und dann wieder ein then, weil man was abgefragt hat. Bei einem else kommt aber kein then, da dort keine Abfrage gemacht wurden ist.
Loops - Wiederholungen
Loops sind Wiederholung, die man nutzt um Sequenzen auszuführen, die mehrmals wiederholt werden müssen. Diese Loops ermöglichen es uns viel Platz zu sparen, indem man nicht den gleichen Code 100x hinschreiben muss.
Es gibt 4 verschiedene Loops in Lua:
- numerisches for
- generic for
- while
- repeat until
Von diesen Arten nutzt ich persönlich nur das generic for, welches auch am meisten benutzt wird. Warum, das seht ihr gleich. Aber ich geh der Reihe nach:
numerisches for
Diese Art wird auch "gezählte Wiederholung" genannt. Wie der Name es schon sagt, wiederholt man eine Sequenz so oft, wie es angegeben wurde. Der Aufbau ist wie folgt:
-- Die Variable ist die Zahl (man nimmt meistens i), die die Zählzahl wird
-- from_number ist die Startnummer
-- to_number ist die Zielnummer
-- step_number ist optional. Dieser Wert gibt an, in welchem Schritt der Loop zähl. Heißt, normal steht da 1. Wenn man 0.5 eingibt, zählt er in 0.5er Schritten. Bei negativen Werten zählt er runter.
for variable = from_number , to_number [, step_number] do
Sequenz()
end
In einem Beispiel würde das wie folgt aussehen:
-- Dieser Loop würde von 1-10 gehen und diese Zahlen ausgeben. Dies würde in 1ner Schritten passieren.
for i = 1, 10 do
print(i)
end
-- Dieser Loop würde bei 10 anfangen und in 0.5er Schritten runterzählen, bis die Zahl 0 erreicht.
for i = 10, 0, -0.5 do
print(i)
end
generic for
Diese Art von Loop wird genutzt um durch Tables durchzugehen. Bevor ich aber zu dem Loop komme, müssen wir uns ansehen, wie Tables aufgebaut sind:
Ein Table ist eine Ansammlung von Werten. Damit man auch mehrere Werte, die auch gleich sein können, in einer Table haben kann, braucht jede Value in einem Table einen Key. Dieser ist eindeutig und es gibt ihn nicht 2 mal. Diese wird entweder automatisch vergeben (dann ist er numerisch) oder man legt ihn selbst fest.
Ein Beispiel für automatische Key-Verteilung wäre:
Der Key von "Value2" wäre 2, da es sich an der 2. Stelle im Table befindet.
Wichtig: Lua fängt in Tables immer mit der 1 an. In anderen Sprachen, wie Java ist der erste Key 0.
Wenn man jedoch Werten ein eigenen Key zuordnen will, dann macht man das so:
local table = {
["key1"] = "value1",
["key2"] = "value2",
["key3"] = "value3",
["key4"] = "value4"
}
Hier wäre der Key von "value2" "key2".
Nachdem wir uns das nun angesehen haben, schauen wir uns den Loop an:
Dieser Loop geht also durch alle Elemente eines Tables durch. key nimmt den Wert des aktuellen Keys und value den Wert des aktuellen Wertes an. Dies sind Variablen, das bedeutet man kann sie auch anders nennen.
Ein Beispiel für diesen Loop:
local healthtable = {
["superadmin"] = 1337,
["admin"] = 200,
["moderator"] = 150,
["user"] = 100
}
for key, ply in pairs(player.GetHumans()) do -- players.GetHumans gibt alle Spieler in einer Table zurück
-- healthtable[key] -> gibt die Value für den eingegebenen Key wieder
-- GetUserGroup() -> gibt den Rang als String wieder, Bsp: "user".
ply:SetHealth(healthtable[ply:GetUserGroup()])
end
Alles anzeigen
while
Dieser Loop wird auch "Wiederholung mit Anfangsbedingung" genannt. Er führt die Sequenz solange aus, bis die Bedingung nicht mehr gegeben ist. (while -> während)
Beispiel zum Verstehen hierzu:
local i = 1
-- Solange i kleiner als 10 ist, führe das nach do aus.
while i < 10 do
print(i)
i = i + 1 -- Zählt i hoch.
end
repeat-until
Anderer Name hierfür: "Wiederholung mit Endbedingung". Dies hat auch zur Folge, dass der Loop mind. 1x ausgeführt wird, auch wenn die Bedingung am Ende false ist.
Beispiel hierzu:
Noch 2 Wörter, die auch wichtig sind:
break - beendet den Loop und es wird mit dem Code weiter gemacht, der nach dem ganzen Loop steht.
continue - überspring den noch folgenden Code im Loop, der in diesem Durchgang noch folgen würde und springt zum nächsten Durchgang
Functions - Funktionen
Was ist eine Funktion?
Nein, nicht die Funktion aus Mathe. Das bedeutet, wir brauchen keine Lösungsformel für quadratische Gleichung, also keine Sorge.
Sie verhält sich, wie eine Variable, das bedeutet, dass man eine Funktion z.B. in einen Table packen kann.
Unter einer Funktion versteht man ein Alias für Code, den man dahinter "versteckt". Dies ist nützlich, wenn man einen bestimmten Code immer wieder und wieder aufruft. Und anstatt diesen Code immer wieder zu schreiben, führt man einfach die Funktion aus, in der dieser Code ist.
Der Vorteil ist, dass es einfach besser aussieht, leichter verständlich und es viel leichter anzuwenden/anzupassen ist.
Wie erstellt man eine Funktion?
-- Funktionen fangen mit function an
-- Danach kommt ein eindeutiger Name.
-- Sofort hinter den Namen ohne Leerzeichen dann Klammern, in denen dann auch später die Parameter stehen, dazu später mehr.
-- Nach den Klammern kommt der Code, der mit der Funktion ausgeführt wird.
-- Dieser Bereich endet mit einem end.
function UniqueName()
-- Code
end
Was sind Parameter?
Parameter sind Variablen, Werte und/oder Funktionen, die bei der Funktion mit übergeben werden, sodass der Code in dieser Funktion diese Variablen, Werte und/oder Funktionen verwenden kann.
Wie übergibt man Parameter?
-- Diese Funktion besitzt die Parameter param1, param2 und param3.
-- Hier wird ebenso keine Datentyp-Definierung vorgenommen, bedeutet, dass diese Parameter jeden Datentyp
-- inne halten könnten.
-- Man kann auch die Parameter von hinter der Reihe nach weglassen.
-- Bedeutet, dass man die Funktion nur mit param1 und param2 aufrufen könnte.
function UniqueName(param1, param2, param3)
-- Code
end
-- Um die Funktion mit Parameter aufzurufen:
-- Die Parameternamen in der ausgeführten Funktion muss nicht mit dem in der Funktion übereinstimmen,
-- es muss nur in der richtigen Reihenfolge sein!
UniqueName(var1, var2, var3)
Alles anzeigen
Das Ganze will ich nochmal in einem Beispiel darstellen:
function AddPlayerHealth(ply, health)
-- Wir überprüfen, ob das angegebene Entity ein Spieler ist (denn auch Spieler sind Entities in Garry's Mod)
-- Falls dies nicht der Fall ist, wird return'ed (darauf geh ich später nochmal ein)
-- Ebenso schaut man, ob überhaupt ein Spieler und der Betrag vom Leben angegeben wurde
if (ply and not ply:IsPlayer()) or not health then return end
ply:SetHealth(health)
end
-- Dies wird die Leben eines Spieler setzen zu dem Betrag, der angegeben wurde.
Jetzt möchte ich erstmal auf das "return" eingehen.
Eine Funktion kann auch etwas zurückgeben, also returnen. Sei es eine Zahl, ein Table oder auch ein Bool. Ebenso wird dies auch genutzt um einfach aus der Funktion rauszuspringen bzw. diese zu stoppen, indem man einfach "return" ohne Werte angibt.
Beispiel hierfür:
function getHighestNumber(num1, num2, num3)
if num1 > num2 then
if num1 > num3 then
return num1
end
return num3
else
if num2 > num3 then
return num2
end
return num2
end
end
Alles anzeigen
Hier gibt man 3 Zahlen an und man bekommt dann am Ende die größte Zahl raus.
Man kann dies entweder durch
print(getHighestNumber(1, 2, 3)
ausgeben lassen, oder man speichert den zurückgegebenen Wert in einer Variable, wie hier:
local hightestNumber = getHighestNumber(1, 2, 3)
Ihr fragt euch doch jetzt auch, warum ich nicht "elseif" verwendet habe, z.B. hier:
Der Grund ist, dass sobald in einer Funktion returned wurde, man aus der Funktion springt und der Code, der unter dem Return steht, wird nicht mehr ausgeführt, unter keinen Umständen.
Hier noch für Funktionen, die z.B. ein Spieler betreffen:
Meta-Table.
Ich greif mal vorraus hier. Man kann, anstatt, dass man in den Parametern den Spieler angibt, einfach vor der Funktion den zugehörigen Table definieren.
Das geht so:
local Player = FindMetaTable("Player")
function Player:addHealth(health)
if not self or health then return end
self:SetHealth(self:GetHealth() + health)
end
Hier wird dieses Objekt, was vor der Funktion als "Parent" gesetzt wird, in der Funktion mit self angesprochen. Was ein Metatable ist, das findet man auch in weiteren Verlauf hier.
Dies ist aber nicht nur mit Metatables möglich, sondern auch mit Tables, die von euch definiert wurden sind. Dies ist nützlich, falls ihr mehrere Funktion mit dem gleichen Namen habt oder ihr einfach wissen wollt zu welchem System das gehört.
Hier ein Beispiel davon:
Bei Fragen, Fehlern o.Ä. einfach bei mir melden!