Tutorials
Tutorials

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

Ukończenie poziomu - przykładowy projekt

W tym przykładowym projekcie, który możesz otworzyć w edytorze albo pobrać z GitHub, pokazujemy efekty służące do prezentowania zliczania wyniku po ukończeniu poziomu. Łączny wynik jest stopniowo zwiększany, a po osiągnięciu kolejnych progów pojawiają się trzy gwiazdki. Przykład korzysta też z funkcji szybkiego przeładowania, żeby przyspieszyć iterowanie podczas dostrajania wartości.

Scena jest uruchamiana wiadomością z gry. Wiadomość zawiera uzyskany łączny wynik oraz poziomy, przy których powinny pojawić się trzy gwiazdki. Gdy to się dzieje, tekst nagłówka (“Level completed!”) zaczyna stopniowo się pojawiać, jednocześnie zmniejszając się do normalnego rozmiaru (100%). Dzieje się to w on_message() poniżej.

Po zakończeniu animacji nagłówka zaczyna się zliczanie całkowitego wyniku. Za każdym razem bieżący wynik jest zwiększany o niewielki krok. Potem sprawdzamy, czy został przekroczony któryś z poziomów gwiazdek; jeśli tak, uruchamia się animacja gwiazdki (patrz niżej). Dopóki nie osiągniemy wyniku docelowego, całkowity wynik jest animowany z efektem odbicia. Im bliżej wyniku docelowego, tym jego skala rośnie aż do wartości maksymalnej. W podobny sposób kolor stopniowo zmienia się z białego na zielony. Dzieje się to w inc_score().

Za każdym razem, gdy pojawia się gwiazdka, najpierw stopniowo się pokazuje, a potem zmniejsza do normalnego rozmiaru. To dzieje się w animate_star().

Gdy animacja gwiazdki dobiegnie końca, wokół większej gwiazdki pojawiają się mniejsze gwiazdki rozmieszczone po okręgu. Dzieje się to w spawn_small_stars().

Następnie są animowane tak, aby losowo wystrzeliły z gwiazdki. Ich prędkość i skala są losowane podczas rozszerzania się na zewnątrz. Potem stopniowo zanikają i ostatecznie są usuwane. Dzieje się to w animate_small_star() i delete_small_star().

Gdy wynik osiągnie wartość docelową, odcisk high score stopniowo się pojawia i wraca na swoje miejsce. Jest to uruchamiane na końcu inc_score() i wykonywane w animate_imprint().

Funkcja setup() dba o to, aby węzły miały poprawne wartości początkowe. Wywołując setup() z on_reload(), upewniamy się, że wszystko zostanie poprawnie skonfigurowane przy każdym przeładowaniu skryptu z Defold Editor.

-- plik: level_complete.gui_script

-- jak szybko wynik zwiększa się w ciągu sekundy
local score_inc_speed = 51100
-- jak długi jest odstęp między kolejnymi aktualizacjami wyniku
local dt = 0.03
-- skala wyniku na początku zliczania
local score_start_scale = 0.7
-- skala wyniku po osiągnięciu wartości docelowej
local score_end_scale = 1.0
-- jak mocno wynik "odbija" przy każdym zwiększeniu
local score_bounce_factor = 1.1
-- ile małych gwiazdek utworzyć dla każdej dużej gwiazdki
local small_star_count = 16

local function setup(self)
    -- ustaw kolor nagłówka jako przezroczysty
    local c = gui.get_color(self.heading)
    c.w = 0
    gui.set_color(self.heading, c)
    -- ustaw cień nagłówka jako przezroczysty
    c = gui.get_shadow(self.heading)
    c.w = 0
    gui.set_shadow(self.heading, c)
    -- początkowo ustaw skalę nagłówka na dwukrotną
    local s = 2
    gui.set_scale(self.heading, vmath.vector3(s, s, s))
    -- ustaw początkowy wynik (0)
    gui.set_text(self.score, "0")
    -- ustaw kolor wyniku na nieprzezroczystą biel
    gui.set_color(self.score, vmath.vector4(1, 1, 1, 1))
    -- ustaw skalę tak, aby wynik mógł rosnąć podczas zliczania
    gui.set_scale(self.score, vmath.vector4(score_start_scale, score_start_scale, 1, 0))

    -- ustaw wszystkie duże gwiazdki jako przezroczyste
    for i=1,#self.stars do
        gui.set_color(self.stars[i], vmath.vector4(1, 1, 1, 0))
    end
    -- ustaw imprint jako przezroczysty
    gui.set_color(self.imprint, vmath.vector4(1, 1, 1, 0))
    -- wynik aktualnie wyświetlany na ekranie
    self.current_score = 0
    -- docelowy wynik podczas zliczania
    self.target_score = 0
