Événements liés
  • Démo technique du samedi #6: 30 Juin 2007
Pages: [1]   Bas de page
Imprimer
Auteur Fil de discussion: [Demo] Des listes d'objets qui vous suivent  (Lu 3942 fois)
0 Membres et 1 Invité sur ce fil de discussion.
Mollusk Hors ligne
PAlib Guru et
Administrateur
*****
Messages: 3094


Voir le profil WWW
Ne vous posez pas de questions, codez !

« 01 Juillet 2007, 01:59:13 »

Il est venu le temps de faire une nouvelle démo. Smiley Cette semaine, on va aborder 2 points importants. Il existe plusieurs façons de réaliser chacun d'eux, et donc on peut tout à fait contester mon code moche et peu optimisé, mais c'est juste pour montrer une manière de faire. Smiley Les 2 éléments dont on va parler :
  • Gérer une liste d'objets en en retirant/rajoutant un peu tout le temps
  • Gérer la trajectoire de type 'tête chercheuse' en cherchant à utiliser le moins de temps CPU possible

Voici donc le résultat de la démo :

Le code source fait une centaine de lignes, et utilise des sprites normaux avec des rotsets pour les rotations. Rien de bien exceptionnel, rien qui ne nécessite une explication. Azn
Bon, il faut bien commencer quelque part, on va voir en premier la gestion des objets. Smiley

Code
(c):
typedef struct{
  s32 fx, fy, x, y, vitesse;
  s16 angle;
  s8 chgangle;
} tir_type;
 
tir_type tirs[100]; // 100 max
u8 ntir[100];
u8 nlibre[100];
u8 ntirs;
Voici pour la déclaration. En gros, pour chaque tir, on a une position en fixed point et en normal (pour ceux qui auraient envie de jouer avec des collisions sans se prendre la tête avec les fixed points), la vitesse, l'angle (direction, de 0 à 511 pour des questions de simplicité et de puissance de 2 Azn), et chgangle, qui est de combien l'angle peut changer par frame.
La variable ntirs correspond au nombre de tirs actifs. On a ensuite 3 tableaux :
  • tir_type tirs[100]; pour les infos des tirs
  • u8 ntir[100]; pour les tirs en cours. De ntir[0] à ntir[ntirs] on trouve les tirs activés avec le numéro correspondant dans le tableau tirs
  • u8 nlibre[100]; pour les slots dispos. Même principe que ntir, mais l'inverse, ici on a les slots non utilisés. Smiley

Donc, le principe est tout con. Au début on remplit le tableau des tirs dispos avec les nombres 0 à 99 (simple boucle for), puis quand on a besoin on pioche le dernier nombre de la liste, et on le retire de celle-ci. Smiley On l'ajoute alors à la liste des tirs, et on ajoute 1 au nombre de tirs :
Code
(c):
void AjouterTir(void){
 
  if(ntirs == 100) return; // On ne fait rien
  u8 n = nlibre[ntirs]; // Récupérer un slot disponible
  ntir[ntirs] = n;
 
  // Position aléatoire pour le nouveau tir...
  tirs[n].x = 8+(PA_Rand()%240);
  tirs[n].y = 8+(PA_Rand()%176);
  tirs[n].angle = PA_Rand()&511; // Direction au hasard
  tirs[n].vitesse = 2+(PA_Rand()&3); // Vitesse aléatoire de 2 à 5
  tirs[n].chgangle = 2+(PA_Rand()&7); // Vitesse de rotation aléatoire de 2 à 9/VBL
 
  tirs[n].fx = tirs[n].x<<8; tirs[n].fy = tirs[n].y<<8; // Fixed point...
  ntirs++;
}
Le reste du code est sans importance, c'est juste de la position/vitesse aléatoire. Smiley

Reste maintenant à gérer la destruction d'un tir... La boucle qui permet de faire bouger tous les tirs est presque toute simple :
Code
(c):
for(i = 0; i < ntirs; i++) i+= DeplacerTir(i);
Alors, pourquoi ce i += au lieu de juste exécuter la fonction ? En fait, le problème se pose quand on retire un tir : si on a 10 tirs, qu'on en retire un au milieu, on va avoir ntirs = 9, et donc la boucle va s'arrêter avant le dernier ! Pour pallier ce problème, j'ai choisi de faire mettre le dernier tir du tableau à la place du tir que l'on retire, et donc il faut "réexécuter" la fonction DeplacerTir pour le numéro que l'on vient de faire... Ainsi, dans le cas d'un retrait de tir, en fait la fonction renvoie -1. On passe de 5 à 4, par exemple, et comme on augmente de 1 à la boucle suivante, ça repasse à 5 et on refait le tir numéro 5. Smiley

