This translation is community contributed and may not be up to date. We only maintain the English version of the documentation. Read this tutorial in English
In diesem Tutorial beginnen wir mit einem leeren Projekt und entwickeln ein vollständiges Runner-Spiel mit einer animierten Figur, physikalischen Kollisionen, Sammelobjekten und einem Punktesystem.
Beim Erlernen einer neuen Game-Engine gibt es viel aufzunehmen. Deshalb haben wir dieses Tutorial erstellt, um dir den Einstieg zu erleichtern. Es ist ein recht umfassendes Tutorial, das dir zeigt, wie die Engine und der Editor funktionieren. Wir setzen voraus, dass du bereits etwas Erfahrung mit dem Programmieren hast.
Wenn du eine Einführung in die Lua-Programmierung brauchst, lies unser Handbuch zu Lua in Defold.
Falls dir dieses Tutorial für den Anfang etwas zu umfangreich erscheint, sieh dir unsere Tutorial-Seite an. Dort findest du eine Auswahl an Tutorials mit unterschiedlichen Schwierigkeitsgraden.
Wenn du lieber Video-Tutorials ansiehst, schau dir die Videoversion auf YouTube an.
Wir verwenden Spiel-Assets aus zwei anderen Tutorials und nehmen daran einige kleine Änderungen vor. Dieses Tutorial ist in mehrere Schritte unterteilt, von denen uns jeder dem fertigen Spiel ein gutes Stück näherbringt.
Am Ende entsteht ein Spiel, in dem du eine Heldenfigur steuerst, die durch eine Umgebung läuft, Münzen sammelt und Hindernissen ausweicht. Die Heldenfigur läuft mit einer festen Geschwindigkeit. Der Spieler steuert nur ihre Sprünge, indem er eine einzige Taste drückt (oder auf einem Mobilgerät den Bildschirm berührt). Das Level besteht aus einem endlosen Strom von Plattformen, auf die du springen kannst, und Münzen zum Einsammeln.
Wenn du an irgendeiner Stelle dieses Tutorials oder beim Erstellen deines Spiels nicht weiterkommst, zögere nicht, uns im Defold-Forum um Hilfe zu bitten. Im Forum kannst du dich über Defold austauschen, das Defold-Team um Hilfe bitten, sehen, wie andere Spieleentwickler ihre Probleme gelöst haben, und neue Anregungen finden. Leg jetzt los.
Ausführliche Beschreibungen von Konzepten und bestimmten Arbeitsschritten sind im gesamten Tutorial so markiert wie dieser Absatz. Wenn dir diese Abschnitte zu sehr ins Detail gehen, überspringe sie.
Fangen wir also an. Wir hoffen, dass dir dieses Tutorial viel Spaß macht und dir beim Einstieg in Defold hilft.
Lade die Assets für dieses Tutorial hier herunter.
Lade als ersten Schritt die folgenden Dateien herunter.
Wenn du den Defold-Editor noch nicht heruntergeladen und installiert hast, ist jetzt der richtige Zeitpunkt dafür:
Rufe die Defold-Downloadseite auf. Dort findest du Download-Schaltflächen für macOS, Windows und Linux (Ubuntu):

Um den Editor zu starten, öffne deinen Ordner “Applications” und doppelklicke auf die Datei “Defold”.

D:\Defold). Du solltest Defold nicht nach C:\Program Files (x86)\ oder C:\Program Files\ verschieben, da dies die Aktualisierung des Editors verhindert.Um den Editor zu starten, öffne den Ordner “Defold” und doppelklicke auf die Datei “Defold.exe”.

Suche im Terminal die Archivdatei “Defold-x86_64-linux.zip” und entpacke sie in ein Zielverzeichnis namens “Defold”.
$ unzip Defold-x86_64-linux.zip -d Defold
Um den Editor zu starten, wechsle in das Verzeichnis, in das du die Anwendung entpackt hast, und führe dann die ausführbare Datei Defold aus oder doppelklicke auf deinem Desktop darauf.
$ cd Defold
$ ./Defold
Unter dem Menüpunkt Help > Create Desktop Entry findest du eine Hilfsfunktion zum Installieren eines Desktop-Eintrags.
Falls beim Starten des Editors, beim Öffnen eines Projekts oder beim Ausführen eines Defold-Spiels Probleme auftreten, lies den Linux-Abschnitt der FAQ.
Jede Betaversion und jede stabile Version von Defold ist auch auf GitHub verfügbar.
Nachdem der Editor installiert und gestartet ist, kannst du ein neues Projekt erstellen und vorbereiten. Erstelle ein neues Projekt mit der Vorlage “Empty Project”.
Dieses Tutorial verwendet Spine-Funktionen aus der Spine-Erweiterung. Füge die Erweiterung zum Abschnitt für Abhängigkeiten in game.project hinzu.
Beim ersten Start ist der Editor leer, ohne geöffnetes Projekt. Wähle deshalb Open Project im Menü und öffne dein gerade erstelltes Projekt. Du wirst außerdem aufgefordert, einen „Branch“ für das Projekt zu erstellen.
Im Bereich Assets pane siehst du nun alle Dateien, die zum Projekt gehören. Wenn du auf die Datei “main/main.collection” doppelklickst, wird sie in der Editoransicht in der Mitte geöffnet:

Der Editor besteht aus den folgenden Hauptbereichen:
print() und pprint() ausgeben. Wenn deine App oder dein Spiel nicht startet, solltest du zuerst die Konsole prüfen. Hinter der Konsole befinden sich mehrere Tabs mit Fehlerinformationen sowie ein Kurveneditor, der beim Erstellen von Partikeleffekten verwendet wird.Die Projektvorlage “Empty” ist tatsächlich vollständig leer. Wähle trotzdem Project ▸ Build, um einen Build des Projekts zu erstellen und das Spiel zu starten.

Ein schwarzer Bildschirm ist vielleicht nicht besonders aufregend, aber dahinter läuft eine Defold-Spielanwendung, die wir leicht zu etwas Interessanterem machen können. Machen wir das also.
Der Defold-Editor arbeitet mit Dateien. Wenn du im Bereich Assets pane auf eine Datei doppelklickst, öffnest du sie in einem geeigneten Editor. Anschließend kannst du den Inhalt der Datei bearbeiten.
Wenn du mit dem Bearbeiten einer Datei fertig bist, musst du sie speichern. Wähle dazu File ▸ Save im Hauptmenü. Der Editor weist auf ungespeicherte Änderungen hin, indem er im Tab der betreffenden Datei ein Sternchen ‘*’ an den Dateinamen anhängt.

Bevor wir beginnen, nehmen wir einige Projekteinstellungen vor. Öffne das Asset game.project im Assets Pane und scrolle zum Abschnitt Display. Setze width und height des Projekts auf 1280 beziehungsweise 720.
Außerdem musst du die Spine-Erweiterung zum Projekt hinzufügen, damit wir die Heldenfigur animieren können. Füge eine Version der Spine-Erweiterung hinzu, die mit deiner installierten Version des Defold-Editors kompatibel ist. Die verfügbaren Spine-Versionen findest du hier:
https://github.com/defold/extension-spine/releases
Klicke mit der rechten Maustaste auf den Link zur ZIP-Datei der Version, die du verwenden möchtest:

Füge den Link zur Version zu deiner Liste der Abhängigkeiten in game.project hinzu. Nachdem du die Spine-Erweiterung hinzugefügt hast, musst du den Editor neu starten, um die mitgelieferte Editorintegration zu aktivieren.
Machen wir die ersten kleinen Schritte und erstellen eine Spielfläche für unsere Figur, genauer gesagt ein Stück scrollenden Boden. Dazu gehen wir in wenigen Schritten vor.
Ein Atlas ist eine Datei, die mehrere einzelne Bilder in einer größeren Bilddatei zusammenfasst. Das spart Speicherplatz und verbessert die Leistung. Mehr über Atlanten und andere Funktionen für 2D-Grafik erfährst du in der Dokumentation zur 2D-Grafik.


Warum funktioniert das nicht!? Ein häufiges Problem beim Einstieg in Defold ist, dass das Speichern vergessen wird! Nachdem du Bilder zu einem Atlas hinzugefügt hast, musst du die Datei speichern, bevor du auf die Bilder zugreifen kannst.
Erstelle für den Boden eine Datei ground.collection für eine Sammlung (collection) und füge ihr 7 Spielobjekte (game objects) hinzu. Klicke dazu in der Ansicht Outline mit der rechten Maustaste auf den obersten Knoten der Sammlung und wähle Add Game Object. Nenne die Objekte “ground0”, “ground1”, “ground2” usw., indem du die Eigenschaft Id in der Ansicht Properties änderst. Beachte, dass Defold neuen Spielobjekten automatisch einen eindeutigen Bezeichner zuweist.
Füge jedem Objekt eine Sprite-Komponente hinzu. Klicke dazu in der Ansicht Outline mit der rechten Maustaste auf das Spielobjekt und wähle Add Component, dann Sprite und schließlich OK. Setze die Eigenschaft Image der Sprite-Komponente auf den gerade erstellten Atlas und die Standardanimation des Sprites auf eines der beiden Bodenbilder. Setze die X-Position der Sprite-Komponente (nicht des Spielobjekts) auf 190 und die Y-Position auf 40. Das Bild ist 380 Pixel breit und wir verschieben es um die Hälfte dieser Pixelzahl zur Seite. Dadurch liegt der Bezugspunkt (pivot) des Spielobjekts am linken Rand des Sprite-Bildes.