end

function init(self)
    -- pobierz węzły dla wygodniejszego dostępu
    self.heading = gui.get_node("heading")
    self.stars = {gui.get_node("star_left"), gui.get_node("star_mid"), gui.get_node("star_right")}
    self.score = gui.get_node("score")
    self.imprint = gui.get_node("imprint")
    -- kolor początkowy wyniku
    self.score_start_color = vmath.vector4(1, 1, 1, 1)
    -- zapisz końcowy kolor wyniku, do którego będziemy później animować
    self.score_end_color = gui.get_color(self.score)
    setup(self)
end

-- usuń małą gwiazdkę, gdy zakończy animację
local function delete_small_star(self, small_star)
    gui.delete_node(small_star)
end

-- animuj małą gwiazdkę zgodnie z podaną pozycją początkową i kątem
local function animate_small_star(self, pos, angle)
    -- kierunek ruchu małej gwiazdki
    local dir = vmath.vector3(math.cos(angle), math.sin(angle), 0, 0)
    -- utwórz małą gwiazdkę
    local small_star = gui.new_box_node(pos + dir * 20, vmath.vector3(64, 64, 0))
    -- ustaw jej teksturę
    gui.set_texture(small_star, "small_star")
    -- ustaw jej kolor na pełną biel
    gui.set_color(small_star, vmath.vector4(1, 1, 1, 1))
    -- ustaw małą skalę początkową
    local start_s = 0.3
    gui.set_scale(small_star, vmath.vector3(start_s, start_s, 1))
    -- zróżnicowanie skali każdej małej gwiazdki
    local end_s_var = 1
    -- faktyczna końcowa skala tej gwiazdki
    local end_s = 0.5 + math.random() * end_s_var
    gui.animate(small_star, gui.PROP_SCALE, vmath.vector4(end_s, end_s, 1, 0), gui.EASING_NONE, 0.5)
    -- zróżnicowanie pokonanego dystansu, czyli w praktyce prędkości gwiazdki
    local dist_var = 300
    -- faktyczny dystans, jaki pokona gwiazdka
    local dist = 400 + math.random() * dist_var
    gui.animate(small_star, gui.PROP_POSITION, pos + dir * dist, gui.EASING_NONE, 0.5)
    gui.animate(small_star, gui.PROP_COLOR, vmath.vector4(1, 1, 1, 0), gui.EASING_OUT, 0.3, 0.2, delete_small_star)
end

-- utwórz określoną liczbę małych gwiazdek
local function spawn_small_stars(self, star)
    -- pozycja dużej gwiazdki, wokół której pojawią się małe gwiazdki
    local p = gui.get_position(star)
    for i = 1,small_star_count do
        -- oblicz kąt dla konkretnej małej gwiazdki
        local angle = 2 * math.pi * i/small_star_count
        -- a także jej pozycję
        local pos = vmath.vector3(p.x, p.y, 0)
        -- utwórz i animuj małą gwiazdkę
        animate_small_star(self, pos, angle)
    end
end

-- rozpocznij animację pojawiania się dużej gwiazdki
local function animate_star(self, star)
    -- czas płynnego pojawiania się
    local fade_in = 0.2
    -- ustaw przezroczystość
    gui.set_color(star, vmath.vector4(1, 1, 1, 0))
    -- pokaż płynnie
    gui.animate(star, gui.PROP_COLOR, vmath.vector4(1, 1, 1, 1), gui.EASING_IN, fade_in)
    -- skala początkowa
    local scale = 5
    gui.set_scale(star, vmath.vector3(scale, scale, 1))
    -- zmniejsz z powrotem do właściwego rozmiaru
    gui.animate(star, gui.PROP_SCALE, vmath.vector4(1, 1, 1, 0), gui.EASING_IN, fade_in, 0, spawn_small_stars)
end

