Scripting

silenceko
Posts: 104
Joined: Mon Feb 13, 2012 9:27 pm

Scripting

Post by silenceko »

Update #1:
Einfaches Script zur Textausgabe auf dem Bildschirm

Einleitung:
Erstmal Hallo an alle Mitleser. Ich würde gerne einen Thread starten, der mir und allen anderen auch das Scripten näher bringt. Es gibt sehr gute Mods für den LS13, jedoch fehlt mir des öfteren etwas Abwechslung in Form eines Wirtschaftskreislaufes oder ähnliches. Ziel soll es hier sein, nach und nach mehr ins Scripten einzusteigen und ein schrittweises lernen bzw. nachvollziehen zu ermöglichen.
Es gibt natürlich Modder, welche ihre Scripte nicht veröffentlichen möchten und diese entsprechend "schützen". Das ist natürlich völlig in Ordnung. Es soll hier auch nicht um das Kopieren vorhandener Scripte gehen, sondern um die Befähigung selbst zu scripten. Wer also hier nicht teilnehmen möchte, muss dies auch nicht machen. Für alle anderen, einschließlich mir, hoffe ich, dass sich hier einige Dinge zusammentragen lassen.
Weiterhin führen viele Wege nach Rom und so lässt sich auch beim Scripten ein Ziel auf verschiedensten Weisen erreichen.
Probleme dürfen hier natürlich auch erörtert werden.
Ich werde auch diesen Thread bei neuen Input auf dem Laufenden halten und diesen ersten Post aktualisieren.
Erklärungen einzelner Codefragmente werden durch Kommentare kenntlich gemacht, welche am doppelten Minus " -- " erkannt werden können.
Da auch ich nicht (oder wohl eher vor allem ich nicht) von Fehlern befreit bin, freue ich mich auf euer Feedback bezüglich Fehlern, aus denen lernt man ja bekanntlich dazu.
Allgemeines:
Zum Anfang benötigt man natürlich eine eingebundene Datei zum scripten, weche in der modDesc.xml eingetragen sein muss, wie z.B.:

Code: Select all

<extraSourceFiles> 
<sourceFile filename="map/scripte/mw.lua" />
</extraSourceFiles> 
Der Name sollte natürlich so gewählt werden, dass ihr etwas damit anfangen könnt und die Datei bei Bedarf wiederfindet.

Programme und wissenswertes:
Zum scripten benutze ich LUAEdit, im Grunde ist das Program aber nicht so wichtig: http://luaedit.sourceforge.net/
Weiterhin ist diese Seite zu empfehlen, da sie Grundkenntnise bezüglich Syntax und Regeln vermittelt: http://lua.coders-online.net/Content/Show/8
Nicht zu vergessen die ScriptDoku: http://www.ls-mods.de/scriptDocumentati ... anguage=de
Und hier die Scriptreferenz für vorhandene Funktionen der LS13 Engine: http://gdn.giants-software.com/document ... ipting.php
Wichtig sind vor allem die Sichtbarkeitsregeln der Variablen: http://lua.coders-online.net/Content/Show/14
Erstes Script zum Rotieren von Objekten:
Wer noch Erklärungen zu den Codefragmenten beisteuern kann, darf dies gerne tun, mich und anderen wird das sicher freuen ;)

Code: Select all

-- Hier wird eine Klasse (engl. Class) erzeugt

Rotation = {} 
-- die geschweiften Klammern erzeugen eine Tabelle (in diesem Fall eine Class), die von uns erzeugte Parameter etc. enthält

local Rotation_mt = Class(Rotation); 
-- der Variable "Rotation_mt" (mt für Metatabelle) weisen wir die Klasse "Rotation" zu, welche wir oben erzeugt haben 

InitObjectClass(Rotation, "Rotation"); 
-- hier initialisieren wir unsere Klasse (starten sie)

-------------------------------------------------------------------------

