Concevoir un jeu d'exploration dans l'espace - Partie 03

< < Concevoir un jeu d'exploration dans l'espace - Partie 02

Tutoriels

Concevoir un jeu d'exploration dans l'espace - Partie 04 > >


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 :

 

Pour l'instant, voici ce que nous avons réalisé jusqu'à présent :

 

Introduction du jeu

Pour l'introduction, nous allons utiliser le DialogManager (voir la documentation).

Il permet d'afficher des textes avec une image de fond. Il est également possible de choisir une musique spécifique.
Mais nous en reparlerons plus en détail ultérieurement.

Vous trouverez le dialogue que j'ai réalisé ici : Fichiers source

Le fichier intro.fil est à copier dans le répertoire datas/menus.

 

Comme toujours, on reprend la structure du Logo pour la nommer Intro.

 

On va rajouter la variable DialogManager juste en dessous de la déclaration du moteur Dina.

local DialogManager = {}

 

On va ensuite l'initialiser dans le load :

DialogManager = Dina("DialogManager", "space", Dina.width, 100, 20)

 

Voyons en détail ses paramètres.

Le premier paramètre correspond à la touche qui sera utilisée pour faire avancer plus vite le texte et passer au texte suivant. Ici, nous avons défini la touche "espace".

Le second paramètre correspond à la largeur que doit occuper le texte. Plus la largeur sera petite, plus vous aurez de lignes affichées. Ici, nous avons pris la largeur complète de l'écran.

Le troisième paramètre correspond à la hauteur estimée du texte.

Le quatrième paramètre correspond à l'espacement entre les bords gauche et droite de la zone d'affichage et le texte.

 

Maintenant, ajoutons nos dialogues :

DialogManager:addDialogs("datas/menus/intro.fil")

 

Voilà, c'est fait. Rien d'autre à faire !

 

Maintenant, nous allons lui indiquer quel dialogue nous voulons afficher.

DialogManager:play("Intro")

 

Il ne nous reste plus qu'à lui indiquer quoi faire quand le dialogue est terminé.

 

Dans le fonction Intro:update, nous allons vérifier si le dialogue est terminé. Et si c'est le cas, nous allons aller à l'écran de jeu.

if DialogManager:isDialogFinished() then
  Dina:setState("game")
end

 

Afin de pouvoir visualiser cette introduction, il y a 2 changements à faire : une première dans le fichier main.lua et la seconde dans le fichier mainmenu.lua.

Dans le fichier main.lua, nous devons rajouter le nouvel état intro comme suit dans le fonction love.load :

Dina:addState("intro", "menus/intro")

Astuce : Vous pouvez mettre les états dans un ordre quelconque, cela n'a aucune incidence sur leur exécution.

 

Dans le fichier mainmenu.lua, on va simplement changer l'état à utiliser par intro dans la fonction LaunchGame.

Dina:setState("intro")

 

Maintenant, vous pouvez lancer le jeu et, au lancement du jeu, vous verrez le dialogue s'afficher en bas de l'écran comme ceci :

 

Il ne nous reste plus qu'à faire en sorte que toutes les touches du clavier ou les boutons du gamepad puissent faire passer au dialogue suivant.

Il suffit de rajouter la ligne suivante dans le load :

Dina:setActionKeys(DialogManager, "continueDialog", "continuous", { "Keyboard", "all" }, { "Gamepad", "all" })

 

Comme nous l'avons vu dans les parties précédentes, cela permet d'exécuter la fonction continueDialog du DialogManager lorsqu'on appuye sur n'importe quelle touche du clavier ou bouton du gamepad.

 

Arrivée sur une nouvelle planète

Création de la planète

En premier, nous allons créer notre planète. Pour cela, nous allons rajouter une fonction Game:createPlanet presque identique à la fonction Game:createAsteroid.

Si vous voulez aller plus vite, vous pouvez copier coller la fonction Game:createAsteroid et la renommer en Game:createPlanet.
Il fautdra bien entendu faire tous les ajustements nécessaires.

 

Comme j'ai trouvé plusieurs images pour les planètes, on va en choisir une parmi toutes (ici, j'ai récupéré 7 images) :

local rnd = love.math.random(7)

 

Comme pour l'astéroïde, on va créer un composant Image dont on va centrer l'origine :

-- Chargement de l'image
local planet = Dina("Image", "datas/images/game/planet_"..rnd..".png")
planet:centerOrigin()

 

Puis, nous allons l'afficher en dehors de l'écran, en haut et au milieu, comme ceci :

-- Ajustement de la position
local _, ph = planet:getDimensions()
planet:setPosition(Dina.width / 2, 0 - ph)

 

Ensuite, nous allons définir la vitesse de déplacement et la rotation (aucune) de la planète :

-- Définition de la vitesse et de la rotation
planet.vx = 0
planet.vy = 1
planet.rspeed = 0

 

Il ne nous reste plus qu'à insérer la planète dans la liste des astéroïdes :

table.insert(Asteroids, planet)

 

Vous devez penser que c'est bizarre de rajouter la planète dans la liste des astéroïdes et je peux le comprendre.
En fait, en faisant comme ça, cela nous permet d'utiliser tout ce qui existe déjà en ne faisant que quelques modifications.

Une première modification est d'indiquer si on vient de créer un astéroïde ou une planète.

Donc, dans la fonction Game:createAsteroid, nous allons indiquer, juste avant la ligne commençant par table.insert, que ce n'est pas une planète :

asteroid.planet = false

 

Et pour la fonction Game:createPlanet, juste avant la ligne commençant par table.insert, que c'est bien une planète :

planet.planet = true

 

Maintenant que nous pouvons créer notre planète, nous allons ajouter le code permettant d'appeler cette fonction.

Mais avant, nous devons définir QUAND elle doit apparaître !
On en profitera pour ne plus faire apparaître d'astéroïdes quand la planète sera affichée et également préparer l'animation.

Il nous faut donc :

Ces 4 variables sont à mettre en dessous des variables asteroidTimer et asteroidDelay.

local addAsteroid
local planetTimer
local planetDelay
local animation

 

De plus, on doit initialiser ces variable dans la fonction load :

addAsteroid = true
planetTimer = 0
planetDelay = 50
animation = false

 

Dans la fonction Game:update, nous allons faire exactement la même chose que pour le timer des astéroïdes :

planetTimer = planetTimer + dt
if planetTimer > planetDelay then
  planetTimer = 0
  self:createPlanet()
  addAsteroid = false
end

 

Ensuite, nous allons rajouter un test pour savoir si nous devons créer de nouveaux astéroïdes ou pas.

Donc, juste avant la ligne asteroidTimer = asteroidTimer + dt, on va rajouter le test pour savoir si on doit ajouter un astéroïde et rajouter le end de ce test juste après la création de la planète pour obtenir ceci :

if addAsteroid then
  asteroidTimer = asteroidTimer + dt
  if asteroidTimer > asteroidDelay then
    asteroidTimer = 0

    -- Création de l'astéroïde
    self:createAsteroid()
  end

  planetTimer = planetTimer + dt
  if planetTimer > planetDelay then
    planetTimer = 0

    -- Création de la planète
    self:createPlanet()

    addAsteroid = false
  end
end

 

Si vous lancez le jeu à cette étape et que vous arrivez à voir la planète, dès que votre vaisseau entre en collision avec elle, vous verrez l'écran de fin.

Comme vous vous en doutez, ce n'est pas ce que l'on veut : on veut afficher une animation puis un mini-jeu de type Lunar-landing.

 

Ajout de l'animation d'entrée dans l'atmosphère

On va déjà commencer par vérifier si l'objet contenu dans la variable asteroid est la planète (attribut planet):
Si oui, alors on indique l'animation doit être jouée (animation = true).
SInon, on indique que c'est la fin du jeu (gamevoer = true)

Vous devriez avoir quelque chose comme ceci :

-- Vérification de la collision entre le vaisseau et l'astéroïde
if CollideAABB(ax - aw/2, ay - ah/2, aw, ah, sx - sw/2, sy - sh/2, sw, sh) then
  if asteroid.planet then
    animation = true
  else
    gameover = true
  end
end

 

Maintenant, nous allons nous attaquer à l'animation avant le lancement du mini-jeu de Lunar-landing.

Comme il s'agit de la rentrée dans l'atmosphère de la planète, j'ai imaginé de faire un petit effet de réduction et de déplacement du vaisseau vers la planète. En gros, on va faire rapeticer le vaisseau tout en le dirigeant vers la planète.

A première vue, cela pourrait vous sembler compliqué mais vous verrez que ça ne l'est pas vraiment. En plus, tout va se passer dans la fonction game:update.

 

On va se créer 3 nouvelles variables :

local minScale
local speedShipAnim
local midScreen

 

Que l'on va ensuite initialiser dans la fonction Game.load :

minScale = 0.2
speedShipAnim = 5
midScreen = Dina.width / 2

 

Dans le fonction Game:update, juste en dessous de la remise à zéro de la rotation du vaisseau, on va rajouter les lignes suivantes :

if animation then
else --if animation

Et pour le end, on le place juste avant la ligne commençant par Dina:update pour obtenir ceci :

end --if animation
Dina:update(dt, false)

Pour mieux me retrouver dans le code, j'ai ris pour habitude de rajouter en commentaire la condition du if. Cela me permet d'avoir une "balise" relativement visible dans le code.

 

Toutes les modifications suivantes pour l'animation sont à rajouter entre le "if animation then" et le "else --if animation".

 

Tout d'abord, nous allons récupérer le ratio actuel du vaisseau et sa position :

-- Ratio du vaisseau
local ssx, ssy = Ship:getScale()
-- Position du vaisseau
local sx, sy = Ship:getPosition()

 

Ensuite, nous allons calculer le pourcentage que représente la valeur du delta-time par rapport à l'écart entre le ratio actuel et le ratio minimum.
Avec la formule, ce sera plus simple à comprendre :

-- Calcul du pourcentage
local percentDtScale = dt / (ssx - minScale) -- on aurait aussi pu utiliser ssy à la place de ssx

 

Ce pourcentage permettra de déplacer le vaisseau d'un nombre de pixels suffisant pour que l'animation puisse sembler quelque peu crédible.

 

Maintenant, nous allons regarder si le vaisseau se trouve à droite ou à gauche du milieu de l'écran. S'il est à gauche (sx < midScreen), on ajoute le pourcentage de la distance restante jusqu'au milieu de l'écran. S'il est à droite, on le retranche.

-- Déplacement du vaisseau
if sx < midScreen then
  sx = sx + (midScreen - sx) * percentDtScale
elseif sx > midScreen then
  sx = sx - (sx - midScreen) * percentDtScale
end

 

Il ne nous reste plus qu'à faire avancer le vaisseau et lui appliquer sa nouvelle position :

sy = sy - speedShipAnim
Ship:setPosition(sx, sy)

 

Maintenant, il ne nous reste plus que la partie facile : le changement du ratio et l'affichage du mini-jeu quand le ratio minimum est atteint (ou dépassé).

Pour le changement de ratio, j'ai fait quelque chose de plus que simple :

ssx = ssx - dt
ssy = ssy - dt
Ship:setScale(ssx, ssy)

 

Pour finir, on vérifie si on a atteint (ou dépassé) le ratio minimum. On en profite également pour supprimer tous les astéroïdes restants :

if ssx < minScale then
  animation = false
  Asteroids = {}
  Dina:setState("landing")
end

On remet la variable animation à false pour que l'animation puisse être rejouée lorsqu'on arrivera sur la planète suivante (après avoir réussi l'atterrissage bien entendu).

Et on vide la variable Asteroids pour ne pas afficher la planète lors du mini-jeu de type Lunar-landing.

 

Un dernier point et non des moindres, on va faire que le vaisseau soit toujours affiché devant la planète.

Comme nous avons créé notre planète après le vaisseau, celle-ci sera toujours affichée en dernier, donc devant notre vaisseau.

Pour corriger cela, on va simplement indiquer, dans la fonction Game:createPlanet, juste après l'ajustement de la position, la ligne suivante :

planet:setZOrder(-50)

 

Et voilà le résultat que l'on obtient :

Dina Space Explorer - Part 03-02

 

Actuellement, vous devriez voir apparaître le message d'erreur suivant dans la console et votre vaisseau partir on ne sait où.

Dina Space Explorer - Part 03-03

 

Pour corriger cette erreur, vous devez effectuer les ajouts suivants :

  1. créer un fichier landing.lua dans le répertoire game avec la structure du Logo.
  2. rajouter l'état landing dans la fonction load du fichier main.lua

 

 

Petit supplément

Je ne sais pas pour vous mais quand je faisais tous mes tests, je me suis posé systématiquement la question suivante : "Quand est-ce que la planète apparaît ?".

Pour remédier à cette situation, je vous propose d'ajouter une barre de progression en haut de l'écran.

 

Nous allons donc créer une nouvelle variable ProgressBar qui va contenir notre barre de progression.

local Progressbar = {}

 

On va la créer dans le fonction Game.load comme suit :

ProgressBar = Dina("ProgressBar",       -- nom du composant
                   5, 5,                -- position
                   Dina.width - 10, 12, -- largeur et hauteur
                   planetDelay,         -- valeur courante
                   planetDelay,         -- valeur maximale
                   Colors.LIME,         -- couleur d'avant-plan
                   Colors.YELLOW,       -- couleur de la bordure
                   Colors.ORANGERED,    -- couleur du fond
                   0,                   -- profondeur d'affichage
                   2)                   -- épaisseur de la bordure

 

Pour plus d'information sur la barre de progression, veuillez consulter la Documentation.

 

Maintenant, il ne nous reste plus qu'à changer la valeur de la barre de progression.

Nous avons défini la valeur maximale comme étant le temps avant l'arrivée de la planète.
Grâce à cela, il nous suffit juste d'utiliser la valeur du timer de la planète dans la barre de progression.

Juste en dessous de l'ajout du dt à planetTimer, on va rajouter la ligne suivante :

ProgressBar:setValue(planetDelay - planetTimer)

Enfin, comme nous sommes arrivé assez proche de la planète, on peut masquer la barre de progression.
Pour cela, il suffit d'ajouter les lignes ci-dessous après la création de la planète :

ProgressBar:setVisible(false)

 

Voici ce que vous devriez avoir :

Dina Space Explorer - Part 03-04

 

 

Petit bogue

J'ai trouvé un bogue assez génant qui permet de "passer" une planète, et ainsi rester bloqué dans l'espace. La raison est simple : les planètes font 640 pixels de large et le vaisseau 68.
Il peut alors se glisser entre le bord de l'écran et la planète vu qu'il reste 80 pixels de chaque côté de la planète.

Pour corriger ce bogue, il suffit de contrôler si la planète a atteint la moitié de la hauteur de l'écran juste avant la suppression des astéroïdes.

if asteroid.planet and ay > Dina.height/2 then
  animation = true
end

 

 

Dans le tutoriel suivant, nous allons finaliser le gameplay de notre jeu en créant le mini-jeu de type Lunar-landing.

 

Le code de ce tutoriel se trouve ici.



< < Concevoir un jeu d'exploration dans l'espace - Partie 02

Tutoriels

Concevoir un jeu d'exploration dans l'espace - Partie 04 > >