Skip to content

Poradnik Developera

Poradnik ten opisuje, jak w prosty sposób rozbudowywać silnik gry, dodawać nowe zasoby wizualne, obiekty na mapie i modyfikować logikę.

1. Jak dodawać nowe modele 3D (.obj) i tekstury?

Silnik posiada zintegrowany własny loader plików .obj (przetwarzający wierzchołki i koordynaty UV) oraz parser obrazów.

Krok 1: Deklaracja wskaźników

W pliku include/Renderer.h w sekcji private: dodaj wskaźnik na nowy obiekt geometrii oraz zmienną przechowującą identyfikator tekstury (VRAM):

TexturedMesh* myNewMesh;
GLuint myNewTexture;

Krok 2: Ładowanie zasobów z dysku

W pliku src/Renderer.cpp, w metodzie Renderer::init(), wczytaj pliki (upewnij się, że są w folderze assets/):

myNewMesh = loadOBJ("assets/nowy_model.obj");
myNewTexture = loadTexture("assets/nowa_tekstura.png");

Krok 3: Rysowanie modelu w pętli renderującej

W metodzie Renderer::render(...) nałóż teksturę, przygotuj transformacje, narysuj model i posprzątaj:

glPushMatrix();

// 1. Przesuń w przestrzeni
glTranslatef(x, y, z);

// 2. Obróć (opcjonalnie)
glRotatef(angle, 0.0f, 1.0f, 0.0f);

// 3. Skaluj
glScalef(1.5f, 1.5f, 1.5f);

// 4. Aktywuj shader teksturowany i przypnij teksturę
texturedShader->use();

glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, myNewTexture);

// 5. Narysuj model z VRAM
myNewMesh->draw();

// 6. Posprzątaj stan
glDisable(GL_TEXTURE_2D);

texturedShader->stop();
glPopMatrix();

2. Tworzenie własnych klas i obiektów fizycznych

Każdy nowy, interaktywny obiekt na mapie (NPC, winda, przeciwnik) powinien dziedziczyć z abstrakcyjnej klasy bazowej GameObject lub Entity.

Przykład własnego przeciwnika

#include "Entity.h"

class EnemyBot : public Entity {
public:
    EnemyBot(float startX, float startY, float startZ)
        : Entity(startX, startY, startZ,
                 1.0f, 2.0f, 1.0f,
                 "WROG") {}

    // Ta funkcja wywołuje się 60 razy na sekundę (Fixed Timestep)
    void update(double fixedDeltaTime) override {
        // Logika AI, np. ruch po osi X
        x += 2.0f * fixedDeltaTime;
    }
};

Po utworzeniu klasy zainicjuj jej instancję w Application, a następnie w głównej pętli odpytuj o kolizję z graczem za pomocą wbudowanej metody:

player.checkCollisionAABB(enemyInstance);

3. Rozszerzanie synchronizacji sieciowej

Jeśli wprowadzisz nową mechanikę, np. pasek zdrowia (HP) lub stany animacji gracza, musisz je synchronizować przez UDP.

Aktualizacja struktury

Otwórz include/PlayerData.h i dodaj nowe pole:

int health;

Aktualizacja interpolacji i odbioru

W NetworkManager.cpp, w metodzie receiveUpdates, przypisz odebrane wartości od przeciwnika (packet.payload.gameData) do jego wirtualnego odpowiednika po stronie Twojego klienta.

Obiekty PlayerData innych graczy w pętli renderowania będą teraz miały dostęp do nowej zmiennej i mogą np. rysować odpowiedni pasek zdrowia nad modelami.

4. Edycja proceduralnego generatora mapy

Klasa LevelGenerator odpowiada za budowanie trasy. Algorytm w pętli wylicza graniczne wartości skoku z fizyki gracza (playerMaxJump, playerMaxHoriz), a następnie rozrzuca przeszkody (Obstacle).

Możesz zmienić zachowanie generatora poprzez:

  • zmianę liczby poziomów w górę (layerCount),
  • zmianę gęstości platform (blocksPerLayer),
  • modyfikację rozmieszczenia checkpointów (isCheckpoint),
  • zmianę ścian ograniczających mapę (isWall).

Generator na samym końcu rozstawia wokół mapy ściany z flagą isWall, uniemożliwiając wypadnięcie poza arenę w poziomie.

5. Dodawanie opcji i przycisków w Menu

Moduł MenuManager korzysta z maszyny stanów i listy wirtualnych przycisków.

Aby dodać nową zakładkę lub przycisk w menu (np. „Twórcy”):

Krok 1

W Protocol.h lub MenuManager.h dodaj nowy stan:

MenuState::CREDITS

oraz nową akcję:

MenuAction::OPEN_CREDITS

Krok 2

W pliku src/MenuManager.cpp, w metodzie loadButtonsForState(), dodaj nowy przycisk:

activeButtons.push_back({
    centerX - 100, // X
    startY + 140,  // Y

    200, 50,       // Szerokość, Wysokość

    "Tworcy Gry",  // Tekst

    MenuAction::OPEN_CREDITS,

    0,
    false
});

Krok 3

W Application.cpp, w pętli sprawdzającej kliknięcia (switch(action)), obsłuż akcję:

MenuAction::OPEN_CREDITS

i przekaż informację do silnika, aby przełączył stan menu lub załadował odpowiedni plik wideo.

6. Odtwarzacz Wideo i Dialogi

Wideo

Klasa VideoPlayer potrafi dekodować pliki wideo na teksturę. Musisz pamiętać o wywoływaniu:

update();

przed:

render();

aby klatki aktualizowały się poprawnie w czasie (deltaTime).

Dialogi

Skrypty rozmów czytane są z pliku:

assets/dialogs.json

Jeśli chcesz dodać np. sekretne zakończenie, dopisz do pliku JSON nową strukturę z własną etykietą, a następnie załaduj ją w kodzie:

dialogManager.startSection("sekretne_zakonczenie");

Silnik wyrenderuje rozmowę automatycznie, wyświetlając nakładkę z tekstem, z uwzględnieniem podziału na wiersze.