-- Script aufruf für das GE Objekt
function Rotation.onCreate(id) -- eine eigene Funktion, welche durch ein Objekt im GE gebraucht wird, was wir noch definieren werden, Erklärung siehe unten ***
    g_currentMission:addUpdateable(Rotation:new(id)); -- hier teilen wir der Engine mit, dass wir ein Objekt erzeugen, welches upgedatet werden soll 
    --print("*****************************************"); -- der Befehl print(""); gibt in der Log aus, was zwischen den Anführungszeichen steht
    --print("created Rotationsscript, id: ", id); -- das 2. id steht nicht in Anführungszeichen, es handelt sich um eine Variable die ausgegeben wird
    --print("*****************************************");
end;

g_onCreateUtil.addOnCreateFunction("RotationOnCreate", Rotation.onCreate); 
-- Zitat Bassaddict: [quote]Soweit ich weiß kann man das g_onCreateUtil.addOnCreateFunction() auch weglassen. Dann benutzt man als ScriptCallback halt gleich das, was im Script steht, nämlich Rotation.onCreate.[/quote]

-- initialisierung
function Rotation:new(nodeId) -- nodeId gibt den Index des Objektes an, welches die Funktion aufruft, Erklärung folgt später
    local self = {}; -- eine neue Tabell in der wir Daten sammeln
    setmetatable(self, Rotation_mt); -- hier verknüpfen wir unsere gerade erzeugte Tabelle "self" mit der oben erstellten Klassentabelle "Rotation_mt"
    self.nodeId = nodeId; 
      -- unsere Variable  "nodeId" innerhalb unserer Tabelle "self" (self.nodeId) bekommt den Wert von "nodeId" aus der Klammer des Funktionsaufrufes oben

	self.hrs = Utils.getNoNil(getUserAttribute(self.nodeId, "HorizRotSpeed"), 0);
      -- Drehung für Y-Achse (horizontal)

      -- unsere eigene Variable hrs (hrs = horizontal rotation speed; selbst ausgedacht) liest ein UserAttribute aus, welches wir im GE unserem Objekt geben müssen,
      -- dabei muss der Name dieses Attributes exakt so geschrieben werden, wie es oben in den Anführungszeichen steht
      -- das "self.nodeId" gibt an, an welcher Stelle das Attribut ausgelesen werden soll, es steht nicht in Anführungszeichen, da es eine Variable ist, anders als 
      -- "HorizRotSpeed", denn das ist ein Text den wir beim Objekt suchen

	self.xrs = Utils.getNoNil(getUserAttribute(self.nodeId, "XRotSpeed"), 0); -- Drehung für X-Achse
	self.zrs = Utils.getNoNil(getUserAttribute(self.nodeId, "ZRotSpeed"), 0); .. Drehung für Z-Achse
 
      -- das Utils.getNoNil sorgt dafür, dass definitiv ein Wert übergeben wird, egal ob wir im GE einen eintragen oder nicht, ist kein Wert vorhanden, wird der Letzte dieser 
      -- Zeile genommen, die Null --> dreht sich nicht

         self.rotObj = getChildAt(self.nodeId, 0);
      -- unsere Variable rotObj (rotierendes Objekt) speichert den Index des Objektes, welches wir rotieren möchten, ausgehend vom aufrufenden Objekt
	
	
    return self; -- ist mir unklar warum das hier steht
end;

function Rotation:delete()
end;
--Zitat Bassaddict: [quote]Sachen wie Trigger, Sounds, ParticleSystems und Subclasses muss man da alle manuell löschen lassen. Macht man das nicht gibts entsprechende Fehler in der LOG. Der bekannteste dürfte wohl der "Warning: Deleting object 'XY' before all triggers are removed" sein. Passiert eben genau dann, wenn man erstellte Trigger in der delete() Funktion nicht selbst löscht.[/quote]


-- Update
function Rotation:update(dt) -- update(dt) aktualisiert unser Script pro Frame, dt ist dabei die Zeit in Millisekungen
   
    rotate(self.rotObj, self.xrs * dt, self.hrs * dt, self.zrs * dt);  
      -- rotate ist die Funktion, welche der Engine sagt, was zu tun ist --> nicht selbst ausgedacht, zu finden in der Scriptreferenz
      -- zuerst wird das zu rotierende Objekt angegeben, dann die Rotation um die X-Achse (gespeichert in der Variablen, welche den Wert aus dem "GE" ausliest),
      -- dann die Y-Achse (die horizontale) und dann die Z-Achse, jede Variable wird mit "dt" (also der Zeit in ms) multipliziert --> Grad pro Millisekunde