Am einfachsten ist es wahrscheinlich, ein vollständiges, skaliertes Spielobjekt mit einer Sprite-Komponente zu erstellen und es anschließend zu kopieren. Markiere es in der Ansicht Outline, wähle Edit ▸ Copy und danach Edit ▸ Paste.
Wenn du größere oder kleinere Kacheln möchtest, kannst du einfach die Skalierung ändern. Dann musst du allerdings auch die X-Positionen aller Boden-Spielobjekte auf Vielfache der neuen Breite ändern.
Speichere die Datei und füge anschließend ground.collection zur Datei main.collection hinzu: Doppelklicke zuerst auf die Datei main.collection. Klicke dann in der Ansicht Outline mit der rechten Maustaste auf das oberste Objekt und wähle Add Collection From File. Wähle im Dialog ground.collection aus und klicke auf OK. Achte darauf, ground.collection an der Position 0, 0, 0 zu platzieren, sonst erscheint sie versetzt. Speichere die Datei.
Starte das Spiel (Project ▸ Build), um zu sehen, ob alles an seinem Platz ist.

Vielleicht bist du inzwischen etwas verwirrt und fragst dich, was all die Dinge, die wir erstellt haben, eigentlich sind. Nehmen wir uns deshalb einen Moment Zeit, um die grundlegenden Bausteine jedes Defold-Projekts anzusehen:
Für den Moment reichen diese Beschreibungen wahrscheinlich aus. Eine wesentlich ausführlichere Erklärung findest du jedoch im Handbuch zu Bausteinen. Es ist sinnvoll, dieses Handbuch später zu lesen, um genauer zu verstehen, wie Defold funktioniert.
Jetzt, wo alle Bodenstücke an ihrem Platz sind, können wir sie recht einfach in Bewegung setzen. Die Idee: Wir bewegen die Stücke von rechts nach links. Sobald ein Stück links außerhalb des Bildschirms angekommen ist, verschieben wir es an die Position ganz rechts. Um all diese Spielobjekte zu bewegen, brauchen wir ein Lua-Skript. Erstellen wir also eines:
-- ground.script
local pieces = { "ground0", "ground1", "ground2", "ground3",
"ground4", "ground5", "ground6" } -- <1>
function init(self) -- <2>
self.speed = 360 -- Speed in pixels/s
end
function update(self, dt) -- <3>
for i, p in ipairs(pieces) do -- <4>
local pos = go.get_position(p)
if pos.x <= -228 then -- <5>
pos.x = 1368 + (pos.x + 228)
end
pos.x = pos.x - self.speed * dt -- <6>
go.set_position(pos, p) -- <7>
end
end
init() wird aufgerufen, wenn das Spielobjekt im Spiel zum Leben erweckt wird. Wir initialisieren eine zum Objekt gehörende Variable, die die Geschwindigkeit des Bodens enthält.update() wird einmal pro Frame aufgerufen, normalerweise 60-mal pro Sekunde. dt enthält die Anzahl der Sekunden seit dem letzten Aufruf.dt, um eine von der Bildrate unabhängige Geschwindigkeit in Pixel/s zu erhalten.Defold ist ein schneller Engine-Kern, der deine Daten und Spielobjekte verwaltet. Alle Logik und jedes Verhalten, das du für dein Spiel brauchst, erstellst du in Lua. Lua ist eine schnelle und schlanke Programmiersprache, die sich hervorragend zum Schreiben von Spiellogik eignet. Es gibt gute Lernmaterialien, zum Beispiel das Buch Programming in Lua und das offizielle Lua-Referenzhandbuch.
Defold ergänzt Lua um mehrere APIs sowie ein System zur Nachrichtenübermittlung, mit dem du die Kommunikation zwischen Spielobjekten programmieren kannst. Wie das funktioniert, erfährst du im Handbuch zur Nachrichtenübermittlung.
Du kannst die Editorbereiche Assets Pane, Console und Outline mit den Tasten F6, F7 beziehungsweise F8 ein- und ausblenden.
Jetzt, wo wir eine Skriptdatei haben, sollten wir eine Referenz darauf in einer Komponente eines Spielobjekts hinterlegen. So wird das Skript als Teil des Lebenszyklus des Spielobjekts ausgeführt. Dazu erstellen wir ein neues Spielobjekt in ground.collection und fügen ihm eine Script-Komponente hinzu, die auf die gerade erstellte Lua-Skriptdatei verweist:

Wenn du das Spiel jetzt startest, führt das Spielobjekt “controller” das Skript in seiner Script-Komponente aus. Dadurch scrollt der Boden gleichmäßig über den Bildschirm.
Die Heldenfigur wird ein Spielobjekt, das aus den folgenden Komponenten besteht:
Importiere zunächst die Bilder der Körperteile und füge sie dann zu einem neuen Atlas hinzu, den wir hero.atlas nennen:

Außerdem müssen wir die Spine-Animationsdaten importieren und dafür eine Spine Scene einrichten:

Die Datei hero.spinejson wurde im Spine-JSON-Format exportiert. Um solche Dateien zu erstellen, benötigst du die Animationssoftware Spine. Wenn du eine andere Animationssoftware verwenden möchtest, kannst du deine Animationen als Sprite-Bögen (sprite sheets) exportieren und sie als Flipbook-Animationen aus Tile Source- oder Atlas-Ressourcen verwenden. Weitere Informationen findest du im Handbuch zu Animationen.
Jetzt können wir mit dem Aufbau des Spielobjekts für die Heldenfigur beginnen:

Jetzt fügen wir Physik hinzu, damit Kollisionen funktionieren:
Bei Kollisionen vom Typ “Kinematic” werden Kollisionen erkannt, aber die Physik-Engine löst sie nicht automatisch auf und simuliert die Objekte nicht. Die Physik-Engine unterstützt verschiedene Typen von Kollisionsobjekten. Mehr darüber erfährst du in der Physikdokumentation.
Wir müssen angeben, womit das Kollisionsobjekt interagieren soll:
Erstelle abschließend eine neue Datei hero.script und füge sie dem Spielobjekt hinzu.
handle_geometry_contact().)
Wir behandeln die Kollisionen selbst, weil die Engine eine Newtonsche Simulation der beteiligten Körper ausführen würde, wenn wir den Typ des Kollisionsobjekts der Figur auf dynamisch setzen würden. Für ein Spiel wie dieses ist eine solche Simulation alles andere als optimal. Anstatt der Physik-Engine mit verschiedenen Kräften entgegenzuwirken, übernehmen wir die volle Kontrolle.
Dafür und für eine korrekte Behandlung der Kollisionen brauchen wir ein wenig Vektorrechnung. Eine ausführliche Erklärung zur Auflösung kinematischer Kollisionen findest du in der Physikdokumentation.
-- gravity pulling the player down in pixel units/sˆ2
local gravity = -20
-- take-off speed when jumping in pixel units/s
local jump_takeoff_speed = 900
function init(self)
-- this tells the engine to send input to on_input() in this script
msg.post(".", "acquire_input_focus")
-- save the starting position
self.position = go.get_position()
-- keep track of movement vector and if there is ground contact
self.velocity = vmath.vector3(0, 0, 0)
self.ground_contact = false
end
function final(self)
-- Return input focus when the object is deleted
msg.post(".", "release_input_focus")
end
function update(self, dt)
local gravity = vmath.vector3(0, gravity, 0)
if not self.ground_contact then
-- Apply gravity if there's no ground contact
self.velocity = self.velocity + gravity
end
-- apply velocity to the player character
go.set_position(go.get_position() + self.velocity * dt)
-- reset volatile state
self.correction = vmath.vector3()
self.ground_contact = false
end
local function handle_geometry_contact(self, normal, distance)
-- project the correction vector onto the contact normal
-- (the correction vector is the 0-vector for the first contact point)
local proj = vmath.dot(self.correction, normal)
-- calculate the compensation we need to make for this contact point
local comp = (distance - proj) * normal
-- add it to the correction vector
self.correction = self.correction + comp
-- apply the compensation to the player character
go.set_position(go.get_position() + comp)
-- check if the normal points enough up to consider the player standing on the ground
-- (0.7 is roughly equal to 45 degrees deviation from pure vertical direction)
if normal.y > 0.7 then
self.ground_contact = true
end
-- project the velocity onto the normal
proj = vmath.dot(self.velocity, normal)
-- if the projection is negative, it means that some of the velocity points towards the contact point
if proj < 0 then
-- remove that component in that case
self.velocity = self.velocity - proj * normal
end
end
function on_message(self, message_id, message, sender)
if message_id == hash("contact_point_response") then
-- check if we received a contact point message. One message for each contact point
if message.group == hash("geometry") then
handle_geometry_contact(self, message.normal, message.distance)
end
end
end
local function jump(self)
-- only allow jump from ground
if self.ground_contact then
-- set take-off speed
self.velocity.y = jump_takeoff_speed
end
end
local function abort_jump(self)
-- cut the jump short if we are still going up
if self.velocity.y > 0 then
-- scale down the upwards speed
self.velocity.y = self.velocity.y * 0.5
end
end
function on_input(self, action_id, action)
if action_id == hash("jump") or action_id == hash("touch") then
if action.pressed then
jump(self)
elseif action.released then
abort_jump(self)
end
end
end
Wenn du möchtest, kannst du die Heldenfigur jetzt vorübergehend zur Hauptsammlung hinzufügen und das Spiel starten, um zu sehen, wie sie durch die Welt fällt.
Damit die Heldenfigur funktioniert, fehlen uns nur noch Eingaben. Das obige Skript enthält bereits eine Funktion on_input(), die auf die Aktionen “jump” und “touch” (für Touchscreens) reagiert. Fügen wir Eingabebindungen (input bindings) für diese Aktionen hinzu.

