Tässä osassa koodataan pelaajan kuolema ja pelin läpäisy.
Pelaajan kuolema
Globals.h
Lisää globals.h-otsikkotiedostoon vakiot TEXT_DEATH_01 ja TEXT_DEATH_02, joille annetut arvot tulostetaan näytölle pelaajan kuollessa.
#define TEXT_DEATH_01 "YOU DIED!"
#define TEXT_DEATH_02 "ENTER TO RESTART OR ESC TO LEAVE."
Types.h
Lisää types.h-otsikkotiedoston Player-rakenteeseen uusi kokonaislukumuuttuja isDead, joka tarkastelee, onko pelaaja kuollut vai elossa.
struct Player {
double posX, posY;
double dirX, dirY;
double moveSpeed;
double radius;
int allowShooting;
int coolDown;
int isDead;
};
Setup.c
Tee sitten setup.c-lähdekooditiedoston SetupGame-funktiomäärittelyyn seuraavat lisäykset:
- anna muuttujille planeX ja planeY arvot 0 ja 158.4 / VIEW3D_HEIGHT, jotta 3D-näkymä resetoidaan pelin alkaessa
- anna muuttujalle player.isDead arvo 0; pelaaja ei siis ole kuollut kun peli alkaa.
void SetupGame()
{
int i;
install_mouse();
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 = 5;
}
for (i = 0; i < MAX_PROJECTILES; i++)
{
projectiles[i].bitmap = projectileBitmap;
}
planeX = 0, planeY = 158.4 / VIEW3D_HEIGHT;
player.posX = 22, player.posY = 12;
player.dirX = -1, player.dirY = 0;
}
Main.c
Muuta main.c-lähdekooditiedoston main-funktiomäärittelyn sisempää while-silmukkaa siten, että pelaajan liikkuminen (MovePlayer), kääntyminen (RotatePlayer) sekä ampumiskyky (PlayerShooting) sallitaan ainoastaan, jos pelaaja ei ole kuollut (if (!player.isDead)).
while(!key[KEY_ESC])
{
while (thCounter > 0)
{
if (!player.isDead)
{
MovePlayer();
PlayerShooting();
}
DrawGame();
thCounter--;
}
}
Player.h
Seuraavaksi koodataan, mitä tapahtuu, kun vihollisen ammus osuu pelaajaan.
Lisää aluksi player.h-otsikkotiedostoon HandlePlayerHit-funktion esittely.
#ifndef PLAYER_H
#define PLAYER_H
void MovePlayer();
void RotatePlayer();
void PlayerShooting();
#endif
Player.c
Lisää sitten player.c-lähdekooditiedostoon HandlePlayerHit-funktion määrittely, jossa toistaiseksi ei tehdä muuta kuin "julistetaan" pelaaja kuolleeksi, jos se ei sitä vielä ole (if (!player.isDead) player.isDead = 1).
void HandlePlayerHit()
{
if (!player.isDead)
player.isDead = 1;
}
Projs.c
Lisää projs.c-lähdekooditiedoston UpdateProjectiles-funktiomäärittelyyn HandlePlayerHit-funktiokutsu siihen kohtaan, jossa tarkastellaan, onko ammuksen etäisyys pelaajan pienempi kuin 0.3 (distToPlayer < 0.3) ja onko ammus vihollisen ampuma (projectile->isEnemyProjectile).
double distToPlayer = sqrt((player.posX - projectile->posX) * (player.posX - projectile->posX) +
(player.posY - projectile->posY) * (player.posY - projectile->posY));
if (distToPlayer < 0.3 && projectile->isEnemyProjectile)
{
projectile->active = 0;
HandlePlayerHit();
continue;
}
Draw.c
Tee sitten draw.c-lähdekooditiedoston DrawGame-funktiomäärittelyyn seuraavat lisäykset.
Huomaa myös player.isDead-ehtolausekkeen ulkopuolelle siirretyt funktiokutsut vsync ja blit.
void DrawGame()
{
// Jos pelaaja on kuollut...
if (player.isDead)
{
// ...poistetaan hiiri, jotta pelaajahahmoa ei voi kääntää...
remove_mouse();
// ...tyhjennetään ruutu mustaksi... clear_to_color(buffer, makecol(0, 0, 0));
int i;
// ...poistetaan kaikki lennossa olleet ammukset, jotta ne eivät enää lennä uuden
// pelin alkaessa...
for (i = 0; i < MAX_PROJECTILES; i++)
{
MapObject* projectile = &projectiles[i];
if (projectile->active)
{
projectile->active = 0;
}
}
// ...ja tulostetaan näytölle TEXT_DEATH_01 ja TEXT_DEATH_02 -vakioihin määritelly
// tekstit punaisella värillä. textout_centre_ex(buffer, font, TEXT_DEATH_01, SCREEN_W / 2, SCREEN_H / 2,
makecol(255,0,0), -1);
textout_centre_ex(buffer, font, TEXT_DEATH_02, SCREEN_W / 2,
SCREEN_H / 2 + 8, makecol(255,0,0), -1);
// Kun pelaaja painaa enteriä...
if (key[KEY_ENTER] && keypressed())
{
// ...aloitetaan peli alusta.
SetupGame();
}
}
else
{
double zBuffer[SCREEN_WIDTH];
clear_to_color(buffer, makecol(47, 47, 47));
HandleWalls(zBuffer);
HandleEnemies(enemies, numEnemies);
UpdateProjectiles(projectiles, MAX_PROJECTILES, enemies, numEnemies);
HandleMapObjects(sprites, numSprites, projectiles, MAX_PROJECTILES, enemies,
numEnemies, zBuffer);
}
vsync();
blit(buffer, screen, 0, 0, 0, 0, SCREEN_WIDTH, VIEW3D_HEIGHT);
}
Nyt pelaaja kuolee yhdestä vihollisen ammuksen osumasta.
Pelaajan terveyspisteet
Seuraavaksi koodataan myös pelaajalle terveyspisteet.
Globals.h
Lisää globals.h-otsikkotiedostoon vakio P_HEALTH ja anna sille arvoksi vaikka 10. Määritetään siis pelaajan terveyspisteet.
Types.h
Lisää types.h-otsikkotiedoston Player-rakenteeseen uusi kokonaislukumuuttuja health.
struct Player {
double posX, posY;
double dirX, dirY;
double moveSpeed;
double radius;
int allowShooting;
int coolDown;
int isDead;
};
Setup.c
Lisää sitten setup.c-lähdekooditiedoston SetupGame-funktiomäärittelyyn arvo player-rakenteen health-muuttujalle:
planeX = 0, planeY = 158.4 / VIEW3D_HEIGHT;
player.health = P_HEALTH;
player.posX = 22, player.posY = 12;
player.dirX = -1, player.dirY = 0;
Player.c
Muuta player.c-lähdekooditiedoston HandlePlayerHit-funktiota niin, että:
- Pelaajan terveyspisteitä vähennetään yhdellä (player.health--).
- Jos pelaaja ei ole kuollut (!player.isDead) ja elämäpisteet laskevat nollaan tai alle (player.health <= 0), pelaaja julistetaan kuolleeksi (player.isDead = 1).
void HandlePlayerHit()
{
player.health--;
if (!player.isDead && player.health <= 0)
player.isDead = 1;
}
Nyt pelaaja kestää enemmän kuin yhden osuman. Mutta, olisi mukava tietää kuinka lähelllä kuolemaa pelaaja on. Seuraavaksi lisätäänkin terveyspisteiden piirtäminen ruudulle.
Globals.h
Lisää globals.h-otsikkotiedostoon uusi BITMAP-muuttuja *hud. Pelinäkymä jaetaan siis kahtia niin, että yläosassa esitetään 3D-näkymä ja alaosaan tulee HUD, joka esittää tietoja pelaajan ja pelin tilanteesta; tässä tapauksessa näytetään HUDissa siis pelaajan terveyspisteet.
extern BITMAP *buffer;
extern BITMAP *hud;
extern BITMAP *textureA;
extern BITMAP *textureB;
extern BITMAP *lampBitmap;
extern BITMAP *projectileBitmap;
extern BITMAP *enemyBitmap;
Main.c
Lisää main.c-lähdekooditiedostoon *hud-BITMAP-muuttujan alustaminen tyhjäksi (BITMAP *hud = NULL).
BITMAP *buffer = NULL;
BITMAP *textureA = NULL;
BITMAP *textureB = NULL;
BITMAP *lampBitmap = NULL;
BITMAP *projectileBitmap = NULL;
BITMAP *enemyBitmap = NULL;
Lisää sitten main-funktion loppuun funktiokutsu destroy_bitmap parametrinaan hud-BITMAP-muuttuja; kuten kaikkien muidenkin bittikarttojen tapauksessa, vapautetaan myös muistista myös hudin vaatima resurssi, kun ohjelma suljetaan.
destroy_bitmap(textureA);
destroy_bitmap(textureB);
destroy_bitmap(lampBitmap);
destroy_bitmap(projectileBitmap);
destroy_bitmap(enemyBitmap);
destroy_bitmap(buffer);
destroy_bitmap(hud);
remove_timer();
Setup.c
Lisää sitten setup.c-lähdekooditiedoston SetupProgram-funktiomäärittelyyn bittikartan luominen hudille (hud = create_bitmap(SCREEN_WIDTH, SCREEN_HEIGHT - VIEW3D_HEIGHT)).
HUDin kooksi tulee vaakasuunnassa siis kuvaruudun leveys (SCREEN_WIDTH) ja pystysuunnassa kuvaruudun korkeus, josta vähennetään 3D-näkymän korkeus (SCREEN_HEIGHT - VIEW3D_HEIGHT).
buffer = create_bitmap(SCREEN_WIDTH, SCREEN_HEIGHT);
hud = create_bitmap(SCREEN_WIDTH, SCREEN_HEIGHT - VIEW3D_HEIGHT);
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("spr_enem.bmp", NULL);
Draw.c
Tee sitten draw.c-lähdekooditiedoston DrawGame-funktiomäärittelyyn seuraava lisäys, jossa pelaajan kuollessa tyhjennetään HUD mustaksi (clear_to_color(hud, makecol(0, 0, 0))).
if (player.isDead)
{
remove_mouse();
clear_to_color(buffer, makecol(0, 0, 0));
clear_to_color(hud, makecol(0, 0, 0));
int i;
Tee sama mustaksi pyyhkiminen myös siihen tilanteeseen, kun pelaaja on elossa (clear_to_color(hud, makecol(0, 0, 0))). Lisää myös pelaajan terveyspisteiden tulostaminen ruudulle funktiokutsulla textprintf_ex, ja lopuksi ehtolausekkeen ulkopuolelle vielä funktiokutsu blit ensimmäisenä parametrinaan hud, jolloin HUD piirretään näytölle.
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, enemies, numEnemies, projectiles,
MAX_PROJECTILES, zBuffer);
textprintf_ex(hud, font, 10, 10, makecol(255, 255, 255), -1, "HEALTH: %d",
player.health);
}
blit(hud, screen, 0, 0, 0, 0 + VIEW3D_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT - VIEW3D_HEIGHT);
blit(buffer, screen, 0, 0, 0, 0, SCREEN_WIDTH, VIEW3D_HEIGHT);
}
Nyt, kun HUD näyttää pelaajan terveyspisteet, pelaajallakin on tieto, kuinka monta osumaa hänen ohjaamansa hahmo vielä kestää.
Pelin läpäisy
Pelin pääsee läpi, kun tappaa kaikki viholliset.
Globals.h
Kuten pelaajan kuollessa, myös pelin läpäistäessä ruudulle tulostetaan ilmoitus tapahtuneesta. Lisää siis globals.h-otsikkotiedostoon vakiot TEXT_WIN_01 ja TEXT_WIN_02 sekä niille haluamasi arvot.
#define TEXT_WIN_01 "YOU WON!"
#define TEXT_WIN_02 "ENTER TO RESTART OR ESC TO LEAVE."
Types.h
Lisää types.h-otsikkotiedoston Playe-rakenteeseen uusi kokonaislukumuuttuja won, jonka avulla tarkastellaan, onko peli läpäisty.
struct Player {
double posX, posY;
double dirX, dirY;
double moveSpeed;
double radius;
int allowShooting;
int coolDown;
int isDead, won;
int health;
};
Setup.c
Lisää sitten setup.c-lähdekooditiedoston SetupGame-funktioon player.won-muuttujan alustus arvolla 0.
player.isDead = 0;
player.won = 0;
player.health = P_HEALTH;
player.posX = 22, player.posY = 12;
player.dirX = -1, player.dirY = 0;
Main.c
Muuta main.c-lähdekooditiedoston ja sen main-funktiossa olevaa sisemmän while-silmukan ehtolausetta niin, että pelaajan liikkuminen, kääntyminen ja ampuminen sallitaan, jos pelaaja ei ole kuollut eikä voittanut peliä (!player.won).
while(!key[KEY_ESC])
{
while (thCounter > 0)
{
if (!player.isDead && !player.won)
{
MovePlayer();
RotatePlayer();
PlayerShooting();
}
DrawGame();
thCounter--;
}
}
Draw.c
Lisää draw.c-lähdekooditiedoston DrawGame-funktiomäärittelyn ehtolausekkeeseen pelaajan elämästä ja kuolemasta tilanne, jossa peli on läpäisty.
Huomaa, että pelaajan kuollessa koko kuvaruutu tyhjennetään mustaksi, mutta pelin läpäistäessä 3D-näkymä jää pysäytyskuvana näkyviin.
if (player.isDead)
{
remove_mouse();
clear_to_color(buffer, makecol(0, 0, 0));
clear_to_color(hud, makecol(0, 0, 0));
int i;
for (i = 0; i < MAX_PROJECTILES; i++)
{
MapObject* projectile = &projectiles[i];
if (projectile->active)
{
projectile->active = 0;
}
}
textout_centre_ex(buffer, font, TEXT_DEATH_01, SCREEN_W / 2, SCREEN_H / 2,
makecol(255,0,0), -1);
textout_centre_ex(buffer, font, TEXT_DEATH_02, SCREEN_W / 2,
SCREEN_H / 2 + 8, makecol(255,0,0), -1);
if (key[KEY_ENTER] && keypressed())
{
SetupGame();
}
}
// Jos peli on läpäisty...
else if (player.won)
{
// ...poistetaan hiiri...
remove_mouse();
// ...ja tulostetaan ruutuun vakioihin TEXT_WIN_01 sekä TEXT_WIN_02 määritellyt tekstit. textout_centre_ex(buffer, font, TEXT_WIN_01, SCREEN_W / 2, VIEW3D_HEIGHT / 2,
makecol(255,255,255), makecol(0,0,255));
textout_centre_ex(buffer, font, TEXT_WIN_02, SCREEN_W / 2,
VIEW3D_HEIGHT / 2 + 8, makecol(255,255,255), makecol(0,0,255));
// Kun painetaan enteriä...
if (key[KEY_ENTER] && keypressed())
{
int i;
// ...poistetaan kaikki lennossa olleet ammukset...
for (i = 0; i < MAX_PROJECTILES; i++)
{
MapObject* projectile = &projectiles[i];
if (projectile->active)
{
projectile->active = 0;
}
}
// ...ja aloitetaan peli alusta. SetupGame();
}
}
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, zBuffer);
textprintf_ex(hud, font, 10, 10, makecol(255, 255, 255), -1, "HEALTH: %d",
player.health);
}
No niin, nyt, kun on koodattu peliin myös tilanne, jossa peli on voitettu, koodataan seuraavaksi pelin läpäiseminen mahdollliseksi. Eli, kun pelaaja tappaa kaikki kentän viholliset, pelin pääsee läpi.
Globals.h
Lisää globals.h-otsikkotiedostoon kokonaislukumuuttuja killedEnemies, joka laskee tapettujen vihollisten määrää.
extern int numSprites;
extern int numEnemies;
extern int killedEnemies;
Main.c
Alusta main.c-lähdekooditiedostossa tapettujen vihollisten määrä antamalla killedEnemies-muuttujalle arvon 0.
int numSprites = 0;
int numEnemies = 0;
int killedEnemies = 0;
Setup.c
Anna myös setup.c-lähdekooditiedoston SetupGame-funktiomäärittelyyn killedEnemies-muuttujalle arvo 0; näin aina uuden pelin alkaessa tappolaskuri nollataan.
player.health = 10;
player.isDead = 0;
player.won = 0;
player.posX = 22, player.posY = 12;
player.dirX = -1, player.dirY = 0;
killedEnemies = 0;
Enemies.c
Tee sitten enemies.c-lähdekooditiedoston HandleEnemyHit-funktiomäärittelyyn seuraavat lisäykset:
-
jos vihollisen terveyspisteet laskevat nollaan tai alle vihollisen ollessa aktiivinen, kasvatetaan killedEnemies-laskuria
-
jos kaikki viholliset on tapettu (killedEnemies == numEnemies), peli julistetaan läpäistyksi (player.won = 1).
void HandleEnemyHit(MapObject* enemy)
{
enemy->health--;
if (enemy->health <= 0 && enemy->active)
{
enemy->active = 0;
enemy->block = 0;
killedEnemies++;
}
if (killedEnemies == numEnemies)
player.won = 1;
}
Draw.c
Lisää lopuksi draw.c-lähdekooditiedoston DrawGame-funktiomäärittelyyn textprintf_ex-funktiokutsu, jolla tulostetaan HUDiin sekä tapettujen vihollisten (killedEnemies) että niiden kokonaismäärä kentässä (numEnemies).
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, 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 pelin voi läpäistä, ja tilanne jäljellä olevien vihollisten suhteen on koko ajan pelaajan tiedossa.

Vau, nythän meillä on kasassa ihan kokonainen peli, jonka voi hävitä ja voittaa!
Kommentit
Lähetä kommentti