Jour 5 : Backgrounds ← Précédent
Ce cours d’instruction contiendra les informations sur la chose suivante :
Les nombres aléatoires sont souvent indispensables. Ils peuvent être utilisés pour la loterie, l’IA, et l’expérience pour les niveaux supérieurs. Je sais ce que vous pensez, “comment obtenir des nombres aléatoires”.
Par chance, il y a une méthode appelée PA_Rand(). Vous pouvez l’utiliser comme ceci:
u32 num = PA_Rand();
Celà génère un nombre au hasard, on est d’accord, mais avec un gros point faible, car il retourne un nombre ENORME, le rendant définitivement inutile pour gérer les level-up ( à moins que votre personnage aspire à devenir impitoyablement fort).
Dans la version 0.73a, ces fonctions ont été créées.
Disons que vous voulez un nombre entre 0 et 4 inclus.
Utilisez PA_RandMax.
u32 max = 4; u32 nb = PA_RandMax(max);
Pour obtenir un nombre entre 1 et 4 inclus...
u32 min = 1; u32 max = 4; u32 nb = PA_RandMinMax(min,max);
Ou/Soit
PA_RandMinMax(1,99);
Une dernière chose à ajouter : si vous essayez ces fonctions dans un programme, vous verrez qu’elles renverront toujours la même séquence de nombres... Pourquoi ? Parce qu’il n’y a pas de parfait générateur de nombres aléatoires sur ordinateur, seulement des méthodes mathématiques pour les simuler... Pour minimiser cet effet, la fonction PA_InitRand() est disponible. Placez-la quelque part avant la boucle principale du jeu car elle n’a besoin d’être exécutée qu’une fois pour fonctionner... Exécutée, elle initialisera les fonctions aléatoires basées sur l’heure et la date actuelle. Ainsi, pour obtenir exactement la même séquence de nombres, vous devriez commencer le jeu exactement à la même date et à la même heure... ce qui ne se produira probablement pas souvent !
Ce tuto montrera comment contrôler les positions, etc... en utilisant des points fixes (sans utiliser les floats), ce qui accélèrera grandement votre jeu...
La DS (comme la GBA et d’autres plateformes) est très lente pour traiter les variables de type float... Cependant, il semble nécessaire d’avoir des nombres avec une valeur décimale, comme 0.5, au cas où vous voudriez déplacer un sprite plus lentement qu’un pixel par frame, par exemple... Voyons comment nous pouvons faire ceci...
En utilisant une variable de 32 bits (s32), nous “réservons” les 8 derniers bits pour un équivalent de la partie décimale... Ainsi, les 8 premiers bits seront pour la partie décimale, et les 24 autres bits pour la partie entière...
Les 8 bits ont une valeur de 0 à 255, ça signifie que vous avez une précision de 1/256e, ce qui est largement suffisant dans la plupart des cas... 24 bits donnera quand même la possibilité de manoeuvrer avec de grands nombres...
En fin de compte, avec un point fixe, une valeur de 256 signifie 1, 512 signifie 2, 1024 signifie 4, etc... Donc, pour avoir 0.5, vous mettrez 128 ! Vous pouvez utiliser n’importe quelle valeur intermediaire, comme 196, qui correspondraient à 3/4, etc... Vous voyez, ce n’est pas trop dur !
Maintenant, voyons comment l’utiliser
Je viens d’ajouter un exemple dans la dernière version de PAlib, though it might not be out yet
Voici le code, j’expliquerai sa majeure partie... Il montre comment vous pouvez utiliser des points fixes pour déplacer un sprite de moins d’un pixel chaque frame...
s32 speed1 = 256; // first speed...0 PA_OutputText(0, 18, 2, " 1 pixel/frame"); s32 speed2 = 128; PA_OutputText(0, 18, 10, "0.5 pixel/frame"); s32 speed3 = 64; PA_OutputText(0, 18, 18, "0.25 pixel/frame"); PA_LoadSpritePal(0, 0, (void*)sprite0_Pal); // Palette name PA_CreateSprite(0, 0, (void*)vaisseau_Sprite, OBJ_SIZE_32X32, 1, 0, 0, 0); PA_CreateSprite(0, 1, (void*)vaisseau_Sprite, OBJ_SIZE_32X32, 1, 0, 0, 64); PA_CreateSprite(0, 2, (void*)vaisseau_Sprite, OBJ_SIZE_32X32, 1, 0, 0, 128); // all sprites stick to the left s32 spritex1 = 0; s32 spritex2 = 0; s32 spritex3 = 0; while(1) // Boucle infinie { // Déplace tous les sprites avec leur vitesse correspondante spritex1 += speed1; spritex2 += speed2; spritex3 += speed3; // Positionne tous les sprites, >>8 pour retourner à la position normale PA_SetSpriteX(0, 0, spritex1>>8); PA_SetSpriteX(0, 1, spritex2>>8); PA_SetSpriteX(0, 2, spritex3>>8); PA_WaitForVBL(); }
Je commenterai seulement les parties importantes de ce code, Et je ne répeterai pas 3 fois la même chose...
s32 speed1 = 256; est la première étape dans l’utilisation des points fixes. Comme je l’ai précisé plus tôt, nos valeurs de points fixes seront des valeurs normales multipliées par 256... Ainsi, pour obtenir une vitesse de 1 pixel par frame, vous devez juste mettre une vitesse de 1*256 = 256 ! C’est simple...s32 speed2 = 128; Ok, maintenant, Essayons de deviner la vitesse, en pixels par frame, ce qui devrait être... Trop tard ! 128 = 256/2, donc, comme 256 est 1 pixel par frame, 128 devrait être 1/2 pixel par frame, donc 0.5 ! s32 speed3 = 64; Pareil ici, combien ? 1/4, ou 0.25 pixel/frame...D’ailleurs, pourquoi avons-nous employé les variables s32 ? s pour signé, parce que nous pourrions avoir des valeurs négatives. Et 32, parce que nous avons besoin de 32 bits : avec comme 8 bits, nous aurions seulement des valeurs jusqu’à 127, qui n’est pas suffisant pour se déplacer d’1 pixel par frame! Avec 32 bits, nous aurons l’abondance des valeurs disponibles. C’était facile ! Maintenant nous devons voir comment changer les sprites du même rang dans le point fixe, et le code correct est : spritex1 += speed1 ; Ok, celui est juste comme tout code que nous avons vu avant, il n’a rien à faire avec le point fixe nous ajoutons juste la vitesse à la position des sprite.
Maintenant, le secret réside dans la façon dont nous plaçons le sprite sur l’écran. Pour faire ainsi, nous emploierons un PA_SetSpriteX simple, parce que nous ne voulons pas toucher à Y pour l’instant : PA_SetSpriteX (0, 0, spritex1 » 8) ; Oho ! ! quels sont ces » 8 ? Il signifie se divisent par 256. Sauf qu’une division est vraiment lente, alors que les » 8 est vraiment rapide. Vous n’avez pas besoin de savoir beaucoup plus, réellement, juste que nous avons divisé la position dans des maths de point fixe par 256 ! Et cela fonctionne parfaitement, comme position de x = 256 moyens X = 1, etc….
Ainsi à la fin :Pour aller de normal (0.5, 1, etc...) au point fixe (128, 256, etc...), multiplier juste par 256 et pour aller du point fixe à la normale (quand vous devez montrer sur l’écran), diviser juste par 256 (ou » 8, car c’est pareil). Et c’est tout pour le premier tuto !
Ceci exige que vous ayez lu le tuto de maths sur les points fixes...C’est un code très simple, qui peut être employé pour n’importe quel jeu de plate-forme... Vous devez comprendre un peu de math avant de commencer ce tuto, alors relisez,si vous en avez besoin, le tuto au cas ou vous n’auriez pas tout compris...
Le code de pesanteur est bien, parce qu’il peut être employé dans des tonnes de jeux, et particulièrement dans des jeux de plate-forme, tels que le mario, etc…. Il se fonde sur une base simple : votre sprite/mario/player/whatever a une vitesse verticale, et la pesanteur est une accélération. Après ce moyens 3 choses :
Puisque l’accélération et la vitesse peuvent avoir beaucoup de valeurs, et que nous ne voulons pas être limités, les valeurs utilisées par volonté sont des points fixe… C’est pourquoi vous avez peu de décalages(«8 et »8 partout dans le code). Cet exemple est juste une petite navette montant, et tombant en arrière vers le bas. Vous pouvez changer la pesanteur et force de départ (équivalente à sauter) pour voir la réaction de la navette ...
#define FLOOR (160<<8) // Floor y level int main(void){ PA_Init(); //PAlib inits PA_InitVBL(); PA_InitText(1, 0); PA_OutputText(1, 2, 4, "Press A to take off !"); PA_OutputText(1, 2, 5, "Gravity change : Left/Right"); PA_OutputText(1, 2, 7, "Takeoff Speed change : Up/Down"); PA_LoadSpritePal(0, 0, (void*)sprite0_Pal) PA_CreateSprite(0, 0, (void*)vaisseau_Sprite, OBJ_SIZE_32X32, 1, 0, 50, 50); s32 gravity = 32; // change the gravity and check the result :) s32 velocity_y = 0; s32 spritey = FLOOR; // at the bottom s32 takeoffspeed = 1000; // Takeoff speed... while(1) // Infinite loops { takeoffspeed += (Pad.Held.Up - Pad.Held.Down)*8; // Change takeoff speed... gravity += (Pad.Held.Right - Pad.Held.Left)*2; // Change gravity speed... PA_OutputText(1, 4, 8, "Takeoff speed : %d ", takeoffspeed); PA_OutputText(1, 4, 6, "Gravity : %d ", gravity); if((spritey <= FLOOR) && Pad.Newpress.A) { // You can jump if not in the air... velocity_y = -takeoffspeed; // Change the base speed to see the result... } // Moves all the time... velocity_y += gravity; // Gravity... spritey += velocity_y; // Speed... if(spritey >= FLOOR) // Gets to the floor ! { velocity_y = 0; spritey = FLOOR; } PA_OutputText(1, 0, 0, "Y : %d \nVY : %d ", spritey, velocity_y); if (spritey>>8 > -32) PA_SetSpriteY(0, 0, spritey>>8); // show if on screen else PA_SetSpriteY(0, 0, 192); PA_WaitForVBL(); } return 0; }
Voici venir l’explication :
Vient après la quantité de base pour changer la pesanteur et vitesse de décollage, elle n’est rien beaucoup davantage que nous avons déjà vu :
Maintenant nous entrons dans la vraie partie importante du code :
if((spritey <= FLOOR) && Pad.Newpress.A) { // You can jump if not in the air... velocity_y = -takeoffspeed; // Change the base speed to see the result... }
Ce n’est pas trop dur, Vous ne pouvez sauter que si vous appuyez sur A et que vous êtes au niveau du sol (ou au-dessous de lui)! Pour décoller, ajouter juste la vitesse de décollage à la vitesse verticale... Pourquoi y a-t-il le bouton A là ? Puisque la vitesse de décollage a lieu à 1000. Donc, si vous êtes en dessous de 1000, vous serez appuyé dessus. Mais plus vous allez haut, plus elle est inférieur, ainsi ça doit être une valeur négative... Vient après le code pour ajuster la vitesse selon la pesanteur, et la position… Tout est dans le point fixe, naturellement :
velocity_y += gravity; // Gravity... spritey += velocity_y; // Speed...
* D’abord nous ajustons la vitesse en y ajoutant la pesanteur (la pesanteur devrait être positive, parce qu’elle réduit le poids du vaisseau… Ainsi c’est le signe opposé pour le décollage...) * Alors nous ajustons la position, selon la nouvelle vitesse... Si vous augmentez sa valeur, que se produira-il ? D’abord, au moment du décollage, vous avez une vitesse vraiment élevée (négative donc) puis à chaque image, celle-ci diminue, parce que la pesanteur vient s’y ajouter. À un moment, elle tombe à nul et change alors de signe, devenant positive... Ainsi le sprite retourne vers le bas, de plus en plus rapidement. Juste comme dans la réalité ! : - P C’en est presque assez pour le faire fonctionner, mais vous devez pas oublier d’ajouter ceci :
if(spritey >= FLOOR) // Gets to the floor ! { velocity_y = 0; spritey = FLOOR; }
Sinon votre bateau tombera sous le niveau du sol ! Avec ca vous lui dites que s’il passe en-dessous, sa vitesse passe à nule et il est automatiquement remis au niveau du sol.
Tout ce qu’il vous reste à faire est d’afficher le sprite à l’écran : * PA_SetSpriteY (0, 0, spritey»8) ; , avec »8, parce que nous devons reconvertir le point fixe en entier pour qu’il retrouve sa position normale !
C’était c’etait dur, hein ? Maintenant, compiler, et essai-le(même dans les dualis) avec différentes valeurs de vitesse et de pesanteur, pour voir comment il affecte le vaisseau.
Cette section sera traitée dans 3 parts :
Les angles utilisés dans PAlib ne sont pas des angles normaux, mais des angles adaptés à l’utilisation de la DS. Ils s’étendent de 0 à 511, et sont dans le sens contraire des aiguilles d’une montre. Ainsi l’angle 0 serait 3 heures, 128 serait 12 heures, 256 serait 9, etc…. Je pense que j’ai déjà expliqué ceci dans le tuto sur la rotation de sprite, regardez cela en images...
Nous verrons une utilisation pratique des angles dans le code de GetAngle ! (Maths/GetAngle/) c’est un exemple très simple, qui peut être utile plus tard si vous employez l’écran tactile… Je signalerai juste le code important :
PA_OutputText(1, 5, 10, "Angle : %d ", PA_GetAngle(128, 96, Stylus.X, Stylus.Y));
Lol, je jure que c’est la seule ligne importante… PA_GetAngle (x1, y1, x2, y2)est une fonction simple qui renvoie l’angle constitué par un trait horizontal et 2 points donnés… La majeure partie du temps, vous voudrez employer Stylus.X et Stylus.Y en tant qu’un des points… Dans cet exemple, vous pouvez voir que nous prenons l’angle entre le centre de l’écran et la position du stylet. Ceci a pu être employé dans un jeu pour obtenir un angle et un point donné sur l’écran, à l’aide du stylet pour viser !
Comment employer PA_Sin et PA_Cos correctement…Vous devez avoir lu le tuto sur les points fixes afin de l’employer parfaitement...
Ces fonctions ne renvoient pas une valeur entre -1 et 1, mais entre -256 et 256. Pourquoi ? Puisque c’est meilleur pour la quantité de point fixe, et celui-ci est celui que vous devriez le plus employer...
Comme vous pouvez voir sur ce schéma, pour un angle donné, Cos est la coordonnée horizontale du point sur le cercle, et Sin la coordonnée verticale… Ainsi en utilisant PA_Cos et PA_Sin, il devrait être possible de déplacer un sprite selon un angle donné, au lieu de faire en ligne haut/bas et gauche/droit ! C’est comme cela que nous pourrons déplacer un sprite selon une trajectoire donnée !
Car les fonctions de PA_Cos et de PA_Sin emploient des maths de point fixe, vous pouvez voir que la valeur plus élevée possible est 256, et le plus bas -256...
Je vous conseille d’aller voir comment employer ceci dans un exemple ship-moving concret !
Cet exemple qui est pris de PAlibExamples/maths/AngleSinCos, sera dans la prochaine mise à jour de PAlib… Que fait-il ? Il montre un vaisseau au centre de l’écran, le tourne vers la gauche quand vous appuier à gauche ! Il montre comment il peut être simple d’employer des angles dans un jeu… Tous cela grâce à PA_Cos employant, PA_Sin, et points fixes.
Voici le code :
PA_InitText(1,0); // On the top screen PA_LoadSpritePal(0, 0, (void*)sprite0_Pal); PA_CreateSprite(0, 0,(void*)vaisseau_Sprite, OBJ_SIZE_32X32,1, 0, 128-16, 96-16); // Create the ship in the center... PA_SetSpriteRotEnable(0,0,0);// Enable rotations and use Rotset 0... s32 x = (128-16) << 8; // ship x position in 8bit fixed point s32 y = (96-16) << 8; // Y u16 angle = 0; // direction in which to move ! while(1) { angle += Pad.Held.Left - Pad.Held.Right; PA_SetRotsetNoZoom(0, 0, angle); // Turn the ship in the correct direction if (Pad.Held.Up){ // Move forward x += PA_Cos(angle); y -= PA_Sin(angle); } if (Pad.Held.Down){ // Move backwards x += -PA_Cos(angle); y -= -PA_Sin(angle); } PA_OutputText(1, 5, 10, "Angle : %d ", angle); PA_SetSpriteXY(0, 0, x>>8, y>>8); // Sprite position converted to normal... PA_WaitForVBL(); }
Voici venir les commentaires… D’abord nous créons un sprite et autorisons des rotations sur lui... Si c’est plus très clair, relire svp le tuto sur les rotations de sprite.
Alors nous initialisons les variables que nous aurons besoin. Car c’est un exemple de base, tout ce que nous avons besoin est x et y pour la position, et angle pour l’angle... Nous devrions employé une structure : ship.x, etc… mais c’est juste un exemple de base
s32 x = (128-16) << 8; // ship x position in 8bit fixed point s32 y = (96-16) << 8; // Y u16 angle = 0; // direction in which to move !
Comme vous pouvez le voir, l’angle n’a rien de spécial, mais x et y emploient le point fixe (par conséquent les « 8).
La rotation du vaisseau est tout à fait simple…
angle += Pad.Held.Left - Pad.Held.Right; PA_SetRotsetNoZoom(0, 0, angle); // Turn the ship in the correct direction
Je pense que la partie la plus importante du code est la suivante :
if (Pad.Held.Up){ // Move forward x += PA_Cos(angle); y -= PA_Sin(angle); }
C’est ce qui fait avancer le vaisseau vers l’avant quand vous appuiez sur la touche haut...
Le reste du code est vraiment facile. D’abord, c’est la même chose, mais pour se déplacer vers l’arrière... Enfin, on dit de déplacer le sprite à la position désirée.
Quoique ce code soit vraiment facile et ne fait pas beaucoup, vous pourriez l’employer et le modifier pour faire plus de chose :
J’ai fini vers le haut d’ajouter un exemple de stylet dans la section de démos : Follwing the Stylus
Il y a quelques fonctions novatrices qui sont vraiment facile à employer avec PAlib…
Nous verrons d’abord 2 genres de collisions de sprite : sprites ronds et sprites carré/rectangle...
Si vous considérez des sprites en tant que rondes, il devient vraiment facile de les faire se heurter correctement… Regarder ce petit schéma, avec 2 cercles là-dessus :
Comme vous pouvez voir, ces cercles se heurtent. À quelle distance sont-ils ? Leurs centres sont à exactement r1 + r2… Ainsi si vous calculez la distance entre les centres des cercles, ils se heurtent si la distance est inférieure à r1 + r2.
maintenant nous verrons ceci dans un exemple concret…
Ce code peut être trouvé dans les maths/CollisionRound/, et est vraiment simple.
Il est basé sur 2 sprites ronds, identiques, quoiqu’ils pourraient avoir différentes tailles… Un des cercles (le centre un) est fixe, l’autre peut être déplacé en utilisant le stylet. Et s’ils se heurtent, il le montre sur l’écran supérieur :
// This'll be the movable sprite... PA_CreateSprite(0, 0,(void*)circle_Sprite, OBJ_SIZE_32X32,1, 0, 0, 0); s32 x = 16; s32 y = 16; // Sprite's center position // This will be the fixed circle PA_CreateSprite(0, 1,(void*)circle_Sprite, OBJ_SIZE_32X32,1, 0, 128-16, 96-16); while(1) { if (PA_MoveSprite(0)){ x = PA_MovedSprite.X; y = PA_MovedSprite.Y; } // Collision ? if (PA_Distance(x, y, 128, 96) < 32*32) PA_OutputText(1, 2, 10, "Collision !!"); else PA_OutputText(1, 2, 10, " "); PA_WaitForVBL(); }
Je ne commenterai pas tout le code, comme toujours… La première partie consiste juste à créer 2 sprites, numéro 0 et 1...
if (PA_MoveSprite(0)){ x = PA_MovedSprite.X; y = PA_MovedSprite.Y; }
Comme nous l’avons fait avant, si le sprite 0 est déplacé, nous connaitrons les coordonnées de X et de Y. Ceci sera employé pour calculer la distance entre les 2 sprites...
if (PA_Distance(x, y, 128, 96) < 32*32) PA_OutputText(1, 2, 10, "Collision !!");
* PA_Distance(x1, y1, x2, y2) est une fonction simple qui renvoie le carré d’une distance... Pourquoi pas la vraie distance ? Puisque cela exigerait l’utilisation d’une racine carrée, alors que la place est assez pour estimer la distance, car nous n’avons pas besoin d’une distance précise, mais juste la comparons... * Pourquoi le comparez à 32*32 ? 32 est la limite de collision pour les 2 cercles : ils ont tous les deux un rayon de 16 Pixel, donc 16 + 16 = 32... Et puisque nous avons besoin d’une distance carrée, nous multiplions 32 par lui-meme... Tout cela est plus rapide qu’en prenant la racine carrée de la distance... * Si la distance est en effet plus petite que la limite, les 2 sprites se heurtent, et nous la montrerons sur l’écran supérieur... Et c’est tout ! Il était vraiment simple.
Vous pourriez penser que la collision de cercle n’est pas trop applicable dans un jeu. En fait, c’est quelque chose que j’emploie vraiment souvent… Le premier exemple serait la fin de support d’un shoot en hausse… Votre vaisseau pourrait être assimilé à un cercle, aussi bien que les balles, vérifiant ainsi si vous êtes frappés, ça serait juste comme pour vérifier une collision entre 2 cercles… rapidement et efficacement ! Et je jure que la majeure partie du temps le jeu va tellement rapidement que vous ne verrez pas que ce n’est pas une collision Pixel-parfaite
Les collisions rectangulaires ne fonctionnent pas tout à fait de la même manière que ceux de la circulaire , ainsi j’ai fait 2 petits schémas pour illustrer comment au chaque ceux-ci :
Comme vous pouvez voir sur ce premier schéma, nous avons 2 rectangles différents, chacun d’eux est défini par sa largeur et sa taille (w1 et h1, w2 et h2). Nous verrons avec le prochain comment ceci peut être employé…
Sur celui-ci, j’ai montré les 4 côtés par lesquels le rectangle rouge peut entrer en collision avec la bleue... Si vous considérez le centre du rectangle rouge quand il se heurte, vous noterez qu’il décrit un rectangle parfait autour du rectangle bleu ! Tout ce que nous devons savoir est la largeur et la longueur de ce nouveau rectangle... Regarder plus étroitement... Oui, le nouveau rectangle a une largeur de w1 + w2 et une longueur de h1 + h2, et est porté au centre du rectangle bleu ! ! !
Ainsi si nous ajoutons x1, y1 et x2, y2 comme rectangles respectifs , nous avons l’équation suivante pour des collisions : (x2 >= x1 - (w1 + w2) /2) et (x2 ⇐ x1 + (w1 + w2) /2) pour la largeur et (y2 >= y1 - (h1 + h2) /2) et (y2 ⇐ y1 + (h1 + h2) /2) pour la longueur ! Et ceci devrait être assez pour calculer s’il y a une collision...
Maintenant que nous avons vu la théorie, le code réel devrait être assez facile !
// This'll be the movable sprite... PA_CreateSprite(0, 0,(void*)rect_Sprite, OBJ_SIZE_32X16,1, 0, 0, 0); s32 x1 = 16; s32 y1 = 8; // Sprite's center position s8 w1 = 32; s8 h1 = 16; // width and height... // This will be the fixed circle PA_CreateSprite(0, 1,(void*)rect_Sprite, OBJ_SIZE_16X32,1, 0, 128-8, 96-16); s32 x2 = 128; s32 y2 = 96; // Sprite's center position s8 w2 = 16; s8 h2 = 32; // width and height... while(1) { if (PA_MoveSprite(0)){ x1 = PA_MovedSprite.X; y1 = PA_MovedSprite.Y; } // Collision ? if ((x2 >= x1 - (w1 + w2)/2) && (x2 <= x1 + (w1 + w2)/2) && (y2 >= y1 - (h1 + h2)/2) && (y2 <= y1 + (h1 + h2)/2)) PA_OutputText(1, 2, 10, "Collision !!"); else PA_OutputText(1, 2, 10, " "); PA_WaitForVBL(); }
D’abord, nous créons un rectangle de 32×16, et mettons dans une variable que la position, la largeur, et la longueur...
PA_CreateSprite(0, 0,(void*)rect_Sprite, OBJ_SIZE_32X16,1, 0, 0, 0); s32 x1 = 16; s32 y1 = 8; // Sprite's center position s8 w1 = 32; s8 h1 = 16; // width and height...
Puis, nous ferons la même chose, mais avec un sprite 16×32... Le code est le même que le code précédent, ainsi je ne le détaillerai pas encore…
Et en dernier, le code réel de collision, qui est juste une copie/coller de ce que j’ai écrit en haut : si 1) PA_OutputText (1, 2, 10, « collision ! ! ») ;
Et c’est tout !
Les collisions rectangulaires peuvent être employées n’importe où les collisions circulaires ne savent pas ëtre utilisé
notez que l’equation...
(x2 >= x1 - (w1 + w2)/2) && (x2 ⇐ x1 + (w1 + w2)/2) && (y2 >= y1 - (h1 + h2)/2) && (y2 ⇐ y1 + (h1 + h2)/2)
peut etre changee en...
(x2 >= x1 - (w1 + w2)»1) && (x2 ⇐ x1 + (w1 + w2)»1) && (y2 >= y1 - (h1 + h2)»1) && (y2 ⇐ y1 + (h1 + h2)»1)
normalement, ceci s’executera un peu plus vite car on a evite des divisions.
L’opération modulo % utilise 2 nombres a et b (ou autre, c’est un exemple) dans l’opération a%b, et renvoie le reste de la division entière a/b. Exemples: 2%4 renvoie 2 (car 2=0*4+2), 4%4 renvoie 0 (car 4=1*4+0) et 5%4 renvoie 1 (5=1*4+1). Cette opération utilise une division et est donc plutôt lente.
Dans certaines conditions, Vous pouvez utiliser l’opération binaire & (ET), qui est infiniment plus rapide, à la place de %. Il faut pour cela que b soit une puissance de 2__ (Vous ne pourrez donc remplacer que x%2, x%4, x%8, x%16, x%32, etc...) La seule chose qui diffère lors de l’utilisation de & par rapport à %, c’est que vous devrez écrire ‘b-1’au lieu de ‘b’ ,i.e.
x%2; -> x&1; x%8; -> x&7; x%16; -> x&15;
Les décalages de bits sont une manière efficace pour multiplier et diviser par des puissances de 2.
Pour diviser par un nombre puissance de deux il faut décaler les bits vers la droite...
8»1 = 4 (8/2^1 = 8/2 = 4)
8»3 = 1 (8/2^3 = 8/8 = 1)
Pour multiplier par un nombre puissance de deux il faut décaler les bits vers la gauche...
2«1 = 4 (2*2^1 = 2*2 = 4)
2«3 = 16 (2*2^3 = 2*8 = 16)
Pour être franc c’est quelque chose de très bizarre, et gardez en tête que je n’écris que ce que je comprends actuellement sur ce sujet, donc ça pourrait être bien si quelqu’un ajoutait ce que j’ai pu oublié.
OK ! Bon, pour ceux qui ne savent pas ce qu’est un pointeur, c’est en gros une variable qui, au lieu de contenir une valeur normale, contient une adresse mémoire. Ils sont définis comme cela :
s16 * intptr; // pointeur vers une variable signée 16 bits char * chrptr; // pointeur vers une variable caractère bool * bptr; // pointeur vers une variable booléenne
Notez que ces pointeurs ne “pointent” sur rien pour l’instant. Pour faire pointer un pointeur vers une variable, il doit être d’abord initialisé avec l’adresse de cette variable. Pour cela il faut utiliser l’opérateur de référencement “&” :
s16 intvalue; char charvalue; bool boolvalue; intptr = &intvalue; // initialisation du pointeur avec l'adresse de la variable "intvalue" chrptr = &charvalue; bptr = &boolvalue;
Maintenant ces pointeurs peuvent être “déréférencés” pour accéder aux valeurs des variables sur lesquelles ils pointeut. Ceci est fait en plaçant une astérisque devant le nom du pointeur, comme cela :
*intptr = 16; // la variable "intvalue" contient maintenant 16 *chrptr = 'g'; *bptr = true;
Si vous afficher le contenu de ces variables pointeurs, cela affichera des adresses mémoire. Ils doivent donc être déréférencés pour utiliser les valeurs sur lesquelles ils pointent :
//Ce n'est pas un code qui tourne sur DS, c'est du C++ standard cout << intptr << endl; // affiche l'adresse mémoire qui ressemble à 0x28482873 etc etc cout << *intptr << endl; // affiche la valeur sur laquelle intptr pointe, qui est donc 16
L’utilisation principale des pointeurs est pour pointer sur une variable qui a été déclarée ailleurs dans le programme, ce qui évite d’en faire une copie dans plusieurs cas :
char * pMychar; // Create character pointer char Mychar = 'a'; // Create character variable pMychar = &Mychar; // Point character pointer to character variable *pMychar = 'b' // This will alter the value of Mychar
Les deux premières lignes définissent deux variables : un pointeur sur caractère et un caractère. La troisième ligne fait pointer le pointeur pMychar sur la variable Mychar.
La dernière ligne déréférence le pointeur et assigne une nouvelle valeur à la variable sur laquelle il pointe. Dans ce cas on a changé la valeur de Mychar depuis le pointeur, plutôt que de le faire directement par la variable Mychar.
Les pointeurs sont très utiles pour les fonctions (on ne parle pas encore de pointeurs de fonctions ; ils viendront plus tard).
// Définit une fonction pour "enrouler" une valeur (la contraindre entre 0 et 299) void wrapValue(s16 * pVal) { // wrap value if it is above 300, // so 300 becomes 0, 301 becomes 1... if(*pVal >= 300) *pVal -= 300; } // Voici comment utiliser cette fonction s16 myVal = 0; myVal = 200; wrapValue(&myVal); // Dans ce cas la variable n'est pas enroulée myVal = 340; wrapValue(&myVal); // Cette fois la variable est enroulée
Je vais détailler ceci pour être sûr que vous le comprenez. Tout d’abord examinons la fonction :
// Définit une fonction pour "enrouler" une valeur (la contraindre entre 0 et 299) void wrapValue(s16 * pVal) { // wrap value if it is above 300, // so 300 becomes 0, 301 becomes 1... if(*pVal >= 300) *pVal -= 300; }
Comme vous le voyez cette fonction prend en paramètre un pointeur vers une variable s16. Elle vérifie si la valeur pointée par ce pointeur est supérieure à 300, si oui elle retranche 300 à cette valeur. Oui cela ne fonctionne que si la variable pointée est inférieure à 600, mais je garde les choses simples pour se concentrer sur les pointeurs.
Donc les pointeurs nous permettent de modifier les valeurs de variables qui sont en dehors de nos fonctions. C’est plus efficace que de prendre en paramètre une valeur s16 normale, car cela impose au programme de faire une copie de la variable pour la passer à la fonction, puis d’utiliser la valeur de retour de la fonction pour modifier la variable d’origine.
Ok, passage suivant :
// Voici comment utiliser cette fonction s16 myVal = 0; myVal = 200; wrapValue(&myVal); // Dans ce cas la variable n'est pas enroulée myVal = 340; wrapValue(&myVal); // Cette fois la variable est enroulée
Donc ici j’ai testé la fonction sur deux valeurs. Le premier appel ne fait rien car la variable est plus petite que 300. Le second appel réduit la variable à 40.
La seule partie compliquée de ce code est wrapValue(&myVal). Si vous vous rappelez dans la déclaration de la fonction, elle accepte une adresse mémoire d’une variable s16. Nous ne pouvons donc pas donner la variable myVal comme argument (cela donnerait la valeur s16 contenue par cette variable), nous devons donner son adresse mémoire. Si vous vous rappelez nous avions fait cela en plaçant l’opérateur de référencement & devant un nom de variable. Donc &myVal va donner l’adresse mémoire de myVal comme argument à cette fonction.
Voilà c’est tout ce qu’il vous faut savoir aujourd’hui sur les pointeurs. Si vous voulez en savoir plus essayez les liens suivants : this site for more advanced pointers info, Site en Français sur les pointeurs.
Maintenant parlons des pointeurs de fonctions. Ce sont aussi des variables, qui cette fois vont pointer sur des fonctions plutôt que sur des variables classiques. Voici un exemple :
//Ce n'est pas un code qui tourne sur DS, c'est du C++ standard void pointlessFunction(char myChar) { // Fonction extrêmement simple ici... cout << "L'argument passé est : " << myChar << endl; } // Voici la partie intéressante : la déclaration d'un pointeur sur fonction void (*pt2Func)(char) = NULL; // Initialisé à NULL // On assigne l'adresse d'une fonction à ce pointeur pt2Func = &pointlessFunction; // Maintenant on peut appeler cette fonction à travers le pointeur : pt2Func('A');
Oui je sais c’est vraiment bizarre... Je vais décrire cette exemple. Je ne vais pas décrire pointlessFunction() car c’est une simple fonction. Donc pour définir un pointeur sur fonction :
// Voici la partie intéressante : la déclaration d'un pointeur sur fonction void (*pt2Func)(char) = NULL; // Initialisé à NULL
Les pointeurs sur fonction sont définis de la façon suivante : returnType (*pointerName)(Arg1Type,Arg2Type,...)
Donc si ma déclaration de fonction est :
int myFunction(char myChar, int myInt, bool myBool, char anotherChar)
Alors pour déclarer un pointeur sur ce type de fonction je ferais ainsi :
int (*pt2MyFunc)(char, int, bool, char);
Pas trop dur une fois qu’on a appris la formule.
Continuons :
// On assigne l'adresse d'une fonction à ce pointeur
pt2Func = &pointlessFunction;
Donc ici nous passons l’adresse mémoire de la fonction (en utilisant comme précédémment l’opérateur & suivit ici par le nom de la fonction) au pointeur.
// Maintenant on peut appeler cette fonction à travers le pointeur : pt2Func('A');
Maintenant nous pouvons utiliser le pointeur pour appeler la fonction ! Les pointeurs sur fonction peuvent nous aider à éviter les Ifs, Elses, Switches, etc. Par exemple au lieu d’avoir :
Switch(variable) { case somecase: function1(val1, val2); break; case somecase2: function2(val1, val2); break; case somecase3: function3(val1, val2); break; //etc... }
Nous pouvons simplement créer un pointeur sur fonction et le laisser “spécifier” quelle fonction nous allons utiliser:
void (*pFunc)(int v1, int v2); Switch(variable) { case somecase: pFunc = &function1; break; case somecase2: pFunc = &function2; break; case somecase3: pFunc = &function3; break; //etc... } *pFunc(val1, val2);
Pour plus d’information sur les pointeurs de fonction essayer les liens suivants : English site Site en Français. Pour ceux qui auraient encore du mal avec les pointeurs et autre références, un bon tuto sur les indirections en C/C++ ici: Signification des indirection en C++ (en français)
Les test if/else sont coûteux en temps d’éxecution, donc il est préférable de les éviter dans certains cas triviaux. Heureusement, vous pouvez user des instructions logiques. Par exemple, au lieu d’utiliser le code suivant:
if (levelup==true) x++; //add one to x
ou :
if (levelup) x++;
ou :
x+=(levelup==true);
ou :
levelup?x++:x;
(C’était du plus simple au plus compliqué ^^)
Pour ceux qui n’ont pas compris l’utilisation du symbole “+=” (symbole préfixé), cela veut dire x = x+(levelup==true);
Ainsi, le compilateur évalue la condition levelup==true, si c’est vrai, ça retourne 1, qui est ajouté à la valeur de x; sinon, l’instruction de test retourne 0, et ajouté à x, cela donne x !
Le code suivant garde la trace de combien de fois le jeu execute une boucle par seconde. Les écrans de la DS se rafraichissent à une fréquence de 60Hz donc le fps sera égal à 60 à moins que le code à l’intérieur de la boucle de jeu ne prenne plus de temps que 16.7ms pour s’exécuter.
Au moment où j’écris ces lignes, les émulateurs ne prennent pas en charge l’horloge interne de la DS donc le code fonctionnera uniquement avec un vrai matériel (DS).
#include <PA9.h> //Include PA Lib s8 fps = 0; //Our FPS s8 time = 0; s8 lastTime = 0; int main(void) { PA_Init(); //Initialize the main library PA_InitVBL(); // Initialize Vertical Blanking PA_InitText(1,2); //Init the text on both screens PA_InitText(0,2); PA_OutputText(1,0,0,"Starting..."); lastTime = PA_RTC.Seconds; //We may start halfway into a second //let's wait until the clock starts the next second while(PA_RTC.Seconds == lastTime) { PA_WaitForVBL(); } lastTime = PA_RTC.Seconds; while(1) { PA_WaitForVBL(); //Wait for Video Blank fps +=1; //up the fps every time the code runs through time = PA_RTC.Seconds; //Grab the time //If the second has changed we are done counting frames if(time != lastTime) { lastTime = time; //Start looking for end of next second PA_ClearTextBg(0); //Clear the text so we don't get text mishaps PA_ClearTextBg(1); PA_OutputText(1,0,0,"Fps %d /60",fps); fps = 0; //Start counting back at zero } } }
suivant → Jour 7 - Le Son