end;
Damit alles Funktioniert benötigt man nun noch die richtigen Einstellungen im GE:
Zunächst benötigt man eine TG (Transformgroup), welche die UserAttribute enthält, Name der TG ist egal.
Innerhalb dieser TG befindet sich das Objekt welches rotiert wird. BEACHTE: Es wird nur das erste Objekt rotiert, weil wir den Index 0 im Script angeben.

Die UserAttribute müssen exakt wie oben geschrieben werden (XRotSpeed, ZRotSpeed und HorizRotSpeed)
Datentypen können hier sein: float (Kommazahl), String (Zeichenkette) oder sogar Integer (Ganzzahl).
Aufgrund der Umdrehungsgeschwindigkeit (Grad pro Millisekunde) würde sich das Objekt rasend schnell drehen, würde man 1 eintragen. Es empfiehlt sich also eine Kommazahl oder einen String zu benutzen, um die Rotationsgeschwindigkeit feiner einstellen zu können.

Diese TG benötigt außerdem den Scriptaufruf, damit unsere Funktionen "gestartet" werden. Benannt wird er "onCreate", Typ = "script callback" und eingetragen werden muss:
"modOnCreate.RotationOnCreate", ohne Anführungszeichen. Diese Bezeichnung hatten wir oben im Script schon angegeben. Auch hier gilt das Beachten der exakten Schreibweise. Kenntlich gemacht ist dies mit den Sternchen ***

Was kann man damit machen? Zum Beispiel:
- Rotoren drehen lassen, an Windrädern oder ähnliches
- Eine Drehscheibe für den Shop erstellen, auf der ein Auto steht
- ...
Das war es mit dem ersten Script.

Script 2:
Mit diesem Script soll ein Text auf dem Bildschirm ausgegeben werden:

Code: Select all



textausgabe = {};
local textausgabe_mt = Class(textausgabe);
InitObjectClass(textausgabe, "textausgabe");

-- hier haben wir wieder unsere Klasse erzeugt

function textausgabe:loadMap(name)
	--print("############# Script Textausgabe erzeugt");
end;

-- mit der Funktion loadMap(name) geben wir an, dass unser Script beim Laden der Map ebenfalls mit geladen wird, es wird also sofort ausgeführt

function textausgabe:deleteMap() 
end;
-- da wir das Script mit Mapstart gestartet haben, löschen wir das Script auch beim Beenden der Map

function textausgabe:keyEvent()
end;
-- diese Funktion registriert Tastatureingaben, wir benötigen sie zwar nicht, müssen sie aber hier mit einfügen, Erklärung folgt ***

function textausgabe:mouseEvent()
end;
-- diese Funktion registriert Mausbewegungen und -eingaben. Hier gilt das gleiche wie mit der oberen Funktion ***

function textausgabe:update()
end;
-- diese bekannte Funktion benötigen wir ebenfalls nicht ***

function textausgabe:draw() 
-- diese Funktion benötigen wir zur Textausgabe

string = "Ein Text zum Probeausgeben"; 
-- die Variable "string" enthält den Text, den wir auf dem Bildschirm ausgeben möchten

renderText(0,0.05,0.02,string);
-- die Funktion "renderText" gibt unseren Text aus. Die Parameter sind: (X-Position [von 0 bis 1] , Y-Position [von 0 bis 1], Schriftgröße, Ausgabetext)

end;

addModEventListener(textausgabe); -- ***
-- diese Zeile benötigen wir, der Grund ist mir noch unklar.
-- Anscheinend sucht diese Zeile nach sämtlichen Veränderungen und Eingaben, weswegen wir die ganzen überflüssigen Funktionen angeben müssen, da sonst Fehlermeldungen entstehen. Die Engine weis sonst wohl nicht wie mit etwaigen Eingaben etc. umzugehen ist
-- Ohne diese Zeile wird auf dem Bildschirm aber auch nichts angezeigt. 
bis denne ;)
Last edited by silenceko on Thu Feb 20, 2014 8:11 pm, edited 3 times in total.
User avatar
bassaddict
GIANTS Software | Web/Script Programmer
Posts: 11983
Joined: Tue Nov 29, 2011 2:44 pm
Location: ER, DE

