článek

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

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

S železnou pravidelností vám opět přinášíme další díl našeho seriálu o tvorbě her, kde si velice jednoduchou cestou poposujeme způsob, jakým si může každý zkusit vytvořit svou hratelnou hříčku. Dnes si povíme něco o praktické implementaci výbuchu a nezapomeneme ani na případné kolize.

Vítejte u dnešního dílu, v kterém dokončíme to, co jsme započali minule. Jak si snad pamatujete (poku ne, tak si znovu přečtěte minulý díl), před týdnem jsme se začali zabývat problematikou řešení explozí bomb v naší hře. Ukázali jsme si, jak naprogramovat pokládání bomb a jak zjišťovat, zda již má položená bomba explodovat. Vysvětlili jsme si pravidla šíření výbuchu - tím jsme tedy "výbuchovou" problematiku zvládli po stránce teoretické, dnes si ji ukážeme v praxi.



Šíření výbuchu a jeho kolize




Šířící se výbuch bude znázorněn sprity, které budou sestávat z rotujících (podstatu této animace jsme si ukzali minule) plamínků, jejichž texturu již máme naloadovanou z minulého dílu. Zároveň s tím, jak se bude výbuch šířit, budeme zjišťovat i jeho kolize s ostatními herními objekty (Mariem, zdmi, nepřáteli). Celý kód jsem zařadil do funkce Render, což, uznávám, je poněkud "prasečí" řešení. Do funkce Render by správně patřila jen část ta kódu, která se stará o vykreslování výbuchu, ostatní záležitosti (kolize), by bylo vhodné řešit v některé jiné funkci, volané před funkcí Render (podobně jako je tomu u funkce SolveExplosions). Položené bomby je nutné animovat (komiksově se napínají), to učiníme obdobným způsobem, jako animaci Maria či nepřátel:




for (int k = 0; k < BOMB_TYPES; ++k)

{ //animace polozenych bomb

  if (bombs[k].planted)

  {


    ++bombs[k].anim_state;

    if(bombs[k].anim_state > 3)

      bombs[k].anim_state=0;

  }

}



Na tomto kódu není nic nového, princip animace je stále stejný (v pravidelných intervalech daných konstantou ANIM_TIME se přepínají animační satvy bomby). Každá bomba má celkem 4 animční satvy (0 až 3), proto po dosažení anim_state=3 se celký cyklus znovu opakuje (anim_state je nastaveno znovu na nula).



Nyní je potřeba bombu vykreslit. Kreslíme samozřejmě jen bomby, které jsou položené (planted je true). Pokud bomba ještě neexplodovala, pak je situace celkem jednoduchá - vykreslíme sprite s patřičnou částí textury bomby v závislosti na hodnotě proměnné anim_state:




for(int a = 0; a < BOMB_TYPES; ++a)