Voici le code en question :
Code
(c):
if(PA_Distance(Stylus.X, Stylus.Y+192, tirs[n].x, tirs[n].y) < 8*8){ // Si touche, on le retire !
ntirs--;
  nlibre[ntirs] = n; // On remet dans le pool...
 
  ntir[i] = ntir[ntirs]; // On met à la place le dernier...
  PA_SetSpriteXY(0, n+1, 256, 192); // Cacher...
  PA_SetSpriteXY(1, n+1, 256, 192); // Cacher...
 
  return -1; // Refaire ce numéro
}
Rien de particulier donc, il suffit de déplacer le numéro du tir dans le tableau. Smiley


Maintenant qu'on a vu tout ça, il faut voir comment gérer l'aspect tête chercheuse... J'ai déjà montré la fonction qui initialise un tir, en gros tout est mis au pif (la position, la vitesse de déplacement, et la vitesse de rotation)... J'utilise une technique que j'ai incorporée à PAlib pour diriger le tir, mais je vais tout vous expliquer. Wink

Code
(c):
tirs[n].angle = PA_AdjustAngle(tirs[n].angle, tirs[n].chgangle, tirs[n].x, tirs[n].y, Stylus.X, Stylus.Y+192);
En fait, on donne à la fonction l'angle actuel, la vitesse de rotation, la position actuelle, et la cible, et l'angle sera ajusté de la vitesse de rotation dans un sens ou dans l'autre si besoin.
Le problème principal avec les angles, c'est qu'un angle fait intervenir des calculs relativement complexes pour le déterminer, ce qui est assez gênant dans des jeux avec plusieurs choses à tête chercheuse... Il est donc préférable d'avoir un algorithme plus optimisé, qui permettrait de ne pas avoir à recalculer l'angle systématiquement...

Code
(c):
extern inline u64 PA_Distance(s32 x1, s32 y1, s32 x2, s32 y2) {
  s64 h = x1 - x2;
  s64 v = y1 - y2;
  return(h*h + v*v);
}
 
u16 PA_AdjustAngle(u16 angle, s16 anglerot, s32 startx, s32 starty, s32 targetx, s32 targety) {
u64 distances[3];
 
startx = startx << 8; // Fixed point...
starty = starty << 8; // Fixed point...
targetx = targetx << 8; // Fixed point...
targety = targety << 8; // Fixed point...
 
     u16 tempangle = (angle - anglerot) & 511;
 
     // Calcul des distances en fonction des angles
     distances[0] = PA_Distance(startx + PA_Cos(tempangle), starty - PA_Sin(tempangle), targetx, targety);
     tempangle += anglerot;
 tempangle &= 511;
     distances[1] = PA_Distance(startx + PA_Cos(tempangle), starty - PA_Sin(tempangle), targetx, targety);
     tempangle += anglerot;
 tempangle &= 511;
     distances[2] = PA_Distance(startx + PA_Cos(tempangle), starty - PA_Sin(tempangle), targetx, targety);
 
     // On regarde si l'angle est optimal. Si ce n'est pas le cas,
     // on fait tourner toujours dans le même sens...
     if (distances[0] < distances[1])  angle -= anglerot;
     else if (distances[2] < distances[1])  angle += anglerot;
 
     return (angle&511);    
}

Premièrement, la fonction PA_Distance. Elle renvoie la distance... AU CARRÉ ! Pourquoi ? Parce qu'on ne va pas avoir besoin d'avoir la distance exacte pour faire marcher le tout, et si on peut éviter d'utiliser une fonction racine carrée un peu couteuse en temps CPU, ce n'est pas plus mal. Wink

Ensuite, la fonction PA_AjustAngle à proprement parler... Le principe sous-jacent est relativement simple. Le missile que l'on dirige va avancer dans la direction actuelle. Il peut tourner dans un sens ou dans l'autre, et on voudrait savoir dans quel sens aller... Pour déterminer ce dernier, il suffit en fait de savoir quelle direction lui permettra d'être le plus proche de la cible en cas de mouvement Cheesy  Il suffit donc de faire comme si on déplaçait le missile à une vitesse petite (pour ne pas dépasser le point cible Azn) dans 3 directions différentes :
  • Angle de base
  • Angle de base - rotation
  • Angle de base + rotation
On compare alors la distance entre ces 3 points et la cible, le point le plus proche est celui qui nous intéresse Langue C'est dans cette direction que le missile doit tourner. Cheesy

Cette méthode est particulièrement rapide et adaptée pour ajuster un angle, mais présente 2 défauts principaux dont il faut avoir conscience :
  • Si on est dans la direction parfaitement opposée à la direction optimale, que l'on tourne à droite ou à gauche, on aura la même distance, et donc ça risque de foirer. Azn
  • Si on définit un trop grand angle de rotation et qu'il faut tourner de quelques degrés à peine, ça n'ira pas non plus, car ça fonctionne sur un mode 'tout ou rien', sans intermédiaire... Pallier à ce problème est très simple, il suffit de faire exécuter 2 fois avec une vitesse de rotation 2 fois plus petite (ou 3, ou 4, etc.).