Re: Scripting

Post by bassaddict »

Es wäre gerade für Anfänger sehr hilfreich, wenn man den ganzen extraSourceFiles-Eintrag posten würde, anstatt nur die eine sourceFile Zeile.

Ich empfehle Notepad++. Funktioniert mit LUA, funktioniert mit XML, funktioniert mit i3d... Ein Programm für alles ist dann sinnvoll, wenn es für alles gut funktioniert. Das ist bei Notepad++ der Fall, kein Grund sich noch irgendwelche anderen Programme zu installieren.

Syntax und Regeln... Dein Link ist für Anfänger eher ziemlich unbrauchbar. Das was da drin steht nennt sich Grammatik und wurde bei mir im Informatikstudium im ersten Semester ausführlich behandelt. Und soviele Informatikstudenten, die da wirklich was mit anfangen können wirds hier wohl kaum geben.

Wenns um scripten lernen geht sollte nicht nur die ScriptDocu verlinkt werden, sondern auch die Scripting Reference im GDN. http://gdn.giants-software.com/document ... ipting.php

Sichtbarkeitsregeln wichtig? Hab ich ehrlich gesagt noch nie wirklich gebraucht.





Soweit ich weiß kann man das g_onCreateUtil.addOnCreateFunction() auch weglassen. Dann benutzt man als ScriptCallback halt gleich das, was im Script steht, nämlich Rotation.onCreate.

Guck mal in die onCreate Funktion, dann kannst du sehen, wieso new() die angelegte Tabelle zurückgibt. Schließlich brauchst du ja irgendein Teil, was du updaten kannst. Und in new() hast du dafür alle benötigten Daten dafür gesammelt.

Grad/ms beim drehen ist wohl eher für extrem schnelle Drehungen gedacht. Wenn man etwas "gemütlich" drehen lassen will muss man entweder unnötig aufwendig kopfrechnen, oder gleich sinnvollerweise bei der Drehung Grad/s verwenden. Also die Parameter nochmal mit 0.001 multiplizieren.

Das delete() hast du aber etwas kurz erklärt. Nochmal etwas ausführlicher: Sachen wie Trigger, Sounds, ParticleSystems und Subclasses muss man da alle manuell löschen lassen. Macht man das nicht gibts entsprechende Fehler in der LOG. Der bekannteste dürfte wohl der "Warning: Deleting object 'XY' before all triggers are removed" sein. Passiert eben genau dann, wenn man erstellte Trigger in der delete() Funktion nicht selbst löscht.



Soweit mein Feedback. Vielleicht hab ich noch was übersehen oder vergessen, aber zumindest hast du jetzt wieder was zum ergänzen und nachdenken^^
silenceko
Posts: 104
Joined: Mon Feb 13, 2012 9:27 pm

Re: Scripting

Post by silenceko »

Ja das hab ich, vor allem weil ich kein Informatikstudium habe, lediglich etwas Schul- und Ausbildungserfahrung. Dort war das aber alles von Grund auf erklärt und nachvollziehbarer als sich hier in das bestehende System einzuarbeiten.
User avatar
bassaddict
GIANTS Software | Web/Script Programmer
Posts: 11983
Joined: Tue Nov 29, 2011 2:44 pm
Location: ER, DE

Re: Scripting

Post by bassaddict »

Da fällt mir gerade noch ein sehr gutes Anfängertutorial ein, was ich vor einiger Zeit mal gesehen habe: http://lua.gts-stolberg.de/index.php
Wer das alles versteht, der wird auch in der Lage sein sich die LS spezifischen Sachen selbst beizubringen. Wer das nicht versteht, dem wird man auch die LS spezifischen Sachen nicht beibringen können. Wohl übrigens eine der Hauptgründe, wieso man kaum Scripting-Tutorials für LS findet.
silenceko
Posts: 104
Joined: Mon Feb 13, 2012 9:27 pm

Re: Scripting

Post by silenceko »

Ich habe dem ersten Post ein Script zur Textausgabe auf den Bildschirm hinzugefügt
Post Reply