Index des articles > Programmation > Régulation de la vitesse d'un jeu

Régulation de la vitesse d'un jeu

Article posté par gOlstein

Réguler la vitesse dans un jeu
Par LionelA

Tout d?abord posons le problème, la vitesse d?exécution d?un jeu peut être différente d?une calculette à une autre si elle n?est pas régulée. En effet, la fréquence du processeur entre modèles HW1 et HW2 étant différente (sans parler des calculatrices overclockées) un jeu non régulé s?exécutera plus vite sur une calculatrice HW2. Cela peut poser des problèmes de gameplay, de synchronisation entre deux calcs connectées via le link, de highscores (time attack), ...
Je propose donc dans cet article une méthode pour réguler la vitesse d?un jeu, se basant sur l?auto interrupt 1 (interruption choisie car rapide et fonctionnant aussi correctement sous VTI).

Il faut savoir que la fréquence de l?auto_int_1 est différente selon le modèle de hardware (cf. j89hw.txt)
Il faut donc d?abord résoudre ce problème :

Le code présenté ci-dessous m?a été généreusement donné par Geoffrey Anneheim (geogeo)

J?ai trouvé la valeur de HARDWARE_FREQUENCY après de multiples tests entre VTI et ma ti89 HW2, il se peut quelle ne soit pas juste a 100% mais la précision suffit amplement

#define HARDWARE_FREQUENCY       20970
static volatile unsigned short counter_hardware=0;

DEFINE_INT_HANDLER (MyInt1)
{
  ExecuteHandler (OldInt1);
  //HARDWARE VERSION 1.0 
  if (HW_VERSION==1)
  {
    //Incrémentation
    counter_hardware+=HARDWARE_FREQUENCY;
  
    //Execution interrupotion
    if (counter_hardware<=32768 )
      return;
    //Remise à zéro
    counter_hardware-=32768;        
  }

  // Exécuter ici le code qui doit se passer avec régulation de la vitesse
}


sur les HW1, ce code a pour effet de sauter des occurrences de l'interrupt 1 pour se re-synchroniser avec l'auto int 1 des HW2,c'est pour ça que OldInt1 (généralement utilisée pour les niveaux de gris et appelée en 1er pour ne pas gêner le déroulement normal de ceux-ci)

OldInt1 est obtenu en sauvegardant le vecteur d?interruption comme ceci :

OldInt1 = GetIntVec (AUTO_INT_1);



méthode de régulation

Certains d?entre vous utilisent une technique de régulation de la vitesse basée sur les interruptions (1 ou 5) mais emploient une mauvaise méthode : attendre dans la boucle principale l?incrémentation d?une variable avec un while et donc bloquer toutes les ressources CPU pendant ce temps d?attente.

Voila la description de la méthode que j?utilise dans le moteur de mode7 (donc dans F-Zero) et qui me semble la meilleure (si quelqu?un a mieux, je suis preneur)

Dans mes projets, je sépare complètement la partie affichage de la partie gameplay, c'est-à-dire que je peux demander un affichage à n?importe quel moment du déroulement du jeu. Cela est possible car je mets tout (sprites, coordonnées, etc...) en global.
De plus cela permet d?accélérer le jeu puisqu?il n?y a plus de passage de paramètres. Même si les professeurs d?informatique vous disent qu?il faut éviter les variables globales pour cause de lisibilité et de maintenance, sur nos machines il vaut mieux en avoir car la moindre ressource économisée n?est pas négligeable.

Nous avons donc une fonction d?affichage qui peut afficher la scène à n?importe quel moment en fonction des données de la scène.
C?est cette fonction qui va prendre la majorité des ressources CPU. Plus la fonction d?affichage est gourmande en ressources, plus les fps (frames per second) seront basses.

La boucle principale du programme appellera donc sans cesse cette fonction.

Vous vous doutez bien que la seule façon de modifier la scène maintenant est de le faire dans l?interruption.

Une façon de le faire est d?appeler une ?fonction de modification? de la scène, fonction qui modifie les données de la scène, dans l?interruption. Comme les données sont globales il est facile de lire le clavier et de modifier la scène en conséquence ou même sans lecture du clavier (exemple : modifier les coordonnées d?un sprite).

Afin de pouvoir changer facilement de mode de modification de la scène, nous utiliserons les pointeurs de fonctions.

Voici un peu de code C :

 #define SHOW_FPS 0 // 0 : don't show, 1 : show fps
#define GAME_SPEED 10 // 0 : the fastest

