![]() |
|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Ouvrir sur le forum | Recherche | Messages du jour | Marquer les forums comme lus |
Tutoriels Tutoriels dédiés au développement sur d'autres supports |
Publicité |
![]() |
|
Outils de la discussion | Modes d'affichage |
![]() |
#1 | |
Maître Chinpoko-extra-mon
|
![]() Gestion de trajectoire de tir ou de bille, les virgules fixes mises en pratique Auteur : Arcadia et YodaJr feat. Jeffres INTRODUCTION Vous avez tous un jour joué à un casse brique, ou à un jeu d'action dans lequel les ennemis vous tiraient dessus. Absorbé dans la peau d'un joueur, vous ne vous êtes jamais posé la question de savoir comment de tels mouvements (la bille du casse brique ou les tirs ennemis) étaient gérés par le programme. Placé dans la peau du programmeur, on se rend compte que tout n'est pas si simple. Pour arriver à déplacer une bille de manière uniforme, quelque soit l'angle, il faut avoir recours à quelques astuces de programmation. Nous allons vous réveler une de ces astuces. Avant tout, nous voulons remercier particulièrement 2 membres du forum. Geogeo et Jeffres, puisqu'il s'agit d'eux, ont été inspirateurs de la méthode qui sera décrite ci-dessous. Geogeo avait même d'ailleurs posté une partie d'un code similaire pour la gestion de sa bille d'Arkanoid II devellopé sur son convertisseur euro . Jeffres quand à lui, nous a éclairé sur les formules de trigonométrie et vulgarisé l'utilisation des nombres à virgule fixe. C'est parti ! THEORIE Les Bases de trigo Avant tout chose, il convient de préciser que nous aurons affaire à des petites formules de trigonométrie pour opérer un tel déplacement. Pour les familiers ou non de ces calculs, nous avons décidé de vous faire un petit résumé au travers d'un excellent cours de Jeffres. Merci maitre Jeffres pour ces explications ! Citation:
Pour ce que nous cherchons à faire, deux formules sont particulièrement intéressantes. Code:
Y = Y + depY X = X + depX avec depY = d * cos b depX = d * sin b X et Y sont les coordonnées de la bille. A chaque tour, nous leurs ajoutons une valeur de déplacement : depX et depY. Ces deux valeurs sont primordiales, ce sont elles qui en fait conditionnent la trajectoire et la vitesse relative. Ces deux valeurs de déplacement sont calculées en fonction de 3 paramètres : le cosinus et le sinus de l'angle de trajectoire et la vitesse d A partir de ce point, on peut bien sûr arreter ici et faire bouger notre projectile en laissant le GBA calculer à chaque angle le sinus et le cosinus correspondant. MAIS (car il y a un mais) je pense que c'est un fait connu que le GBA n'est vraiment pas fait pour ce genre de calculs . Manipuler les nombres a virgule flottantes (float) peut s'avérer désastreux pour les performance d'une machine. Ce qui nous interesse c'est de faire un jeu et quand on programme un jeu, on recherche toujours à épargner des temps de calculs. alors on fait quoi ? On laisse tomber ? NON, heureusement il y a une solution : Les nombres précalculés en virgule fixes. Le principe est simple dans notre cas. On va calculer les valeurs des sinus et des cosinus de tous les angles possibles (nous verrons plus loin qu'il nen faut "que" 256), seules éléments de calculs à faire intervenir des nombres à virgule. Nous irons chercher ces valeurs précalculées quand nous en auront besoin au travers d'un tableau. Ainsi, il n'y aura jamais besoin de calculer un cosinus ou un cosinus. Mais la certains se disent deja : "Oui mais cosinus x fait 0.654 alors même si on a pas besoin de faire le calcul pour le trouver, il faut quand même le multiplier a la vitesse pour obtenir un deplacement !" Pas si vite jeunes gens, je n'ai pas tout dit. En effet, tout le monde le sait, le cosinus ou le sinus d'un angle se situe entre 0 et 1. Il est donc par conséquent composé de nombres à virgule ? C'est la que le principe des virgule fixe (fixed point) fait son entrée. Nous allons convertir tous les nombres à virgule en entier.... en les multipliant par un grand nombre. Le principe des nombres à virgule fixe Alors la attention, le but de ce cours et de démontrer un exemple concret de l'utilisation de nombres à virgule fixe. il ne s'agit en aucun cas de faire un cours sur le sujet. Aussi, nous nous concentrerons dans cette démonstration à vulgariser au maximum le principe, et de la maniere suivante : Imaginez que vous ne travaillez qu'avec des nombres entier. Mais on vous donne comme variables à manipuler des nombres à decimale. Chaque fois que vous ferez un calcul, vous perdrez de la précision... Un exemple : je veux additionner deux nombres, 10.2 et 14.4. Code:
Valeur entière : 10.2 + 14.4 = 10 + 14 = 24 Valeur reelle : 10.2 + 14.4 = 24.6 Valeur arrondie du resultat : 25 On se rends compte que le résultat peut diverger suivant que nous arrondissont avant de faire les calculs ou pas. La solution pour eviter cela aurait été de faire une chose bien simple, rendre entier les nombres en les multipliant pas un facteur commun. Prenons 10, afin de faire dans la simplicité : Code:
10.2 x 10 = 102 14.4 x 10 = 144 donc : 102 + 144 = 246 Divisons par 10 le resultat : 246 : 10 = 24.6 Soit en entier arrondis 25 ! Voila un des principes des nombres à virgule fixe. il s'agit de faire des calculs avec des nombre volontairement gonflés, et d'en extraire le résultat à la fin. Certes, ce genre de système n'est pas d'une précision extraordinaire, mais pour un jeu, c'est largement suffisant et le temps de calcul épargné plus qu'interessant . Vous n'etes pas convaincu ? J'ai un autre exemple dans le même genre, et la vous vous direz : "Ouais, effectivement, les fixed point -car c'est plus classe en anglais- sont quelque choses qui marqueront ma vie de programmeur desormais" . Imaginez une boucle dans laquelle je dois additionner 20 fois une même valeur, prenons 5.4 En arrondi, 5.4 donne 5. donc 5 x 10 = 50. En virgule fixe, nous l'aurions fait passer à 54 (en considerant que nous prenons comme convention de multiplier par 10 !). Donc 54 x 10 = 540. On repasse en réel (on divise par 10 donc) et nous obtenons 54. Dans la realité, 5.4 x 10 donnent vraiment 54. (CQFD) Remplir un tableau de cosinus Nous voulons déplacer notre bille dans tous les sens possibles. Ce qui veut dire que nous devons considérer un cercle entier. Dans un cercle, il y a 360 degrés (soit 360 angles d'un degré). Cela fait beaucoup, d'autant plus que 360 dépasse la valeur d'une variable de type "short" (u8). Si nous voulons limiter un peu les angles et nous en tenir à des valeurs "informatiques", 256 angles sera un bon compromis. En gros, notre cercle aura donc non pas 360 degrés, mais 256 angles (chaque angle faisant 1.4 degrés, mais ça on s'en fout un peu ) Détaillons un peu le cercle. En trigo classique, un demi cercle represente Pi, et donc 180 degrés. Dans notre cercle, il representera 128 (128 quoi ?, je sais pas moi, 128 trucs...). 1/4 de cercle, doit 90 degrés, represente lui pi/2 et 64 trucs. Vous voyez, on s'y retrouve. Pour remplir le tableau, il suffit donc de calculer le cosinus de chaques multiple de 1.4 degrés (finalement on s'en fout pas tant que ça ), nous aurons nos 256 valeurs correspondant au cercle complet. Nous savons également que le cosinus d'un angle est égal au sinus de ce même angle plus 1/4 de tour (90°). Donc on peut combiner les 2 tableaux de valeurs cos et sin en un seul, il suffira pour obtenir les valeurs de sinus d'effectuer un décalage de 90° (64 dans notre convention) pour obtenir au final qu'un seul tableau qui contiendra les 256 valeurs des cosinus + les valeurs des sinus décalées de 64, soit 320 valeurs. PRATIQUE En application des notions expliquées dans le post précedent, nous pouvons donc établir le code suivant, qui repésente le plus gros point du programme. Pour mettre cela en pratique, nous vous fournissons la source d'une démo dans laquelle vous pourrez retrouver cette partie de code... Le Code complet Code:
#define Tbl_pcos(x) Tbl_cos[x] // Définition du tableau de cosinus #define Tbl_psin(x) Tbl_cos[x+64] // Définition du tableau des sinus // ************* // Les variables // ************* const s8 Tbl_cos [320] = {127, 126, 126, 126, ..., 12, 9, 6, 3, 0}; u8 vitesse = 2; // La vitesse de la bille int angle = 32; // l'angle de la trajectoire s16 pos_x = 128; // Position X de la bille s16 pos_y = 68; // Position X de la bille s16 pos_x_fixed = 16384; // Coordonnée de X en virgule fixe s16 pos_y_fixed = 8704; // Coordonnée de Y en virgule fixe u16 dep_x,dep_y; // La valeur des déplacement de la bille actualisées à chaque tour // ******************* // Fonction principale // ******************* void deplacement_bille() { dep_x = (Tbl_pcos (angle_tir) * (vitesse << 7)) >> 7; dep_y = (Tbl_psin (angle_tir) * (vitesse << 7)) >> 7; pos_x_fixed += dep_x; pos_y_fixed += dep_y; pos_x = pos_x_fixed >> 7; pos_y = pos_y_fixed >> 7; } Explication du code Code:
#define Tbl_pcos(x) Tbl_cos[x] // Défnition du tableau de cosinus #define Tbl_psin(x) Tbl_cos[x+64] // Définition du tableau des sinus ![]() Code:
const s8 Tbl_cos [320] = {127, 126, 126, 126, ..., 12, 9, 6, 3, 0}; A noter que ces valeurs sont déja converties en virgule fixe Code:
u8 vitesse = 2; // La vitesse de la bille int angle = 32; // l'angle de la trajectoire s16 pos_x = 128; // Position X de la bille s16 pos_y = 68; // Position X de la bille s16 pos_x_fixed = 16384; // Coordonnée de X en virgule fixe s16 pos_y_fixed = 8704; // Coordonnée de Y en virgule fixe u16 dep_x,dep_y; // La valeur des déplacement de la bille actualisées à chaque tour Bien sûr, les valeurs de pos_x et pos_y ont étés prises au pif, c'est pour l'exemple... tout le monde avait compris j'espère . Remarquez les valeurs des fixed correspondants (résultats de la multiplication par 128) Code:
dep_x = (Tbl_pcos (angle_tir) * (vitesse << 7)) >> 7; dep_y = (Tbl_psin (angle_tir) * (vitesse << 7)) >> 7; Il s'agit ni plus ni moins des formules depY = d * cos b et depX = d * sin b mises en pratique. Pour les cosinus et sinus, pas besoin de faire un dessin . << et >> sont des décalages de bit, on les utilise plutot que la division et la multiplication, car au niveau traitement, c'est plus rapide . Et comme pour notre application nous voulons pour la transformation en virgule fixe des multiplications/divisions par 128 (car nous travaillons toujours avec des multiples de puissances de 2) et que pour multiplier/diviser par 128, il suffit d'effectuer un décalage de 7 bits avec ces symboles respectif : << >> Admettez ce fait ou suivez ce ce lien pour comprendre ) : Alors donc pour revenir à l'explication du code. Chaque << 7 équivaut donc à * 128 et chaque >> 7 équivaut à / 128 vitesse est en fait le d de la formule. vitesse << 7 est la conversion de la vitesse en virgule fixe. Même si elle est toujours entière, pour les calculs avec des nombres à virgule fixe, tout doit être au même niveau, il faut donc passer le tout en virgule fixe, ce qui revient donc à multiplier (<<7) par 128 tout ce qui n'a pas encore été converti. . Alors les plus observateurs vont remarquer que l'on multiplie vitesse par 128, pour rediviser ensuite l'ensemble par 128. Cela peut sembler idiot...et ça l'est. On peut simplifier à ce niveau et cela va marcher sans problème. Seulement, j'ai laissé la formule dans sa forme "complexe" pour mettre en évidence le fait que la vitesse également peut être également en nombre à virgule fixe. Par exemple, avec le systeme actuel, nous avons des choix de vitesses fixes vitesse : 1 equivaut (1 << 7) soit 128 vitesse : 2 equivaut (2 << 7) soit 256 vitesse : 3 equivaut (3 << 7) soit 384 etc... Mais si on veut par exemple mettre une vitesse plus précise encore, genre 1.5. Avec notre système, il suffira de remplacer le vitesse << 7 par 192 . Et oui, 1.5 x 128 = 192. Pas bête hein ? Evidemment, la variable vitesse n'a plus lieu d'être, à moins qu'elle serve à déclarer directement la vitesse en nombre à virgule fixe (comme c'est la cas dans mon 1942).On peut même imaginer un tableau de vitesse si on souhaite s'y retrouver dans les différents degrés. Genre une vitesse comprise entre 1 et 2, avec une précision au 5ème pourrait ressembler à cela : Code:
const char tab_vitesse[6] = {128, 154, 180, 205, 230, 256}; Il serait donc plus aisé de gérer les vitesse avec une petite variable allant de 1 à 6. Le code ressemblerait à cela : Code:
// declarations des variables const char tab_vitesse[6] = {128, 154, 180, 205, 230, 256}; // Mon tableau de vitesse en nombre à virgule fixe vitesse = 3; // Variable de la vitesse modulaire allant de 0 à 5 // Calcul du déplacement de la bille dep_x = (Tbl_pcos (angle_tir) * tab_vitesse[vitresse]) >> 7; dep_y = (Tbl_psin (angle_tir) * tab_vitesse[vitresse]) >> 7; EXEMPLES ET SOURCES Voici un exemple que je qualifierais de parfait parce qu'il reprends les éléments expliqués ci-dessus (c'est un peu normal puisque les éléments en question étaient extraits de ce code). Le programme se contente de faire rebondir une boule qui pourrait être un tir d'avion ou une bille de casse brique. Vous pouvez jouer sur l'angle et la vitesse et constater ainsi qu'elle avance uniformement quelque soit l'angle. Mine de rien, ce type de déplacement rends un projet plus professionnel, je pense même que les professionnels déplacent leurs objets de cette manière. Les sources sont commentées, et pour ce qui est des affichages de sprites ou de fonds, et bien il faut HAM (et oui, c'est comme ça ). Bullet_Final.zip Dernière modification par Bobby Sixkilla ; 14/11/2006 à 01h26. |
|
![]() |
![]() |
Publicité |
![]() |
#2 |
Maître Chinpoko-extra-mon
|
![]() Yoda, t'as encore la démo et les sources?
|
![]() |
![]() |
![]() |
#3 | |
Modérateur saisonnier
|
![]() Citation:
![]() Les sources et la démo originale se trouvent dans le Bullet_Final.zip. J'ai retrouvé aussi une démo un peu plus parlante du principe. Un avion fixe au centre de l'écran "visait" en permanence le joueur. C'est pas grand chose mais bon, ça donne une bonne idée du système pour un débutant qui se lancerait dans la prog de ce genre de jeu. @++ PS : Merci d'avoir ressorti ce tuto Bobby ![]()
__________________
. Mes homebrews GBA : 1942 - Ghost'n Goblins ![]() ![]() Mes autres passions, les flippers : Restauration d'un High Speed (Williams - 1985) Dernière modification par Arcadia ; 14/11/2006 à 01h09. |
|
![]() |
![]() |
![]() |
#4 | |
Modérateur
Date d'inscription: 10/11/2005
Localisation: Mad Monster Mansion
Messages: 3 755
|
![]() Citation:
![]()
__________________
News GBA/DS: Portable DEV Colorer un jeu GB? Débutant - Avancé - Expert Projets : Banjo Advance - The Last Quest - Klungo's Brain School Site des jeux : BanjoKazooie.Free.Fr Web Site ![]() "La Vie n'est pas aussi simple qu'un Jeu Vidéo..." Dernière modification par omg ; 14/11/2006 à 01h12. |
|
![]() |
![]() |
![]() |
#5 | |||
Maître Chinpoko-extra-mon
|
![]() Citation:
![]() ![]() Citation:
![]() Citation:
![]() |
|||
![]() |
![]() |
![]() |
#6 |
Administrateur
Date d'inscription: 09/11/2005
Messages: 3 750
|
![]() Désolé bobby, j'avais pas vu ta question, fallait relancer
![]() Ce tuto a demandé beaucoup de temps (hein rystan ![]() ![]() |
![]() |
![]() |
![]() |
Liens sociaux |
Publicité |
Utilisateurs regardant la discussion actuelle : 1 (0 membre(s) et 1 invité(s)) | |
Outils de la discussion | |
Modes d'affichage | |
|
|