Tasohyppelypeli 3: Kuolema!

Elämä on lyhyt, etenkin pelihahmoilla. Tässä osassa koodataankin pelaajan kuoleminen, joka voi tapahtua kahdella tavalla: osumalla punaiseen tiileen tai putoamalla kentästä pois. Tällöin tulostetaan ruutuun teksti pelaajan kuolemisesta ja annetaan vaihtoehdoiksi joko aloittaa peli uudestaan painamalla Enteriä tai poistumalla päävalikkoon painamalla Esciä.

Teknisesti kuolemisen koodaaminen on aika yksinkertaista, kiitos hyvin tehtyjen kentän alkioiden keräämisen sekä törmäystunnistamisen aiemmassa osassa. Määritetään kenttään vain "vihollisten" paikat alkion arvolla 2, kerätään niiden sijainnit ja katsotaan osuuko pelaaja niihin; jos osuu, asetetaan muuttuja player.isDead todeksi, jolloin pelaaja todetaan kuolleeksi. Kuolema koittaa myös siinä tilanteessa, jos pelaajan pystysijainti on suurempi tai yhtä suuri kuin ruudun alareunan pystysijainti.

Vihollisten piirtäminen

Lisää main.h-otsikkotiedostoon vakio COLOR_ENEMY ja määritä se vaikkapa punaiseksi, kuten alla. Tämä tekee vihollistiilistä punaisia.

#define COLOR_ENEMY makecol(255,0,0)

Muuta main.h-otsikkotiedostoon rakennettua map-kenttää niin, että osa alkioista saa arvon 2. Huomaa myös alareunan muutokset, joissa arvoja 1 on muutettu 0:ksi, mikä poistaa osan lattiasta.

int map[MAP_HEIGHT][MAP_WIDTH] =
{
    {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,2,1,1},
    {1,0,0,0,0,0,2,0,0,0,0,1,1,0,0,1,1,1,1,1},
    {1,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,1,1,1},
    {1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,1},
    {1,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,1,1,1,1,2,0,0,0,0,0,0,0,0,0,2,0,1},
    {1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,1,1,1,1}
};

Lisää sitten main.c-lähdekooditiedostoon DrawMap-funktiokutsu parametreilla 2 ja COLOR_ENEMY, jolloin punainen vihollistiili piirretään niihin kohtiin, joissa kentän alkiolla on arvo 2.

void DrawGame()
{  
    clear_to_color(buffer, COLOR_GAME_BG);

    GetPositions(1, tilePositions, &numTilePositions);

    DrawMap(1, COLOR_TILE);
    DrawMap(2, COLOR_ENEMY);

    DrawPlayer();
}

Ja näin kentässä on ilkeän näköisiä punaisia neliöitä – jotka ovat kuitenkin vielä aivan vaarattomia.

Vihollistiiliin törmääminen

Lisää main.h-otsikkotiedostoon seuraavat vakiot TEXT_DEATH_01 ja TEXT_DEATH_02 ja anna niiden arvoiksi tekstit, jotka tulostetaan ruudulle pelaajahahmon kuoltua.

#define TEXT_DEATH_01 "YOU DIED!"
#define TEXT_DEATH_02 "ENTER TO RESTART OR ESC TO LEAVE."

Lisää Player-tietueeseen jäsen isDead, jolla selvitetään, onko pelaajahahmo elossa vai kuollut.

struct Player
{
    short int x, y;
    short int width, height;
    unsigned short int isDead;
} player;

Lisää myös Tile-tietuemuuttuja enemyPositions[MAP_WIDTH * MAP_HEIGHT], jota käytetään kentän vihollistiilien sijaintien selvittämiseen.

struct Tile
{
    int x1, y1, x2, y2;
} tile,
tilePositions[MAP_WIDTH * MAP_HEIGHT],
enemyPositions[MAP_WIDTH * MAP_HEIGHT];

Lisää kokonaislukumuuttuja numEnemyPositions, johon tallennetaan kentän vihollistiilien määrä.

int numTilePositions, numEnemyPositions;

Lisää myös KillPlayer-funktion esittely.

void KillPlayer();

Siinä kaikki main.h-otsikkotiedostoon tehtävät lisäykset ja muutokset tällä kertaa. Seuraavaksi siirrytään main.c-lähdekooditiedoston pariin.