-- rozpocznij animację pojawiania się imprint
local function animate_imprint(self)
    -- chwilę odczekaj, zanim pojawi się imprint
    local delay = 0.8
    -- czas płynnego pojawiania się
    local fade_in = 0.2
    -- skala początkowa
    local scale = 4
    gui.set_scale(self.imprint, vmath.vector4(scale, scale, 1, 0))
    -- zmniejsz z powrotem do właściwego rozmiaru
    gui.animate(self.imprint, gui.PROP_SCALE, vmath.vector4(1, 1, 1, 0), gui.EASING_IN, fade_in, delay)
    -- jednocześnie pokazuj płynnie
    gui.animate(self.imprint, gui.PROP_COLOR, vmath.vector4(1, 1, 1, 1), gui.EASING_IN, fade_in, delay)
end

-- zwiększ wynik o jeden krok w stronę wartości docelowej
local function inc_score(self, node)
    -- o ile wynik zwiększa się w tym kroku
    local score_inc = score_inc_speed * dt
    -- nowy wynik po zwiększeniu
    local new_score = self.current_score + score_inc
    for i = 1,#self.stars do
        -- uruchom animację dużej gwiazdki, jeśli przekroczymy próg jej pojawienia się
        if self.current_score < self.star_levels[i] and new_score >= self.star_levels[i] then
            animate_star(self, self.stars[i])
        end
    end
    -- zaktualizuj wynik, ale ogranicz go do wartości docelowej
    self.current_score = math.min(new_score, self.target_score)
    -- zaktualizuj wynik na ekranie
    gui.set_text(self.score, tostring(self.current_score))
    -- jeśli to jeszcze nie koniec, kontynuuj animację i zwiększanie
    if self.current_score < self.target_score then
        -- określ, jak blisko jesteśmy celu
        local f = self.current_score / self.target_score
        -- mieszaj kolor, aby uzyskać powolne przejście
        local c = vmath.lerp(f, self.score_start_color, self.score_end_color)
        gui.animate(self.score, gui.PROP_COLOR, c, gui.EASING_NONE, dt, 0, inc_score)
        -- nowa skala dla tego kroku
        local s = vmath.lerp(f, score_start_scale, score_end_scale)
        -- zwiększ skalę o współczynnik odbicia
        local sp = s * score_bounce_factor
        -- animuj od powiększonej skali z powrotem do właściwej
        gui.set_scale(self.score, vmath.vector4(sp, sp, 1, 0))
        gui.animate(self.score, gui.PROP_SCALE, vmath.vector4(s, s, 1, 0), gui.EASING_NONE, dt)
    else
        -- gotowe, pokaż płynnie imprint
        -- UWAGA! w prawdziwym przypadku należałoby to sprawdzić względem faktycznie zapisanego rekordu
        animate_imprint(self)
    end
end

function on_message(self, message_id, message, sender)
    -- ktoś informuje nas, że trzeba wyświetlić ekran ukończenia poziomu
    if message_id == hash("level_completed") then
        -- pobierz uzyskany wynik oraz progi punktowe dla wyświetlenia gwiazdek
        self.target_score = message.score
        self.star_levels = message.star_levels
        -- pokaż płynnie nagłówek ("Level completed!")
        local c = gui.get_color(self.heading)
        c.w = 1
        gui.animate(self.heading, gui.PROP_COLOR, c, gui.EASING_IN, dt, 0.0, inc_score)
        c = gui.get_shadow(self.heading)
        c.w = 1
        gui.animate(self.heading, gui.PROP_SHADOW, c, gui.EASING_IN, dt, 0.0)
        -- zmniejsz go do właściwego rozmiaru
        gui.animate(self.heading, gui.PROP_SCALE, vmath.vector4(1, 1, 1, 0), gui.EASING_IN, 0.2, 0.0)
    end
end

-- ta funkcja jest wywoływana przy przeładowaniu skryptu
-- ustawiając scenę i symulując ukończenie poziomu, uzyskujemy bardzo szybki przebieg pracy do strojenia
function on_reload(self)
    -- dopilnuj, by uwzględnić wszelkie zmiany w konfiguracji
    setup(self)
    -- zasymuluj ukończenie poziomu
    msg.post("#gui", "level_completed", {score = 102000, star_levels = {40000, 70000, 100000}})
end