Tutoriels Blitzmax :
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.
Le concept de machine à états
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.
La syntaxe
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
Les mots clef / types de donnée
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 :
Syntaxe Description Raccourci
Byte Entier non signé de 8 bits @
Short Entier non signé de 16 bits @@
Int Entier signé de 32 bits %
Long Entier signé de 64 bits %%
Float Réel de 32 bits #
Double Réel de 64 bits !
String Chaîne de caractère unicode de 16 bits $
ElementType[] Tableau d’éléments d’ElementType
TypeName Objet du type spécifié
VariableType Ptr Pointeur sur une variable
VariableType Var Variable
Les variables et les constantes
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
Les classes
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
If Rand (1,2) = 1
explosion.image = explosionAnimImage
Else
explosion.image = explosionAnimImage2
EndIf
explosionlist.AddLast (explosion)
End Function
End Type
Les fonctions
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
End Function
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)
Les tests
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 :
If KeyHit(KEY_1) Then Difficulty = 1
If KeyHit(KEY_2) Then Difficulty = 2
If KeyHit(KEY_3) Then Difficulty = 3
Les boucles
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.
For Local x% = 0 To 20 Step 2
Print x/2
Next
For Local x% = 0 Until 11
Print x
Next
Local array[]=[0,1,2,3,4,5,6,7,8,9,10]
For Local 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 GraphicsWidth() Or proj.yGraphicsHeight() Then projectiles.remove(proj)
Next
End Function
End Type
Pour l’utiliser, il faudra appeler la fonction create à partir d’un événement clavier par
exemple :
If KeyDown (Key_Space) And bulletTimer (x2 + w2) Or (x0 + w0) (y2 + h2) Or (y0 + h0) (x0+w0) Then TestX=(x0+w0)
If TestY (y0+h0) Then TestY=(y0+h0)
Return ((cX-TestX)*(cX-TestX)+(cY-TestY)*(cY-TestY))player.x And
MouseY()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
La puissance dans Vibz
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 = 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
End Function
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 !
Couleur, transparence, lumière
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 :
Mode de transparence Effet
MASKBLEND Les pixels sont dessinés uniquement si leur alpha est supérieur à
0.5.
SOLIDBLEND Les pixels remplacent les pixels existants du backbuffer.
ALPHABLEND Les pixels sont dessinés en transparence par-dessus ceux existants
dans le buffer, en fonction de leur alpha.
LIGHTBLEND Les composantes de couleur des pixels sont ajoutées à celles des
pixels déjà existants dans le buffer.
SHADEBLEND Les composantes de couleur des pixels sont multipliées avec celles
des pixels déjà existants dans le buffer.
Motion blur
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
For Local 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 Then ListRemove(coordsList,coord)'
Next
SetScale 1,1
SetAlpha 1
End Function
Les particules
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=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
End Method
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
End Function
End Type
Function firepaint(centerX,centerY,image:TImage, sparksNumber = 3)
Local rgb[]=[Rand(0,255),Rand(0,255),Rand(0,255)]
For Local k=1 To sparksNumber
TSpark.CreateSpark centerX,centerY,rgb,image
Next
End Function
Les effets spéciaux dans Vibz
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
Performances
Compilation
Taille du code, conventions …