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
W tym samouczku zaczniemy od pustego projektu i zbudujemy kompletną grę z niekończącym się biegiem, z animowaną postacią, kolizjami fizycznymi, przedmiotami do zbierania i punktacją.
W trakcie nauki nowego silnika gier trzeba przyswoić sporo rzeczy, dlatego przygotowaliśmy ten samouczek, aby ułatwić ci start. To dość kompletny przewodnik pokazujący, jak działa silnik i edytor. Zakładamy, że masz już pewną znajomość programowania.
Jeśli potrzebujesz wprowadzenia do programowania w Lua, zajrzyj do naszego podręcznika Lua w Defold.
Jeśli czujesz, że ten samouczek to na początek zbyt dużo, zajrzyj na naszą stronę z samouczkami, gdzie znajdziesz zestaw materiałów o różnym poziomie trudności.
Jeśli wolisz oglądać samouczki wideo, zajrzyj do wersji wideo na YouTube.
Korzystamy z zasobów gry z dwóch innych samouczków, wprowadzając przy tym kilka drobnych zmian. Samouczek jest podzielony na kilka kroków, a każdy z nich prowadzi nas znacząco bliżej końcowej gry.
Efektem końcowym będzie gra, w której sterujesz bohaterem biegnącym przez otoczenie, zbierającym monety i omijającym przeszkody. Bohater porusza się ze stałą prędkością, a gracz kontroluje wyłącznie skok bohatera, naciskając jeden przycisk (albo dotykając ekranu na urządzeniu mobilnym). Poziom składa się z nieskończonego ciągu platform, po których można skakać, oraz monet do zebrania.
Jeśli na dowolnym etapie utkniesz w tym samouczku albo podczas tworzenia własnej gry, nie wahaj się poprosić nas o pomoc na Forum Defold. Na forum możesz rozmawiać o Defold, prosić zespół Defold o pomoc, zobaczyć, jak inni twórcy gier rozwiązali swoje problemy, i znaleźć nowe inspiracje. Zacznij już teraz.
W całym samouczku szczegółowe opisy pojęć i sposobów wykonania niektórych kroków są oznaczone właśnie w ten sposób. Jeśli uznasz, że te sekcje wchodzą w zbyt duży szczegół, po prostu je pomiń.
Zacznijmy więc. Mamy nadzieję, że przejście przez ten samouczek sprawi ci dużo radości i pomoże ci ruszyć z Defold.
Pobierz zasoby do tego samouczka tutaj.
Pierwszym krokiem jest pobranie następujących plików.
Jeśli jeszcze nie pobrałeś i nie zainstalowałeś edytora Defold, to czas to zrobić:
Przejdź na stronę pobierania Defold, gdzie znajdziesz przyciski pobierania dla macOS, Windows i Linuksa (Ubuntu):

Aby uruchomić edytor, otwórz folder “Applications” i dwukrotnie kliknij plik “Defold”.

D:\Defold. Nie przenoś Defold do C:\Program Files (x86)\ ani C:\Program Files\, ponieważ uniemożliwi to aktualizowanie edytora.Aby uruchomić edytor, otwórz folder “Defold” i dwukrotnie kliknij plik “Defold.exe”.

W terminalu znajdź plik “Defold-x86_64-linux.zip” i rozpakuj go do katalogu docelowego o nazwie “Defold”.
$ unzip Defold-x86_64-linux.zip -d Defold
Aby uruchomić edytor, przejdź do katalogu, do którego rozpakowałeś aplikację, a następnie uruchom plik wykonywalny Defold albo dwukrotnie kliknij go na pulpicie.
$ cd Defold
$ ./Defold
W menu `Help > Create Desktop Entry` znajduje się pomocnik do instalowania wpisu na pulpicie.
Jeśli napotkasz jakiekolwiek problemy z uruchamianiem edytora, otwieraniem projektu lub uruchamianiem gry w Defold, zajrzyj do sekcji FAQ dla Linuksa.
Każda wersja beta i stabilna Defold jest też dostępna na GitHubie.
Gdy edytor jest już zainstalowany i uruchomiony, pora utworzyć nowy projekt i przygotować go do pracy. Utwórz nowy projekt z szablonu “Empty Project”.
Ten samouczek korzysta z funkcji Spine, które od wersji Defold 1.2.188 zostały przeniesione do własnego rozszerzenia. Jeśli używasz nowszej wersji, dodaj Spine Extension do sekcji zależności w game.project.
Gdy uruchamiasz edytor po raz pierwszy, startuje on pusty, bez otwartego projektu, więc wybierz z menu Open Project i wskaż nowo utworzony projekt. Zostaniesz też poproszony o utworzenie dla projektu “branch”.
W panelu Assets pane zobaczysz wszystkie pliki należące do projektu. Jeśli dwukrotnie klikniesz plik “main/main.collection”, otworzy się on w centralnym widoku edytora:

Edytor składa się z następujących głównych obszarów:
print() i pprint() pochodzące ze skryptów. Jeśli aplikacja lub gra nie chce się uruchomić, konsola jest pierwszym miejscem, które warto sprawdzić. Za konsolą znajduje się zestaw kart pokazujących informacje o błędach, a także edytor krzywych używany podczas tworzenia efektów cząsteczkowych.Szablon projektu “Empty” jest w rzeczywistości całkowicie pusty. Mimo to wybierz Project ▸ Build, aby zbudować projekt i uruchomić grę.

Czarny ekran może nie wyglądać zbyt ekscytująco, ale to działająca aplikacja gry w Defold i możemy łatwo zmodyfikować ją w coś ciekawszego. Zróbmy więc to.
Edytor Defold pracuje na plikach. Dwukrotne kliknięcie pliku w Assets pane otwiera go w odpowiednim edytorze. Następnie możesz pracować z zawartością tego pliku.
Gdy skończysz edycję pliku, musisz go zapisać. Wybierz File ▸ Save z głównego menu. Edytor daje podpowiedź, dodając gwiazdkę ‘*’ do nazwy pliku na karcie każdego pliku, który zawiera niezapisane zmiany.

Zanim zaczniemy, ustawmy kilka parametrów naszego projektu. Otwórz zasób game.project z panelu Assets Pane i przewiń do sekcji Display. Ustaw width i height projektu odpowiednio na 1280 i 720.
Musisz też dodać do projektu rozszerzenie Spine, abyśmy mogli animować postać bohatera. Dodaj wersję rozszerzenia Spine zgodną z wersją edytora Defold, którą masz zainstalowaną. Dostępne wersje Spine możesz zobaczyć tutaj:
https://github.com/defold/extension-spine/releases
Kliknij prawym przyciskiem myszy link do pliku zip z wydaniem, którego chcesz użyć:

Dodaj link do wydania do listy zależności game.project. Gdy rozszerzenie Spine zostanie dodane, musisz też zrestartować edytor, aby aktywować integrację edytora dołączoną do Spine Extension.
Zróbmy pierwsze małe kroki i stwórzmy arenę dla naszej postaci, a właściwie fragment przewijanego podłoża. Zrobimy to w kilku etapach.
Atlas to plik, który łączy zestaw oddzielnych obrazów w jeden większy plik graficzny. Robi się tak, aby oszczędzić miejsce i poprawić wydajność. Więcej o atlasach i innych funkcjach grafiki 2D przeczytasz w dokumentacji grafiki 2D.


Dlaczego to nie działa!? Częsty problem osób zaczynających pracę z Defold polega na tym, że zapominają zapisać plik! Po dodaniu obrazów do atlasu musisz zapisać plik, zanim będziesz mógł uzyskać do tych obrazów dostęp.
Utwórz plik kolekcji ground.collection dla podłoża i dodaj do niego 7 obiektów gry (kliknij prawym przyciskiem korzeń kolekcji w widoku Outline i wybierz Add Game Object). Nazwij obiekty “ground0”, “ground1”, “ground2” itd., zmieniając właściwość Id w widoku Properties. Zwróć uwagę, że Defold automatycznie przypisuje nowym obiektom gry unikalne id.
Do każdego obiektu dodaj komponent sprite (kliknij prawym przyciskiem obiekt gry w widoku Outline i wybierz Add Component, następnie wybierz Sprite i kliknij OK), ustaw właściwość Image komponentu sprite na właśnie utworzony atlas i ustaw domyślną animację sprite’a na jeden z dwóch obrazów podłoża. Ustaw pozycję X komponentu sprite (nie obiektu gry) na 190, a pozycję Y na 40. Ponieważ szerokość obrazu wynosi 380 pikseli i przesuwamy go w bok o połowę tej wartości, punkt zakotwiczenia obiektu gry znajdzie się na lewej krawędzi obrazu sprite’a.


Najłatwiej będzie najpierw utworzyć jeden kompletny, przeskalowany obiekt gry z komponentem sprite, a potem go skopiować. Zaznacz go w widoku Outline, a następnie wybierz Edit ▸ Copy i potem Edit ▸ Paste.
Warto zauważyć, że jeśli chcesz większych lub mniejszych kafli, wystarczy zmienić skalowanie. Będzie to jednak wymagało również zmiany pozycji X wszystkich obiektów gry podłoża na wielokrotności nowej szerokości.
Zapisz plik, a następnie dodaj ground.collection do pliku main.collection: najpierw dwukrotnie kliknij plik main.collection, potem kliknij prawym przyciskiem korzeń obiektu w widoku Outline i wybierz Add Collection From File. W oknie dialogowym wybierz ground.collection i kliknij OK. Upewnij się, że umieszczasz ground.collection w pozycji 0, 0, 0, bo inaczej będzie wizualnie przesunięta. Zapisz ją.
Uruchom grę (Project ▸ Build), aby sprawdzić, że wszystko jest na miejscu.

Na tym etapie możesz być już nieco zdezorientowany i zastanawiać się, czym właściwie są wszystkie rzeczy, które tworzymy, więc zatrzymajmy się na chwilę i przyjrzyjmy się najbardziej podstawowym elementom każdego projektu Defold:
Na razie te opisy prawdopodobnie wystarczą. Znacznie pełniejsze omówienie tych kwestii znajdziesz jednak w podręczniku o blokach budujących. Warto zajrzeć do niego później, aby lepiej zrozumieć, jak rzeczy działają w Defold.
Teraz, gdy wszystkie fragmenty podłoża są już na miejscu, ich poruszanie jest dość proste. Chodzi o to: przesuwać elementy z prawej do lewej, a gdy któryś z nich dotrze do lewego skraju poza ekranem, przenieść go na prawy skraj. Aby poruszać wszystkimi tymi obiektami gry, potrzebujemy skryptu Lua, więc utwórzmy go:
-- plik: ground.script
local pieces = { "ground0", "ground1", "ground2", "ground3",
"ground4", "ground5", "ground6" } -- <1>
function init(self) -- <2>
self.speed = 360 -- Prędkość w pikselach/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() jest wywoływana, gdy obiekt gry “ożywa” w grze. Inicjalizujemy lokalną zmienną członka obiektu, która przechowuje prędkość podłoża.update() jest wywoływana raz na klatkę, zwykle 60 razy na sekundę. dt zawiera liczbę sekund od poprzedniego wywołania.dt, aby uzyskać prędkość niezależną od liczby klatek, wyrażoną w pikselach/s.Defold to szybki rdzeń silnika, który zarządza danymi i obiektami gry. Całą logikę lub zachowanie potrzebne w grze tworzysz w języku Lua. Lua to szybki i lekki język programowania, świetny do pisania logiki gier. Dostępnych jest wiele świetnych źródeł do nauki języka, na przykład książka Programming in Lua oraz oficjalny Lua reference manual.
Defold dodaje do Lua zestaw API, a także system przekazywania wiadomości, który pozwala programować komunikację między obiektami gry. Szczegóły działania znajdziesz w podręczniku o przekazywaniu wiadomości.
Możesz przełączać widoczność sekcji Assets Pane, Console i Outline w edytorze za pomocą klawiszy F6, F7 i F8 odpowiednio.
Teraz, gdy mamy plik skryptu, powinniśmy dodać do niego referencję w komponencie obiektu gry. Dzięki temu skrypt będzie wykonywany jako część cyklu życia obiektu gry. Zrobimy to, tworząc nowy obiekt gry w ground.collection i dodając do niego komponent Script, który odwołuje się do właśnie utworzonego pliku skryptu Lua:

Teraz, gdy uruchomisz grę, obiekt gry “controller” uruchomi skrypt w swoim komponencie Script, przez co podłoże będzie płynnie przewijać się przez ekran.
Postać bohatera będzie obiektem gry złożonym z następujących komponentów:
Zacznij od zaimportowania obrazów części ciała, a następnie dodaj je do nowego atlasu, który nazwiemy hero.atlas:

