PureBasic:Listes chainées et SpriteCollision



PureBasic:Listes chainées et SpriteCollision

<< Précédent | Sommaire | Suivant >>


Sommaire

[modifier] Introduction

Dans ce tut vous apprendrez à utiliser :

  • Les listes chaînées
  • Les collisions des sprites

Dans cet exemple , nous allons afficher des météorites qui défilent à l'écran , le joueur pourra piloter un vaisseau et tirer sur les météorites. les météorites volent en éclat lorqu'elles sont touchées par un missile du joueur.

[modifier] Initialisation

Ce tutoriel reprend le programme minimal déjà évoqué au cours du tutoriel Premiers pas avec PureBasic.

Ensuite on déclare les procédures utilisées par le programme .

;Declare les Procedures
Declare AddTir(X.l,Y.l)
Declare GestionTir()
Declare AddMeteorite()
Declare GestionMeteorite()
Declare AddEclats(ID.l,X.l,Y.l)
Declare GestionEclats()

Puis on définit quelques constantes.Elles permettront d'identifier plus aisément les sprites par la suite.
C'est plus parlant d'écrire DisplaySprite(#Vaisseau,x,y) que DisplaySprite(0,x,y).

;Sprites
;Nombre de météorites différentes
#MaxMeteorites=25

Enumeration 
	#Vaisseau
	#Tir
	#Meteorite
	#Eclats = #PB_Compiler_EnumerationValue + #MaxMeteorites
EndEnumeration

Et pour finir , on définit deux structures.

Structure s_sprite
	ID.l  ; No du sprite
	X.l
	Y.l
	Vitesse.l
EndStructure

Structure s_particule Extends s_sprite
 DX.l
 DY.l
EndStructure 

[modifier] Listes Chaînées

PureBasic possède une bibliothèque permettant de gérer très simplement les listes chaînées.

  • NewList permet d'initialiser une liste chaînée.

Dans notre exemple , nous aurons besoin d'une liste pour les météorites , les tirs et les éclats des météorites.

NewList TirJoueur.s_sprite()
NewList Meteorite.s_sprite()
NewList Eclats.s_particule()
  • AddElement() permet d'ajouter un élément à une liste chaînée.
  • CountList() renvoie le nombre d'éléments contenus dans la liste spécifiée.

On affiche en permanence des météorites sur l'écran .

Procedure  AddMeteorite()
   If CountList(Meteorite())<10       ; si le nombre de météorites est inférieur à 10
	AddElement(Meteorite())    ; On ajoute une météorite
	Meteorite()\id=#Meteorite+Random(#MaxMeteorites)
	Meteorite()\x=Random(800-SpriteWidth(#Meteorite))
	;La météorite est créée en dehors de l'écran 
	Meteorite()\y=-Random(500)
	Meteorite()\vitesse=1+Random(3)
   EndIf
EndProcedure

Quand le joueur tire on ajoute un tir à la liste.

Procedure AddTir(X.l,Y.l)
   AddElement(TirJoueur())
   TirJoueur()\id = #Tir    ; No de sprite  
   TirJoueur()\x=x          ;Position de départ du tir
   TirJoueur()\y=y
   TirJoueur()\vitesse=4    ;Vitesse de déplacement d'un tir
EndProcedure

Quand un tir touche une météorite celle ci explose et des éclats sont affichés

Procedure AddEclats(ID.l,X.l,Y.l)

   n=4+Random(5)                             ;Nombre d'éclats
   For i=1 To n
      AddElement(Eclats())                   ;Ajoute un éclat à la liste Eclats() 
      Eclats()\id=ID+#MaxMeteorites+1        ;Détermine le sprite de l'éclat 
      Eclats()\x=x                           ;Position de départ d'un éclat 
      Eclats()\y=y
      While Eclats()\DX=0 Or Eclats()\DY=0   ;DX ou DY doivent être différents de zéro
         Eclats()\DX=2-Random(4)             ;Pour éviter qu'un éclat reste sur place
	 Eclats()\DY=2-Random(4)
      Wend    
   Next i	
	
EndProcedure


  • DeleteElement() supprime l'élément courant de la liste spécifiée.
  • ForEach() énumère tous les élèments d'une liste chaînée. Si la liste est vide, ForEach : Next quitte immédiatement, sans entrer dans la boucle.

Cette fonction est utile pour tester l'ensemble d'une liste, par exemple pour savoir si un tir touche une météorite.

Procedure GestionTir()
ForEach TirJoueur()
	TirJoueur()\y - TirJoueur()\vitesse
	DisplaySprite(TirJoueur()\id,TirJoueur()\x,TirJoueur()\y)
	;Si le tir sort de l'écran on l'efface
	If TirJoueur()\y<0  
	   DeleteElement(TirJoueur())
	   Continue
	EndIf
	;Test si un tir touche une météorite
	ForEach Meteorite()
		If SpritePixelCollision(TirJoueur()\ID,TirJoueur()\x, TirJoueur()\y,Meteorite()\ID,Meteorite()\x,Meteorite()\y)
		   DeleteElement(TirJoueur())
		   AddEclats(Meteorite()\ID,Meteorite()\x,Meteorite()\y)
		   DeleteElement(Meteorite())
		   Break 2
		EndIf	
	Next	

Next	
EndProcedure

[modifier] Les collisions

Pour en savoir plus sur les collisions : Détection pixel-perfect des collisions 2D.

PureBasic est doté de deux fonctions pour la gestion des collisions.

Remarque : SpritePixelCollision() vérifie d'abord s'il y a une collision possible en testant les boîtes englobantes des sprites, il est donc inutile de faire ce test soi même avec SpriteCollision() avant d'appeler la fonction SpritePixelCollision().

[modifier] Programme complet

;Initialise l'environnement nécessaire au fonctionnement des sprites et pour ouvrir un écran.
InitSprite()

;Initialise l'environnement propre à la gestion du clavier. 
InitKeyboard()

;Ouvre un nouvel écran avec les caractéristiques Largeur, Hauteur et Profondeur. 
OpenScreen(800,600,32,"Demo")

;Declare les Procedures
Declare AddTir(X.l,Y.l)
Declare GestionTir()
Declare AddMeteorite()
Declare GestionMeteorite()
Declare AddEclats(ID.l,X.l,Y.l)
Declare GestionEclats()


;Sprites
;Nombre de météorites différentes
#MaxMeteorites=25

Enumeration 
	#Vaisseau
	#Tir
	#Meteorite
	#Eclats = #PB_Compiler_EnumerationValue + #MaxMeteorites
EndEnumeration

Structure s_sprite
	ID.l  ; No du sprite
	X.l
	Y.l
	Vitesse.l
EndStructure

Structure s_particule Extends s_sprite
 	DX.l
	DY.l
EndStructure 

NewList TirJoueur.s_sprite()
NewList Meteorite.s_sprite()
NewList Eclats.s_particule()
Global Vaisseau.s_sprite,Meteorite.s_sprite
  
;Sprite vaisseau
CreateSprite(#Vaisseau,32,32)

StartDrawing(SpriteOutput(#Vaisseau))
	Line(SpriteWidth(#Vaisseau)/2,0,SpriteWidth(#Vaisseau)/2,SpriteHeight(#Vaisseau),RGB(255,255,0))
	Line(SpriteWidth(#Vaisseau)/2,0,-SpriteWidth(#Vaisseau)/2,SpriteHeight(#Vaisseau),RGB(255,255,0))
	Line(0,SpriteHeight(#Vaisseau)-1,SpriteWidth(#Vaisseau),0,RGB(255,255,0))
	FillArea(SpriteWidth(#Vaisseau)/2,SpriteHeight(#Vaisseau)/2,RGB(255,255,0),RGB(255,155,0))
StopDrawing()

Vaisseau\x=400-SpriteWidth(#Vaisseau)/2 ; Centre le Vaisseau
Vaisseau\y=600-SpriteHeight(#Vaisseau)
Vaisseau\vitesse=2   
   

;Sprite pour les tirs
CreateSprite(#Tir,3,3)
StartDrawing(SpriteOutput(#Tir))
	Box(0,0,3,3,RGB(255,55,55))
StopDrawing()  

;Sprite des météorites et des particules
For i=#Meteorite To #Meteorite+#MaxMeteorites
	Couleur=RGB(Random(255),Random(255),Random(255))
	Size=25+Random(25)
	CreateSprite(i,size,size)
	StartDrawing(SpriteOutput(i))
		Circle(SpriteWidth(i)/2,SpriteHeight(i)/2,(SpriteWidth(i)/2),Couleur)
	StopDrawing()  


	;Sprite des particules
	Size/2
	p=i+#MaxMeteorites+1
	CreateSprite(p,size,size)
	StartDrawing(SpriteOutput(p))
		DrawingMode(4)
		Circle(SpriteWidth(p)/2,SpriteHeight(p)/2,SpriteWidth(p)/2,Couleur)
	StopDrawing()  
Next i

Repeat
	;Inverse le buffer d'arrière plan avec le buffer visible à l'écran. 
  ;La partie invisible du buffer remplace alors complètement La partie visible. 
  FlipBuffers()
    
  ;Efface l'écran courant avec la couleur specifiée. 
  ClearScreen(0,0,30)
  
  ;Met à jour l'état du clavier.Cette fonction doit être appelée avant d'utiliser les commandes 
  ;KeyboardInkey(), KeyboardPushed() et KeyboardReleased(). 
  
  If ExamineKeyboard()
    If KeyboardPushed(#PB_Key_Left)
    	Vaisseau\x - Vaisseau\Vitesse
	    If Vaisseau\x <0
    		Vaisseau\x=0
	    EndIf
		ElseIf KeyboardPushed(#PB_Key_Right)
    	Vaisseau\x + Vaisseau\Vitesse
	    If Vaisseau\x > 800-SpriteWidth(#Vaisseau)
    		Vaisseau\x=800-SpriteWidth(#Vaisseau)
	    EndIf
		EndIf    
		If KeyboardPushed(#PB_Key_Space) And ElapsedMilliseconds()-DernierTir>100 ;Autorise un tir tous les 100 ms
			DernierTir=ElapsedMilliseconds()
			AddTir(Vaisseau\x+SpriteWidth(#Vaisseau)/2,Vaisseau\y)
		EndIf
	EndIf	

	AddMeteorite()
	GestionMeteorite()
	GestionTir()
  GestionEclats()
  DisplayTransparentSprite(#Vaisseau,Vaisseau\x,Vaisseau\y)
  
Until KeyboardPushed(#PB_Key_Escape)

Procedure AddTir(X.l,Y.l)

	AddElement(TirJoueur())
  TirJoueur()\id = #Tir
  TirJoueur()\x=x
  TirJoueur()\y=y
  TirJoueur()\vitesse=4 
    
EndProcedure

Procedure AddEclats(ID.l,X.l,Y.l)

	n=4+Random(5)                              ;Nombre d'éclats
	For i=1 To n
		AddElement(Eclats())                   ;Ajoute un éclat à la liste Eclats() 
		Eclats()\id=ID+#MaxMeteorites+1        ;Détermine le sprite de l'éclat en fonction de la météorite touchée 
		Eclats()\x=x                           ;Position de départ d'un éclat 
		Eclats()\y=y
		While Eclats()\DX=0 Or Eclats()\DY=0  ;DX ou DY doivent être différents de zéro
		    Eclats()\DX=2-Random(4)            ;Pour éviter qu'un éclat reste sur place
		    Eclats()\DY=2-Random(4)
    Wend    
	Next i	
	
EndProcedure

Procedure  GestionEclats()

	ForEach Eclats()
  	Eclats()\x+Eclats()\DX
    Eclats()\y+Eclats()\DY
    ;Si un éclat sort de l'écran , on le supprime
    If Eclats()\x>800 Or Eclats()\x<SpriteWidth(Eclats()\id) Or Eclats()\y>600 Or Eclats()\y<SpriteHeight(Eclats()\id)
    	DeleteElement(Eclats())
      Continue
    EndIf
    DisplayTransparentSprite(Eclats()\id,Eclats()\x,Eclats()\y)
	Next
    
EndProcedure

Procedure GestionTir()

	ForEach TirJoueur()
  	TirJoueur()\y - TirJoueur()\vitesse
    DisplaySprite(TirJoueur()\id,TirJoueur()\x,TirJoueur()\y)
    ;Si le tir sort de l'écran on l'efface
    If TirJoueur()\y<0  
    	DeleteElement(TirJoueur())
    	Continue
    EndIf
    ;Test si un tir touche une météorite
    ForEach Meteorite()
    	If SpritePixelCollision(TirJoueur()\ID,TirJoueur()\x ,TirJoueur()\y,Meteorite()\ID,Meteorite()\x,Meteorite()\y)
    		DeleteElement(TirJoueur())
    		AddEclats(Meteorite()\ID,Meteorite()\x,Meteorite()\y)
    		DeleteElement(Meteorite())
    		Break 2
    	EndIf	
    Next	
	Next
	
EndProcedure

Procedure  AddMeteorite()

	If CountList(Meteorite())<10
  	AddElement(Meteorite())
    Meteorite()\id=#Meteorite+Random(#MaxMeteorites)
    Meteorite()\x=Random(800-SpriteWidth(#Meteorite))
    ;La météorite est créée en dehors de l'écran 
    Meteorite()\y=-Random(500)
    Meteorite()\vitesse=1+Random(3)
  EndIf	

EndProcedure
Procedure GestionMeteorite()

	ForEach Meteorite()
		Meteorite()\y + Meteorite()\vitesse
		;Si une météorite sort de l'écran , on la supprime
		If Meteorite()\y>600
			DeleteElement(Meteorite())
			Continue
		EndIf
		DisplayTransparentSprite(Meteorite()\id,Meteorite()\x,Meteorite()\y)
	Next
	
EndProcedure	


[modifier] Passage à la version V4.0

Il y a deux choses à faire pour que le programme précédent fonctionne avec la version 4.0 de PureBasic.

  • Rendre toutes les listes chaînées globales
Global NewList TirJoueur.s_sprite()
Global NewList Meteorite.s_sprite()
Global NewList Eclats.s_particule()

C'est la solution la plus simple. L'autre solution consisterait à modifier les procédures pour qu'elles acceptent en paramètre les listes chaînées. Vous trouverez un exemple dans ce tutoriel

  • Mettre un seul paramètre à la commande ClearScreen()
;Efface l'écran courant avec la couleur specifiée. 
ClearScreen(RGB(0,0,30))

Et voila, vous pouvez à nouveau faire joujou :)