Jetzt, wo unsere Heldenfigur samt Kollisionen eingerichtet ist, müssen wir auch dem Boden Kollisionen hinzufügen. So bekommt die Figur etwas, mit dem sie kollidieren beziehungsweise auf dem sie laufen kann. Das machen wir gleich. Zunächst sollten wir aber etwas aufräumen: Wir legen alle Levelinhalte in eine separate Sammlung und ordnen die Dateistruktur neu:
Wie du vielleicht schon bemerkt hast, ist die Dateihierarchie im Bereich Assets pane von der Inhaltsstruktur getrennt, die du in deinen Sammlungen aufbaust. Einzelne Dateien werden aus Sammlungs- und Spielobjektdateien referenziert, ihr Speicherort ist aber völlig frei wählbar.
Wenn du eine Datei an einen neuen Ort verschieben möchtest, hilft Defold dir, indem es die Referenzen auf diese Datei automatisch aktualisiert (Refactoring). Beim Entwickeln komplexer Software wie eines Spiels ist es äußerst hilfreich, die Projektstruktur mit dem wachsenden und sich verändernden Projekt anpassen zu können. Defold unterstützt das und sorgt für einen reibungslosen Ablauf. Du brauchst also keine Scheu davor zu haben, deine Dateien zu verschieben!
Außerdem sollten wir der Levelsammlung ein Spielobjekt für die Steuerung mit einer Skriptkomponente hinzufügen:
Öffne die Skriptdatei, kopiere den folgenden Code hinein und speichere sie:
-- controller.script
go.property("speed", 360) -- <1>
function init(self)
msg.post("ground/controller#ground", "set_speed", { speed = self.speed })
end

Das Spielobjekt “controller” liegt nicht in einer eigenen Datei vor, sondern wird direkt in der Levelsammlung erstellt. Die Spielobjektinstanz wird somit aus den dort eingebetteten Daten erzeugt. Für Spielobjekte mit einer einzigen Aufgabe wie dieses ist das in Ordnung. Wenn du mehrere Instanzen eines Spielobjekts brauchst und die Möglichkeit haben möchtest, den Prototyp beziehungsweise die Vorlage anzupassen, aus der jede Instanz entsteht, erstelle einfach eine Spielobjektdatei und füge das Spielobjekt aus dieser Datei zur Sammlung hinzu. Dadurch entsteht ein Spielobjekt, das die Datei als Prototyp beziehungsweise Vorlage referenziert.
Das Spielobjekt “controller” soll alles steuern, was mit dem laufenden Level zusammenhängt. Bald wird dieses Skript Plattformen und Münzen dynamisch erzeugen, mit denen die Heldenfigur interagieren kann. Vorerst legt es aber nur die Geschwindigkeit des Levels fest.
In seiner Funktion init() sendet das Skript zur Levelsteuerung eine Nachricht an die Skriptkomponente des Bodensteuerungsobjekts. Es adressiert sie über ihren Bezeichner:
msg.post("ground/controller#controller", "set_speed", { speed = self.speed })
Der Bezeichner des Spielobjekts für die Steuerung lautet "ground/controller", da es in der Sammlung “ground” liegt. Den Komponentenbezeichner "controller" hängen wir hinter dem Rautenzeichen "#" an, das den Objektbezeichner vom Komponentenbezeichner trennt. Beachte, dass das Bodenskript noch keinen Code enthält, der auf die Nachricht set_speed reagiert. Deshalb müssen wir eine Funktion on_message() zu ground.script hinzufügen und die entsprechende Logik ergänzen.
-- ground.script
function on_message(self, message_id, message, sender)
if message_id == hash("set_speed") then -- <1>
self.speed = message.speed -- <2>
end
end

An dieser Stelle sollten wir physikalische Kollisionen für den Boden hinzufügen:

