Buumeriräiskintä 5: Kerättävät esineet

Tässä osassa koodataan ensiapupakkaus, jonka kerätessään pelaaja saa menettämänsä terveyspisteet takaisin.

Alkuvalmistelut

Luo items.c-lähdekooditiedosto, sekä sille items.h-otsikkotiedosto.

Lisää items.c-tiedostoon:
 
#include <allegro.h>
#include "globals.h"
 
...sekä items.h-tiedostoon: 
 
#ifndef ITEMS_H
#define ITEMS_H
 
// Kaikki sisältö tähän.

#endif

Build.bat

Lisää items.c-lähdekooditiedosto myös build.bat-skriptitiedostoon, jotta sekin on mukana ohjelmaa koostettaessa. 
 
@echo off
gcc main.c utils.c draw.c player.c setup.c projs.c enemies.c items.c -o game.exe -lalleg

Ensiapupakkauksen piirtäminen

Kuten ammusten ja vihollistenkin tapauksessa, ensiksi koodataan ensiapupakkausten piirtäminen. 

Globals.h 

Lisää globals.h-otsikkotiedostoon BITMAP-muuttuja *itemBitmap.

extern BITMAP *textureA;
extern BITMAP *textureB;
extern BITMAP *lampBitmap;
extern BITMAP *projectileBitmap;
extern BITMAP *enemyBitmap;
extern BITMAP *itemBitmap;

Lisää myös kokonaislukumuuttuja numItems.
 
extern int numSprites;
extern int numEnemies;
extern int numItems;

Main.c 

Lisää main.c-lähdekooditiedostoon *itemBitmap-BITMAP-muuttuja alustus antamalla sille arvo NULL.
 
BITMAP *textureA = NULL;
BITMAP *textureB = NULL;
BITMAP *lampBitmap = NULL;
BITMAP *projectileBitmap = NULL;
BITMAP *enemyBitmap = NULL;
BITMAP *itemBitmap = NULL;

Muuta sitten osa map-taulukkomuuttujan alkioarvoista arvoksi 4.
 
Esine ilmestyy siis näihin kohtiin kentässä.
 
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,1,1,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,3,0,0,1},
    {1,2,4,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1},
    {1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1},
    {1,0,0,0,0,0,0,2,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,2,0,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,3,0,0,1,1,1,1,1,1,1,0,0,0,3,0,1},
    {1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,2,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,3,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,1,2,2,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,2,0,0,0,0,0,0,1,1,0,0,0,0,1,0,0,0,1},
    {1,0,0,0,4,0,1,0,0,0,0,0,1,1,1,0,0,0,0,1,0,0,0,1},
    {1,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1},
    {1,0,0,0,1,2,2,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,4,0,0,1},
    {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
};

Alusta numItems-kokonaislukumuuttuja antamalla sille arvo 0.
 
int numSprites = 0;
int numEnemies = 0;
int numItems = 0;
 
Lisää vielä itemBitmap-muuttujan vapautus muistiresurssista funktiokutsulla destroy_bitmap.
 
destroy_bitmap(textureA);
destroy_bitmap(textureB);
destroy_bitmap(lampBitmap);
destroy_bitmap(projectileBitmap);
destroy_bitmap(enemyBitmap);
destroy_bitmap(itemBitmap);

Types.h 

Lisää types.h-otsikkotiedostoon uusi MapObject-taulukkomuuttuja items ja anna sen kooksi esim. 100.
 
MapObject sprites[100];
MapObject projectiles[100];
MapObject enemies[100];
MapObject items[100];

Setup.c

Tee setup.c-lähdekooditiedostoon seuraava lisäys, jossa itemBitmap-muuttujalle annetaan arvoksi load_bitmap-funktiokutsu ja sille parametriksi item.bmp-bittikarttakuva.
 
Item.bmp.

textureA = load_bitmap("walla.bmp", NULL);
textureB = load_bitmap("wallb.bmp", NULL);
lampBitmap = load_bitmap("lamp.bmp", NULL);
projectileBitmap = load_bitmap("projecti.bmp", NULL);
enemyBitmap = load_bitmap("enemy.bmp", NULL);
itemBitmap = load_bitmap("item.bmp", NULL); 

Lisää SetupGame-funktiomäärittelyyn InitMapObjects-funktiokutsu ja sille parametrit items, &numItems, 4 ja GetItemBitmap. Kolmas parametri arvolla 4 määrittelee siis, millä map-taulukkomuuttujan alkioarvolla esineet luodaan karttaan.
 
Lisää samaan funktiomäärittelyyn myös for-silmukka, jossa items-rakenteen block-muuttujan arvo asetetaan 0:ksi. Näin pelaaja pystyy kävelemään esineiden läpi.

InitMapObjects(sprites, &numSprites, 2, lampBitmap);
 
InitMapObjects(enemies, &numEnemies, 3, enemyBitmap);
 
for (i = 0; i < numEnemies; i++)
{
    enemies[i].seesPlayer = 0;
    enemies[i].shootCooldown = 0;
    enemies[i].health = E_HEALTH;        
}

InitMapObjects(items, &numItems, 4, itemBitmap);
 
for (i = 0; i < numItems; i++)
{
    items[i].block = 0;
}

for (i = 0; i < MAX_PROJECTILES; i++)
{
    projectiles[i].bitmap = projectileBitmap;
}

Draw.h 

Lisää sitten draw.h-otsikkotiedoston HandleMapObjects-funktioesittelyyn seuraavat parametrit:
  • MapObject*-muuttuja items 
  • kokonaislukumuuttuja numItems.
void HandleMapObjects(MapObject* sprites, int numSprites, MapObject* projectiles,
int numProjectiles, MapObject* enemies, int numEnemies, MapObject* items,
int numItems, double zBuffer[]);

Draw.c

Tee sama parametrilisäys myös draw.c-lähdekooditiedoston HandleMapObjects-funktiomäärittelyyn. Tee funktiomäärittelyyn myös muut lisäykset:
  • lasketaan yhteen lamppuspritejen, ammusten ja vihollisten lisäksi myös esineet ja tallennetaan summa totalObjects-kokonaislukumuuttujaan
  • otetaan myös esineet mukaan, kun kopioidaan kaikki spritet yhtenäiseksi listaksi (memcpy(allObjects + index, items, numItems * sizeof(MapObject)) ja index += numItems).
void HandleMapObjects(MapObject* sprites, int numSprites, MapObject* projectiles,
int numProjectiles, MapObject* enemies, int numEnemies, MapObject* items,
int numItems, double zBuffer[])
{
    int totalObjects = numSprites + numProjectiles + numEnemies + numItems;

    MapObject* allObjects = (MapObject*)malloc(totalObjects * sizeof(MapObject));
 
    int index = 0;
   
    memcpy(allObjects + index, sprites, numSprites * sizeof(MapObject));
    index += numSprites;

    memcpy(allObjects + index, projectiles, numProjectiles * sizeof(MapObject));
    index += numProjectiles;

    memcpy(allObjects + index, enemies, numEnemies * sizeof(MapObject));
    index += numEnemies;
 
    memcpy(allObjects + index, items, numItems * sizeof(MapObject));
    index += numItems;

Lisää vielä DrawGame-funktiomäärittelyssä olevan HandleMapObjects-funktiokutsun parametreihin uudet lisäykset items ja numItems.

else
{
    double zBuffer[SCREEN_WIDTH];
 
    clear_to_color(buffer, makecol(47, 47, 47));
    clear_to_color(hud, makecol(0, 0, 0));
   
    HandleWalls(zBuffer);
   
    HandleEnemies(enemies, numEnemies);
   
    UpdateProjectiles(projectiles, MAX_PROJECTILES, enemies, numEnemies);
   
    HandleMapObjects(sprites, numSprites, projectiles, MAX_PROJECTILES, enemies,
    numEnemies, items, numItems, zBuffer);
 
    textprintf_ex(hud, font, 10, 10, makecol(255, 255, 255), -1, "HEALTH: %d",
    player.health);

    textprintf_ex(hud, font, 10, 20, makecol(255, 255, 255), -1, "KILLS: %d/%d",
    killedEnemies, numEnemies);
}
 
Nyt onnistuu ensiapupakkausten piirtäminen. Muuta niille ei vielä tapahdukaan, eli seuraavaksi koodataan pelaajan törmääminen niihin sekä se, mitä törmätessä tapahtuu.
 

Ensiapupakkauksen törmäystunnistus

Utils.h

Lisää utils.h-otsikkotiedostoon TouchedMapObject-funktion esittely. Tällä funktiolla tarkkaillaan, osuuko annettu sijainti (posX, posY) ja säde (radius) johonkin spriteen (MapObject* sprites) ja lisäksi, deaktivoidaanko objekti, kun siihen kosketaan.
 
int TouchedMapObject(double posX, double posY, double radius, MapObject* sprites,
int numSprites, int deactivate);

Utils.c

Lisää sitten utils.c-lähdekooditiedostoon TouchedMapObject-funktion määrittely, jossa käydään läpi kaikki funktiokutsun parametriin syötety spritet ja lasketaan etäisyys pelaajan sijainnista spriten alueeseen:

  • Jokaiselle objektille määritellään pieni neliömäinen alue (objMinX–objMaxX, objMinY–objMaxY).
  • Lasketaan lähin piste tästä alueesta suhteessa pelaajan sijaintiin (nearestX ja nearestY).
  • Jos pelaajan sijainnin ja tämän lähimmän pisteen välinen etäisyys on pienempi kuin radius, tapahtuu törmäys.
  • Kun törmäys havaitaan ja objekti on aktiivinen (obj->active), objekti deaktivoidaan eli poistetaan näkyvistä (obj->active = 0) ja funktio palauttaa arvon 1 (return 1).
  • Jos mikään objekti ei osu pelaajaan, palautetaan arvo 0 (return 0).
int TouchedMapObject(double posX, double posY, double radius, MapObject* sprites,
int numSprites, int deactivate)
{
    int i;

    for (i = 0; i < numSprites; i++)
    {
        MapObject* obj = &sprites[i];
 
        double objMinX = obj->posX - 0.25;
        double objMaxX = obj->posX + 0.25;
        double objMinY = obj->posY - 0.25;
        double objMaxY = obj->posY + 0.25;

        double nearestX = (posX < objMinX) ? objMinX : ((posX > objMaxX) ? objMaxX : posX);
        double nearestY = (posY < objMinY) ? objMinY : ((posY > objMaxY) ? objMaxY : posY);

        double distX = posX - nearestX;
        double distY = posY - nearestY;

        if ((distX * distX + distY * distY) < (radius * radius) && obj->active)
        {
            if (deactivate)
                obj->active = 0;

            return 1;
        }
    }

    return 0;
}

Items.h

Lisää items.h-otsikkotiedostoon HandleItems-funktion määrittely. Tätä funktiota kutsutaan, kun pelaaja törmää esineeseen.
 
#ifndef ITEMS_H
#define ITEMS_H

void HandleItems();

#endif

Items.c

Lisää sitten items.c-lähdekooditiedostoon HandleItems-funktion määrittely, jossa:

  • Ensin tarkistetaan, onko pelaajan terveys alle maksimirajan (player.health < P_HEALTH);
    • jos pelaajan terveyspisteet ovat jo täynnä, esinettä ei voi kerätä.
  • Jos taas pelaajan terveyspisteet ovat alle täyden määrän, kutsutaan TouchedMapObject-funktiota pelaajan sijainnilla ja säteellä (player.posX, player.posY, player.radius). Se tarkistaa osuuko pelaaja johonkin items-taulukon esineeseen. Ja kun viimeisen parametrin arvo on 1, esine myös häviää näkyvistä, eikä sitä voi kerätä uudestaan.
  • Jos törmäys havaitaan, pelaajan terveys palautetaan suoraan maksimilukuun (player.health = P_HEALTH).

Tämän ansiosta ensiapupakkauksen kerääminen täyttää pelaajan terveyden kokonaan.

void HandleItems()
{
    if (player.health < P_HEALTH)
    {
        if (TouchedMapObject(player.posX, player.posY, player.radius, items, numItems, 1))
            player.health = P_HEALTH;
    }
}

Main.c

Lisää lopuksi main.c-lähdekooditiedoston main-funktiomäärittelyn sisempään while-silmukkaan kutsu HandleItems-funktiolleKatsotaan siis jatkuvasti, ovatko pelaajan terveyspisteet alle maksimimäärän ja osutaanko esineeseen.
 
while (thCounter > 0)
{
    if (!player.isDead && !player.won)
    {
        MovePlayer();

        RotatePlayer();

        PlayerShooting();

        HandleItems();
    }

    DrawGame();

    thCounter--;
}
 
Nyt kentässä on ensiapupakkauksia, jotka "kerätessä" palauttavat pelaajan terveyspisteet alkuperäiseen arvoon.
 
Huh, vihdoin on buumeriräiskintämme kokonaisuudessaan valmis! 

Kommentit