Musimy też zaimportować dane animacji Spine i skonfigurować dla nich Spine Scene:

Plik hero.spinejson został wyeksportowany w formacie Spine JSON. Do tworzenia takich plików potrzebujesz oprogramowania Spine do animacji. Jeśli chcesz używać innego programu do animacji, możesz wyeksportować animacje jako sprite-sheety i używać ich jako animacji flip-book z zasobów Tile Source albo Atlas. Więcej informacji znajdziesz w podręczniku Animacja.
Teraz możemy zacząć budować obiekt gry bohatera:

Teraz czas dodać fizykę, aby kolizje działały:
Kolizja “Kinematic” oznacza, że chcemy rejestrować zderzenia, ale silnik fizyki nie będzie automatycznie rozwiązywał kolizji ani symulował obiektów. Silnik fizyki obsługuje kilka różnych typów obiektów kolizji. Więcej o nich przeczytasz w dokumentacji fizyki.
Ważne jest, aby określić, z czym obiekt kolizji ma wchodzić w interakcję:
Na koniec utwórz nowy plik hero.script i dodaj go do obiektu gry.
handle_geometry_contact().)
Powód, dla którego obsługujemy kolizję samodzielnie, jest taki, że gdybyśmy zamiast tego ustawili typ obiektu kolizji postaci na dynamiczny, silnik wykonałby newtonowską symulację zaangażowanych ciał. W grze takiej jak ta taka symulacja jest daleka od optymalnej, więc zamiast walczyć z silnikiem fizyki przy użyciu różnych sił, przejmujemy pełną kontrolę.
Aby to zrobić i poprawnie obsługiwać kolizje, potrzeba trochę matematyki wektorowej. Szczegółowe wyjaśnienie, jak rozwiązywać kolizje kinematyczne, znajdziesz w dokumentacji fizyki.
-- grawitacja ściągająca gracza w dół, w pikselach/sˆ2
local gravity = -20
-- prędkość wybicia przy skoku, w pikselach/s
local jump_takeoff_speed = 900
function init(self)
-- to mówi silnikowi, aby wysyłał wejście do on_input() w tym skrypcie
msg.post(".", "acquire_input_focus")
-- zapisz pozycję początkową
self.position = go.get_position()
-- śledź wektor ruchu i to, czy postać ma kontakt z podłożem
self.velocity = vmath.vector3(0, 0, 0)
self.ground_contact = false
end
function final(self)
-- oddaj fokus wejścia, gdy obiekt zostanie usunięty
msg.post(".", "release_input_focus")
end
function update(self, dt)
local gravity = vmath.vector3(0, gravity, 0)
if not self.ground_contact then
-- zastosuj grawitację, jeśli nie ma kontaktu z podłożem
self.velocity = self.velocity + gravity
end
-- zastosuj prędkość do postaci gracza
go.set_position(go.get_position() + self.velocity * dt)
-- zresetuj stan chwilowy
self.correction = vmath.vector3()
self.ground_contact = false
end
local function handle_geometry_contact(self, normal, distance)
-- rzutuj wektor korekcji na normalną kontaktu
-- (wektor korekcji jest wektorem zerowym dla pierwszego punktu kontaktu)
local proj = vmath.dot(self.correction, normal)
-- oblicz kompensację potrzebną dla tego punktu kontaktu
local comp = (distance - proj) * normal
-- dodaj ją do wektora korekcji
self.correction = self.correction + comp
-- zastosuj kompensację do postaci gracza
go.set_position(go.get_position() + comp)
-- sprawdź, czy normalna jest skierowana dostatecznie w górę, aby uznać, że gracz stoi na ziemi
-- (0.7 odpowiada mniej więcej odchyleniu o 45 stopni od pionu)
if normal.y > 0.7 then
self.ground_contact = true
end
-- rzutuj prędkość na normalną
proj = vmath.dot(self.velocity, normal)
-- jeśli rzut jest ujemny, oznacza to, że część prędkości jest skierowana w stronę punktu kontaktu
if proj < 0 then
-- w takim przypadku usuń tę składową
self.velocity = self.velocity - proj * normal
end
end
function on_message(self, message_id, message, sender)
if message_id == hash("contact_point_response") then
-- sprawdź, czy otrzymaliśmy wiadomość o punkcie kontaktu; jedna wiadomość przypada na każdy punkt kontaktu
if message.group == hash("geometry") then
handle_geometry_contact(self, message.normal, message.distance)
end
end
end
local function jump(self)
-- pozwól skakać tylko z podłoża
if self.ground_contact then
-- ustaw prędkość wybicia
self.velocity.y = jump_takeoff_speed
end
end
local function abort_jump(self)
-- skróć skok, jeśli nadal poruszamy się w górę
if self.velocity.y > 0 then
-- zmniejsz prędkość skierowaną w górę
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
Jeśli chcesz, możesz teraz tymczasowo dodać postać bohatera do głównej kolekcji i uruchomić grę, aby zobaczyć, jak spada przez świat.
Ostatnią rzeczą, której potrzebujemy, aby bohater działał poprawnie, jest wejście użytkownika. Powyższy skrypt zawiera już funkcję on_input(), która reaguje na akcje “jump” i “touch” (dla ekranów dotykowych). Dodajmy wiązania wejść dla tych akcji.

