Games programming: Coding

Op deze pagina wat uitleg over het eigenlijke programmeren van PinPin.

Het lastigste met spelletjes is de 'game loop': hoe zorg je er voor dat het spel altijd op de juiste snelheid loopt, onafhankelijk van de snelheid van de computer en het beeldscherm? Scherm refresh rates lopen van 50 Hz tot 85 Hz (en zelfs daarbuiten), toch moeten de figuurtjes even snel bewegen, onafhankelijk van het systeem.

Dit bereik ik door de snelheid van de figuurtjes op een vaste frequentie te berekenen, onafhankelijk van de beeldscherm frequentie. De main loop van het programma ziet er (iets versimpeld) uit als hier beneden weergegeven. De 'main loop' draait altijd op een snelheid bepaald door de scherm-update, doordat de display-update-routine wacht op een vertical refresh signaal van de beeldschermkaart (dit voorkomt een knipperend beeld). Daarnaast is er een loop (de 'game loop') die zorgt voor het bijwerken van de positie van de spelers etc, de timing hiervoor wordt bepaald door een interrupt signaal dat altijd op bijvoorbeeld 50 Hz (20 miliseconden) loopt, onafhankelijk van de beeldscherm update frequentie (lees verder...).

Dit gebeurt via een variabele genaamd fm_framer, welke welke 20 miliseconde via een interrupt wordt opgehoogd (zie verderop voor de interrupt routine etc). Aan de hand van de stand van deze variabele wordt in de main loop de 'game loop' 0, 1 of meerdere keren uitgevoerd, al naar gelang wat nodig is.

Als de display frequentie boven de game loop frequentie ligt, zal de game loop zo nu en dan worden overgeslagen om toch het goede tempo te hebben. Bijvoorbeeld: 75 Hz display (D), 50 Hz game (G): de volgorde zal dan zijn D D G D G D D G D G D D G D G ....., verhouding van 3:2.

Mocht de display frequentie juist te traag zijn, dan zal de game loop juist vaker worden uitgevoerd, zodat deze toch altijd gemiddeld 50 keer per seconde wordt uitgevoerd. Dit kan ook bijvoorbeeld gebeuren op een trage computer, waarbij zoveel gerekend wordt dat de display update een aantal beeldjes mist (er zijn al een aantal vertical sync's voorbij voor de volgende aanroep van update_display( ) ). Beweging kan op een gegeven moment dan wat schokkerig worden ( D G G G G G G D G G G G G G D G G G .....), maar de bewegingssnelheid van de spelers blijft gelijk!

void main_loop(void)
{
    init_mytimers();

/* This is the 'main loop', repeated until SPACE or ESC is pressed */
   
while (!quit)
    {

/* -- 'game loop': this loop is running at the game rate,
   not the display frame rate --- */
     
while(fm_framer>0)
      {
        keyhandler(0); /* function keys, Esc key etc */
       
fm_framer--; /* may be inc'd by interrupts! */
        update_pinguin(); /* the players */
        MapUpdateAnims(); /* other animations */
       
if(fish_on) update_fish();
      }
/* --------- end of game loop --------- */

/* The drawing of the various layers and characters
   on the internal bitmap memory for later display */
     
update_background_layer();
      if(fish_on) draw_fish();
      draw_animals();
      update_foreground_layer();
      if(snow_on) let_it_snow();
      display_score();
      display_helptext();

/* copy the internal bitmap to the display via DirectX,
   this waits for a display vertical sync signal */
     
update_display();
    }
}

Mocht de computer erg traag zijn (en het beeld erg knipperend), dan kan je een aantal 'features' uitschakelen om zo rekentijd te besparen. Dit zie je ook in de loop: de vissen en de sneeuw (die best nogal wat vragen van het systeem) kunnen via de variabelen fish_on en snow_on aan- en uit-geschakeld worden (bijvoorbeeld via een configuratiemenu, of hier via bepaalde toetsen).

Een paar bijhorende routines rond de interrupts, met wat extra 'code' nodig voor Allegro, bijvoorbeeld om te zorgen dat de interrupt routines in het cache geheugen blijven en niet uitgeswapt worden:

void frame_timer()            /* increment frame_time */
{
      fm_framer++;            /* set up at 50 Hz (20 ms) */
}
END_OF_FUNCTION(frame_timer);

void init_mytimers()          /* set up game timers etc */
{
    LOCK_VARIABLE(fm_framer); /* may not be swapped out! */
    LOCK_FUNCTION(frame_timer);

    install_int_ex(frame_timer, MSEC_TO_TIMER(20));
    fm_framer = 0;
}

Hopelijk geeft dit voldoende inzicht in de game timing.