Tout au long de cet article nous allons créer un jeu très simple avec les principales fonctions nécessaires pour la conception de jeux plus complexes.
Le mouvement est simulé dans les jeux informatique de la même manière qu'à la télévision ou au cinéma. Une séquence d'images (le contenu de la fenêtre graphique GraphicsWindow dans Small Basic) qui changent par petite zones et qui sont affichées rapidement les unes après les autres. Habituellement nous cherchons à atteindre une cadence de 20 à 50 FPS (Frames Per Second - Images Par Seconde).
Pour contrôler la rapidité de l'action nous faisons souvent une petite pause pendant chaque image pour maintenir la cadence à la vitesse désirée. Donc tout ce qui doit être fait à chaque image doit être fait durant ce laps de temps, sinon le jeu va ralentir ou être trop "nerveux". Une conception soignée du programme est nécessaire pour s'assurer que tout fonctionne avec fluidité. Il est souvent préférable de faire quelque chose d'approximatif et qui suit la cadence, que d'en faire trop et avoir des ralentissements. Le gameplay est bien plus important que les calculs compliqués.
Un jeu contient habituellement un arrière-plan et des sprites. Les sprites sont des objets qui peuvent bouger et interagir par dessus l'arrière-plan qui est statique. L'arrière-plan peut changé avec le jeu, mais il ne doit pas être trop complexe pour maintenir la fluidité du jeu.
Généralement nous avons phases pour mettre à jour une image, elles sont:
La tentation lorsque l'on commence à gérer les mouvements dans Small Basic est d'utiliser Shapes.Animate. C'est très facile à utiliser et idéale pour les animations visuelles comme les écrans d'introductions fantaisie avec des apparitions de texte animé. Toutefois, ce n'est pas très adapté pour un jeu dynamique ou nous avons besoin de savoir où chaque élément se trouve dans l'image et ainsi pouvoir détecter les collision ou d'autres aspects du jeu lorsqu'ils dépendent de la position des éléments.
Les 3 phases décrites précédemment sont exécutées séquentiellement à chaque mise à jour d'image. Cela se fait habituellement dans une "Boucle de Jeu" répétée continuellement (boucle infinie). Souvent dans Small Basic la boucle de jeu est codée avec une boucle 'While ("True")' qui s'exécute indéfiniment pendant que le jeu évolue, juste en mettant à jour l'écran pendant le déroulement du jeu.
En plus de la boucle de jeu, nous avons généralement d'autres sections dans notre code, comme:
Le code suivant est un bon début pour structurer un jeu graphique. Nous le compléterons un peu plus dans les sections suivantes, mais globalement ceci sera 'votre jeu'.
Initialise
(
)
'------------------------------------------------
'BOUCLE DE JEU
While
"True"
start
=
Clock
.
ElapsedMilliseconds
UpdateGamePlay
UpdateFrame
'Un délai de 20 ms (pour 50 fps) dépendant du temps passé à faire le traitement de mise à jour de l'image
delay
20
-
If
>
0
Then
Program
Delay
EndIf
EndWhile
'SOUS-ROUTINES
Sub
'Enregistre les événements
GraphicsWindow
KeyDown
OnKeyDown
KeyUp
OnKeyUp
MouseMove
OnMouseMove
MouseDown
OnMouseDown
'A FAIRE
EndSub
'SOUS-ROUTINES D'EVENEMENT
Nous pouvons définir un événement pour détecter la position de la souris et si un clic a été effectué. Il s'agit simplement de définir des variables pour indiquer ce qu'il s'est passé.
Pour prendre en compte les mouvements de la souris et les clics, nous ajoutons le code suivant.
mouseMove
mouseDown
Définir les indicateurs 'mouseMove' et 'mouseDown' initialement à "False" dans la sous-routine Initialise.
'Indicateurs d'événement
"False"
'TODO
Pour finir définissons le code pour traiter l'événement dans la boucle de jeu, sous-routine UpdateGamePlay.
'Réinitialise l'événement à "False" (car nous l'avons traité)
ouseDown
Nous avons encore des choses à faire avec ces événement, mais la structure pour les gérer est en place. A noter que nous pouvons obtenir la position de la souris avec GraphicsWindow.MouseX et GraphicsWindow.MouseY, par conséquent il n'est pas nécessaire de les enregistrer dans les événements de la souris.
C'est le même principe que la gestion de la souris, sauf sur un point. Lorsqu'une touche est appuyée, il y a un délai imposé par le système d'exploitation avant qu'un autre appui de touche soit enregistré (délai d'auto répétition). C'est la raison pour laquelle lorsque nous appuyons sur une touche nous n'obtenons pas un caractère à répétition tant que nous n'avons pas relevé la touche avant une certaine période de temps (généralement une demi-seconde).
Dans un jeu, nous voulons détecter qu'une touche a été appuyée et utiliser l'événement d'appui de touche détectera l'appui initial de la touche, et donc ne va pas enregistrer que la touche reste appuyée jusqu'à ce que le délai d'auto répétition soit passé, ce qui provoque un délai entre le premier appui de touche et les suivants.
Pour contourner cela nous pouvons les événements d'appui et de relâchement de touche pour définir l'état des touches qui nous intéressent, sans se préoccuper du délai d'auto-répétition.
lastKey
LastKey
"Left"
keyLeft
ElseIf
"Right"
keyRight
"Up"
keyUp
"Down"
keyDown
Initialisation de l'état des touches dans la sous-routine Initialise.
On gère les indicateurs de touche dans UpdateGamePlay. A noter que nous ne réinitialisons pas les indicateurs de touches avec "False" étant donné que c'est géré dans l'événement OnKeyUp et que nous voulons rester réactif tant que la touche est appuyée. Si nous voulons seulement réagir à chaque appui de touche individuellement (peut-être la barre espace pour tirer), alors nous réinitialiserons à "False" l'indicateur une fois que nous aurons fait notre traitement, imposant ainsi un nouvel appui pour provoquer une nouvelle action.
Jusqu'à présent nous avons mis en place une structure de base, mais nous n'avons pas encore de jeu. C'est à l'inverse de ce que la plupart des débutants commence à écrire pour un jeu - ils ont les sprites, les règles et les images, mais la structure et la gestion n'est pas encore proprement défini.
La leçon à retenir c'est tout d'abord d'avoir la structure et la gestion, ensuite nous ajoutons le gameplay et le contenu.
Donc maintenant nous allons ajouter quelques sprites, ici que des rectangles - encore une fois l'idée est d'avoir une base de jeu et ensuite d'ajouter les détails, pas l'inverse.
Nous ajouterons quelques ennemis et un joueur. Les ennemis seront stockés dans des tableaux - nous avons besoin d'enregistrer les objets sprites ainsi que leur position et leurs vitesses.
Nous créons les sprites dans la sous-routine Initialise.
'GraphicsWindow
Title
"Mon Jeu"
gw
600
gh
Width
Height
'Indicateurs d'événements
'Sprites
size
50
BrushColor
"Blue"
numEnemy
5
For
i
1
To
enemy
[
]
Shapes
AddRectangle
,
enemyPosX
+
Math
GetRandomNumber
100
enemyPosY
enemyVelX
3
enemyVelY
EndFor
"Red"
player
playerSpeed
playerPosX
/
2
playerPosY
*
Ensuite nous partons du principe que le mouvement du joueur est basé sur les touches fléchées, nous utilisons une variable playerSpeed pour contrôler la vitesse du joueur. C'est une bonne idée d'utiliser des variables plutôt que juste entrer les valeurs quand c'est possible; cela nous permet de les modifier facilement (même pendant le jeu pour accélérer ou ralentir le joueur). Notons également que nous ne laissons pas le joueur sortir de l'écran.
'Réinitialise l'événement à "False" (nous l'avons traité)
<
Nous allons définir que les ennemis se déplacent avec leurs vitesses, rebondissent sur les murs dans la sous-routine UpdateGamePlay. Sur un rebond contre un mur nous modifions la vitesse et replaçons le sprite de l'ennemi sur le mur; ça évite que les ennemis apparaissent partiellement sortis de l'écran si leur vitesse est grande.
'Mouvement des ennemis
Pour finir nous actualisons l'affichage du joueur et des ennemis dans la sous-routine UpdateFrame.
Move
A noter que la position des sprites que nous utilisons et celle de leur centre, et que l'opération Shapes.Move utilise la position du coin supérieur gauche, par conséquent nous soustrayons la moitié de la largeur (et de la hauteur) pour le déplacement. Ça facilite la logique pour plus tard si nous faisons des calculs basé sur le centre de la forme au lieu du coin supérieur gauche et nécessite uniquement un offset lors du Shapes.Move.
De base, la détection de collision cherche si un objet est par dessus un autre. Il y a deux méthodes simples pour faire ça:
Nous utiliserons la seconde méthode, lorsqu'un ennemi touche le joueur. Nous allons créer une sous-routines appelée Collision, et déterminer si un des ennemis chevauche le joueur en utilisant la méthode 2 ci-dessus.
Collision
sepX
Abs
sepY
And
Sound
PlayClick
AndWait()
Nous appelons cette sous-routines à la fin de UpdateGamePlay.
Ce n'est pas encore un jeu et nous n'avons même pas utilisé les événements souris, mais les bases sont là pour commencer à ajouter du contenu intéressant. Voici le code complet.
'Mouvement du joueur
'Vérification des collisions
PlayClickAndWait
Si vous créez ou supprimez des sprites pendant le jeu, il y a quelques précautions à prendre. En particulier si vous en avez terminé avec un sprite ne vous contentez pas de le masquer (avec Shapes.Hide) ou de le sortir de l'écran et de l'oublier, supprimez le (Shapes.Remove). Si vous supprimez pas les sprites inutilisés votre jeu va ralentir. Ça peut se compliquer lorsque les sprites sont stockés dans des tableaux, lorsque vous devez supprimer tous les éléments de tableau associés à ce sprite (forme, position, vitesse etc), qui peuvent laisser les tableaux avec quelques éléments oubliés, ainsi de simple boucles For sur les indexes de ces tableaux peuvent échouer ou être inefficace.
Une bonne méthode pour gérer cela est de masquer temporairement les sprites, mais de les réutiliser en les affichant lorsqu'un nouveau sprite de même type (par exemple un missile) est nécessaire.
Par conséquent soyez prudent lorsque vous créez un nouveau sprite pendant le jeu (pas lors de l'initialisation). Ne les laissez pas s'accumuler sans discernement.
Avoir des sprites qui rebondissent sur les autres de manière réaliste peut être compliqué. Fondamentalement nous avons besoin de faire la collision sur le centre de gravité du cadre de référence en utilisant la géométrie comme la figure ci-dessous, puis convertir depuis le centre de gravité du cadre vers le cadre. Traduction de la figure (Note du Traducteur : mes notions de mathématiques sont un peu floues dans ce contexte, j'ai donc des doutes sur ma traduction, n'hésitez pas à laisser un commentaire ou à modifier cet article si ce n'est pas correct) : B et C sont les centre des balles en collision. AB est la vitesse de l'incidence du centre de gravité de la balle B BD est la vitesse du rebond du centre de gravité de la balle B. BC est le vecteur entre les centres des balles, soit le vecteur d'unité dans cette direction est n B est donc -2n.AB (2* produit scalaire) DE = -AB BE = BD+DE = BD-AB BD = AB-2n.AB
Voici un exemple de l'implémentation de tout ça dans Small Basic.
CollisionCheck
Ball_Number
j
dx
Ball_X
dy
Ball_Y
Distance
SquareRoot
Ball_Diameter
Cx
Ball_vX
ball_vX
Cy
Ball_vY
ball_vY
Relative_vX
Relative_vY
Nx
Ny
L
Un moteur physique prend en charge les collisions dynamiques pour vous, résolvant les forces et interactions des objets dynamiques. Vous interagissez uniquement avec ce genre de simulation en appliquant des forces et couples sur les objets, plutôt que de calculer les vitesse par vous-même.
L'extension LitDev (LDPhysics) utilise le moteur physique Box2D et à beaucoup d'exemple et de documentation. Elle est plutôt évoluée et des bases de compréhension de la physique est nécessaire.
Jusque là nous n'avons étudiez que les modèles 2D. La 3D est considérablement plus complexe à code par soi-même et le nombre élevé de calculs géométriques et de rendu nécessite l'utilisation de méthode d'accélération matérielle.