INT_HANDLER OldInt1 = NULL;
#if SHOW_FPS == 1
char fpsStr[10];
#endif
char hwVersion;
volatile char quit = 0;
char modifying = 0;
char nextModif = 0;
short timeCount = 0;
volatile short fCount = 0;
void (*display_Modif)();

void Display();

// [Thanks to Geogeo from www.tigen.org for this code]
#define HARDWARE_FREQUENCY        20970
static volatile unsigned short counter_hardware=0;

DEFINE_INT_HANDLER (SceneModif)
{
  ExecuteHandler (OldInt1);
  //HARDWARE VERSION 1.0 
  if (hwVersion==1)
  {
    //Incrémentation
    counter_hardware+=HARDWARE_FREQUENCY;
  
    //Execution interrupotion
    if (counter_hardware<=32768 )
      return;
    //Remise à zéro
    counter_hardware-=32768;        
  }
// [/Thanks to Geogeo from www.tigen.org for this code]
  timeCount++;
  if (timeCount == (256))
  {
    timeCount = 0;
#if SHOW_FPS == 1
      PortSet(Plane0,239,127);
      DrawStr (0, 0, "          ", A_REPLACE);
      sprintf (fpsStr, "%d", fCount);
      DrawStr (0, 0, fpsStr, A_REPLACE);
      PortRestore();
#endif
    fCount = 0;
  }
  
  if (!modifying && !nextModif)
  {
    modifying = 1;
    nextModif = GAME_SPEED;
    display_Modif();
    modifying = 0;
  }
  if(nextModif)
    nextModif--;
}


void SetLoopFunc(void (*modifFunc)())
{
  display_Modif = modifFunc;
}


void LoopInit()
{
  quit = 0;
  modifying = 0;
  timeCount = 0;
  fCount = 0;
  hwVersion = HW_VERSION;
  OldInt1 = GetIntVec (AUTO_INT_1);
}

void LoopEnd()
{
  SetIntVec (AUTO_INT_1, OldInt1);  
}

void MainLoop()
{
  SetIntVec (AUTO_INT_1, SceneModif);
  while(!quit)
  {
    Display();
    fCount++;    
  }  
}

void LoopQuit()
{
  quit = 1;
}


Le code ci-dessus déclare un vecteur d?interruption SceneModif, qui utilise la méthode de re-synchronisation HW1/HW2 citée plus haut. Il y rajoute un compteur de frames pour calculer le nombre de fps, ainsi que le blocage de la modification si l?on se trouve déjà en train de modifier (utile si la fonction de modif est trop lourde, mais il vaut mieux l?eviter). Il y a aussi un compteur de modification pour pouvoir choisir la vitesse de jeu (GAME_SPEED).

Pour l?utiliser il faut tout d?abord déclarer une fonction de modif et la passer en paramètre à la fonction d?initialisation de la boucle.

Exemple :
 // fonction de modification de la scene
void jeu()
{
  // c'est ici qu'il faut capter le clavier et changer les coordonnées, animations, etc ... des sprites
  kbd_Scan();
  
  //ex : if le gars est dead, tu peux changer de fonction de modif car il n'y a plus besoin de capter un gameplay
  //dans ce cas tu fais : SetLoopFunc(gameover);
  // si tu veu quitter le jeu tu fais
  if (ESC)
    LoopQuit();
  
}

void game()
{
  // main loop
  SetLoopFunc(jeu);
  LoopInit();
  MainLoop();
  LoopEnd();
}

// Main Function
void _main(void)
{
// inits
  kbd_Init();
  clrscr();
  GrayInit();

  game();
  
  GrayEnd();
  kbd_End();
  PortRestore();
}


Pour finir j?ai déjà regroupé toutes ces features dans un squelette de projet pour TIGCC. Je l?ai fait pour un ami et il fait son jeu avec (peut être qu?un jour il le releasera :D).

Vous pouvez télecharger le zip sur ma page :
skeletor

Vous aurez besoin de extgraph 2.00b3 disponible sur le site de la TICT : ExtGraph Library v2.00 Beta 3

>> Vos commentaires [2]

pratique :: [Commentaire n°1]

Posté par Syfo-Dias le 25/11/2005
pratique touut ça vu que jai tjs des problemes avec la vitesse


[Commentaire n°2]

Posté par tismaster le 13/01/2006
Le lien de skeletor ne fonctionne plus.


Poster un commentaire


Seuls les membres peuvent poster des commentaires