{

  if(bombs[a].planted)

  {

    pos.x=32*bombs[a].x;

    pos.y=32*bombs[a].y;



    PosCorrection(pos);



    SetRect(&SrcRect,32*bombs[a].anim_state,0,32*bombs[a].anim_state + 32,32);



    bomb -> Begin(NULL);



    if (bombs[a].explosion_st ==0 )

    {

      bomb->Draw(pBombTex[a],&SrcRect,NULL,&pos,0xffffffff);

      bomb->End();

    }

    .

    .

    .



K vykreslovacímu kódu není snad co dodávat, neboť je to stále to samé. For-cyklus této ukázky kódu však není uzavřený - záměrně - musíme se ototiž postarati o případ, že daná bomba již expolodvala (a exploze ještě neskončila). To řešíme v následujícím kódu:




    .

    .

    .

  else

  {

    if( InSquare (Mario.x+8) == bombs[a].x && InSquare(Mario.y+8) == bombs[a].y)

    {

      RestartLevel();

    }


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

    {



      if(mushrooms[i].in_game && (InSquare(mushrooms[i].x+8)==bombs[a].x) && (InSquare(mushrooms[i].y+8)==bombs[a].y))

      {

        mushrooms[i].in_game = false;

      }

    }



    fire->Begin(NULL);



    SetRect(&SrcRect,0,32*(bombs[a].explosion_st-1),32,32*(bombs[a].explosion_st-1)+32);



    fire->Draw(pFireTex,&SrcRect,NULL,&pos,0xffffffff);




    for(int r = 1; r <= bombs[a].flame_len; ++r)

    {

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

      {



        if(mushrooms[i].in_game && (InSquare(mushrooms[i].x+8)==bombs[a].x + r) && (InSquare(mushrooms[i].y+8)==bombs[a].y))

        {

          mushrooms[i].in_game = false;

          --enemy_count;

        }



        if (mushrooms[i].in_game && (InSquare(mushrooms[i].x+8)==bombs[a].x - r) && (InSquare(mushrooms[i].y+8)==bombs[a].y))

        {

          mushrooms[i].in_game = false;

          --enemy_count;

        }



        if (mushrooms[i].in_game && (InSquare(mushrooms[i].x+8)==bombs[a].x) && (InSquare(mushrooms[i].y+8)==bombs[a].y+r))

        {

          mushrooms[i].in_game = false;

          --enemy_count;

        }



        if(mushrooms[i].in_game && (InSquare(mushrooms[i].x+8)==bombs[a].x + r) && (InSquare(mushrooms[i].y+8)==bombs[a].y-r))

        {

          mushrooms[i].in_game = false;

          --enemy_count;

        }

      }



      if(InSquare(Mario.x+8)==bombs[a].x+r && InSquare(Mario.y+8) == bombs[a].y)

      {

          RestartLevel();

      }



      if (InSquare(Mario.x+8)==bombs[a].x-r && InSquare(Mario.y+8) == bombs[a].y)

      {

          RestartLevel();

      }



      if (InSquare(Mario.x+8)==bombs[a].x && InSquare(Mario.y+8) == bombs[a].y+r)

      {

          RestartLevel();

      }



      if(InSquare(Mario.x+8)==bombs[a].x && InSquare(Mario.y+8) == bombs[a].y-r)

      {

          RestartLevel();

      }




      if(map[bombs[a].y][bombs[a].x+r] < 2)

      { //doprava



        pos.x = 32*(bombs[a].x + r);

        pos.y = 32*bombs[a].y;



        PosCorrection(pos);



        if(r < bombs[a].flame_len)

          SetRect(&SrcRect,32,32*(bombs[a].explosion_st-1),64,32*(bombs[a].explosion_st-1)+32);

        else SetRect(&SrcRect,64,32*(bombs[a].explosion_st-1),96,32*(bombs[a].explosion_st-1)+32);



        fire->Draw(pFireTex,&SrcRect,NULL,&pos,0xffffffff);



        if(map[bombs[a].y][bombs[a].x+r] == 1)

        {

          map[bombs[a].y][bombs[a].x+r] = 0;

        }


      }



      if(map[bombs[a].y][bombs[a].x-r] < 2)

      { //doleva

        pos.x = 32*(bombs[a].x - r);

        pos.y = 32*bombs[a].y;



        PosCorrection(pos);


        if(r< bombs[a].flame_len)

          SetRect(&SrcRect,32,32*(bombs[a].explosion_st-1),64,32*(bombs[a].explosion_st-1)+32);

        else SetRect(&SrcRect,64,32*bombs[a].explosion_st,96,32*bombs[a].explosion_st+32);





        fire->Draw(pFireTex,&SrcRect,NULL,&pos,0xffffffff);




        if(map[bombs[a].y][bombs[a].x-r] == 1)


        {


          map[bombs[a].y][bombs[a].x-r] = 0;


        }


      }




      if(map[bombs[a].y+r][bombs[a].x] < 2)

      {//dolu



        pos.x = 32*bombs[a].x;

        pos.y = 32*(bombs[a].y + r);



        PosCorrection(pos);



        if(r < bombs[a].flame_len)


          SetRect(&SrcRect,32,32*bombs[a].explosion_st,64,32*bombs[a].explosion_st+32);

        else SetRect(&SrcRect,64,32*bombs[a].explosion_st,96,32*bombs[a].explosion_st+32);




        fire->Draw(pFireTex,&SrcRect,NULL,&pos,0xffffffff);




        if(map[bombs[a].y+r][bombs[a].x] == 1)

        {

          map[bombs[a].y+r][bombs[a].x] = 0;

        }


      }



      if(map[bombs[a].y-r][bombs[a].x] < 2)

      { //nahoru




        pos.x = 32*bombs[a].x;

        pos.y = 32*(bombs[a].y - r);



        PosCorrection(pos);



        if(r < bombs[a].flame_len)

          SetRect(&SrcRect,32,32*bombs[a].explosion_st,64,32*bombs[a].explosion_st+32);

        else SetRect(&SrcRect,64,32*bombs[a].explosion_st,96,32*bombs[a].explosion_st+32);



        fire->Draw(pFireTex,&SrcRect,NULL,&pos,0xffffffff);



        if(map[bombs[a].y-r][bombs[a].x] == 1)

        {

          map[bombs[a].y-r][bombs[a].x] = 0;

        }



      }

    }

  }


  fire->End();


 }

}