Lisää main.c-lähdekooditiedostoon KillPlayer-funktion määrittely, jossa ainoastaan katsotaan, osuuko pelaaja vihollistiileen (Collided(&player, enemyPositions, numEnemyPositions, 0, 0) tai onko pelaajan pystysijainti suurempi tai yhtä suuri kuin ruudun alareunan sijainti (player.y >= 240). Jos jompikumpi näistä toteutuu, asetetaan muuttuja player.isDead todeksi (player.isDead = 1).

void KillPlayer()
{
    if (Collided(&player, enemyPositions, numEnemyPositions, 0, -1)
    || player.y >= 240)
    {
        player.isDead = 1;
    }
}

Lisää main-funktion while-silmukkaan:

  • tarkistus, onko pelaaja elossa vai kuollut (if (!player.isDead));
    • jos player.isDead-muuttujan arvo on epätosi, eli pelaaja ei ole kuollut, sallitaan MovePlayer-funktion kutsuminen
  • KillPlayer-funktion kutsu.
while (thCounter > 0)
{    
    if (!player.isDead)
        MovePlayer();
               
    KillPlayer();
               
    thCounter--;
}

Muuta MovePlayer-funktion tarkistusta nuolinäppäimen alas osalta niin, että poistetaan pelaajan pystysijainnin tarkistaminen, mikä sallii pelaajan pääsemisen ruudun alapuolelle.

void MovePlayer()
{
    int dx = 0, dy = 0;

    if (key[KEY_UP] && player.y >= 2 &&
    !Collided(&player, tilePositions, numTilePositions, 0, -2))
        dy -= 2;

    if (key[KEY_DOWN] &&
    !Collided(&player, tilePositions, numTilePositions, 0, 2))
        dy += 2;

    player.y += dy;

    if (key[KEY_LEFT] && player.x >= 2 &&
    !Collided(&player, tilePositions, numTilePositions, -2, 0))
        dx -= 2;

    if (key[KEY_RIGHT] && player.x <= SCREEN_WIDTH - (TILE_SIZE + 2) &&
    !Collided(&player, tilePositions, numTilePositions, 2, 0))
        dx += 2;

    player.x += dx;
}

Lisää SetupGame-funktion määrittelyyn player.isDead-muuttujan arvon asettaminen epätodeksi (player.isDead = 0); pelaaja on siis elossa kun peli alkaa.

void SetupGame()
{
    player.x = 152;
    player.y = 112;
    player.width = TILE_SIZE;
    player.height = TILE_SIZE;

    player.isDead = 0;

    setupGameDone = 1;

    fadeInProgress = 1;
}

Lisää DrawGame-funktioon seuraavat lauseet:

  • selvitetään kaikkien vihollistiilien sijainti ja niiden määrä (GetPositions(2, enemyPositions, &numEnemyPositions))
  • tarkistetaan, onko pelaaja kuollut vai elossa (if (player.isDead));
    • jos pelaaja on kuollut, tulostetaan ruutuun vakioihin TEXT_DEATH_01 ja TEXT_DEATH_02 määritetyt tekstit funktiokutsuilla textout_centre_ex
    • jos pelaaja on elossa, piirretään pelaaja funktiokutsulla DrawPlayer.
void DrawGame()
{  
    clear_to_color(buffer, COLOR_GAME_BG);

    GetPositions(1, tilePositions, &numTilePositions);
    GetPositions(2, enemyPositions, &numEnemyPositions);

    DrawMap(1, COLOR_TILE);
    DrawMap(2, COLOR_ENEMY);

    if (player.isDead)
    {
        textout_centre_ex(buffer, font, TEXT_DEATH_01, SCREEN_W / 2, SCREEN_H / 2,
        makecol(255,255,255), makecol(255,0,0));
        textout_centre_ex(buffer, font, TEXT_DEATH_02, SCREEN_W / 2,
        SCREEN_H / 2 + 8, makecol(255,255,255), makecol(255,0,0));
    }
    else
    {
        DrawPlayer();
    }
}

Lisää MenuKeyInput-funktion Enter-näppäimen tarkistukseen lisätarkistus, jossa katsotaan, onko ohjelman aktiivinen tila pelitila ja että onko pelaaja kuollut (else if (activeGameState == GAME && player.isDead)); jos kumpikin toteutuu, sallitaan häivytykset (allowFadeOut = 1 ja allowFadeIn = 1) ja pelin alustaminen (setupGameDone = 0).

else if (key[KEY_ENTER] && keypressed())
{
    if (activeGameState == MENUSTATE)
    {
        if (selectedMenuItem == NEW_GAME)
        {
            allowFadeOut = 1;
            allowFadeIn = 1;

            setupGameDone = 0;
 
            activeGameState = GAME;
        }
        else if (selectedMenuItem == EXIT)
        { 
            exitProgram = 1;
        }
    }
    else if (activeGameState == GAME && player.isDead)
    {
        allowFadeOut = 1;
        allowFadeIn = 1;
           
        setupGameDone = 0;
    }
}

Nyt punaiset neliöt sekä näyttävät vaarallisilta että ovat sitä, sillä pienikin kosketus niihin tekee pelaajahahmosta selvää. Lisäksi kentän alapuolella odottaa vaarallinen rotko.


Kommentit