Concevoir un jeu d'exploration dans l'espace - Partie 04
< < Concevoir un jeu d'exploration dans l'espace - Partie 03
Concevoir un jeu d'exploration dans l'espace - Partie 05 > >
Ce tutoriel a été réalisé avec la version 3.1.7 du moteur Dina.
Rappel des tutoriels précédents
Notre objectif est de concevoir un jeu qui se composera des éléments suivants :
- un écran pour afficher un logo (celui de votre futur studio, par exemple)
- un écran principal
- un écran pour les crédits (toujours sympa de citer les personnes qui nous ont aidées, par exemple les créateurs d'images ou de musique)
- le jeu en lui-même qui sera découpé comme suit :
- une introduction (pour essayer de mettre le joueur dans l'ambiance du jeu)
- la phase d'exploration qui se limitera à éviter des astéroïdes
- une animation lorsqu'on decouvrira une planète
- un mini-jeu pour se poser sur la planète (de type Lunar-landing)
- un écran de fin
Pour l'instant, voici ce que nous avons réalisé jusqu'à présent :
- un écran pour afficher un logo
- un écran principal
- un début de jeu qui contient pour le moment :
- une introduction
- la phase d'exploration où on doit éviter des astéroïdes
- une animation lorsqu'on découvre la planète
- un écran de fin
Pour finaliser le gameplay du jeu, il ne nous reste plus qu'à concevoir notre jeu d'atterrissage.
Préparations
Si vous ne l'avez pas encore fait, il faut créer le fichier landing.lua dans le répertoire game.
On reprend le même principe que pour le Logo : on charge le moteur Dina et on crée les fonctions load, update et draw.
Si vous avez fait un copier/coller depuis le fichier logo.lua, n'oubliez pas de renommer la variable Logo en Landing.
Ensuite, il faut rajouter le nouvel état dans le fichier main.lua :
Dina:addState("landing", "game/landing")
Avant de commencer à afficher des éléments, on va se créer plusieurs variables pour :
- le vaisseau et sa gestion :
Shipqui contiendra notre vaisseauEnginequi contiendra le "moteur" du vaisseauMovequi nous servira pour les touches d'actions (comme dans le fichiergame.lua)
- le terrain où on va atterrir :
Terrainqui stockera les points du terrainGROUND_HEIGHTqui indiquera la hauteur maximale du terrain
- les éléments de l'interface graphique :
LifeGroupqui contiendra la représentation des viesnbLifesqui indiquera le nombre de vies restantes
- les constantes
MAX_LANDING_SPEEDqui indique la vitesse maximum à avoir pour atterrir en sécuritéMIN_GROUND_LENGTHqui indique la distance minimum qui définit que le vaisseau a atterriGRAVITYqui indique la gravité sur la planèteMAX_SHIP_SPEEDqui indique la vitesse maximum possible du vaisseau (gravité inclus)
local Ship = {}
local Engine = {}
local Move = {}
local LifeGroup = {}
local nbLifes = 0
local GROUND_HEIGHT = 80
local Terrain = {}
local MAX_LANDING_SPEED = 0.5
local MIN_GROUND_LENGTH = 3
local GRAVITY = 0.6
local MAX_SHIP_SPEED = 1.5
Affichage des éléments du jeu
J'ai trouvé une image pour le fond qui reflète bien l'idée du Lunar-landing (lien dans les crédits). Nous allons donc l'ajouter.
Dans la fonction Landing.load, rajoutez les lignes suivantes :
local background = Dina("Image", "datas/images/game/landscape.png")
background:centerOrigin()
background:setPosition(Dina.width/2, Dina.height/2)
background:setZOrder(-100)
Le setZOrer permet de définir l'ordre d'affichage des images : plus il est petit et plus l'image sera affichée en premier.
Ensuite, nous allons afficher notre vaisseau. On va également centrer l'origine de l'image. Vous comprendrez pourquoi ultérieurement.
-- Affichage du vaisseau
Ship = Dina("Image", "datas/images/game/landing_ship.png")
Ship:centerOrigin()
Attention, il ne s'agit pas de la même image que pour le fichier game.lua ! Je vous les fournis dans les sources (lien tout en bas de la page).

Auteur de l'image originelle (celle de droite) : mongames (ship-space-craft)
Maintenant, on va le centrer en haut de l'écran. Pour cela, il nous faut ses dimensions.
local _, sh = Ship:getDimensions()
Et il ne nous reste plus qu'à indiquer la nouvelle position :
Ship:setPosition(Dina.width/2, sh/2)
On va rajouter quelques attributs à notre vaisseau :
- une vitesse latérale que l'on va nommer
vx - une vitesse verticale que l'on va nommer
vy - une vitesse d'accélération que l'on va nommer
speed
Ship.vx = 0
Ship.vy = 0
Ship.speed = 3
Maintenant, nous allons ajouter le "moteur". Bon, en vrai, ce ne sont que les flammes mais cela devrait suffire non ?
Pour que le rendu soit "crédible", j'ai fait en sorte que le centre de l'image soit le même que celui du vaisseau. Vous comprendrez pourquoi plus loin.
--Affichage de l'engin
local sx, sy = Ship:getPosition()
Engine = Dina("Image", "datas/images/game/landing_engine.png", sx, sy)
Engine:centerOrigin()
Mais, on ne va pas afficher notre moteur tout de suite. On va le masquer :
Engine:setVisible(false)
Mouvements du vaisseau (partie 1)
Pour l'instant, tout ce que l'on a, c'est un fond d'écran avec un vaisseau en haut et au centre de l'écran.
Mettons un peu de mouvement dans tout cela !
On va se créer 3 fonctions :
Move:leftpour faire tourner le vaisseau vers la gaucheMove:rightpour faire tourner le vaisseau vers la droiteMove:uppour faire avancer le vaisseau
Ces 3 fonctions recevrons toutes les 2 paramètres suivants :
Forcepour indiquer la force du déplacement à appliquer (valable pour les sticks des gamepads; autrement c'est toujours 1 (100%) )dtqui représente le delta-time
function Move:left(Force, dt)
end
function Move:right(Force, dt)
end
function Move:up(Force, dt)
end
On va rajouter une 4ème fonction Move:move (avec les mêmes paramètres) qui sera appellée depuis Move:left et Move:right.
Comme dans game.lua, ces 2 fonctions font exactement la même chose. Donc, pour éviter de saisir 2 fois le même code (et devoir corriger à 2 emplacements si nécessaire), on se crée une fonction.
function Move:move(Force, dt)
end
Dans Move:left, on rajoute :
self:move(math.abs(Force) * -1, dt)
Et dans Move:right :
self:move(math.abs(Force), dt)
Les déplacements d'une fusée ne sont pas directement de droite à gauche mais elle tourne sur elle-même.
Nous allons donc remplir Move:move.
En premier, nous devons récupérer l'angle de rotation du vaisseau. Facile avec :
local angle = Ship:getRotation()
Ensuite, nous allons calculer l'angle de déplacement que nous ajoutons à l'angle du vaisseau comme ceci :
angle = angle + 90 * dt * Force
Notez que Force contient également la direction de la rotation.
Si vous trouvez que le vaisseau tourne trop lentement, vous devrez changer la valeur 90 par une valeur plus grande (ou une valeur plus petite s'il tourne trop vite).
On vérifie si l'angle est supérieur à 360 degrés: Si c'est le cas, on met l'angle à 0.
Si l'angle est inférieur à 0, on le met à 360 degrés.
if angle > 360 then angle = 0 end
if angle < 0 then angle = 360 end
Il ne reste plus qu'à appliquer la rotation au vaisseau et au moteur.
Ship:setRotation(angle)
Engine:setRotation(angle)
Comme le centre de l'image du vaisseau et celui de l'image du moteur sont identique, on peut les faire tourner de la même façon sans devoir faire des calculs savants.
On va ensuite définir les touches d'actions pour faire tourner notre vaisseau.
Dans landing.load, on doit rajouter les lignes suivantes :
--Définition des touches
Dina:setActionKeys(Move, "left", "continuous", {"Keyboard", "left"}, {"Gamepad", "leftx", -1}, {"Gamepad", "dpleft"})
Dina:setActionKeys(Move, "right", "continuous", {"Keyboard", "right"}, {"Gamepad", "leftx", 1}, {"Gamepad", "dpright"})
On va faire une petite modification qu'il ne faudra pas oublier de changer une fois tout fini (je vous le rappellerai tout de même).
Au lieu de devoir afficher le logo, le menu et d'arriver jusqu'à la planète, on va tricher un peu : on va aller directement au mini-jeu.
Dans le fichier main.lua, on va changer la ligne suivante :
Dina:setState("logo")
Par :
Dina:setState("landing")
Maintenant, si vous lancez le jeu, vous pourrez faire tourner votre vaisseau avec les touches définies.
Interface graphique utilisateur (partie 1)
Si notre joueur n'est pas assez habile, on va lui laisser quelques chances (j'ai opté pour 3). Affichons donc son nombre d'essais à l'écran.
Dans Landing.load, nous allons rajouter 3 images du vaisseau en haut et à gauche de l'écran.
Une première solution serait de créer 3 composants Image. Mais cela vous force à gérer individuellement chaque élément.
J'ai une autre solution qui va demander moins d'effort : utiliser un composant Group (voir la Documentation pour plus de détails).
Commençons par initialiser le nombre de vies et créer le composant Group :
nbLifes = 3
LifeGroup = Dina("Group")
Il y a plusieurs manières pour gérer notre affichage.
On peut :
- positionner chaque image à inclure à la bonne place à l'écran
- mettre les images dans le groupe avec un espacement entre elles puis de positionner le groupe à la bonne place à l'écran
Je vais vous présenter les 2 façons. Ce sera à vous d'utiliser celle que vous comprenez le mieux.
Cas 1 - Théorie
On boucle sur le nombre de vies.
Pour chaque vie, on crée une image.
On positionne ensuite l'image à l'écran.
Puis on ajoute l'image au groupe.
Cas 1 - Pratique
On boucle sur le nombre de vie :
for i = 1, nbLifes do
end
Pour chaque vie (donc, dans la boucle), on crée une image :
local lifeImg = Dina("Image", "datas/images/game/landing_ship.png", 0, 0, 0.2, 0.2)
On positionne ensuite l'image à l'écran :
-- On récupère les dimensions de l'image
local lw, lh = lifeImg:getDimensions()
-- on calcule sa position :
-- 3 : espace à gauche pour ne pas coller sur le bord de l'écran
-- lw * (i - 1) : on ajoute la largeur du vaisseau (sauf pour le premier) pour qu'ils ne s'affichent pas les uns sur les autres
-- 2 * (i - 1) : espace entre les vaisseaux sauf pour le premier vaisseau
local x = 3 + lw * (i - 1) + 2 * (i - 1)
local y = 3
lifeImg:setPosition(x, y)
Puis on ajoute l'image au groupe.
LifeGroup:add(lifeImg)
Cas 2 - Théorie
On boucle sur le nombre de vies.
Pour chaque vie, on récupère les dimensions du groupe.
On crée l'image.
On positionne l'image à côté du groupe en ajoutant un décalage.
On ajoute l'image au groupe.
On positionne le groupe à l'écran.
Cas 2 - Pratique
On boucle sur le nombre de vie :
for i = 1, nbLifes do
end
Pour chaque vie (donc dans la boucle), on récupère les dimensions et le nombre d'éléments du groupe :
local lgw, lgh = LifeGroup:getDimensions()
local nbi = LifeGroup:nbComponents()
On crée l'image.
local lifeImg = Dina("Image", "datas/images/game/landing_ship.png", 0, 0, 0.2, 0.2)
On positionne l'image à côté du groupe en ajoutant un décalage :
local x = lgw + 2 * (nbi - 1)
local y = 0
lifeImg:setPosition(x, y)
On ajoute l'image au groupe.
LifeGroup:add(lifeImg)
On positionne le groupe à l'écran. Donc, après la boucle, on rajoute :
LifeGroup:setPosition(3,3)
J'ai laissé les 2 versions dans le code pour que vous puissiez voir une autre façon de procéder.
Construction du terrain
Pour construire le terrain d'atterrissage, je suis parti d'un tutoriel que j'avais réalisé : Générateur de sol pour Lunar-Landing.
Il a été adapté pour utiliser le moteur Dina.
Au début de cette partie du tutoriel, je vous ai fait rajouter 2 variables :
local GROUND_HEIGHT = 80
local Terrain = {}
On va créer une nouvelle fonction ConstructTerrain qui prendre 2 paramètres :
-- Fonction pour la construction du terrain
local function ConstructTerrain(minSections, maxSections)
end
En premier, nous allons calculer la coordonnée minimum en Y du terrain :
local minTerrain = Dina.height - GROUND_HEIGHT
Ensuite, nous allons calculer la taille minimale et maximale de chaque section :
local maxLargeurSection = math.floor(Dina.width / minSections)
local minLargeurSection = math.ceil(Dina.width / maxSections)
Puis, on calcule les coordonnées du premier point (on le positionne àdroite de l'écran) :
local x = 0
local y = love.math.random(minTerrain, Dina.height)
Ensuite, on va boucler tant que la coordonnée en X ne dépasse pas la largeur de l'écran.
On ajoute ce point dans la liste des points (la variable Terrain) puis on recalcule un nouveau point.
while (x < Dina.width) do
local vector = Dina("Vector", x, y)
table.insert(Terrain, vector)
-- Calcul d'une nouvelle position
x = x + love.math.random(minLargeurSection, maxLargeurSection)
y = love.math.random(minTerrain, Dina.height)
end
Puis, pour finir, on crée le dernier point le plus à droite de l'écran qu'on ajoute dans la liste des points :
table.insert(Terrain, Dina("Vector", Dina.width, Terrain[1].y))
Vous comprendrez plus loin pourquoi j'utilise la même hauteur que le premier point.
Il ne nous reste plus qu'à appeler la fonction dans Landing.load :
love.math.setRandomSeed(love.timer.getTime()) -- pour avoir un terrain différent à chaque fois
--Construction du terrain
ConstructTerrain(10,20)
J'ai mis un minimum de 10 sections et un maximum de 20 sections mais vous pouvez augmenter ou réduire ces nombres, dépendant de ce que vous voulez obtenir.
Les valeurs 10 et 20 me semblent être un bon compromis entre des zones trop petites (donc trop escarpées) ou trop grande (donc peu de dénivelé).
Maintenant que nous avons créé nos points, on va les afficher pour former le terrain.
Pour cela, on va simplement boucler sur la liste de points tout à la fin de Landing:draw.
for ind=1,#Terrain - 1 do
local pointA = Terrain[ind]
local pointB = Terrain[ind + 1]
love.graphics.line(pointA.x, pointA.y, pointB.x, pointB.y)
end
Important : on s'arrête à l'avant-dernier point.
Gravité et collision
A ce stade, on affiche le sol de la planète, notre vaisseau et le nombre de vies disponibles.
On va rajouter un petit élément : la gravité.
Dans Landing:update, après l'appel de Dina:update, nous allons rajouter la ligne suivante :
-- Application de la gravité sur le vaisseau
Ship.vy = Ship.vy + (GRAVITY * dt)
Cette ligne permet de simuler la gravité.
On va ensuite mettre à jour la position du vaisseau et du moteur. Pour cela, nous allons (encore) créer une nouvelle fonction :
local function MoveShip()
end
En premier lieu, on va limiter la vitesse de déplacement :
-- Limitation des vitesses de déplacement
if math.abs(Ship.vx) > MAX_SHIP_SPEED then
if Ship.vx > 0 then
Ship.vx = MAX_SHIP_SPEED
else
Ship.vx = MAX_SHIP_SPEED * -1
end
end
if math.abs(Ship.vy) > MAX_SHIP_SPEED then
if Ship.vy > 0 then
Ship.vy = MAX_SHIP_SPEED
else
Ship.vy = MAX_SHIP_SPEED * -1
end
end
Ensuite, on va repositionner le vaisseau :
--Positionnement du vaisseau
local sx, sy = Ship:getPosition()
Ship:setPosition(sx + Ship.vx, sy + Ship.vy)
Et le moteur à la même position que le vaisseau :
--Positionnement du moteur
sx, sy = Ship:getPosition()
Engine:setPosition(sx, sy)
Enfin, on rajoute l'appel de la fonction MoveShip juste après le calcul de la gravité :
-- Application de la gravité sur le vaisseau
Ship.vy = Ship.vy + (GRAVITY * dt)
--Déplacement du vaisseau
MoveShip()
Maintenant que notre vaisseau "tombe", on va regarder s'il entre en collision avec le sol.
Comme notre sol est composé de sections, on va rechercher sous quelle section se trouve notre vaisseau.
Pour cela, on va parcourir l'ensemble des points et regarder si la position du vaisseau en X est entre 2 points (ou sur un point).
Voici ce que cela donne :
local function SearchSectionX(posX)
local ind=1
for ind=1,#Terrain - 1 do
if Terrain[ind].x <= posX and posX < Terrain[ind+1].x then
return ind
end
end
return 0
end
Cette fonction ne retourne 0 que dans les cas suivants :
- la liste
Terrainne contient aucun éléments (oubli d'appeler la fonctionConstructTerraindansLanding.load) - le vaisseau est en dehors de l'écran
On va ensuite récupérer la hauteur de la section exactement sous la position du vaisseau.
Je vous invite à lire mon tutoriel Générateur de sol pour Lunar-Landing pour plus d'explications (troisième étape).
local function getTerrainHauteur(posX)
local debSection = SearchSectionX(posX)
local dx = Terrain[debSection + 1].x - Terrain[debSection].x
local as = posX - Terrain[debSection].x
local dy = Terrain[debSection + 1].y - Terrain[debSection].y
local ap = dy * (as / dx)
return Terrain[debSection].y + ap
end
Dans Landing:update, après avoir déplacé le vaisseau (appel de MoveShip), on va lancer le calcul de la hauteur du sol. On en profite pour récupérer la position et la dimension du vaisseau :
--Vérification de la collision avec le terrain
local GroundY = getTerrainHauteur(Ship.x)
local sx, sy = Ship:getPosition()
local _, sh = Ship:getDimensions()
Après avoir calculé la hauteur du sol, on va vérifier si le vaisseau ne rentre pas en collision avec le sol.
Comme nous avons centré l'origine de l'image, on va devoir ajouter une demi-hauteur du vaisseau à sa position.
Comme notre vaisseau est en mouvement (gravité oblige), on va vérifier si la différence entre la position du sol et celle du vaisseau est inférieure à une valeur minimum (ici, MIN_GROUND_LENGTH vaut 3 pixels). Si c'est inférieur à 0, c'est que le vaisseau est en dessous du sol, donc il s'est écrasé...
Ensuite, on va contrôler la vitesse d'atterrissage. Si elle est supérieure à MAX_LANDING_SPEED (qui vaut 0.5), la vitesse est trop grande et il s'écrasera au sol.
Bien entendu, si le vaisseau s'écrase, le joueur perd une vie et on repositionne le vaisseau à sa position de départ. Si le joueur n'a plus de vie, on affiche le GameOver.
Autrement, on va stopper la vitesse et la rotation du vaisseau, repositionner le vaisseau et le moteur et indiquer que le vaisseau vient d'atterrir (Ship.landed = true).
if (GroundY - (sy + sh/2)) < MIN_GROUND_LENGTH then
if Ship.vy < MAX_LANDING_SPEED then
--Atterrissage réussi
Ship.landed = true
Ship.vx = 0
Ship.vy = 0
local y = GroundY - sh/2
Ship:setPosition(sx, y)
Ship:setRotation(0)
Engine:setVisible(false)
Engine:setPosition(sx, y)
Engine:setRotation(0)
else
--Atterrissage manqué
Ship:setPosition(Dina.width/2, sh/2)
nbLifes = nbLifes - 1
LifeGroup:removeComponent(LifeGroup.components[LifeGroup:nbComponents()])
if nbLifes < 1 then
Dina:setState("gameover")
end
end
end
Il y a un point dans le code ci-dessus qui doit être expliqué.
Quand le joueur s'est écrasé au sol, on repositionne le vaisseau à sa position de départ (ça vous l'avez compris).
Puis on décrémente nbLifes. Jusqu'ici, vous devriez avoir tout compris.
La ligne suivante est plus difficile à appréhender car c'est elle qui permet de retirer une vie de l'affichage. Avec quelques détails, vous allez mieux comprendre.
Comme nous avons utilisé un groupe, c'est lui qui gère la position, la mise à jour et l'affichage de tous les composents qu'il contient.
Donc, il nous suffit de supprimer le dernier élément du groupe pour que celui-ci ne soit plus affiché.
Pour vous simplifier la compréhension, je vais décomposer la ligne : LifeGroup:removeComponent(LifeGroup.components[LifeGroup:nbComponents()])
Pour supprimer le dernier élément du groupe, on va récupérer le nombre d'éléments dans le groupe :
local nbElems = LifeGroup:nbComponents()
Ensuite, on va récupérer le dernier élément du groupe :
local lastElem = LifeGroup.components[nbElems]
Puis, on le supprime du groupe :
LifeGroup:removeComponent(lastElem)
Donc la ligne :
LifeGroup:removeComponent(LifeGroup.components[LifeGroup:nbComponents()])
équivaut aux lignes suivantes :
local nbElems = LifeGroup:nbComponents()
local lastElem = LifeGroup.components[nbElems]
LifeGroup:removeComponent(lastElem)
Comme par magie, lorsqu'on retire un élément du groupe, il n'est plus affiché ("évidemment", vous me direz).
Mais, et c'est là où est la "force" de Dina, c'est qu'il ne faut que la ligne ci-dessous dans Landing:draw pour que la magie opère :
Dina:draw(false)
Mouvements du vaisseau (partie 2)
Maintenant, je vous entends me dire : "OK, notre vaisseau subit la gravité mais comment on allume les moteurs ?".
Et bien, on s'en occupe de suite.
Je vous avais fait préparer une fonction Move:up dans la première partie des mouvements du vaisseau. Nous allons la remplir !
Dans cette fonction Move:up, on va, dans un premier temps, vérifier si le vaisseau est posé.
On enclenche les moteurs (c'est juste les rendre visibles mais ça fait plus classe je trouve).
Ensuite, on va calculer la force de poussée des moteurs selon l'angle du vaisseau.
Pour cela, on a besoin de l'angle de rotation du vaisseau, de sa vitesse et de la force mise sur le stick du gamepad (entre -1 et 0 ou entre 0 et 1).
Info : si le joueur utilise le clavier ou un bouton, la force est toujours de 1.
De plus, si la force calculée est inférieure à 0.001, on considère qu'elle vaut 0.
Enfin, on va appliquer cette force à la vitesse du vaisseau.
Info : si vous ne le saviez pas, deux forces s'additionnent toujours.
Voici ce que cela donne :
function Move:up(Force, dt)
if not Ship.landed then
Engine:setVisible(true)
local angle = math.rad(Ship:getRotation())
local force_x = math.sin(angle) * (Ship.speed * dt) * math.abs(Force)
local force_y = math.cos(angle) * (Ship.speed * dt) * math.abs(Force) * -1
if math.abs(force_x) < 0.001 then force_x = 0 end
if math.abs(force_y) < 0.001 then force_y = 0 end
Ship.vx = Ship.vx + force_x
Ship.vy = Ship.vy + force_y
end
end
Pour que le joueur puisse enclencher les moteurs, il ne reste plus qu'à définir les touches d'action dans Landing.load.
Dina:setActionKeys(Move, "up", "continuous", {"Keyboard", "up"}, {"Gamepad", "lefty", -1}, {"Gamepad", "dpup"})
On va rajouter les ligne ci-dessous au tout début de la fonction Landing:update :
--On masque le moteur
Engine:setVisible(false)
Cela permet de "couper" les moteurs.
Résolution de problèmes
Actuellement, il y a les problèmes suivants :
- Si le vaisseau dépasse l'un des bords, le jeu crashe avec un message d'erreur
- Quand on a atterri, on peut faire tourner le vaisseau
Et un dernier problème, visible uniquement si on refait une tentative après un GameOver :
- Lorsqu'on arrive sur le jeu de Lunar-landing, le sol est affiché plusieurs fois.
On va régler les problèmes du plus facile au plus compliqué.
Problème 1 - Sol affiché plusieurs fois
Avoir plusieurs sols affichés nous indique que la liste des points n'a pas été effacée. Pour régler cela, il suffit de rajouter la ligne ci-dessous avant le changement d'état :
if nbLifes < 1 then
Terrain = {} --efface la liste des points
Dina:setState("gameover")
end
Problème 2 - Faire tourner lorsqu'on a atterri
Pour ne plus avoir ce problème, on va empêcher le vaisseau de tourner quand le joueur appuie sur la touche d'action.
Pour cela, nous allons rajouter une condition dans la fonction Move:move :
function Move:move(Force, dt)
if not Ship.landed then --le vaisseau n'a pas atterri donc on peut le faire tourner
local angle = Ship:getRotation()
angle = angle + 90 * dt * Force
if angle > 360 then angle = 0 end
if angle < 0 then angle = 360 end
Ship:setRotation(angle)
Engine:setRotation(angle)
end --if not Ship.landed
end
Problème 3 - Crash du jeu lorsque le vaisseau sort de l'écran
Pour ce problème, on va simplement faire en sorte que le vaisseau change de position.
De plus, on va utiliser 2 autres images du vaisseau et de son moteur. Cela permettra de faire que le vaisseau ne se téléporte pas pour le joueur.
On va donc créer 4 nouvelles variables :
ShipLeftetEngineLeftpour le vaisseau qui sera affiché à gauche de l'écranShipRightetEngineRightpour le vaisseau qui sera affiché à droite de l'écran
local ShipLeft = {}
local EngineLeft = {}
local ShipRight = {}
local EngineRight = {}
Ensuite, nous allons créer les 2 nouveaux vaisseaux et leurs engins dans Landing.load.
Les lignes ci-dessous doivent être insérées juste après la création du moteur
--Affichage de l'engin
local sx, sy = Ship:getPosition()
Engine = Dina("Image", "datas/images/game/landing_engine.png", sx, sy)
Engine:centerOrigin()
Engine:setVisible(false)
-- Création de la copie gauche du vaisseau
ShipLeft = Dina("Image", "datas/images/game/landing_ship.png")
ShipLeft:centerOrigin()
ShipLeft:setPosition(sx - Dina.width, sy) --on retire la largeur de l'écran
local slx, sly = ShipLeft:getPosition()
EngineLeft = Dina("Image", "datas/images/game/landing_engine.png", sx, sy)
EngineLeft:centerOrigin()
EngineLeft:setVisible(false)
EngineLeft:setPosition(slx, sly)
-- Création de la copie droite du vaisseau
ShipRight = Dina("Image", "datas/images/game/landing_ship.png")
ShipRight:centerOrigin()
ShipRight:setPosition(sx + Dina.width, sy) --on ajoute la largeur de l'écran
local srx, sry = ShipRight:getPosition()
EngineRight = Dina("Image", "datas/images/game/landing_engine.png", sx, sy)
EngineRight:centerOrigin()
EngineRight:setVisible(false)
EngineRight:setPosition(srx, sry)
Dans la fonction Move:up, on va allumer les moteurs des copies en même temps que l'original :
Engine:setVisible(true)
EngineLeft:setVisible(true)
EngineRight:setVisible(true)
Dans la fonction Move:move, on rajoute les lignes suivantes juste après le changement d'angle du vaisseau et du moteur pour avoir le même angle pour les 3 vaisseaux et leurs moteurs respectifs :
Ship:setRotation(angle)
Engine:setRotation(angle)
EngineLeft:setRotation(angle)
ShipLeft:setRotation(angle)
ShipRight:setRotation(angle)
EngineRight:setRotation(angle)
Dans la fonction MoveShip, juste après le positionnement du vaisseau, on va vérifier si le vaisseau est en dehors de l'écran. Si oui, on le téléporte de l'autre côté et on change les positions des 2 autres vaisseaux. Avec ça, on a l'impression que le vaisseau passe d'un côté à l'autre tout à fait normalement alors qu'il se téléporte. Sympa ce petit truc non ?
--Positionnement du vaisseau
local sx, sy = Ship:getPosition()
Ship:setPosition(sx + Ship.vx, sy + Ship.vy)
-- Téléportation du vaisseau de l'autre côté
sx, sy = Ship:getPosition()
if sx < 0 then
Ship:setPosition(Dina.width - sx, sy)
elseif sx > Dina.width then
Ship:setPosition(sx - Dina.width, sy)
end
--Positionnement du moteur
sx, sy = Ship:getPosition()
Engine:setPosition(sx, sy)
--Positionnement de la copie gauche du vaisseau
ShipLeft:setPosition(sx - Dina.width, sy)
local slx, sly = ShipLeft:getPosition()
EngineLeft:setPosition(slx, sly)
--Positionnement de la copie droite du vaisseau
ShipRight:setPosition(sx + Dina.width, sy)
local srx, sry = ShipRight:getPosition()
EngineRight:setPosition(srx, sry)
Enfin, nous allons refaire la vérification de la collision du vaisseau :
if (GroundY - (sy + sh/2)) < MIN_GROUND_LENGTH then
if Ship.vy < MAX_LANDING_SPEED then
--Atterrissage réussi
Ship.landed = true
Ship.vx = 0
Ship.vy = 0
local y = GroundY - sh/2
Ship:setPosition(sx, y)
Ship:setRotation(0)
Engine:setVisible(false)
Engine:setPosition(sx, y)
Engine:setRotation(0)
else --if Ship.vy < MAX_LANDING_SPEED
--Atterrissage manqué
Ship:setPosition(Dina.width/2, sh/2)
MoveShip() --ici, c'est pour forcer le calcul des positions de tous les vaisseaux
nbLifes = nbLifes - 1
LifeGroup:removeComponent(LifeGroup.components[LifeGroup:nbComponents()])
if nbLifes < 1 then
Terrain = {} -- efface la liste des points
Dina:setState("gameover")
end
end --if Ship.vy < MAX_LANDING_SPEED
end --if (GroundY - (sy + sh/2)) < MIN_GROUND_LENGTH
J'ai obtenu un crash lorsque je faisais les tests de cette correction. Il s'est avéré que la fonction SearchSectionX retournait 0. Pour éviter cela, on va simplement modifier le premier et le dernier point pour respectivement retirer 1 pixel et ajouter 1 pixel.
Donc, dans la fonction ConstructTerrain, on va changer la ligne :
local x = 0
Par :
local x = -1
Et la ligne :
table.insert(Terrain, Dina("Vector", Dina.width, math.random(minTerrain, Dina.height)))
Par :
table.insert(Terrain, Dina("Vector", Dina.width + 1, math.random(minTerrain, Dina.height)))
Ici, on a ajouté le + 1.
Vous devriez avoir ceci :


Le code de ce tutoriel se trouve ici.
Me contacter