Nieuws — Lesstof — Pengo — Projecten — Console API — Links
Pengo © 2002-2003, Joost Ronkes Agerbeek
In Pengo is het veld opgebouwd uit blokken. De speler kan deze blokken verplaatsen door er tegen te schoppen. In deze les programmeren we de blokken.
Net als bij de speler beginnen we met het maken van een datastructuur voor de blokken. Om te beginnen heeft een blok in ieder geval een positie. De structure komt in het bestand Block.h te staan.
/****************************************************************************** Bestand: Block.h Project: Pengo Copyright: (c) 2003 Joost Ronkes Agerbeek Auteur: Joost Ronkes Agerbeek <joost@ronkes.nl> Datum: 18 januari 2003 ******************************************************************************/ #ifndef __BLOCK_H__ #define __BLOCK_H__ /****************************************************************************** Structures ******************************************************************************/ /** * Een blok. */ struct Block { /** * De positie van het blok. */ int X, Y; }; #endif
Het aanmaken van een blok gaat met de functie CreateBlock. Als parameters krijgt deze functie de positie van het blok mee. In Pengo gebruiken we meerdere blokken en die willen we natuurlijk niet allemaal op dezelfde plaats hebben staan. De code komt in Block.cpp.
/****************************************************************************** Bestand: Block.cpp Project: Pengo Copyright: (c) 2003 Joost Ronkes Agerbeek Auteur: Joost Ronkes Agerbeek <joost@ronkes.nl> Datum: 18 januari 2003 ******************************************************************************/ #include "Block.h" /****************************************************************************** Globale functies ******************************************************************************/ /** * Maakt een nieuw blok aan en initialiseert de gegevens van het blok. * * @param x de x-coordinaat van het te maken blok * @param y de y-coordinaat van het te maken blok * * @return een nieuw blok */ Block CreateBlock(int x, int y) { // maak nieuw blok Block myBlock; // stel standaardwaarden in myBlock.X = x; myBlock.Y = y; // geef blok terug return myBlock; }
We moeten CreateBlock aan kunnen roepen vanuit andere bestanden, dus we nemen het prototype op in Block.h.
/****************************************************************************** Globale functies - definities ******************************************************************************/ /** * Maakt een nieuw blok aan en initialiseert de gegevens van het blok. * * @param x de x-coordinaat van het te maken blok * @param y de y-coordinaat van het te maken blok * * @return een nieuw blok */ Block CreateBlock(int x, int y);
Voordat we een blok aanmaken, zorgen we er eerst voor dat het blok getekend wordt. Het tekenen van een blok is vrij rechttoe-rechtaan. We moeten alleen een ASCII-teken en kleuren kiezen voor het blok. De functie DrawBlock roept WriteText aan om het block te tekenen. Net als bij het tekenen van de speler, zetten we de code voor het tekenen van een blok in Graphics.cpp. Vergeet niet Block.h te includen.
/** * Het ASCII-teken dat een blok voorstelt. */ const char BlockCharacter = 4; /** * De kleuren van een blok. */ const Color BlockForegroundColor = Yellow; const Color BlockBackgroundColor = Black;
/** * Tekent een blok naar het scherm. * * @param block het blok dat getekend moet worden */ void DrawBlock(const Block& block) { // teken blok WriteText(BlockCharacter, block.X, block.Y, BlockForegroundColor, BlockBackgroundColor); }
De parameter block wordt niet veranderd door de functie DrawBlock, dus we maken hem constant door het woord const ervoor te zetten.
Nu kunnen we een blok aanmaken en op het scherm zetten.
// maak blok aan Block myBlock = CreateBlock(30, 11); // teken blok DrawBlock(myBlock);
De beweging van een blok in Pengo lijkt erg veel op de beweging van een speler. De code zal daarom niet erg veel verrassingen opleveren. Eerst maar eens de structure uitbreiden.
/** * Een blok. */ struct Block { /** * De positie van het blok. */ int X, Y; /** * De richting waarin het blok beweegt. */ Directions Direction; /** * Geeft aan of het blok beweegt. */ bool IsMoving; };
Dit levert een probleem op. De enum Directions is namelijk gedefinieerd in Player.h en de compiler geeft dus een foutmelding. We kunnen Player.h natuurlijk includen in Block.h, maar dat is niet zo netjes. Immers een blok is helemaal niet afhankelijk van een speler. We maken dus een aparte header file voor Directions. We noemen het bestand Movement.h; hierin kunnen we alle code kwijt die gedeeld wordt door objecten die kunnen bewegen.
/****************************************************************************** Bestand: Movement.h Project: Pengo Copyright: (c) 2003 Joost Ronkes Agerbeek Auteur: Joost Ronkes Agerbeek <joost@ronkes.nl> Datum: 19 januari 2003 ******************************************************************************/ #ifndef __MOVEMENT_H__ #define __MOVEMENT_H__ /****************************************************************************** Enums ******************************************************************************/ /** * De bewegingsrichtingen van een speler. */ enum Directions { Up, Down, Left, Right }; #endif
Je moet nu de enum Directions verwijderen uit Player.h en Movement.h includen in zowel Player.h als Block.h.
Bij het aanmaken van een nieuw blok, staat het blok natuurlijk stil, dus dat nemen we op in CreateBlock.
myBlock.IsMoving = false;
Nu nog de functie MoveBlock die het blok ook echt verplaatst. Vergeet niet de functie declaratie op te nemen in Block.h.
/** * Verplaatst het blok één positie. * * @param block het blok dat verplaatst moet worden */ void MoveBlock(Block& block) { // beweegt het blok? if (block.IsMoving) { // ja, in welke richting beweegt het blok? switch (block.Direction) { case Up: { // kan blok nog omhoog? if (block.Y > 0) { // ja, verplaats blok omhoog block.Y--; } else { // nee, zet blok stil block.IsMoving = false; } } break; case Down: { // kan blok nog omlaag? if (block.Y < 24) { // ja, verplaats blok omlaag block.Y++; } else { // nee, zet blok stil block.IsMoving = false; } } break; case Left: { // kan blok nog naar links? if (block.X > 0) { // ja, verplaats blok naar links block.X--; } else { // nee, zet blok stil block.IsMoving = false; } } break; case Right: { // kan blok nog naar rechts? if (block.X < 79) { // ja, verplaats blok naar rechts block.X++; } else { // nee, zet blok stil block.IsMoving = false; } } break; } } }
En tot slot zetten we het blok in beweging en roepen we MoveBlock aan vanuit de game loop.
// maak blok aan Block myBlock = CreateBlock(30, 11); myBlock.IsMoving = true; myBlock.Direction = Left;
// verwijder blok van scherm MoveCursor(myBlock.X, myBlock.Y); cout << " "; // verplaats het blok MoveBlock(myBlock); // teken block DrawBlock(myBlock);
Voordat je nou denkt dat we ineens met een kaartspel bezig zijn, het gaat hier om Pengo die tegen een blok aan schopt. Ten eerste moeten we een toets toevoegen waarmee de speler Pengo kan laten schoppen. Net als alle andere toetsen zetten we deze in een constante.
const int PengoKick = VK_SPACE;
Nu we de toets bepaald hebben, moeten we ook op deze toets reageren. Dit doen we door de functie Kick aan te roepen, die we straks gaan schrijven. Eerst maar even de toets afhandelen.
case PengoKick: { // laat speler schoppen Kick(myPlayer, myBlock); } break;
De functie Kick krijgt twee parameters mee: de speler die schopt en het blok dat op het veld staat. Het eerste dat de functie doet, is controleren of de speler het blok raakt. Daarbij houden we rekenen met de richting waarin de speler kijkt, want dat is ook de richting waarin hij schopt.
/** * Laat de speler schoppen. * * @param player de speler die schopt * @param block het blok waar de speler tegenaan kan schoppen */ void Kick(const Player& player, Block& block) { // in welke richting schopt de speler? switch (player.Direction) { case Up: { // schopt speler tegen blok aan? if ((player.X == block.X) & ((player.Y - 1) == block.Y)) { // ja, zet blok in beweging block.Direction = Up; block.IsMoving = true; } } case Down: { // schopt speler tegen blok aan? if ((player.X == block.X) & ((player.Y + 1) == block.Y)) { // ja, zet blok in beweging block.Direction = Down; block.IsMoving = true; } } case Left: { // schopt speler tegen blok aan? if (((player.X - 1) == block.X) & (player.Y == block.Y)) { // ja, zet blok in beweging block.Direction = Left; block.IsMoving = true; } } case Right: { // schopt speler tegen blok aan? if (((player.X + 1) == block.X) & (player.Y == block.Y)) { // ja, zet blok in beweging block.Direction = Right; block.IsMoving = true; } } } }
Omdat Kick een parameter van het type Block meekrijgt, moet je Block.h includen. De parameter block kan in deze functie gewijzigd worden, maar de parameter player niet. We maken player dus constant door er het woord const voor te zetten. Neem de declaratie van de functie op in Player.h.
En dat is het voor deze keer. We hebben gezien hoe we de code voor het bewegen van de speler kunnen toepassen voor het bewegen van een blok. Bovendien hebben we ervoor gezorgd dat de speler tegen een blok aan kan schoppen.
De volgende keer houden we ons bezig met het programmeren van het level. Tot die tijd raad ik je aan om veel te experimenteren met de code die we tot nu toe hebben geschreven. Voer wat wijzigingen door en zie wat het effect is.
Programmeren is leuk. :-)
Als je de bovenstaande code af hebt en je wilt Pengo graag uitbreiden, dan kun je de volgende features toevoegen.