I když je kód docela rozsáhlý, rozhodně se ho nemusíte děsit - jeho funkce není složitá a jeho rozsáhlost způsobuje jen to, že jsem rozepsal šířku výbuchu do všech čtyř směrů... Tak tedy, nejprve se postaráme přímo o políčko, na kterém se dotyčná bomba nacházela, tedy políčko, kde je "epicentrum" výbuchu. Pokud se na tomto políčků nachází Mario, pak je zabit (zatím řešeno opět jen restartem levelu), poté projdeme všechny nepřátele a opět, poked se daný nepřítel nachází na tomto políčku, pak je též zabit - vyřazen ze hry. Nakonec ještě na daném políčku výbuch vykreslíme - v místě exploze bomby je výbuch znázorněn nejsvětlejším plamínkem. Tímto jsme tedy prověřili epicentrum, nyní je třeba prověřit políčka ve všech čtyřech směrech od epicentra až do vzdálenost flame_len (délky plamene) dané bomby.



Budeme tak činit iteračně ve for-cyklu (šlo by to třeba i rekurzí, zkuste se nad tím zamyslet). Iterační proměnnou bude proměnná r, která udává vzdálenostprávě prověřovaného čtverečku od políčka, kde se nachází epicentrum výbuchu. Nejprve tedy prověříme čtveřici políček, které mají od epicentra vzdálenost 1 (jde tedy o sousední políčka) - prověříme políčko nad, pod, velvo a vpravo od epicentra. Poté zvýšíme hodnotu proměnné r o jedna a prověříme další čtveřici políček, které mají tentokrát od epicentra vzdálenost 2, opět zvýšíme hodnotu r...atd. až r=flame_len, protože dále, než je dosah plamene bomby se již výbuch šířit nemůže. Ukažme si nyní, jak se děje ono prověřování daného políčka - situaci si předvedem např. ve směru doleva od epicentra.



Pokud ma políčko s epicentrem souřadnice [x,y], pak má právě prověřované políčko nalevo od něj souřadnice [x-r, y]. Zjištění kolize výbuchu s Mariem a/nebo nepřítelem na daném políčku probíhá stejným způsobem, jako v případě zhišťování kolize v epicentru, dále je uveden kód, který se stará jednak o vykreslení výbuchu na daném políčku a jednak o kolizi výbuch se stěnami bludiště (pokud je stěna z oceli, pak se výbuch v daném směru zastaví a již se tím směrem nešíří)




if(map[bombs[a].y][bombs[a].x-r] < 2)

{ //doleva

  pos.x = 32*(bombs[a].x - r);

  pos.y = 32*bombs[a].y;



  PosCorrection(pos);



  if(r
    SetRect(&SrcRect,32,32*(bombs[a].explosion_st-1),64,32*(bombs[a].explosion_st-1)+32);

  else SetRect(&SrcRect,64,32*bombs[a].explosion_st,96,32*bombs[a].explosion_st+32);


  fire->Draw(pFireTex,&SrcRect,NULL,&pos,0xffffffff);


  if(map[bombs[a].y][bombs[a].x-r] == 1)

  {

    map[bombs[a].y][bombs[a].x-r] = 0;

  }

}



Pokud je prověřované políčko průchozí, nebo cihlové (tedy hodnota uložená na potřičné pozici v poli map je menší než 2), pak výbuch na daném políčku vykreslíme - opět sprite plamínku. Pokud je právě prověřované políčko poslední v daném směru (r je již rovno flame_len) pak bude plamínek tmavší. Nakonec jesště zajistíme zniční cihlové zdi, pokud se tato na prověřovaném políčku nacházel (provedem to tak, že na příslušnou pozici do pole map uložíme číslo 0). Obdobně provedeme tyto operace ve všech zbývajících směrech - kód se liší je tím, od které souřadnice přičítáme (odčítáme) r. Tímto je tedy bombová problematika dokončena.



Co příště?



Všechny důležité aspekty Bombermana jsme již probrali, náš seriál se tak blíží ke konci a příští díl již možná bude dílem posledním. V rychlosti si ukážeme jednoduché ozvučení hry (použijeme knihovnu fmod, ale opět, podobně jako s grafickým rozhraním, je jedno pokud použijete cokoliv jiného (třeba DirectSound)), zavedem si také časovač (timer), který nám bude sloužit k počítání FPS a díky němu budeme též "brzdit" naši hru na požadovanou úroveň.



Zatím nashledanou!



Související odkazy:








jan_kohout

autor
/ jan_kohout

Publikováno: 17.06.2007


další články této kategorie





diskuze

odeslat

Nejsi automat? Napiš výsledek 5 + 5 =

phanka

phanka | 21.04.15 v 21:30

chtěla bych to umět určitě to vyzkouším

PataS | 10.11.09 v 16:53

Rozhodl jsem se to zkusit mé snažení můžete vydět na http://patasweb.wz.cz/