Teraz, gdy mamy już skonfigurowanego bohatera z kolizjami i wszystkim innym, musimy dodać kolizje także do podłoża, aby bohater miał z czym się zderzać (albo po czym biec). Zrobimy to za chwilę, ale najpierw wykonamy trochę refaktoryzacji i przeniesiemy wszystkie elementy poziomu do osobnej kolekcji oraz nieco uporządkujemy strukturę plików:
Jak mogłeś już zauważyć, hierarchia plików widoczna w Assets pane jest odłączona od struktury zawartości, którą budujesz w swoich kolekcjach. Pojedyncze pliki są odwołaniami z plików kolekcji i obiektów gry, ale ich położenie jest całkowicie dowolne.
Jeśli chcesz przenieść plik w nowe miejsce, Defold pomaga automatycznie aktualizując odwołania do pliku (refaktoryzacja). Podczas tworzenia złożonego oprogramowania, takiego jak gra, możliwość zmieniania struktury projektu wraz z jego wzrostem i zmianami jest niezwykle pomocna. Defold zachęca do tego i sprawia, że proces przebiega płynnie, więc nie bój się przestawiać plików!
Powinniśmy też dodać obiekt gry controller z komponentem skryptu do kolekcji poziomu:
Otwórz plik skryptu, skopiuj do niego poniższy kod i zapisz plik:
-- controller.script
go.property("speed", 360) -- <1>
function init(self)
msg.post("ground/controller#ground", "set_speed", { speed = self.speed })
end

Obiekt gry “controller” nie istnieje w pliku, lecz jest tworzony bezpośrednio w kolekcji poziomu. Oznacza to, że instancja obiektu gry jest tworzona z danych zapisanych bezpośrednio w miejscu umieszczenia. To jest w porządku w przypadku obiektów gry o jednym przeznaczeniu, takich jak ten. Jeśli potrzebujesz wielu instancji jakiegoś obiektu gry i chcesz móc modyfikować prototyp/szablon używany do tworzenia każdej instancji, po prostu utwórz plik obiektu gry i dodaj obiekt gry z pliku do kolekcji. W ten sposób powstaje obiekt gry z odwołaniem do pliku jako prototypu/szablonu.
Celem tego obiektu gry “controller” jest sterowanie wszystkim, co dotyczy uruchomionego poziomu. Wkrótce ten skrypt będzie odpowiadać za tworzenie platform i monet, z którymi będzie wchodzić w interakcję bohater, ale na razie będzie ustawiać tylko prędkość poziomu.
W funkcji init() skryptu kontrolera poziomu wysyłana jest wiadomość do komponentu skryptu obiektu kontrolera podłoża, adresowana przez jego id:
msg.post("ground/controller#controller", "set_speed", { speed = self.speed })
Id obiektu gry kontrolera jest ustawione na "ground/controller", ponieważ znajduje się on w kolekcji “ground”. Następnie dodajemy id komponentu "controller" po znaku hash "#", który oddziela id obiektu od id komponentu. Zwróć uwagę, że skrypt podłoża nie ma jeszcze kodu reagującego na wiadomość set_speed, więc musimy dodać do ground.script funkcję on_message() i logikę dla niej.
-- 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

Na tym etapie powinniśmy dodać kolizje fizyczne do podłoża:

Teraz powinieneś móc uruchomić grę (Project ▸ Build). Bohater powinien biec po podłożu i powinno być możliwe skakanie przyciskiem Space. Jeśli uruchomisz grę na urządzeniu mobilnym, możesz skakać, stukając w ekran.
Aby trochę urozmaicić życie w naszym świecie gry, powinniśmy dodać platformy, po których można skakać.
Utwórz plik Script o nazwie platform.script (kliknij prawym przyciskiem w Assets pane i wybierz New ▸ Script File) i wstaw do pliku poniższy kod, a następnie zapisz go:
-- plik: platform.script
function init(self)
self.speed = 540 -- Domyślna prędkość w pikselach/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

Zwróć uwagę, że zarówno platform.go, jak i platform_long.go mają komponenty Script, które odwołują się do tego samego pliku skryptu. To dobrze, bo każda zmiana w tym pliku skryptu wpłynie na zachowanie zarówno zwykłych, jak i długich platform.
Pomysł na tę grę jest taki, że ma to być prosta gra z niekończącym się biegiem. Oznacza to, że obiektów gry platform nie można umieszczać w kolekcji w edytorze. Zamiast tego musimy tworzyć je dynamicznie:
-- plik: 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
-- Być może utwórz platformę na losowej wysokości
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, {}, 0.6)
msg.post(p, "set_speed", { speed = self.speed })
end
end
end
1- Wstępnie zdefiniowane wartości położenia Y, na których będą tworzone platformy.
2- Funkcja update() jest wywoływana raz na klatkę i używamy jej do decydowania, czy utworzyć zwykłą czy długą platformę w określonych odstępach czasu i na określonych wysokościach, aby uniknąć nakładania się obiektów. Łatwo można eksperymentować z różnymi algorytmami tworzenia, aby uzyskać różne sposoby rozgrywki.
Teraz uruchom grę (Project ▸ Build).
Wow, to zaczyna już przypominać coś, co można (prawie) zagrać…

Pierwszą rzeczą, którą zrobimy, jest tchnienie życia w bohatera. W tej chwili biedak utknął w pętli biegu i nie reaguje dobrze na skoki ani na nic innego. Plik Spine, który dodaliśmy z paczki zasobów, zawiera właśnie zestaw animacji do tego celu.
update(): -- plik: hero.script
local function play_animation(self, anim)
-- odtwarzaj tylko animacje, które nie są już odtwarzane
if self.anim ~= anim then
-- poleć modelowi Spine odtworzyć animację
local anim_props = { blend_duration = 0.15 }
spine.play_anim("#spinemodel", anim, go.PLAYBACK_LOOP_FORWARD, anim_props)
-- zapamiętaj, która animacja jest odtwarzana
self.anim = anim
end
end
local function update_animation(self)
-- upewnij się, że odtwarzana jest właściwa animacja
if self.ground_contact then
play_animation(self, hash("run"))
else
play_animation(self, hash("jump"))
end
end
update() i dodaj wywołanie update_animation: ...
-- zastosuj to do postaci gracza
go.set_position(go.get_position() + self.velocity * dt)
update_animation(self)
...

Lua ma dla zmiennych local “zakres leksykalny” i jest wrażliwa na kolejność, w jakiej umieszczasz lokalne funkcje. Funkcja update() wywołuje lokalne funkcje update_animation() i play_animation(), co oznacza, że runtime musi już znać te lokalne funkcje, aby móc je wywołać. Dlatego musimy umieścić je przed update(). Jeśli zmienisz kolejność funkcji, otrzymasz błąd. Zwróć uwagę, że dotyczy to tylko zmiennych local. Więcej o regułach zakresu w Lua i lokalnych funkcjach przeczytasz na http://www.lua.org/pil/6.2.html
To wszystko, czego potrzeba, aby dodać do bohatera animacje skoku i upadku. Jeśli uruchomisz grę, zauważysz, że gra się od razu przyjemniej. Możesz też zauważyć, że platformy niestety potrafią zepchnąć bohatera poza ekran. To efekt uboczny obsługi kolizji, ale rozwiązanie jest proste: dodajmy trochę przemocy i sprawmy, by krawędzie platform były niebezpieczne!
Zapisz plik.

Otwórz hero.go, zaznacz Collision Object i dodaj nazwę “danger” do właściwości Mask. Następnie zapisz plik.

Otwórz hero.script i zmień funkcję on_message() tak, aby bohater reagował na kolizję z krawędzią “danger”:
-- plik: 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
-- sprawdź, czy otrzymaliśmy wiadomość o punkcie kontaktu
if message.group == hash("danger") then
-- Zgiń i uruchom ponownie
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
Zmień funkcję init(), aby wysyłała wiadomość “reset” inicjalizującą obiekt, a następnie zapisz plik:
-- plik: hero.script
function init(self)
-- to pozwala nam obsługiwać wejście w tym skrypcie
msg.post(".", "acquire_input_focus")
-- zapisz pozycję
self.position = go.get_position()
msg.post("#", "reset")
end
Jeśli teraz spróbujesz uruchomić grę, szybko stanie się jasne, że mechanizm resetu nie działa. Reset bohatera jest w porządku, ale łatwo możesz zresetować się do sytuacji, w której natychmiast spadniesz na krawędź platformy i zginiesz ponownie. Chcemy więc poprawnie resetować cały poziom po śmierci. Ponieważ poziom to po prostu seria tworzonych platform, wystarczy śledzić wszystkie utworzone platformy i usuwać je przy resecie:
Otwórz plik controller.script i zmodyfikuj kod tak, aby przechowywał id wszystkich tworzonych platform:
-- plik: 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
-- Być może utwórz platformę na losowej wysokości
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, {}, 0.6)
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>
-- Poleć bohaterowi się zresetować.
msg.post("hero#hero", "reset")
-- Usuń wszystkie platformy
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
Otwórz platform.script i zmodyfikuj go tak, aby zamiast po prostu usuwać platformę, która dotarła do lewego skraju, wysyłał do kontrolera poziomu wiadomość z prośbą o usunięcie platformy:
-- plik: platform.script
...
if pos.x < -500 then
msg.post("/level/controller#controller", "delete_spawn", { id = go.get_id() })
end
...

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

I w ten sposób mamy już podstawową pętlę restartu i śmierci!
Następny krok - coś, dla czego warto żyć: monety!
Pomysł jest taki, aby umieszczać w poziomie monety do zebrania przez gracza. Pierwsze pytanie brzmi: jak w ogóle wstawiać je do poziomu. Można na przykład opracować schemat tworzenia, który będzie w jakiś sposób zsynchronizowany z algorytmem tworzenia platform. Ostatecznie wybraliśmy jednak znacznie prostsze podejście i pozwoliliśmy, aby same platformy tworzyły monety:
Utwórz nowy plik skryptu coin.script (kliknij prawym przyciskiem level w Assets pane i wybierz New ▸ Script File). Zastąp kod szablonu poniższym:
-- plik: 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
Dodaj plik skryptu jako komponent Script do obiektu monety (kliknij prawym przyciskiem korzeń w Outline i wybierz Add Component from File).

Plan jest taki, aby monety były tworzone przez obiekty platform, więc dodajmy fabryki monet do platform.go i platform_long.go.

Teraz musimy zmodyfikować platform.script, aby tworzył i usuwał monety:
-- plik: platform.script
function init(self)
self.speed = 540 -- Domyślna prędkość w pikselach/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
Relacje rodzic-dziecko są ściśle modyfikacją grafu sceny. Dziecko będzie transformowane razem ze swoim rodzicem, czyli przesuwane, skalowane lub obracane. Jeśli potrzebujesz dodatkowych relacji “własności” między obiektami gry, musisz śledzić je osobno w kodzie.
Ostatnim krokiem w tym samouczku jest dodanie kilku linii do controller.script:
-- plik: 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 -- Dwa razy więcej monet na długich platformach
end
...
-- controller.script
...
msg.post(p, "set_speed", { speed = self.speed })
msg.post(p, "create_coins", { coins = coins })
table.insert(self.spawns, p)
...

A teraz mamy prostą, ale działającą grę! Jeśli dotarłeś aż tutaj, możesz chcieć kontynuować samodzielnie i dodać następujące elementy:
Pobierz ukończoną wersję projektu tutaj
Na tym kończy się ten samouczek wprowadzający. Teraz śmiało zanurz się w Defold. Przygotowaliśmy wiele podręczników i samouczków, które poprowadzą cię dalej, a jeśli utkniesz, zapraszamy na forum.
Miłego tworzenia w silniku Defold!