Voilà pour les infos. Cheesy Je pense que vous avez toutes les clés en main pour comprendre ce code relativement bête, mais pas si méchant. J'espère que ça pourra servir à quelqu'un. Azn

Bon Week-end à tous et à bientôt. Si vous avez des idées particulières de démos à faire, on est toujours preneurs. Wink
Journalisée

http://www.palib.info/images/mollusK.png
shell64 Hors ligne
Newbie
*
Messages: 4


Voir le profil
« Réponse #1 : 01 Juillet 2007, 04:59:57 »

très utile pour les programmeurs en herbe Azn
Journalisée
Pitt Hors ligne
Administrateur
*****
Messages: 574


Voir le profil WWW
« Réponse #2 : 02 Juillet 2007, 08:44:39 »

Ca donne un peu la gerbe mais c'est sympa Tongue
Quand au côté codage, pas grand chose de complexe mais ça peut être utile dans tout plein de choses Smiley
Journalisée
Toiletking Hors ligne
Mega Member
***
Messages: 1103


Voir le profil WWW
"Caca Molluskien et kukulcanien de 1807 à 2008"

« Réponse #3 : 25 Septembre 2007, 23:56:33 »

En quelques lignes, tu expliques un effet qui a fait galérer le développeur d'Uridium 2 sur Amiga ! Notamment pour la gestion des missiles à têtes chercheuses !

Si si, je me souviens d'une interview du programmeur dans un vieux joystick de l'époque. Azn

Op un petit screen pour les nostalgiques : Uridium 2
« Dernière édition: 26 Septembre 2007, 01:24:23 par Yus » Journalisée
Mollusk Hors ligne
PAlib Guru et
Administrateur
*****
Messages: 3094


Voir le profil WWW
Ne vous posez pas de questions, codez !

« Réponse #4 : 26 Septembre 2007, 08:01:34 »

En même temps, quand tu vois ça :
Citation de: Wikipedia
Les Amiga 1000, 500, 2000, 1500, 500+ et 600 étaient tous basés sur un microprocesseur Motorola 68000 à un peu plus de 7 Mhz.
Pas étonnant qu'ils aient eu plus de mal Wink

Et sinon, hors sujet total, mais en regardant le-dit Motorola 68000 sur Wikipedia... Documentation de fou, je trouve ça impressionnant de trouver autant d'informations sur une 'simple' encyclopédie en ligne Azn Tu as de quoi faire des émulateurs là je pense !
Journalisée

http://www.palib.info/images/mollusK.png
Toiletking Hors ligne
Mega Member
***
Messages: 1103


Voir le profil WWW
"Caca Molluskien et kukulcanien de 1807 à 2008"

« Réponse #5 : 26 Septembre 2007, 09:58:29 »

Il est ou Redbug? Wink
Journalisée
Pitt Hors ligne
Administrateur
*****
Messages: 574


Voir le profil WWW
« Réponse #6 : 26 Septembre 2007, 18:23:49 »

Qui fout gbatek sur wikipedia ? Langue
Journalisée
Toiletking Hors ligne
Mega Member
***
Messages: 1103


Voir le profil WWW
"Caca Molluskien et kukulcanien de 1807 à 2008"

« Réponse #7 : 26 Septembre 2007, 22:53:35 »

Citation
En même temps, quand tu vois ça :
Citation de: Wikipedia
Les Amiga 1000, 500, 2000, 1500, 500+ et 600 étaient tous basés sur un microprocesseur Motorola 68000 à un peu plus de 7 Mhz.
Pas étonnant qu'ils aient eu plus de mal Wink

Bah, l'amiga était super bien épaulé par paula (sans jeux de mot Azn ), non sérieux, en gestion de sprite, elle en avait pas moins dans le sac qu'une mégadrive, ou une super nintendo... et pis en tâtant du croco ds, j'ai trouvé un jeux qui exploite carrément cette technique sur amstrad cpc : gauntlet!

op la preuve en image :

http://www.cpcgamereviews.com/g/gauntlet_2.png

Si vous regardez bien, il y a des fantômes :  et bien pour le fun, lancer croco ds, avec le dsk de gauntlet (http://www.phenixinformatique.com/CPCGAMES/index.php?page=detail&num=960 sur le site de kukulcan : très très bien ce site  Azn) et allez au niveau 3 : c'est blindé de fantômes qui vous suivent, peut-être 50!

CPC POWA!  Cheesy

ps : bah il manque juste les rotsets... (ca pompe un peu ca, non?  Roll Eyes )
« Dernière édition: 27 Septembre 2007, 00:47:15 par Toiletking » Journalisée
Pages: [1]   Haut de page
Imprimer

Aller à: