| autoloads | ||
| cannon | ||
| flag | ||
| fonts | ||
| game | ||
| kra | ||
| misc | ||
| music | ||
| player | ||
| sfx | ||
| sky | ||
| terrain | ||
| textures/icons | ||
| .editorconfig | ||
| .gitattributes | ||
| .gitignore | ||
| default_bus_layout.tres | ||
| export_presets.cfg | ||
| LICENSE.md | ||
| project.godot | ||
| README.md | ||
Toss the Wolf
Poniższa dokumentacja opisuje architekturę, główne moduły oraz mechaniki gry Toss the Wolf. Projekt został zrealizowany w silniku Godot 4.6.2, wykorzystuje Jolt Physics jako silnik fizyczny oraz renderowanie w trybie Mobile.
Roadmap
- Dodać więcej elementów interaktywnych
- Obiekty Powietrzne
- Latający kibel
- Latające kolce
- Wybuchające ptaki z przyczepianymi detonatorami
- Chmury burzowe spowalniające i paraliżujące gracza na ok. 1 sekundę
- Latające Kowadła
- UFO: Statek kosmiczny porywa wilka na ułamek sekundy i „wypluwa” go 500 pikseli dalej z pełną prędkością.
- Obiekty Lądowe
- Miny lądowe paraliżujące gracza, ale dające mu duży boost.
- Czołg z Newgrounds, przejeżdza gracza dając mu mały impuls do góry, większy do przodu.
- Obiekty Powietrzne
- Nowe biomy
- Bagno: Teren ze zwiększonym tłumieniem liniowym (linear damping). Uderzenie w niego drastycznie redukuje siłę odbicia.
- Zamarznięte Jezioro: Powierzchnia o niemal zerowym tarciu. Gracz po upadku może sunąć po niej przez setki metrów, jeśli upadł pod odpowiednim kątem.
- Zastosować wzorzec projektowy (object pooling) w generatorze terenu, by jeszcze bardziej go usprawnić
- Dodać lekkie kierowanie graczem w kierunku kursora (lewym przyciskiem myszy) po wystrzale
- Strzelanie w gracza pistoletem + ammunicja którą można zdobywać w locie. Strzelanie gdy gracz leci w dół podbija go bardziej
- Integracja i uwierzytelnianie za pomocą Newgrounds - osiągnięcia, zapisy, tablice wyników w chmurze
- Inne wyrzutnie
- Proca Gigantów z mechaniką naciągania jak w Angry Birds. Strzela strzałą - nabicie kilku żywych przeszkód daje impuls graczowi
- Wulkaniczny Gejzer - wyrzuca wilka idealnie pionowo w górę; gracz musi użyć wiatru lub własnego ekwipunku, by skierować lot do przodu.
- Wyrzutnia Rakietowa - słaby impuls początkowy, ale ogromne przyspieszenie nabierane w pierwszych sekundach lotu, dopóki rakieta nie wybuchnie.
- Działo Grawitacyjne = zamiast nadawać maksymalny pęd, wystrzał drastycznie obniża skalę grawitacji wilka na pierwsze 10 sekund lotu.
Architektura Globalna (Autoloads / Singletons)
W grze zaimplementowano scentralizowany system zarządzania stanem oparty na wzorcu Singleton (Autoload). Gwarantuje to łatwy dostęp do najważniejszych danych i funkcji z poziomu każdego węzła w drzewie sceny.
🌐 Globals
Prosty skrypt przechowujący globalny stan sesji. Zapisuje na bieżąco wektor pozycji gracza (player_position), flagę stanu ładowania (is_loading) oraz pozwala na globalne wyłączenie kontroli gracza (disable_controls).
⚡ SignalBus
Centralna szyna zdarzeń. Optymalizuje komunikację między węzłami, unikając bezpośrednich referencji.
- Aktualnie odpowiedzialny głównie za optymalizację logiki terenu – co
0.2sekundy emituje sygnałtick_terrain, zamiast zmuszać każdy chunk terenu do aktualizacji w klatce (w_process).
🎬 LevelManager
Moduł obsługujący płynne przejścia pomiędzy scenami. Udostępnia metodę restart(), która przeładowuje główną scenę (game.tscn) używając efektów zanikania ekranu (fade_in, fade_out) trwających 1 sekundę, kontrolując przy tym flagę blokady w Globals.
💾 SaveManager
Zarządza systemem zapisu najlepszego wyniku (personal_best).
- Odczytuje i zapisuje dane z/do pliku
user://savegame.savew formacie JSON. - Posiada zabezpieczenie sprawdzające, czy nowy osiągnięty dystans (pobierany z armaty) jest rzeczywiście większy od poprzedniego rekordu. Jeśli tak, emituje sygnał
new_personal_best, na który reagują elementy interfejsu (np. Flaga). - Generowanie Nicku (
username): Przy pierwszym uruchomieniu gry, gdy plik zapisu nie istnieje, skrypt używa funkcjigenerate_random_username()do stworzenia identyfikatora gracza. Ten identyfikator jest trwale zapisywany w pliku JSON i wykorzystywany do identyfikacji gracza na serwerze wyników.
Więcej o tym w sekcji Save Manager.
🎵 SoundManager
Zarządza systemem audio w grze, obsługując niezależnie muzykę i efekty dźwiękowe.
- Odtwarzacz SFX: Wykorzystuje dynamicznie tworzony
AudioStreamPolyphonic, wspierając do 64 nakładających się na siebie dźwięków jednocześnie (maksymalna polifonia). Dzięki temu nasilone akcje, takie jak szybkie uderzenia o ziemię (wywołujące pliki audio zBLOOD_SPLATTER_SOUNDS), nie "ucinają" się nawzajem. - Odtwarzacz Muzyki: Implementuje system inteligentnych przejść (crossfade). Funkcja
play_music()korzysta z drzewa Tweenów (krzyweTRANS_EXPO,EASE_INiEASE_OUT), by miękko wyciszyć aktualnie odtwarzany utwór do -80.0 dB, podmienić ścieżkę dźwiękową i łagodnie przywrócić jej głośność.
Mechaniki Główne
💣 Armata (Cannon)
Moduł odpowiedzialny za celowanie, ładowanie i wystrzał gracza.
- Ładowanie strzału: Kiedy gracz przytrzymuje przycisk, skrypt oblicza siłę strzału. Wykorzystuje funkcję ping-pong i clampowaną krzywą wykładniczą (
exp_clamped), aby wskaźnik siły płynnie rósł i malał w zakresie od0.0do1.0, dając graczowi lepsze wyczucie "timingu". - Wystrzał: Zwolnienie przycisku aplikuje ostateczną siłę impulsywną (mnożnik
MAX_FORCE = 7000.0) za pomocą osi lokalnej obiektu (global_transform.x). Wystrzał odtwarza specjalny efekt dźwiękowy (SFX) przy użyciu zintegrowanego SoundManagera. - Śledzenie postępu: Armata odpowiada również za przeliczanie przebytego dystansu względem początkowego punktu strzału (
start_pos) i aktualizację wartości do UI z zaokrągleniem do jednego miejsca po przecinku. Oblicza moment, w którym gracz całkowicie wyhamuje w powietrzu lub na ziemi.
🐺 Gracz (Player)
Główny fizyczny obiekt gry, dziedziczący po RigidBody2D.
- Fizyka i Kolizje: Opiera się na silniku Jolt. W metodzie
_on_body_enterednasłuchuje zderzeń z podłożem (collision layer 2). - System obrażeń (Bloodsplats): Jeśli gracz uderzy w podłoże, a jego wektor prędkości (długość
linear_velocity) przekracza stałąMIN_IMPACT(1700.0), system tworzy instancję obiektuBLOODSPLAT(bloodsplat.gd), który dla urozmaicenia wizualnego losuje swoją klatkę animacji z atlasu (obliczane za pomocąrandi_rangena bazie hframes i vframes), po czym zostaje osadzony w hierarchii obiektu terenu. - Koniec lotu: Zatrzymanie się gracza uruchamia funkcję
_on_player_stopped(), która wyświetla ekran Game Over i wysyła polecenie zapisu postępu doSaveManager.
🎥 Kamera (Camera2D)
Dynamiczna kamera śledząca gracza na bieżąco.
Po przekroczeniu pierwszych 20 metrów lotu, kamera płynnie zmienia swoje przesunięcie (offset). Używa funkcji lerp, dodając dynamiczny offset w osi Y bazujący na wektorze opadania gracza (linear_velocity.y / 40). Dzięki temu kamera patrzy w kierunku lotu gracza, znacząco poprawiając widoczność toru lotu na osi pionowej (clampowane miedzy -120 a 120 pikseli).
🖥️ Interfejs i Scena Główna
- Game (
game.gd): Pełni rolę głównego węzła scalającego środowisko startowe. Kontroluje bazowe referencje, m.in. dla węzła armaty. - Przycisk Resetu (
reset_button.gd): Element interfejsu (UI) pojawiający się na ekranie końcowym lotu gracza (Game Over). Nasłuchuje wbudowanego sygnałupressed, po którym błyskawicznie chowa sam siebie i wywołuje na obiekcieLevelManagerproceduręrestart(), wznawiając nową rundę.
🌍 Tablica Wyników Online (Leaderboard)
Zintegrowany system sieciowy pozwalający na rywalizację z innymi graczami, zaimplementowany w skrypcie label_scores.gd i powiązany z kontrolką UI (RichTextLabel).
- Komunikacja HTTP: Gra wykorzystuje węzeł
HTTPRequestdo komunikacji z zewnętrznym serwerem (API pod adresem IP zdefiniowanym w stałejSERVER_URL). Komunikacja opiera się na wymianie pakietów w formacie JSON (metody GET i POST). - Automatyczne przesyłanie postępów: Skrypt jest podłączony do szyny
SaveManageri nasłuchuje sygnałunew_personal_best. Gdy tylko gracz ustanowi nowy rekord na swoim urządzeniu, gra wywołuje funkcjęsubmit_score(), wysyłając paręusernameorazscoredo bazy danych, a po otrzymaniu kodu200 OKodświeża lokalną tablicę. - Dynamiczne wyświetlanie (TOP 10): Pobrana z serwera lista najlepszych wyników jest parsowana z JSONa i formatowana jako tekst. Dzięki włączonej opcji
bbcode_enabledwRichTextLabel, skrypt iteruje po wpisach i porównuje pobrane nazwy użytkowników z lokalnym zmiennąSaveManager.username. Jeśli nastąpi dopasowanie, wynik aktualnego gracza zostaje pogrubiony i podświetlony na żółto (tagi[color=yellow]), znacznie poprawiając czytelność własnego pozycjonowania w rankingu.
Moduły Wizualne
🚩 Flaga Rekordu (Flag Shader)
Flaga wskazująca najlepszy do tej pory wynik gracza wykorzystuje zaawansowany shader (flag_wave.gdshader) typu canvas_item.
- Animuje wierzchołki materiału używając funkcji trygonometrycznych (sinus) bazujących na upływie czasu (
TIME) i zmiennych UV. - Pozwala na dostosowanie parametrów takich jak amplituda, częstotliwość i szybkość falowania.
- Za pomocą parametru
pole_sidepotrafi sztucznie wygasić (wytłumić) falowanie z odpowiedniej strony tekstury (tam, gdzie flaga dotyka masztu), korzystając z funkcjismoothstep, dając realistyczny efekt przypięcia materiału. Co więcej, posiada zaprogramowany animowany „wjazd” w kadr (Tween na bazie osi X), z jednoczesnym kręcącym się licznikiem metrów za każdym razem, gdy padnie nowy rekord.
Generacja Terenu
Node odpowiedzialny za proceduralne, pseudo-losowe generowanie nieskończonego terenu w grze Toss the Wolf. System dynamicznie ładuje fragmenty mapy oraz elementy interaktywne na podstawie wybranego środowiska, dbając jednocześnie o optymalizację pamięci poprzez automatyczne usuwanie niewidocznych obiektów.
TerrainGenerator (główny kontroler)
Główny Node2D który zarządza logiką powstawania mapy. Istnieje on jako lokalny node w scenie Game.
- Metoda
start_generator(biome_name)ładuje dane wybranego biomu i pregeneruje początkowy pas terenu (domyślnie 16 chunków). Powinna zostać wywołana przed jakąkolwiek inną operacją generatora. - Pierwsze 6 chunków (
UNPOPULATED_CHUNK_COUNT) jest celowo pozbawionych elementów interaktywnych, zapewniając bezpieczną strefę startową na początku rundy. - System opiera się na sygnale
SignalBus.tick_terrain. Oblicza dystans i dynamicznie dostawia nowe segmenty, utrzymując stały bufor wygenerowanego terenu przed pozycją gracza (domyślnie 4 chunki wyprzedzenia).
BiomeData (Resource)
Zasób danych przechowujący konfigurację unikalną dla konkretnego środowiska (np. grassy). Definiuje dwie pule obiektów gotowych do instancjowania:
chunks(Array[PackedScene]) - kolekcja wariantów podłoża.features(Array[PackedScene]) - kolekcja możliwych do wylosowania elementów interaktywnych.
TerrainChunk (segment terenu)
Pojedynczy skrawek mapy o stałej szerokości 1000px.
Każdy nowo powołany chunk (poza strefą startową) wywołuje metodę populate_features(). Szansa na postawienie wylosowanego elementu interaktywnego w wyznaczonym punkcie (Marker2D) wynosi 20% (1 na 5, zdefiniowane przez stałą FEATURE_RARITY).
Każdy TerrainChunk nasłuchuje sygnału ticków terenu i automatycznie zwalnia pamięć (queue_free()), gdy znajdzie się w odległości 3000px za graczem, dbając o stałe, niskie zużycie pamięci.
BiomeFeature
Dedykowany obiekt pojawiający się na chunku (maksymalnie jeden element na segment). Zwykle jest to w pełni oskryptowany Node2D (np. grzyb odbijający z folderu f_bounce_shroom) realizujący własną logikę poprzez interakcję fizyczną z obiektami RigidBody2D, a konkretnie graczem. Uderzenie wywołuje apply_impulse(), nadając graczowi nagłe przyspieszenie, odpala lokalną animację aktywacji (np. sprężynowania) i wywołuje dźwięk za pomocą SoundManager.play_sfx().
Przepływ działania (w skrócie)
- Start: Uruchomienie rundy wywołuje
start_generator("nazwa_biomu"), co konfiguruje dane zBiomeData. - Pregeneracja: Generator stawia początkowe 16 chunków, w tym bezpieczny bufor startowy wolny od interakcji.
- Synchronizacja: Pętla nasłuchująca
SignalBus.tick_terrainna bieżąco analizuje współrzędną X gracza. - Rozbudowa: Gdy gracz zbliża się do granicy pregenerowanego terenu, dodawane są kolejne chunki z 20% szansą na wygenerowanie
BiomeFeature. - Czyszczenie: Fragmenty mapy, które gracz minie o 3000px, są bezpowrotnie niszczone.
Save Manager
Save Manager to Autoload który zarządza zapisywaniem i wczytywaniem postępu gracza (aktualnie jedynie najlepszym wynikiem personal_best).
Zmienna personal_best przechowuje lokalnie najwyższy zdobyty dotychczas wynik (przebyty dystans w “metrach”).
Przy pierwszym uruchomieniu gry, gdy plik zapisu nie istnieje, skrypt używa funkcji generate_random_username(). Łączy ona losowy przymiotnik (np. "Szalony", "Krwawy") z losowym rzeczownikiem (np. "Wilk", "Kibel") oraz liczbą (10-99), tworząc unikalny identyfikator gracza (np. "PoteznyKibel99"). Ten identyfikator jest trwale zapisywany w pliku JSON i wykorzystywany do identyfikacji gracza na serwerze wyników.
Funkcja save_game()
Pobiera aktualny dystans za pomocą cannon.get_distance(), skaluje go (dzieląc przez 100.0) i zaokrągla do jednego miejsca po przecinku metodą snapped(). Zapis do pliku następuje wtedy, gdy nowo obliczony dystans jest większy niż dotychczasowy (personal_best). Gdy warunek jest spełniony, aktualizuje zmienną personal_best, emituje zintegrowany sygnał na magistralę, po czym tworzy nową strukturę danych JSON do zapisu w dedykowanym pod ścieżką user://savegame.save.
Funkcja load_game()
Odpowiedzialna za odczyt danych z pliku zapisanego przez save_game(). Zwraca wartość typu float. Najpierw sprawdza, czy plik user://savegame.save istnieje. Jeśli go nie ma, natychmiast zwraca 0.0. Gdy plik zostanie znaleziony, otwiera go za pomocą FileAccess.READ, pobiera zawartość jako string tekstowy i przetwarza go za pomocą wbudowanego obiektu JSON. Jeśli parsowanie zakończy się sukcesem (if parse_result == OK) i zebrane dane zawierają klucz personal_best, zwracana jest jego wartość dla całego projektu.