Jour 5 : Backgrounds ← Précédent

Jour 6 - Outils mathématiques adaptés au Dev DS

Ce cours d’instruction contiendra les informations sur la chose suivante :

Nombres Aléatoires

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”.

PA_Rand()

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).

PA_RandMax(max)

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);

PA_RandMinMax(min,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);

PA_InitRand()

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 !

Points fixes

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...

Théorie

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 :-)

Exemple basique

Je viens d’ajouter un exemple dans la dernière version de PAlib, though it might not be out yet :-P

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 !

Utilisation de la Gravité (pour sauter, etc...)

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 :

  • Chaque tour, la vitesse change selon son accélération
  • Chaque tour, la position des sprite change selon la vitesse
  • À tout moment, si votre joueur touche la terre (ou est « sous » elle), il devrait être remis au-dessus d’elle, ce qui signifie que votre vitesse devrait être nulle (0)...

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 :

  • le #define FLOOR (160«8) est le niveau de plancher. Nous avions l’habitude du «8pour le convertir en point fixe... 160 est vers le bas de l’écran...
  • le s32 pesanteur = 32; est la pesanteur par défaut que nous avons employée. C’est 100% aléatoire, lol, vous pourrait employer toute autre chose. 32 étant 256/8, il est équivalent de 1/8 à l’accélération de pixel par frame
  • le s32 velocity_y = 0 ; est la vitesse par défaut du vaisseau… Par défaut, il est à 0 !
  • le s32 spritey = FLOOR ; est la position par défaut du vaisseau, au niveau du sol...
  • le s32 takeoffspeed = 1000; est la vitesse de décollage du vaisseau. Ceci déterminera, ainsi que la pesanteur, jusqu’à quelle taille le bateau peut disparaître...

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 :

  • takeoffspeed += (Pad.Held.Up - Pad.Held.Down) *8; pour changer la vitesse de décollage...
  • gravity += (Pad.Held.Right - Pad.Held.Left)*2; pour changer la vitesse de pesanteur...

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.

Trajectoires et Angles

Cette section sera traitée dans 3 parts :

  • D’abord nous verrons comment sont les angles spécifiques de PAlib, avec un petit et simple exemple
  • puis comment une petite théorie sur Cos et Sin fonctionne
  • Et en dernier, un exemple concret pour appliquer ceci dans un jeu…

Les Angles Dans PAlib

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 !

Sin et Cos Basiques

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 !

Exemple de Trajectoire

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 :-P

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
  • D’abord vous adaptez l’angle avec la pression principale… C’est comme nous avons souvent vu. vous voulez que le vaisseau tourne plus rapidement, dans ce cas multiplier juste le tout par la vitesse de rotation choisie (1 pour la normale, 2 pour deux fois plus rapidement, etc…)
  • Alors nous tournons juste le vaisseau selon l’angle, en utilisant la fonction normale de rotation… ici, nous n’avons pas employé n’importe quel raisonnement...

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...

  • Pour la position de x, ajouter juste PA_Cos (angle). Comme dit avant, PA_Cos et PA_Sin sont déjà configuré pour les maths 8bit, car ils renvoient des valeurs de -256 à 256 ! Utiles, elles facilitent beaucoup de choses maintenant, j’espère que vous comprenez pourquoi...
  • Pour y, il y a un signe moin, Pourquoi ? parce que dans une fonction normale de Sin, en se relevant, le Sin devrait être positif... Sur l’écran de DS, le dessus de l’écran a y = 0 comme valeur... C’est ainsi qu’est l’opposé du Sin normal ! ! ! Pour équilibrer ceci, ajouter juste le signe négatif...

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 :

  • Vitesse de rotation variable
  • Vitesse variable du vaisseau en déplaçant en haut et en bas
  • Ajouter des balles ! Maintenant que vous avez vu comment employer des angles dans un jeu, il est indispensable pour tirer et déplacer les balles !
  • Une variation utile que j’aime, employer la spécificité de la DS, changer l’angle par le PA_GetAngle de l’exemple précédent, et change Pad.Held.Up par Stylus.Held... Que fera-t-elle ? Elle tournera le vaisseau dans la direction du stylet et le fera avancer quand vous touchez l’écran ! Est-ce que ce n’est pas cool ? : -)

J’ai fini vers le haut d’ajouter un exemple de stylet dans la section de démos : Follwing the Stylus

Fonctions novatrices

Il y a quelques fonctions novatrices qui sont vraiment facile à employer avec PAlib…

Collisions de Sprite

Nous verrons d’abord 2 genres de collisions de sprite : sprites ronds et sprites carré/rectangle...

Round Sprites

Théorie de collision circulaire

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…

Code de Collision Circulaire

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

Rectangular Sprites

Theorie Collision Rectangulaire

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...

Code de Collision Rectangulaire

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é :-P

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.

Utilisez & plutôt que % si possible

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;

Utilisation des décalages de bits (<< et >>)

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)

Pointeurs de fonction

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)

Using a Division Table

Optimisation des if/else

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 !

Recevoir le FPS

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

 
day6.txt · Dernière modification: 05/03/2010 23:49 par sylux
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki