Nieuws — Lesstof — Pengo — Projecten — Console API — Links
Pengo © 2002-2003, Joost Ronkes Agerbeek
Op dit moment beweegt alles in Pengo even snel. Dat komt doordat we de game loop maar op één punt vertraagd hebben. Het maakt het spel niet echt leuk, dus daar gaan we deze les verandering in brengen.
Wat we gaan doen om het probleem op te lossen, is voor elk object een aparte vertraging inbouwen. We krijgen dus drie vertragingen: één voor de speler, één voor de blokken en één voor de vijanden. Deze vertragingen slaan we op in globale constanten.
/** * De vertragingen van de speler, blokken en vijanden. */ const int PlayerDelay = 150; const int BlockDelay = 10; const int EnemyDelay = 250;
Omdat we de objecten individueel gaan vertragen, moeten we de algemene game loop vertraging weghalen. Verwijder dus alle regels die te maken hebben met het vertragen van de game loop. Oké oké, ik zal vertellen welke regels dat zijn.
/** * De vertraging van de game loop in milliseconden. */ const int GameDelay = 250;
// vraag huidige tijd op DWORD myTime = timeGetTime();
// wacht tot vertraging om is while (timeGetTime() < (myTime + GameDelay));
De truc is nu dat we per object (speler, blokken, vijanden) de tijd bijhouden en dat we per object bepalen of de vertraging voorbij is. Om de tijd bij te houden, hebben we dus drie vertragingen nodig. Deze komen boven de game loop te staan.
// vraag tijd op voor speler, blokken en vijanden DWORD myPlayerTime = timeGetTime(); DWORD myBlockTime = timeGetTime(); DWORD myEnemyTime = timeGetTime();
Om nu de speler te vertragen, zet we een if-statement rondom de code die de speler beweegt. De logische expressie lijkt erg veel op die van de vertraging van de game loop: als de opgeslagen tijd plus de vertraging groter is dan de huidige tijd, dan is het tijd om de speler te verplaatsen.
// is het tijd om de speler te verplaatsen? if ((myPlayerTime + PlayerDelay) < timeGetTime()) { // ja, begin opnieuw met de timing voor de speler myPlayerTime = timeGetTime(); // beweegt de speler? if (GlobalLevel.Player.IsMoving) { // ja, verwijder speler van het scherm ClearPlayer(GlobalLevel.Player); // verplaats de speler MovePlayer(GlobalLevel.Player); } }
Je ziet dat myPlayerTime weer wordt ingesteld op de huidige tijd. Hiermee reset je als het ware de timer van de speler.
In bovenstaand stukje code wordt timeGetTime() twee keer aangeroepen. Bovendien moeten we timeGetTime() straks ook nog aanroepen voor de blokken en de vijanden. Omdat een functieaanroep relatief traag is, passen we het zó aan, dat timeGetTime() maar één keer wordt aangeroepen.
DWORD myTime = timeGetTime(); // is het tijd om de speler te verplaatsen? if ((myPlayerTime + PlayerDelay) < myTime) { // ja, begin opnieuw met de timing voor de speler myPlayerTime = myTime; // beweegt de speler? if (GlobalLevel.Player.IsMoving) { // ja, verwijder speler van het scherm ClearPlayer(GlobalLevel.Player); // verplaats de speler MovePlayer(GlobalLevel.Player); } }
Inderdaad, je had het vorige blok code net zo goed niet kunnen kopiëren. :-P
Het vertragen van de blokken en van de vijanden gaat natuurlijk net zo als het vertragen van de speler.
// is het tijd om de blokken te verplaatsen? if ((myBlockTime + BlockDelay) < myTime) { // ja, begin opnieuw met de timing voor de blokken myBlockTime = myTime; // verplaats de blokken for (int i = 0; i < GlobalLevel.Blocks.size(); i++) { // beweegt het blok? if (GlobalLevel.Blocks.at(i).IsMoving) { // ja, verwijder het van het scherm ClearBlock(GlobalLevel.Blocks.at(i)); // verplaats het blok MoveBlock(GlobalLevel.Blocks.at(i)); } } } // is het tijd om de vijanden te verplaatsen? if ((myEnemyTime + EnemyDelay) < myTime) { // ja, begin opnieuw met de timing voor de vijanden myEnemyTime = myTime; // verplaats de vijanden }
Omdat we nog geen code hebben geschreven om de vijanden te laten verplaatsen, ontbreekt dat gedeelte. Ik heb de vertraging er wel alvast bij gezet; dan hoeven we dat volgende keer niet meer te doen.
Nou vooruit, laten we de basis voor het verplaatsen van vijanden maar alvast leggen. Dat scheelt ons volgende keer werkt. Het verplaatsen van vijanden gaat net als het verplaatsen van blokken: we lopen alle vijanden stuk voor stuk langs. Eerst halen we ze van het scherm met ClearEnemy en daarna verplaatsen we ze met MoveEnemy.
// is het tijd om de vijanden te verplaatsen? if ((myEnemyTime + EnemyDelay) < myTime) { // ja, begin opnieuw met de timing voor de vijanden myEnemyTime = myTime; // verplaats de vijanden for (int i = 0; i < GlobalLevel.Enemies.size(); i++) { // verwijder vijand van het scherm ClearEnemy(GlobalLevel.Enemies.at(i)); // verplaats het vijand MoveEnemy(GlobalLevel.Enemies.at(i)); } }
Nu hebben we alleen nog de functies ClearEnemy en MoveEnemy nodig. Ik zet hier allebei meteen maar onder. Denk eraan om de declaraties in de juiste headerbestanden op te namen.
/** * Verwijdert een vijand van het scherm. * * @param enemy de vijand die verwijderd moet worden */ void ClearEnemy(const Enemy& enemy) { // verwijder vijand WriteText(" ", enemy.X, enemy.Y); }
/** * Verplaats een vijand één positie. * * @param enemy de vijand die verplaatst moet worden */ void MoveEnemy(Enemy& enemy) { }
Je ziet, de functie MoveEnemy is nog leeg en de vijanden bewegen dus nog niet. Volgende les zullen we MoveEnemy invullen. Op dit moment staat de functie er alleen maar om te zorgen dat de code compileert.
En dat is het alweer voor deze keer. Dat was toch niet zo heel erg veel? (Zeker niet in vergelijking met de vorige keer). We hebben de algemene vertraging van de game loop vervangen door individuele vertragingen van de speler, de blokken en de vijanden.
De volgende keer laten we de vijanden achter ons aan rennen en daarvoor moeten we ze wat intelligentie geven. Oftewel, we gaan ons bezig houden met één van de moeilijkste onderwerpen van het spelprogrammeren: artificial intelligence. Maak je borst maar nat.
Programmeren is leuk. :-)
Als je de bovenstaande code af hebt en je wilt Pengo graag uitbreiden, dan kun je de volgende features toevoegen.
De volgende bestanden horen bij deze les.