Jetzt solltest du das Spiel probeweise starten können (Project ▸ Build). Die Heldenfigur sollte auf dem Boden laufen und mit der Taste Space springen können. Wenn du das Spiel auf einem Mobilgerät ausführst, kannst du durch Tippen auf den Bildschirm springen.
Um das Leben in unserer Spielwelt etwas weniger eintönig zu machen, sollten wir Plattformen hinzufügen, auf die wir springen können.
Erstelle eine Script-Datei namens platform.script. Klicke dazu im Bereich Assets pane mit der rechten Maustaste und wähle New ▸ Script File. Füge den folgenden Code in die Datei ein und speichere sie:
-- platform.script
function init(self)
self.speed = 540 -- Default speed in pixels/s
end
function update(self, dt)
local pos = go.get_position()
if pos.x < -500 then
go.delete() -- <1>
end
pos.x = pos.x - self.speed * dt
go.set_position(pos)
end
function on_message(self, message_id, message, sender)
if message_id == hash("set_speed") then
self.speed = message.speed
end
end

Beachte, dass sowohl platform.go als auch platform_long.go eine Script-Komponente haben, die auf dieselbe Skriptdatei verweist. Das ist praktisch, denn jede Änderung an dieser Skriptdatei wirkt sich sowohl auf das Verhalten der normalen als auch der langen Plattformen aus.
Das Spiel soll ein einfacher Endless Runner werden. Deshalb können wir die Spielobjekte der Plattformen nicht im Editor in einer Sammlung platzieren. Stattdessen müssen wir sie dynamisch erzeugen:
-- controller.script
go.property("speed", 360)
local grid = 460
local platform_heights = { 100, 200, 350 } -- <1>
function init(self)
msg.post("ground/controller#controller", "set_speed", { speed = self.speed })
self.gridw = 0
end
function update(self, dt) -- <2>
self.gridw = self.gridw + self.speed * dt
if self.gridw >= grid then
self.gridw = 0
-- Maybe spawn a platform at random height
if math.random() > 0.2 then
local h = platform_heights[math.random(#platform_heights)]
local f = "#platform_factory"
if math.random() > 0.5 then
f = "#platform_long_factory"
end
local p = factory.create(f, vmath.vector3(1600, h, 0), nil, {}, vmath.vector3(0.6, 0.6, 1))
msg.post(p, "set_speed", { speed = self.speed })
end
end
end
update() wird einmal pro Frame aufgerufen. Wir nutzen sie, um in bestimmten Abständen (damit keine Überlappungen entstehen) und auf bestimmten Höhen zu entscheiden, ob eine normale oder eine lange Plattform dynamisch erzeugt werden soll. Du kannst leicht mit verschiedenen Algorithmen für die Erzeugung experimentieren, um unterschiedliches Gameplay zu gestalten.Starte jetzt das Spiel (Project ▸ Build).
Wow, langsam wird daraus etwas (fast) Spielbares …

Als Erstes erwecken wir die Heldenfigur zum Leben. Im Moment steckt die arme Figur in einer endlosen Laufanimation fest und reagiert weder auf Sprünge noch auf andere Ereignisse passend. Die Spine-Datei, die wir aus dem Asset-Paket hinzugefügt haben, enthält bereits mehrere Animationen genau dafür.
update() ein: -- hero.script
local function play_animation(self, anim)
-- only play animations which are not already playing
if self.anim ~= anim then
-- tell the spine model to play the animation
local anim_props = { blend_duration = 0.15 }
spine.play_anim("#spinemodel", anim, go.PLAYBACK_LOOP_FORWARD, anim_props)
-- remember which animation is playing
self.anim = anim
end
end
local function update_animation(self)
-- make sure the right animation is playing
if self.ground_contact then
play_animation(self, hash("run"))
else
play_animation(self, hash("jump"))
end
end
update() und füge einen Aufruf von update_animation hinzu: ...
-- apply it to the player character
go.set_position(go.get_position() + self.velocity * dt)
update_animation(self)
...

Lua verwendet für lokale Variablen einen „lexikalischen Gültigkeitsbereich“. Deshalb kommt es auf die Reihenfolge an, in der du Funktionen mit local anordnest. Die Funktion update() ruft die lokalen Funktionen update_animation() und play_animation() auf. Die Laufzeitumgebung muss diese lokalen Funktionen also bereits kennen, um sie aufrufen zu können. Daher müssen wir die Funktionen vor update() platzieren. Wenn du die Reihenfolge der Funktionen vertauschst, erhältst du einen Fehler. Beachte, dass dies nur für local-Variablen gilt. Mehr über die Gültigkeitsbereiche und lokalen Funktionen in Lua erfährst du unter http://www.lua.org/pil/6.2.html
Mehr ist nicht nötig, um der Heldenfigur Sprung- und Fallanimationen hinzuzufügen. Wenn du das Spiel startest, wirst du merken, dass es sich viel besser spielt. Vielleicht fällt dir auch auf, dass die Plattformen die Heldenfigur leider vom Bildschirm schieben können. Das ist eine Nebenwirkung der Kollisionsbehandlung. Die Lösung ist aber einfach: Füge etwas Gewalt hinzu und mache die Kanten der Plattformen gefährlich!
Speichere die Datei.

Öffne hero.go, markiere das Collision Object und füge den Namen “danger” zur Eigenschaft Mask hinzu. Speichere anschließend die Datei.

Öffne hero.script und ändere die Funktion on_message(), damit eine Reaktion erfolgt, wenn die Heldenfigur mit einer “danger”-Kante kollidiert:
-- hero.script
function on_message(self, message_id, message, sender)
if message_id == hash("reset") then
self.velocity = vmath.vector3(0, 0, 0)
self.correction = vmath.vector3()
self.ground_contact = false
self.anim = nil
go.set(".", "euler.z", 0)
go.set_position(self.position)
msg.post("#collisionobject", "enable")
elseif message_id == hash("contact_point_response") then
-- check if we received a contact point message
if message.group == hash("danger") then
-- Die and restart
play_animation(self, hash("death"))
msg.post("#collisionobject", "disable")
-- <1>
go.animate(".", "euler.z", go.PLAYBACK_ONCE_FORWARD, 160, go.EASING_LINEAR, 0.7)
go.animate(".", "position.y", go.PLAYBACK_ONCE_FORWARD, go.get_position().y - 200, go.EASING_INSINE, 0.5, 0.2,
function()
msg.post("#", "reset")
end)
elseif message.group == hash("geometry") then
handle_geometry_contact(self, message.normal, message.distance)
end
end
end
Ändere die Funktion init() so, dass sie zum Initialisieren des Objekts eine Nachricht “reset” sendet. Speichere anschließend die Datei:
-- hero.script
function init(self)
-- this lets us handle input in this script
msg.post(".", "acquire_input_focus")
-- save position
self.position = go.get_position()
msg.post("#", "reset")
end
Wenn du das Spiel jetzt ausprobierst, wird schnell klar, dass das Zurücksetzen nicht richtig funktioniert. Die Heldenfigur selbst wird zwar korrekt zurückgesetzt, aber sie kann dabei leicht in eine Situation geraten, in der sie sofort auf eine Plattformkante fällt und erneut stirbt. Wir möchten beim Tod das gesamte Level richtig zurücksetzen. Da das Level nur aus einer Folge dynamisch erzeugter Plattformen besteht, müssen wir lediglich alle erzeugten Plattformen erfassen und sie beim Zurücksetzen löschen:
Öffne die Datei controller.script und passe den Code so an, dass er die Bezeichner aller dynamisch erzeugten Plattformen speichert:
-- controller.script
go.property("speed", 360)
local grid = 460
local platform_heights = { 100, 200, 350 }
function init(self)
msg.post("ground/controller#controller", "set_speed", { speed = self.speed })
self.gridw = 0
self.spawns = {} -- <1>
end
function update(self, dt)
self.gridw = self.gridw + self.speed * dt
if self.gridw >= grid then
self.gridw = 0
-- Maybe spawn a platform at random height
if math.random() > 0.2 then
local h = platform_heights[math.random(#platform_heights)]
local f = "#platform_factory"
if math.random() > 0.5 then
f = "#platform_long_factory"
end
local p = factory.create(f, vmath.vector3(1600, h, 0), nil, {}, vmath.vector3(0.6, 0.6, 1))
msg.post(p, "set_speed", { speed = self.speed })
table.insert(self.spawns, p) -- <1>
end
end
end
function on_message(self, message_id, message, sender)
if message_id == hash("reset") then -- <2>
-- Tell the hero to reset.
msg.post("hero#hero", "reset")
-- Delete all platforms
for i,p in ipairs(self.spawns) do
go.delete(p)
end
self.spawns = {}
elseif message_id == hash("delete_spawn") then -- <3>
for i,p in ipairs(self.spawns) do
if p == message.id then
table.remove(self.spawns, i)
go.delete(p)
end
end
end
end
Öffne platform.script und ändere es so, dass es eine Plattform, die den linken Rand erreicht hat, nicht einfach löscht. Stattdessen soll es eine Nachricht an die Levelsteuerung senden und sie auffordern, die Plattform zu entfernen:
-- platform.script
...
if pos.x < -500 then
msg.post("/level/controller#controller", "delete_spawn", { id = go.get_id() })
end
...

-- hero.script
...
go.animate(".", "position.y", go.PLAYBACK_ONCE_FORWARD, go.get_position().y - 200, go.EASING_INSINE, 0.5, 0.2,
function()
msg.post("controller#controller", "reset")
end)
...

Damit steht jetzt der grundlegende Ablauf aus Neustart und Tod!
Als Nächstes kommt etwas, für das es sich zu leben lohnt: Münzen!
Wir möchten Münzen im Level platzieren, die der Spieler einsammeln kann. Zuerst stellt sich die Frage, wie wir sie ins Level bekommen. Wir könnten beispielsweise ein Verfahren zur dynamischen Erzeugung entwickeln, das auf den Algorithmus zum Erzeugen der Plattformen abgestimmt ist. Letztlich haben wir uns aber für einen viel einfacheren Ansatz entschieden: Die Plattformen selbst erzeugen die Münzen:
Erstelle eine neue Skriptdatei namens coin.script. Klicke dazu im Bereich Assets pane mit der rechten Maustaste auf level und wähle New ▸ Script File. Ersetze den Vorlagencode durch Folgendes:
-- coin.script
function init(self)
self.collected = false
end
function on_message(self, message_id, message, sender)
if self.collected == false and message_id == hash("collision_response") then
self.collected = true
msg.post("#sprite", "disable")
elseif message_id == hash("start_animation") then
pos = go.get_position()
go.animate(go.get_id(), "position.y", go.PLAYBACK_LOOP_PINGPONG, pos.y + 24, go.EASING_INOUTSINE, 0.75, message.delay)
end
end
Füge dem Münzobjekt die Skriptdatei als Script-Komponente hinzu (Rechtsklick auf den obersten Knoten in Outline und Add Component from File).

Die Münzen sollen von den Plattformobjekten dynamisch erzeugt werden. Füge deshalb Fabriken für die Münzen in platform.go und platform_long.go ein.

Jetzt müssen wir platform.script so ändern, dass es die Münzen dynamisch erzeugt und löscht:
-- platform.script
function init(self)
self.speed = 540 -- Default speed in pixels/s
self.coins = {}
end
function final(self)
for i,p in ipairs(self.coins) do
go.delete(p)
end
end
function update(self, dt)
local pos = go.get_position()
if pos.x < -500 then
msg.post("/level/controller#controller", "delete_spawn", { id = go.get_id() })
end
pos.x = pos.x - self.speed * dt
go.set_position(pos)
end
function create_coins(self, params)
local spacing = 56
local pos = go.get_position()
local x = pos.x - params.coins * (spacing*0.5) - 24
for i = 1, params.coins do
local coin = factory.create("#coin_factory", vmath.vector3(x + i * spacing , pos.y + 64, 1))
msg.post(coin, "set_parent", { parent_id = go.get_id() }) -- <1>
msg.post(coin, "start_animation", { delay = i/10 }) -- <2>
table.insert(self.coins, coin)
end
end
function on_message(self, message_id, message, sender)
if message_id == hash("set_speed") then
self.speed = message.speed
elseif message_id == hash("create_coins") then
create_coins(self, message)
end
end
Eltern-Kind-Beziehungen verändern ausschließlich den Szenengraphen. Ein untergeordnetes Objekt wird zusammen mit seinem übergeordneten Objekt transformiert, also verschoben, skaliert oder gedreht. Wenn du zusätzliche „Besitzbeziehungen“ zwischen Spielobjekten benötigst, musst du diese ausdrücklich im Code verwalten.
Als letzten Schritt dieses Tutorials fügen wir einige Zeilen zu controller.script hinzu:
-- controller.script
...
local platform_heights = { 100, 200, 350 }
local coins = 3 -- <1>
...
-- controller.script
...
local coins = coins
if math.random() > 0.5 then
f = "#platform_long_factory"
coins = coins * 2 -- Twice the number of coins on long platforms
end
...
-- controller.script
...
msg.post(p, "set_speed", { speed = self.speed })
msg.post(p, "create_coins", { coins = coins })
table.insert(self.spawns, p)
...

Jetzt haben wir ein einfaches, aber funktionsfähiges Spiel! Wenn du es bis hierher geschafft hast, möchtest du vielleicht selbstständig weitermachen und Folgendes hinzufügen:
Lade die fertige Version des Projekts hier herunter.
Damit endet dieses einführende Tutorial. Leg jetzt los und erkunde Defold. Wir haben viele Handbücher und Tutorials vorbereitet, die dich unterstützen. Wenn du nicht weiterkommst, bist du im Forum willkommen.
Viel Spaß mit Defold!