článek

Udělej si vlastní hru – IV. část

Udělej si vlastní hru – IV. část

Vývoj naší gamesky pokračuje velice dobře. Na dokončení si sice ještě nějaký ten týden počkáme, ovšem dnes se podíváme na jednu z nejdůležitějších součástí hry, bez které by téměř žádný titul neměl ten správný šmrnc. Ptáte se o čem bude řeč? V tom případě bez obav nahlédněte do našeho článku.


V dnešním díle seriálu nebudeme mluvit prakticky o ničem jiném, než o problematice nepřátel. Nejprve si shrňme, jaké vlastnosti vlastně budou nepřátelé mít: Po bludišti se budou pohybovat stejným způsobem
jako Mario a budou pro ně platit stejná pohybová omezení, co se neprostupnosti některých čtverečků týče. Pokud se některý nepřítel nachází na stejném čtverečku jako Mario, pak Mario
ztratí život (zatím to vyřešíme restartem levelu zavoláním funkce RestartLevel). Každý nepřítel se bude pohybovat daným směrem, dokud:



  • nenarazí na překážku


  • nenastane čas pro změnu směru (tedy jde daným směrem již dostatečně dlouho)










V obou případech použijeme k nalezení nového směru funkci FindNewDirection, pojďme si však ještě objasnit, jak poznáme, že nepřítel jde některým směrem "již dostatečně dlouho".
V minulém díle jsem uvedl, že nejen Mario, ale i nepřátelé budou reprezentováni datovou strukturou LIVING_OBJECT a tato struktura obsahuje, mimo jiné, i proměnné last_dir_change a new_dir_time a tyto právě nyní využijeme. Proměnná new_dir_time obsahuje hodnotu, která v milisekundách udává délku časového intevalu, ve kterém budeme měnit směr chůze nepřítele a proměnná last_dir_change obsahuje čas, ve kterém naposledy u daného nepřítele ke změně směru došlo, abychom věděli, jestli je čas směr opět změnit. Hodnoty new_dir_time se pro jednotlivé nepřátele mohou samozřejmě lišit pro větší pestrost hry.



Deklarace nepřátel a načtení obrázku Nepřátele budeme vykreslovat stejným způsobem jako Maria a použijeme pro ně tedy i stejný způsob načtení obrázku.



Nepřítele deklarujeme takto:



LIVING_OBJECT mushrooms[MAX_MUSHROOMS]; //houbičky



Konstanta MAX_MUSHROOMS udává maximální počet nepřátel v jednom levelu, nastavme ji třeba na 40.



Načtení textury:




D3DXCreateTextureFromFileEx

(

  pD3DDevice,

  "data/mushroom.bmp",

  D3DX_DEFAULT,

  D3DX_DEFAULT,

  D3DX_DEFAULT,

  0,

  D3DFMT_A8R8G8B8,

  D3DPOOL_MANAGED,

  D3DX_FILTER_TRIANGLE,

  D3DX_FILTER_TRIANGLE,

  D3DCOLOR_ARGB(255,255,0,255),

  NULL,

  NULL,

  &pMushroomTex

);




Ukazatel na tuto texturu jsme samozřejmě globálně deklarovali a nesmíme ji ani zapomenout zrušit.



Načtení nepřátel




Nejdříve musíme načíst informace o počtu a rozmístění nepřátel v daném levelu. Jako jednoduché řešení se nabízí rozšířit kód funkce LoadMap tak, aby kromě toho, že ze
souboru načte informace o políčkách mapy načetla i parametry jednotlivých nepřátel, které do souboru předtím uložíme. Nová funkce LoadMap může vypadat třeba takto:





void LoadMap(const char* file_name)
{

  FILE *file;


  file = fopen(file_name, "r");


  for(int x=0; x<30; ++x)

  {

    for(int y=0; y<40; ++y)

    {

      fscanf(file,"%d",&map[x][y]);

    }

  }


  fscanf(file,"%d",&enemy_count);


  for(int i=0; i
  {


    fscanf(file, "%d", &mushrooms[i].type);

    fscanf(file, "%d", &mushrooms[i].x);

    fscanf(file, "%d", &mushrooms[i].y);


    int pom = 0;

    fscanf(file,"%d",&pom);


    switch(pom)

    {


      case 0:

      { mushrooms[i].heading = UP; break; }


      case 1:

      { mushrooms[i].heading = DOWN; break; }


      case 2:

      { mushrooms[i].heading = RIGHT; break; }


      case 3:

      { mushrooms[i].heading = LEFT; break; }


    }


    fscanf(file,"%d",&mushrooms[i].new_dir_time);


    mushrooms[i].in_game = true;

  }


  fclose(file);

}




Proměnná enemy_count (globální, celočíselná) udává počet nepřátel v daném levelu, u každého nepřítele načítáme celkem pět parametrů:



  • Type - zatím vždy 0, proměnná je pro případ, že bychom měli více typů nepřátel.


  • x - počáteční x-ová souřadnice nepřítele na mapě

  • y - počáteční y-ová souřadnice nepřítele na mapě

  • heading - počáteční směr chůze nepřítele

  • new_dir_time - vysvětlení viz výše







Nakonec každému načtenému nepříteli nastavíme proměnnou in_game na true, abychom věděli, že je ve hře.



Pohyb nepřátel



Toto téma jsme již trochu nakousli v úvodu článku, nyní si ukážeme potřebné funkce. Pohyb každého nepřítele budeme řešit funkcí SolveEnemies, kterou voláme pokaždé
před zavoláním funkce Render. Funkce SolveEnemies vypadá následovně:





void SolveEnemies(void)
{


  for(int i=0; i
  {


  if(mushrooms[i].in_game)

  {


    if(InSquare(Mario.x+8)==InSquare(mushrooms[i].x+8) && InSquare(Mario.y+8)==InSquare(mushrooms[i].y+8))

    { //kolize s Mariem

      RestartLevel();

    }


    if(GetTickCount() - mushrooms[i].last_dir_change >= mushrooms[i].new_dir_time)

    { //reseni pohybu

      mushrooms[i].last_dir_change = GetTickCount();

      FindNewDirection(mushrooms[i]);

    }


    switch (mushrooms[i].heading)

    {


      case UP:

      {

        if((map[InSquare(mushrooms[i].y)][InSquare(mushrooms[i].x+4)] == 0) &&

          (map[InSquare(mushrooms[i].y)][InSquare(mushrooms[i].x+25)] == 0)) {

          mushrooms[i].y -= 1;

          mushrooms[i].walking=true;}

        else FindNewDirection(mushrooms[i]);


        break;

      }


      case DOWN:

      {


        if((map[InSquare(mushrooms[i].y+31)][InSquare(mushrooms[i].x+4)] == 0) &&

          (map[InSquare(mushrooms[i].y+31)][InSquare(mushrooms[i].x+25)] == 0)) {

          mushrooms[i].y += 1;

          mushrooms[i].walking=true;}

          else FindNewDirection(mushrooms[i]);



        break;

      }


      case RIGHT:

      {


        if((map[InSquare(mushrooms[i].y+1)][InSquare(mushrooms[i].x+31)] == 0) &&

          (map[InSquare(mushrooms[i].y+30)][InSquare(mushrooms[i].x+31)] == 0)) {

          mushrooms[i].x += 1;

          mushrooms[i].walking=true;}

          else FindNewDirection(mushrooms[i]);



        break;

      }


      case LEFT:

      {


        if((map[InSquare(mushrooms[i].y+1)][InSquare(mushrooms[i].x+3)] == 0) &&

          (map[InSquare(mushrooms[i].y+30)][InSquare(mushrooms[i].x+3)] == 0)) {

          mushrooms[i].x -= 1;

          mushrooms[i].walking=true;}

          else FindNewDirection(mushrooms[i]);



        break;

      }

    }

  }

  }

}



Postupně procházíme celé pole nepřátel a pokud je daný nepřítel ve hře, pak řešíme jeho pohyb. Nejprve zjišťujeme kolizi nepřítele s Mariem - pokud se oba nachází na stejném
čtverečku mapy, pak zajistíme odpovídající řešení této situace (zatím restartem levelu). Dále pokračujeme buď posunutím nepřítele ve směru jeho chůze (to je podobné jako chůze Maria), nebo, pokud je k tomu důvod (nastal správný čas a/nebo nepřítel narazil na překážku)
určíme nepříteli nový směr chůze funkcí FindNewDirection. Podívejme se též na tělo funkce FindNewDirection:




bool FindNewDirection(LIVING_OBJECT &who)
{

  int p = GetTickCount() % 4;


  switch (p)

  {


    case 0: who.heading = UP; return true;

    case 1: who.heading = DOWN; return true;

    case 2: who.heading = RIGHT; return true;

    case 3: who.heading = LEFT; return true;


  }


  return false;

}



Jako parametr předáme funkci adresu, na které leží datová struktura nepřítele, jehož směr chceme měnit (označen who). Přiřazení nového směru probíhá vygenerování náhodného čísla (od 0 do 3, tedy čtyř čísel, neboť směry jsou také čtyři). Tato implementace mi připadá
vhodnější oproti funkci rand, která generuje pseudonáhodná čísla, ale pokud použijete nějaký Váš vlastní gemerátor, vůbec to nebude vadit.











Vykreslení nepřátel





Nyní ještě zbývá podívat se na vykreslení a nimaci nepřátel. Obě věci provedem prakticky totžně jako v případě Maria. Při vykreslení projdeme sekvenčně pole nepřátel a pokud je nepřítel ve hře (in_game je true), pak ho na příslušnou pozici vykreslím. Kreslení provádíme
ihned po vykreslení spritu Maria:




for(int b = 0; b < MAX_MUSHROOMS; ++b)

{ //kresleni nepratel


  if(mushrooms[b].in_game)

  {


    pos.x = mushrooms[b].x;

    pos.y = mushrooms[b].y;


    PosCorrection(pos);


    if(OnScreen(pos))

    {

      SetRect(&SrcRect,32*mushrooms[b].heading,32*mushrooms[b].anim_state, 32 * mushrooms[b].heading+32,32*mushrooms[b].anim_state+32);



    creature->Draw(pMushroomTex,&SrcRect,NULL,&pos,0xffffffff);

    }

  }

}



A ještě animace nepřátel:




for(int q = 0; q < MAX_MUSHROOMS; ++q)

{ //animace nepratel

  if(mushrooms[q].walking)

  {

    if (mushrooms[q].anim_state==0) mushrooms[q].anim_state=1;

    else mushrooms[q].anim_state=0;

    mushrooms[q].walking=false;

  }

}




Poznámka



Jelikož se pohyb, tedy posun, nepřátel provádí v každém vykresleném snímku, nevypadá z důvodu vysokého počtu FPS na většině počítačů pohyb nepřátel příliš dobře - je moc rychlý.
Pro tento díl můžeme "zbrzdění" hry vyřešit provizorně funkcí Wait, která prostě v každém snímku počká příslušný počet milisekund a tím se počet FPS sníží. Takové řešení však rozhodně nemůžeme brát jako konečné, někdy v dalších dílech si proto ukážeme omezení počtu FPS lépe.
Funkce Wait vypadá takto:




void Wait(DWORD time)

{

  DWORD p = GetTickCount();

  while(GetTickCount() - p < time);

}




Na co se těšit a co dělat?



Příště si předvedeme pokládání a řešení explozí bomb, dostojíme tedy konečně názvu naší hry :-). Do příště Vám zatím doporučuji opět si projít zdrojové kódy dnešního dílu a dobře se s nimi seznámit. Zkuste modifikovat některé proměnné ohledně nepřátel - jejich počet, čas pro změnu směru... Ujistěte se též, že rozumíte způsobu načítání nepřátel ze souboru a řešení jejich pohybu - v tomto směru se také nebojte experimentů a zkoušejte si v kódu měnit co jde.



Nashledanou příště!





Zdrojové kódy: 317_ctvrta_cast.zip



Související odkazy:









jan_kohout

autor
/ jan_kohout

Publikováno: 03.06.2007


další články této kategorie





diskuze

odeslat

Nejsi automat? Napiš výsledek 3 + 4 =

phanka

phanka | 21.04.15 v 21:31

zajimavé


naše databáze obsahuje: 26 944 her

Sponzoři ligy

inzerce