![]() |
![]() |
Accueil > Intranet Michel Buffa > Projet Blitzmax EPU 2009
Projet Blitzmax EPU 2009De $1Table des matières
/p[3]/span, reference to undefined name 'flickr'
Nous avons créé un jeu-vidéo complet pour découvrir autant que possible les fonctionnalités qu’offre Blitzmax en matière de programmation orientée jeux-vidéo. Notre jeu, Vibz, est un shoot them up 2D vertical. Nous aspirons à partager notre expérience à travers ce tutoriel. Celui-ci est le premier que nous écrivons et espérons qu’il est adapté. Tout conseil pour l’améliorer est bienvenu ! Nous allons vous présenter les aspects que l’on retrouve dans beaucoup de jeux tels que la création d’un avatar pour le joueur, son déplacement, sa capacité à agir sur son environnement, etc. Cette présentation se fera à chaque fois sur plusieurs niveaux : d’abord la base, ce que Blitzmax permet de faire en quelques lignes de code, puis ce que nous avons implémenté dans notre jeu. Tout au long du tutoriel, des concepts de base de la programmation seront abordés de manière assez simplifiée. Les lecteurs ayant une bonne expérience de la programmation pourront passer ces rappels et se concentrer sur les exemples de code. Le meilleur moyen d’apprendre étant d’expérimenter, il ne faut pas hésiter à passer les bouts de code indépendants dans BlitzMax pour en éprouver le comportement. Les bases de la programmation Cette partie du tutoriel est un peu à part. Elle traite surtout de la syntaxe et de la manière de programmer en BlitzMax. Il n’est pas nécessaire de la connaître en entier avant de faire son premier jeu en BlitzMax, même si c’est conseillé. Cependant, de nombreux exemples du tutoriel feront référence à des notions présentes dans cette section. BlitzMax est un langage mixte : pour utiliser les termes techniques, il est possible de l’utiliser de manière procédurale et orientée objet à la fois. Il est donc possible de définir des variables, des fonctions, des tests et des objets comme dans de nombreux langages de programmation haut-niveau. Voyons cela au cas par cas. BlitzMax (comme OpenGL), peut se voir comme une machine à états. En clair, on travaille en permanence avec un certain nombre d’états (la couleur, le mode et le coefficient de transparence, la rotation, l’échelle, la police de caractère …) qui s’appliquent à chaque objet affiché. Nous les verrons en détail plus loin dans ce tutoriel. Ces états ne changent que si on le demande explicitement et ne sont jamais remis automatiquement à leur valeur par défaut. Il faut donc toujours garder en tête dans quel état se trouve la machine avant chaque affichage et optimiser les changements d’états, qui sont coûteux pour le processeur graphique. Pour que l’ordinateur comprenne les instructions et informations qu’on lui donne, il faut respecter une certaine grammaire. Avant de voir le nécessaire au cas par cas, voici un aperçu de cette syntaxe, pour commencer sans se perdre. Tout d’abord, à la fin de chaque action, il faut soit un retour à la ligne (dans la plupart des cas), soit un point-virgule, lorsque l’on veut enchaîner plusieurs actions sur une même ligne. Comme en mathématiques, il est possible de regrouper certaines informations à l’aide de parenthèses. Il est possible d’écrire des commentaires dans notre code, c’est-à-dire des informations qui ne seront pas interprétées par l’ordinateur. Elles ne servent qu’aux programmeurs, c’est-à-dire vous ou ceux qui vous succéderont, pour prendre des notes ou autre. Les commentaires peuvent s’écrire sur une ligne à l’aide d’une apostrophe ou sur plusieurs lignes à l’aide des mots clé Rem et End Rem. Afin de rendre le code plus facile à lire et à comprendre, il est fortement conseillé de l’indenter correctement. Indenter signifie décaler l’écriture du code, un peu comme les alinéas lorsque l’on écrit un texte. Le décalage se fait en général à l’aide de tabulations. Par convention, on décale le code d’une tabulation à chaque fois que l’on rentre dans un contexte indépendant. Par exemple, dans une fonction, une déclaration de classe, dans un test ou dans une boucle. A noter que les contextes tels que les boucles, les tests et autres définitions de classes ou de méthodes sont fermés non pas par des accolades mais par des End suivis du type de contexte (If, While, Type, Function, Method ...) en général. En effet, certaines exceptions sont à noter, comme nous le verrons par la suite. Une particularité très importante de BlitzMax est qu’il n’est PAS case sensitive. Cela veut dire que vous pouvez écrire exactement le même code avec des majuscules n’importe où, il fonctionnera quand même. Il est toutefois conseillé de respecter les conventions classiques d’écriture de code pour qu’il soit plus simple à relire. Voici un exemple de code valide regroupant ces différentes notions : Rem Voici un exemple de commentaire sur plusieurs lignes End Rem ' commentaire sur une ligne action1() ; action2(var1,var2,var3) If (test()) action3(var4) ' un niveau d'indentation If (var1 = 3) action4(var1) ' deux niveaux d'indentation End If' fin de contexte avec ou sans espace EndIf ' les deux fonctionnent Il existe des mots clef réservés en BlitzMax qui désignent des types ou des fonctions de base. Il est impossible de s’en servir pour désigner autre chose. Nous parlerons de certaines fonctions au cours du tutoriel. Il est tout de même important de connaître les types de donnée de base :
Une variable, comme en mathématique, peut se voir comme un nom représentant une information. Cette information peut être modifiée au cours de la vie du programme et la variable associée suivra. Cela peut servir, entre autres, à stocker des informations à une place où l’on pourra les retrouver facilement. Une variable peut être globale ou locale. Les variables globales sont « visibles » à n’importe quel endroit du programme alors que les variables locales ne le sont que dans le contexte où elles ont été définies. Il est également possible de définir des constantes. Cela permet de stocker une information qu’il sera impossible de modifier dans le programme sans erreur à la compilation. En BlitzMax, les variables ont un type qu’il faut préciser à leur création. Voici un exemple de création et d’utilisation de variables en BlitzMax : Const longueur:Float = 15.5 ' constante Global largeur:Float = 10 ' variable globale Local aire:Float = longueur*largeur ' variable locale BlitzMax, en tant que langage orienté objet, permet la définition de classes. Les classes représentent un concept, une catégorie d’objets. Les objets, ou instances de classe, peuvent correspondre à des objets du monde réel (par exemple notre bâtiment des Templiers de l’Ecole Polytech’Nice), les classes correspondent à une entité abstraite de niveau supérieur (en l’occurrence le concept de « bâtiment »). Les classes rassemblent les attributs (définissant l’état) et les méthodes (définissant les comportements) communs à une catégorie d’objets. Les classes en BlitzMax peuvent être abstraites et dériver d’autres classes. Elles peuvent posséder des attributs et des méthodes qui leur sont propres. Par exemple, voici la classe TEnemyExplosion qui dérive de la classe TExplosion dans Vibz : Type TEnemyExplosion Extends TExplosion Function Make(x,y,MaxRadius# = 10, growSpeed = 1) Local Explosion:TEnemyExplosion = New TEnemyExplosion explosion.x = x explosion.y = y+60 IfRand (1,2) = 1 explosion.image = explosionAnimImage Else explosion.image = explosionAnimImage2 EndIf explosionlist.AddLast (explosion) EndFunction EndType Les fonctions sont des programmes dans le programme. Elles servent en général à donner un résultat dépendant de la ou des valeur(s) entrée(s) dans celle-ci. Cela peut être explicite ou implicite. En effet, parfois, il n’y a pas de valeur d’entrée ni de sortie indiquée. Dans ces cas-là, le plus souvent, une action est appliquée aux valeurs globales du programme ou aux attributs de classe. En BlitzMax, de nombreuses fonctions de base existent déjà. Elles sont répertoriées et expliquées dans la documentation. Voici un code très simple de fonction valide (et inutile ; c’est pour l’exemple !). Elle retourne le résultat de l’addition des variables a, b et c. Function add%( a% , b% , c% = 0 ) Return a + b + c EndFunction Pour l’utiliser dans le code, il est nécessaire de donner au moins deux arguments, et possible d’en donner un troisième. En effet, si l’on n’affecte pas de valeur à l’argument « c », il aura par défaut la valeur 0. Par exemple, result vaudra ici 16 : Local a%=1 Local result% = add(a,15) On utilise un test lorsqu’on veut vérifier si une condition est bien remplie et effectuer une action en fonction du résultat. Avant d’écrire la première condition d’un test, il faut écrire If. Avant de tester d’autres conditions, on écrit Else If. Enfin, avant d’effectuer l’action par défaut, on écrit Else. Il peut être utile d’utiliser Then après une condition pour indiquer que l’on va effectuer une action par la suite (notamment si l’on veut le faire sur la même ligne). Pour fermer un test, on écrit EndIf ou End If. Pour simplifier, il n’est pas nécessaire de finir les tests comportant seulement un If par un EndIf. Voici par exemple comment l’on change la difficulté du jeu en appuyant sur une touche dans le menu : IfKeyHit(KEY_1) Then Difficulty = 1 IfKeyHit(KEY_2) Then Difficulty = 2 IfKeyHit(KEY_3) Then Difficulty = 3 Une boucle permet d’exécuter une action un certain nombre de fois. Elle est composée de la condition de sortie et de l’action elle-même. La condition de sortie peut se présenter de plusieurs manières et cela donne le type de boucle : a. Les boucles for : Il y a trois manières de les utiliser : avec To, Until ou EachIn. Les deux premiers sont des conditions sur des valeurs numériques. Avec To, la condition de sortie est atteinte lorsque la valeur testée atteint la valeur de test. Avec Until, la sortie est atteinte à l’itération précédant la valeur de test. Il est possible de définir un pas (Step), un décalage (par défaut 1) pour ces boucles. Enfin, avec EachIn, l’action de la boucle s’effectue sur tous les membres d’une collection. ForLocal x% = 0 To 20 Step 2 Print x/2 Next ForLocal x% = 0 Until 11 Print x Next Local array[]=[0,1,2,3,4,5,6,7,8,9,10] ForLocal x% = EachIn array Print x Next b. Les boucles while : Les boucles while s’exécutent tant que leur condition n’est pas atteinte. Local x% = 0 While x < 11 Print x x:+1 Wend c. Les boucles repeat : Ce sont exactement les mêmes que les boucles while sauf que la condition de sortie s’écrit à la fin. A la place de la condition de sortie, on peut utiliser Forever pour que la boucle s’exécute indéfiniment. Local x%=0 Repeat Print x x:+1 Until x = 11 Cependant, quelque soit le type de boucle, il est toujours possible de changer de type de boucle en réécrivant la condition de sortie pour obtenir exactement le même résultat, comme dans les exemples précédents. Pour finir sur les boucles, il faut savoir que dans un programme BlitzMax, on utilisera en général une boucle principale qui ne se terminera que lorsque l’utilisateur voudra fermer le programme. Cette boucle principale sera exécutée systématiquement en fonction de la fréquence de rafraîchissement de l’application. Nous parlerons de « tour de boucle » dans la suite du tutoriel pour représenter une exécution de cette boucle principale. Les collections servent à stocker plusieurs variables au même endroit. En BlitzMax, il existe trois collections de base : les tableaux, les listes et les maps. Leur utilisation reste très classique. Les tableaux se définissent et s’utilisent ainsi : Local nameList$["alpha","bravo","charlie","delta","echo"] ' Crée un tableau composé de 5 Strings Local a$ = nameList[0] ' La variable a aura la valeur alpha Comme souvent, les tableaux sont indexés à partir de 0. Les variables du tableau sont toutes du même type, défini selon le tableau. A noter qu’il peut également exister des tableaux à plusieurs dimensions. Les listes se définissent et s’utilisent ainsi : Global alphabet:TList = CreateList() alphabet.addFirst("b") alphabet.addLast("c") alphabet.addFirst("a") alphabet.addLast("d") Cette liste aura dans l’ordre les caractères a, b, c et d. On peut entre autres supprimer les éléments d’une liste, vérifier qu’elle contient un élément donné et boucler facilement dessus avec EachIn. Les maps se définissent et s’utilisent ainsi théoriquement : Global alphabetMap:TMap = CreateMap() alphabet.insert(2,"b") alphabet.insert(3,"c") alphabet.insert(1,"a") alphabet.insert(4,"d") Nous avons précisé ici « théoriquement » car nous n’avons pas réussi à créer de maps bien qu’elles existent dans la documentation. Peut-être n’existent-elles que dans la dernière version de BlitzMax que nous ne possédons pas. Je veux créer ! Bien, maintenant que nous sommes d’accord sur la syntaxe et les détails du langage, passons aux choses sérieuses. Nous allons voir comment obtenir rapidement un aperçu de la puissance de BlitzMax par l’affichage simple de graphismes. Commençons par afficher une fenêtre avec une résolution de 800 par 600 pixels. Nous voulons qu’elle reste affichée tant qu’on ne ferme pas le programme. Pour cela, trois lignes de code ! Graphics 800,600 ' Affichage de la fenêtre Repeat' Boucle principale ' ... Ici, les futurs affichages UntilAppTerminate() ' Condition de fin : lorsque l'utilisateur ferme la fenêtre A noter que la fonction Graphics peut avoir plus d’arguments : mode fenêtré ou non, taux de rafraîchissement des images ... Il faut également noter que le système de coordonnées est le suivant : l’origine se trouve dans le coin supérieur gauche de la fenêtre. L’abscisse augmente donc vers la droite et l’ordonnée vers le bas. BlitzMax permet de dessiner des rectangles, des ovales, des lignes, des points et du texte, respectivement à l’aide des fonctions : DrawRect, DrawOval, DrawLine, Plot et DrawText. Il faut leur renseigner les dimensions et la position de la forme. Deux petites subtilités. Tout d’abord, pour afficher effectivement ces formes, nous allons, à chaque tour de boucle, effacer le contenu actuel de l’écran et afficher son nouveau contenu. Cela se fait respectivement à l’aide des fonctions Cls (pour clear screen) et Flip. Ensuite, nous allons utiliser une fonction intéressante de BlitzMax qui nous renvoie la position actuelle du curseur pour positionner notre rectangle par exemple. Voici comment tout cela fonctionne : Graphics 800,600 ' Affichage de la fenêtre Repeat' Boucle principale Cls DrawRectMouseX(), MouseY(), 20,20 ' Position puis dimension DrawOval 20,200,50,100 ' Position puis dimension DrawLine 0,0,800,600 ' Positions des deux extrémités Plot 50,500 ' Position du point DrawText"What's up World ?",30,400 ' Chaîne de caractères puis position Flip UntilAppTerminate() ' Condition de fin Pour afficher des images, il faut au préalable charger l’image concernée dans un objet de type TImage, puis l’afficher simplement avec DrawImage. Graphics 800,600 ' Affichage de la fenêtre Global image:TImage = LoadImage("music_tribal.jpg") ' Chargement de l'image dans un objet Repeat' Boucle principale Cls DrawImage image,300,200 ' Affichage de l'image Flip UntilAppTerminate() ' Condition de fin Pour travailler l’affichage de ces images à l’aide de BlitzMax, de nombreuses fonctionnalités vous sont proposées. Faites un tour dans la section effets spéciaux pour y trouver votre bonheur ! Les animations en BlitzMax sont tout simplement des parties d’images indexées. Il n’y a pas de moyen simple en BlitzMax de faire des interpolations de mouvement ou de déformation. Leur utilisation est rigide mais cela a l’avantage d’être facile à comprendre et à gérer. Pour créer une animation, il suffit de charger une image d’un format quelconque (bmp, jpg, png …) en précisant les dimensions d’une « frame » de l’animation, l’index de la première frame qu’on utilisera dans cette image pour l’animation et l’index de la dernière. Pour l’utiliser, il suffit de dessiner l’image comme précédemment mais en précisant la frame que l’on utilise. Pour faire défiler les frames, il est possible d’utiliser de nombreuses astuces. Créer sa propre classe d’animation, gérer la fréquence de défilement en fonction des tours de boucles ou du temps de jeu, etc. Voici un exemple simple d’animation qui se répète : Graphics 800,600 ' Affichage de la fenêtre Global explosion:TImage = LoadAnimImage("explosion.png",200,250,0,12) ' Chargement de l'animation dans un objet Global loopsCount% ' Compteur de tours de boucles Repeat' Boucle principale Cls loopsCount :+ 1 DrawImage explosion,300,200, loopsCount/6 Mod 12 ' Affichage de l'animation, défilement de frames tous les 6 tours de boucle Flip UntilAppTerminate() ' Condition de fin Elle est facilitée par la fonction Millisecs() qui renvoie le temps courant en millisecondes comme son nom l’indique. Cela peut être utile pour afficher la durée d’une partie, gérer des « timers », des délais d’action, et bien d’autres. Voyons tout de suite un exemple de gestion du temps dans le mini-jeu que nous vous proposons d’implémenter dans ce tutoriel. Essayons de créer des plateformes pour notre mini-jeu. Il va nous falloir une structure pour les stocker et les afficher à chaque tour de boucle. Pour les représenter, nous utiliserons simplement des rectangles. Pour gérer le temps, nous utiliserons une variable globale définie au lancement du jeu par la valeur de Millisecs(). Le temps courant de la partie sera donc le temps courant, Millisecs(), moins la valeur d’origine. Ajoutons également une variable pour garder le meilleur score du joueur au clavier. Nous les afficherons ensuite dans la boucle principale. Attention : il faut bien réfléchir à ce que l’on met dans la boucle principale pour ne pas surcharger la mémoire ou engendrer des comportements indésirables. Pour simplifier la lecture du code, nous allons placer au maximum les actions dans les classes utilisées. Graphics 800,600 ' Liste des plateformes Global platforms:TList = CreateList() 'Gestion du temps Global timeOrigin= MilliSecs() Global survival = 0 'Classe pour les plateformes Type Platform Field x% Field y% Field height% = 20 Field width% = 200 ' Fonction de création des plateformes Function create(x,y,w,h) pf:Platform = New Platform pf.x = x ; pf.y = y pf.width = w ; pf.height = h platforms.addLast(pf) EndFunction Function Update() ForLocal pf:Platform = EachIn platforms DrawRect (pf.x,pf.y,pf.width,pf.height) Next EndFunction EndType ' Création des plateformes Platform.create(0,500,800,20) Platform.create(10,450,50,10) Platform.create(500,450,50,10) Platform.create(300,350,50,10) Platform.create(200,400,50,10) ' Boucle principale Repeat Cls Platform.Update()' Affichage des plateformes DrawText"Survival time : " + (MilliSecs() - timeOrigin)/1000 + " seconds", 20, 30 DrawText"Survival record : " + survival + " seconds", 20, 10 Flip UntilAppTerminate() Dans Vibz, les chargements de toutes les images et animations se font dans des fichiers spécifiques. La plupart sont stockées dans des variables globales, que l’on peut utiliser dans le code à tout moment. Ainsi, elles ne sont chargées qu’une fois. La plupart des objets possèdent leur propre méthode Update, appelée à chaque tour dans la boucle principale, qui régit leur comportement et leur affichage. Il existe différentes méthodes pour structurer un programme, n’hésitez pas à en tester plusieurs et à choisir celle qui vous correspond le mieux. Après un essai de gestion automatique des animations par une classe spéciale, il s’est avéré plus simple de gérer les animations « à la main » dans les classes qui les utilisaient. Voir les fichiers Effets.bmx et Classes.bmx. Je veux du mouvement ! Il existe encore une fois beaucoup de façons de gérer le mouvement dans BlitzMax. Voulez-vous utiliser le clavier ou la souris ? Ces événements sont extrêmement simples à gérer, voyons cela tout de suite. A la souris : comme vu plus haut, on peut connaître à tout moment la position du curseur à l’aide des fonctions MouseX() et MouseY(). Il est alors très simple de modifier la position d’un objet en modifiant son attribut en fonction de la position de la souris. Par exemple : DrawOvalMouseX(), MouseY(), 20,20 Au clavier : le plus simple est d’effectuer un déplacement du joueur à chaque appui sur une touche. Par exemple, pour les touches directionnelles : IfKeyDown (Key_Right) Then player.x :+ player.speed IfKeyDown (Key_Left) Then player.x :- player.speed IfKeyDown (Key_Up) Then player.y :- player.speed IfKeyDown (Key_Down) Then player.y :+ player.speed La rotation dans Vibz se gère à l’aide de la machine à états. En d’autres termes, lorsque la rotation aura été modifiée, elle s’appliquera à tous les objets affichés jusqu’à ce qu’on la remette à zéro. Dans BlitzMax, la rotation s’exprime en degrés. Le rectangle dessiné ici sera incliné de 45 degrés. SetRotation 45 DrawRectMouseX(), MouseY(), 20, 20 SetRotation 0 Ajoutons à présent un fond à notre écran. Il suffit de dessiner une image de la taille de la fenêtre en premier dans notre boucle principale pour ne pas cacher les autres objets. Nous pouvons ensuite le faire bouger par exemple en fonction du temps ou en fonction des déplacements du joueur. Cela s’appelle le scrolling. BlitzMax permet de réaliser le scrolling automatiquement en appelant simplement la fonction TileImage à la place de la fonction DrawImage. Par exemple, avec bg1 l’objet TImage du fond : TileImage bg1,200 + mapX,mapY Pour que l’effet soit réussi, il faut que le fond d’écran soit raccordable, c’est-à-dire qu’il n’y ait pas de frontière visible lorsque l’on accole ce fond à lui-même, comme dans une mosaïque. La plupart des éditeurs graphiques possèdent des algorithmes pour rendre une image raccordable. Les trajectoires des objets peuvent encore une fois être gérées de nombreuses manières. Nous verrons ici comment les implémenter de manière simple. Le principe d’implémentation de la trajectoire consiste à mettre à jour la position de l’objet concerné en fonction du temps (ou ici, des tours de boucle). On peut alors jouer avec les fonctions mathématiques pour donner des trajectoires linéaires, sinusoïdales, circulaires ou autres aux objets. Il faut pour cela rajouter dans notre jeu une classe « joueur », une variable globale qui instancie cette classe, et sa fonction de mise à jour dans la boucle principale. Notre joueur sera représenté par un cercle blanc. Avant la boucle principale : Global player:TPlayer = New TPlayer ' Classe pour le joueur Type TPlayer Field x# = 400 Field y# = 300 Field speed = 15 Function Update() IfKeyDown (Key_Right) Then player.x :+ player.speed IfKeyDown (Key_Left) Then player.x :- player.speed IfKeyDown (Key_Up) Then player.y :- player.speed IfKeyDown (Key_Down) Then player.y :+ player.speed DrawOval player.x,player.y,20,20 EndFunction EndType Dans la boucle principale : TPlayer.Update() ' Gestion du joueur Dans Vibz, le joueur se déplace à l’aide du clavier, d’une manière similaire à ce que l’on vient de voir. Le vaisseau possède sa propre vitesse qui ne change jamais. Les ennemis utilisent des trajectoires complexes, les B-Splines. Ils suivent ces trajectoires à vitesse constante. Ces trajectoires sont calculées à partir de points donnés dans les fichiers des différents niveaux du jeu. Le fond d’écran défile en permanence à la verticale et défile horizontalement avec le déplacement du joueur. Le fond d’écran est en fait composé de deux fonds superposés dont les vitesses de défilement diffèrent, afin de donner un effet de profondeur à l’ensemble. Une des grandes originalités de Vibz est le système de ralenti porté au gameplay des shoot them up. En effet, en appuyant sur Shift gauche, on active le mode ralenti, ce qui ralentit tout l’environnement du joueur (projectiles et vaisseaux ennemis, défilement du paysage), mais pas le joueur, qui a alors un avantage conséquent pour une durée limitée. Voir les fichiers xml des niveaux ainsi que Vibz.bmx et Classes.bmx . Je veux de la puissance ! Pour gérer des projectiles, vous aurez besoin de types spécifiques. Ils auront au minimum leur position en attribut et une fonction pour calculer leur trajectoire et les afficher. Un timer est nécessaire si vous voulez que le joueur ne puisse tirer qu’avec un délai. A vous de voir si vous avez besoin de plus ! Les stocker dans une liste facilite leur mise à jour, à l’aide d’une boucle. Les projectiles seront supprimés de la liste s’ils sortent de l’aire de jeu. Il faudra ensuite tester les collisions avec les cibles à chaque tour de boucle. Voici un exemple qui peut servir de base pour vos projectiles. Celui-ci crée un projectile avec une direction aléatoire et une trajectoire linéaire. N’oubliez pas de définir une liste qui contiendra ces projectiles. Global projectiles:TList = CreateList() Type TProjectile Field x# Field y# Field angle# Field speed# = 10 Function create(x,y) bulletTimer = MilliSecs() proj:TProjectile = New TProjectile proj.x = x proj.y = y proj.angle = Rand(0,8) * 45 projectiles.addLast(proj) EndFunction Function update() For proj:TProjectile = EachIn projectiles proj.x :+ proj.speed * Cos(proj.angle) proj.y :+ proj.speed * Sin(proj.angle) DrawOval proj.x,proj.y,20,20 If proj.x<0 Or proj.x>GraphicsWidth() Or proj.y<0 Or proj.y>GraphicsHeight() Then projectiles.remove(proj) Next EndFunction EndType Pour l’utiliser, il faudra appeler la fonction create à partir d’un événement clavier par exemple : IfKeyDown (Key_Space) And bulletTimer < MilliSecs()-1000 Then TProjectile.create(player.x, player.y) Ici, le projectile part du joueur et il faut attendre une seconde avant de pouvoir tirer à nouveau. Il faudra donc ajouter le bulletTimer quelque part, en variable globale par exemple. Il faudra ensuite appeler la fonction update de la classe dans la boucle principale : TProjectile.update() ' Gestion des projectiles Ce paragraphe aurait pu se trouver tout aussi bien dans la partie « Mouvement » car nous allons parler à présent de tous types de collisions : collisions entre le joueur et le paysage, collisions entre des projectiles et des cibles, etc. Comme à l’accoutumée, plusieurs façons de faire : A la main : Penchons-nous sur la collision la plus simple : lorsque deux rectangles se touchent ou se chevauchent. Voici une manière rudimentaire de l’implémenter : Function rectsOverlap(x0, y0, w0, h0, x2, y2, w2, h2) If x0 > (x2 + w2) Or (x0 + w0) < x2 ThenReturnFalse If y0 > (y2 + h2) Or (y0 + h0) < y2 ThenReturnFalse ReturnTrue EndFunction Pour l’utiliser, il faut bien sûr donner la position et la dimension de chaque rectangle impliqué. Au passage, on voit avec cette fonction deux subtilités : quand on ne déclare pas de type en BlitzMax, le type par défaut est Int. Deuxième subtilité, un booléen en BlitzMax n’est autre qu’un Int avec False = 0 et True = autre chose. Un peu plus complexe, la collision entre un rectangle et un cercle : Function circRectsOverlap(x0, y0, w0, h0, cx, cy, r) testX=cX testY=cY If TestX < x0 Then TestX=x0 If TestX > (x0+w0) Then TestX=(x0+w0) If TestY < y0 Then TestY=y0 If TestY > (y0+h0) Then TestY=(y0+h0) Return ((cX-TestX)*(cX-TestX)+(cY-TestY)*(cY-TestY))<r*r EndFunction Les tests à la main peuvent être utiles, soit pour expliciter le code (mais on peut considérer que les fonctions fournies par BlitzMax sont déjà assez explicites), soit pour faire des tests que BlitzMax ne fait pas de base, comme par exemple des tests de collision sur une seule arête d’un rectangle, etc. ImagesCollide : Cette fonction teste si deux images entrent en collision au pixel près ! Voici sa signature : FunctionImagesCollide(image1:TImage,x1,y1,frame1,image2:TImage,x2,y2,frame2) Pour l’utiliser, il suffit de donner une image, sa position et sa frame (0 pour les images simples) puis la même chose pour une deuxième image et la fonction renvoie True s’il y a collision et False sinon. CollideRect : BlitzMax propose des fonctions très avancées pour tester les collisions. Elles utilisent le concept de « masques de collision ». Il y a 32 masques différents que l’on peut utiliser. Attention, ce sont des masques un peu spéciaux, des « bitmasks ». Les étapes pour utiliser ce mécanisme sont les suivantes : a. S’assurer que les masques de collision sont remis à zéro ResetCollisions() b. Ecrire dans le masque de collision les images avec lesquelles on va tester la collision c. Comparer une image avec les images présentes dans le masque de collision Ces deux dernières étapes seront réalisées ici à l’aide de la fonction CollideRect, qui teste la collision entre un rectangle et les objets d’un masque de collision donné. Voici sa signature : FunctionCollideRect:Object[](x,y,w,h,collidemask%,writemask%,id:Object=Null) x, y, w et h correspondent à la position et à la dimension du rectangle dont on va tester la collision. On indiquera le masque de collision avec lequel on veut tester le rectangle dans collidemask le cas échéant. On indiquera le masque dans lequel on veut inclure le rectangle dans writemask. En règle générale, on n’utilisera qu’un seul de ces deux arguments, l’autre sera mis à 0. Voyons à présent à quoi servent l’argument optionnel id et le tableau Object[] retourné par la function. Lorsqu’on veut tester une collision, il est souvent utile de savoir quel objet particulier dans un masque entre en collision avec l’objet testé. Ainsi, lorsqu’on écrit un objet dans un masque, on peut l’identifier à l’aide de l’argument id. Lorsqu’on effectuera un test de collision par la suite, les identifiants des objets en collision avec l’objet testé seront renvoyés dans le tableau Object[]. Voici un exemple d’utilisation de la fonction CollideRect avec au passage la syntaxe pour une sélection de cas : ' Ecriture de deux rectangles dans les masques CollideRect(10,10,100,100,0,1,1) CollideRect(200,200,200,200,0,1,2) ' Test de collision Local p:Object[]=CollideRect(MouseX(),MouseY(),100,100,1,0) ForLocal i=EachIn p Select i Case 1 Print ("Le premier rectangle a été touché") Case 2 Print ("Le second rectangle a été touché") EndSelect Next CollideImage : La fonction CollideImage s’utilise de la même manière que la fonction CollideRect, sauf qu’elle teste des collisions au pixel près entre des images et non des rectangles. Il est d’ailleurs possible de combiner des CollideImage et des CollideRect utilisant le même masque de collisions. Voici la signature de la fonction : FunctionCollideImage:Object[](image:TImage,x,y,frame,collidemask%,writemask%,id:Object=Null) Avec cette fois l’image, sa position et sa frame (0 pour les images simples) en arguments. Notez que cette fonction tient compte des états actuels de rotation et d’échelle ! Rajoutons un système de leurre pour perdre le joueur à la souris ! Ce sera simplement la classe de projectile vue plus haut à rajouter dans le programme. On ajoutera ensuite dans la fonction update de la classe TPlayer : IfKeyDown (Key_Space) And bulletTimer < MilliSecs()-1000 Then TProjectile.create(player.x, player.y) Et la mise à jour dans la boucle principale : TProjectile.update() Ainsi, toutes les secondes, le joueur au clavier pourra lancer un double de lui-même avec une direction aléatoire pour tromper l’autre joueur. Testons ensuite si le joueur à la souris arrive à cliquer sur le joueur au clavier. Nous utiliserons pour cela un test de collision « à la main » entre le point où la souris clique et un carré englobant le joueur. Nous placerons ce test dans la fonction update du joueur : IfMouseDown(1) IfMouseX()<player.x+player.width AndMouseX()>player.x AndMouseY()<player.y+player.height AndMouseY()>player.y DrawText"Caught in " + (MilliSecs() - timeOrigin)/1000 + " seconds", 20, 50 If (MilliSecs() - timeOrigin)/1000 > survival Then survival = (MilliSecs() - timeOrigin)/1000 timeOrigin = MilliSecs() player.x = Rand(0,800) EndIf EndIf Dans Vibz, il existe cinq principaux types de projectiles : le laser rouge, l’onde bleue, les missiles alliés rouges, les bombes et les tirs ennemis. Ces projectiles sont gérés différemment. Les bombes, l’onde bleue et les tirs ennemis ont un comportement très classique, avec une trajectoire linéaire et une vitesse constante. En revanche, le laser rouge a été très complexe à mettre en place. C’est en fait une avalanche de mini-projectiles animés lancés à grande vitesse en face du joueur. La position en x de tous les mini-projectiles qui composent le laser suit celle du joueur. Côté missiles alliés, ils ont trois originalités : une vitesse exponentielle, un angle donné en fonction de la direction du joueur, et ils rebondissent sur les bords du terrain de jeu. Comme c’est notre jour de bonté, nous vous offrons la petite fonction magique pour gérer les trajectoires linéaires et les rebonds des projectiles, à placer avant l’affichage du projectile : ' Arguments : le projectile, les coefficients de déplacements en x et y Function computeAngle(bullet:TBullet,coeffX#=1,coeffY#=1) If bullet.bouncing ' Si le projectile rebondit ... If bullet.x <= leftedge Then bullet.angle = 360-bullet.angle ' Collision avec le bord gauche If bullet.x >= rightedge Then bullet.angle = 360-bullet.angle ' Collision avec le droit EndIf bullet.x :+ Sin(bullet.angle)*coeffX ' Déplacement du projectile bullet.y :+ Cos(bullet.angle)*coeffY SetRotation bullet.angle ' Change l'angle de l'image EndFunction Toutes les collisions sont faites à la main dans Vibz. Nous utilisons des masques de collision rectangulaires adaptés au cas par cas. Ainsi, on utilise moins de mémoire qu’avec des tests au pixel près. De plus, le vaisseau du joueur est assez large et il aurait été extrêmement difficile pour le joueur d’éviter les tirs ennemis sans un masque de collision réduit. Il y a deux types de collision gérés en fait de la même façon : les collisions entre vaisseaux et les collisions entre vaisseaux et projectiles. Si le joueur est touché par un vaisseau ou tir ennemi, il perd une vie. Si un ennemi est touché par un projectile du joueur, il perd des points de vie en fonction de sa « fréquence de résonance » représentée par sa couleur et de la fréquence du tir du joueur. Je veux des effets spéciaux ! Pour travailler les images, les textes ou les formes dans BlitzMax, quelques outils très puissants nous sont donnés à travers la machine à états. Ainsi, il est possible de définir une couleur, un coefficient et un mode de transparence courants respectivement à l’aide de : SetColor 255,255,255 SetAlpha 1 SetBlend ALPHABLEND Les valeurs données ici en arguments sont les valeurs par défaut pour ces états. Les trois arguments de SetColor sont les composantes rouge, verte et bleue de la couleur, de 0 à 255 pour chacune. L’argument de SetAlpha est le coefficient de transparence, entre 0 pour totalement transparent et 1 pour totalement opaque. L’argument de SetBlend est une constante qui doit faire partie de :
Le motion blur est un effet intéressant qui consiste à laisser une trace du déplacement d’un objet. Il peut être utilisé pour représenter un effet de déformation du temps (ralenti, accélération). Nous avons testé deux moyens d’implémenter cet effet : soit en conservant dans une liste les dernières positions de l’objet, soit en les recalculant à partir de l’équation de sa trajectoire. Dans les deux cas, le concept est d’afficher le même objet avec un alpha de plus en plus faible au fur et à mesure qu’on remonte dans ses positions antérieures. Voici une fonction qui affiche cet effet à partir d’une liste de coordonnées et d’une image. En option : l’échelle, la frame et le nombre maximum d’images affichées. Function motionBlur(coordsList:TList, image:TImage, scale# = 1, frame#=0, maxImages# = 20) Local c# = 0 ForLocal coord:coordinate = EachIn coordsList If c Mod (2) = 0 SetBlend alphablend Local csurmax# = c/maxImages SetAlpha(1-csurmax) SetColor (100,100,100) SetScale scale,scale DrawImage image, coord.a, coord.b,frame ' On affiche une fois sur deux EndIf c:+1 If c=maxImages ThenListRemove(coordsList,coord)' Next SetScale 1,1 SetAlpha 1 EndFunction Une particule n’est rien d’autre qu’un objet voué à être fabriqué et affiché dans notre jeu un grand nombre de fois, avec un comportement différent pour chaque instance de ce type d’objet. Il est alors pratique de créer une fonction pour les fabriquer à la chaîne automatiquement. Comme pour les autres objets, nous utiliserons deux étapes principales - La création, qui consistera à créer une particule, lui donner une valeur initiale pour ses attributs et la placer dans une liste de particules. - La mise à jour, qui consistera à les déplacer, les modifier, les afficher et les détruire au bout d’un moment. Voici un exemple de moteur à particule assez impressionnant, à essayer ! N’oubliez pas de rajouter la liste de particules et d’appeler la mise à jour sur ses éléments dans la boucle principale. Type TSpark Extends TEntity Field x#,y#,xs#,ys# ' Position et vitesse Field color[3],rot#,rots# ' Couleur, rotation et vitesse de rotation Field image:TImage ' Image de la particule Method Update() ' Méthode de mise à jour par objet ys:+GRAVITY x:+xs y:+ys If x<0 Or x>=GraphicsWidth() Or y>=GraphicsHeight() remove Return EndIf rot=rot+rots SetHandle 8,8 SetRotation rot# SetColor color[0],color[1],color[2] DrawImage image,x,y SetHandle 0,0 SetRotation 0 SetAlpha 1 EndMethod Function CreateSpark:TSpark( x#,y#,color[],image:TImage ) ' Création et ajout d'une particule dans la liste "sparks" Local spark:TSpark=New TSpark Local an#=Rnd(360),sp#=Rnd(3,5) spark.x=x spark.y=y spark.xs=Cos(an)*sp spark.ys=Sin(an)*sp spark.rots=Rnd(-15,15) spark.color=color spark.image = image spark.AddLast sparks Return spark EndFunction EndType Function firepaint(centerX,centerY,image:TImage, sparksNumber = 3) Local rgb[]=[Rand(0,255),Rand(0,255),Rand(0,255)] ForLocal k=1 To sparksNumber TSpark.CreateSpark centerX,centerY,rgb,image Next EndFunction D’autres moteurs à particules sont implémentés dans Vibz pour donner de nombreux effets spectaculaires (les particules flottantes dans le menu, les impacts sur les vaisseaux ennemis, l’apparition du vaisseau, etc.). Le motion blur est également implémenté de deux manières différentes, l’une pour les vaisseaux, dont les trajectoires ne dépendent pas d’une équation, l’autre pour les tirs ennemis qui en revanchent dépendent d’une équation simple. Le motion blur s’active sur la plupart des objets du jeu en mode ralenti. Voir les classes Effets.bmx et Firepaint.bmx Références pour les tutoriels La documentation officielle : http://www.blitzbasic.com/bmdocs/docs.php Le wiki officiel : http://blitzmax.org/index.php?title=Main_Page La page WikiBooks, très utile pour les bases et détails de syntaxe : http://en.wikibooks.org/wiki/BlitzMax/Language La page essentielle de tutoriels sur le forum de BlitzMax : http://www.blitzbasic.com/Community/topics.php?forum=112s Un tutoriel assez clair en Anglais pour débuter et sa traduction en Français : http://www.truplo.com/docs/BeginnersGuideToBlitzMax10.pdf http://ben.kicks-ass.net/BlitzMaxGuide.txt Un tutoriel assez complet sur certains aspects de BlitzMax : http://www.2dgamecreators.com/tutorials/gameprogramming/ Un tutoriel pour faire un jeu d’arcade : http://rveilleux.googlepages.com/blitzmaxarcadeemulatortutorial Comparaison avec les autres langages
Mots clés:
|
||||||||||||||||||||||||||||||||||||||||||||||||
Powered by MindTouch Deki Open Source Edition v.8.08 |