Tworzenie gry strategicznej typu Dune 2.


Poprzedni wpis (1) | Następny wpis (3)

Wpis 2 (23.09.2016)

Temat: Dodawanie i obsługa wydarzeń.

W grze wiele akcji będzie miało swoje źródło w przeróżnego rodzaju wydarzeniach. Wydarzeniem szczególnym jest upłynięcie jakiegoś czasu.

Zauważmy też, że wiele akcji w grze nie musi być wykonywana w czasie rzeczywistym. Można zachować odstęp czasowy pomiędzy nimi. Ta prosta obserwacja pozwoli na zaoszczędzenie czasu procesora.

Zrealizuję to w ten sposób, że wszelakie akcje powodowane minięciem danego czasu będa przechowywane w tablicy, której rozmiar to maksymalny odstęp czasowy liczony w jednostkach czasu. Przyjmijmy, że jednostką czasu w naszej grze będzie 1/50 sekundy przy normalnej prędkości.

Chodzi o to, by mieć błyskawiczny dostęp do tej tablicy po indeksie, by móc łatwo dodawać wydarzenia. Tablica będzie przechowywać listy akcji, jakie muszą być wykonane, gdy licznik czasu przybierze odpowiednią wartość.

Teraz co każdą jednostkę czasową procedura wykonywania akcji będzie zaglądać do odpowiedniego elementu tablicy i wykonywać wszystkie akcje tam umieszczone.

Jest jeszcze jedna kategoria wydarzeń czasowych - upłynięcie jakiegoś czasu liczonego w minutach. Tego typu wydarzenia czasowe będą obsługiwane co sekundę za pomocą odpowiedniej procedury obsługi takich zdarzeń.

Zauważmy, że ta lista akcji może też być powiązana z innymi wydarzeniami, np. zniszczenie budynku itp., więc przyda się później.

Zdefiniujmy najpierw strukturę Akcja. Głównym elementem będzie wskaźnik do funkcji, która pobiera strukturę Wydarzenie. Struktura zawiera również wskaźnik do danych pomocniczych interpretowanych przez tą funkcję oraz wskaźnik na następną akcję w kolejce.

struct Akcja
{
    LONG (*akcja)(struct Wydarzenie *wydarzenie, APTR dane); /* Wywołaj tą funkcję, 
	by podjąć akcję */
    APTR dane; /* Dane pomocnicze */
    struct Akcja *nastepna; /* Następna akcja powiązana z tym samym wydarzeniem */
};
Dane pomocnicze są ustawiane przez funkcję dodającą akcję do kolejki i interpretowane przez funkcję składową. Mogą np. zawierać adres budynku, który jest w budowie, a funkcja składowa to BudujBudynek, która jest odpowiedzialna za postęp w budowie.

Teraz zadeklarujmy tablicę wskaźników do tej struktury - akcje podejmowane w przypadku przybrania przez licznik jednostek czasu odpowiedniego indeksu:

#define MAX_OKRES_CZASU (256) /* Maksymalny możliwy okres czasu */

struct Akcja *akcje_okresowe[MAX_OKRES_CZASU]; /* Akcje podejmowane w danej 
	jednostce czasowej */
Co zawiera struktura wydarzenie? Na pewno typ wydarzenia. Później dojdą dodatkowe informacje, jak np. zniszczona jednostka, zniszczony budynek itp.
struct Wydarzenie
{
    UBYTE typ; /* Typ wydarzenia */
};

enum
{
    WYDARZENIE_CZASOWE
};
Przykładowo funkcja wykonująca akcje powiązane z wydarzeniami czasowymi może wyglądać tak:
UBYTE okres=0; /* Okres jako zmienna globalna */

void Wykonaj_Akcje_Okresowe()
{
    struct Akcja *akcja, *nastepna;
    struct Wydarzenie wydarzenie = { WYDARZENIE_CZASOWE };

    akcja = akcje_okresowe[okres]; /* Pobieramy pierwszą akcję */
    akcje_okresowe[okres] = NULL; /* Odłączamy kolejkę akcji z tablicy */

    /* Wykonujemy wszystkie akcje w kolejce */
    while (akcja != NULL)
    {
        nastepna = akcja->nastepna;
        akcja->akcja(&wydarzenie, akcja->dane); /* Wykonujemy akcję */
        Zwolnij_Akcje(akcja); /* Zwalniamy pamięć */
        akcja = nastepna; /* Przechodzimy do następnej akcji w kolejce */
    }
}
Przykład: Dajmy na to, że chcemy zrobić okresowe wydarzenie polegające na tym, że postęp budowy inkrementuje się od 0 do 100% co 1% co 50 jednostek czasowych. Najpierw napiszmy funkcję, która dodaje tą akcję do kolejki:
struct Postep
{
    ULONG postep;
    BOOL gotowe;
} postep = { 0, FALSE };

void Dodaj_Akcje_Przyklad()
{
    struct Akcja *akcja;

    if (akcja = AllocMem(sizeof(struct Akcja), MEMF_PUBLIC|MEMF_CLEAR))
    {
        akcja->akcja = Postep_Budowy;
        akcja->dane  = &postep;
        Dodaj_Akcje(&akcje_okresowe[okres + 50], akcja); /* Dodajemy akcję 
		do odpowiedniej kolejki */
        return;
    }
}
A teraz funkcja składowa wywoływana przez Wykonaj_Akcje_Okresowe():
LONG Postep_Budowy(struct Wydarzenie *wydarzenie, APTR dane)
{
    struct Postep *postep = (struct Postep *)dane;

    if ((!postep->gotowe) && wydarzenie->typ == WYDARZENIE_CZASOWE)
    {
        if (++postep->postep < 100)
            Dodaj_Akcje_Przyklad(); /* Wykonujemy akcję za sekundę */
        else
            postep->gotowe = TRUE; /* Budowa zakończona */
    }
    return(0L);
}
Robert Szacki e-mail: robert.szacki(małpa)gmail.com