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

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

Tutoriels

 


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 :

 

Interface graphique utilisateur (partie 2)

Lorsque nous atterrissons, nous avons défini une valeur fixe dans MAX_LANDING_SPEED.

Cependant, le joueur ne connait pas la vitesse du vaisseau.
Au lieu de rajouter la valeur de la vitesse directement sur l'écran, je vous propose de rajouter une barre de vitesse.
Selon sa couleur, le joueur aura une idée de sa vitesse.

On fera également en sorte qu"elle s'affiche par-dessus le vaisseau pour qu'elle soit toujours visible.

 

On va rajouter 2 nouvelles variables :

local SpeedBar = {}
local SPEEDBAR_LENGTH = 150

Nous allons l'initialiser dans la fonction Landing.load comme suit :

SpeedBar = Dina("Progressbar",
                (Dina.width - SPEEDBAR_LENGTH)/2, 5, --position
                SPEEDBAR_LENGTH, 20,                 --largeur et hauteur
                0, MAX_SHIP_SPEED,                   --valeurs min et max
                Colors.GREEN,                        --couleur de la barre
                Colors.ORANGERED,                    --couleur de la bordure
                nil,                                 --pas de couleur du fond
                10,                                  --ordre d'affichage (Z-Order)
                2)                                   --épaisseur de la bordure

 

Ensuite, on va changer sa valeur et sa couleur dans Landing:update, juste après le déplacement du vaisseau, avec le code suivant :

SpeedBar:setValue(Ship.vy)
if Ship.vy < MAX_LANDING_SPEED then
  SpeedBar:setColor(Colors.GREEN)
elseif Ship.vy < (MAX_SHIP_SPEED - MAX_LANDING_SPEED) then
  SpeedBar:setColor(Colors.YELLOW)
else
  SpeedBar:setColor(Colors.RED)
end

Et pour finir, quand l'atterrissage est réussi, on masque la barre (on en profite pour masquer les vies) :

--On masque les vies et l'indicateur de vitesse
LifeGroup:setVisible(false)
SpeedBar:setVisible(false)

 

 

Décollage du vaisseau

Tout d'abord, nous allons nous créer une nouvelle fonction Move:takeOff qui sera exécuté lorsque le joueur appuiera sur une touche pour déclencher le décollage.

function Move:takeOff()
  if Ship.landed then
    Ship.takeOff = true
  end
end

On vérifie si le vaisseau est bien posé pour ne pas déclencher le décollage par inadvertance.

 

Comme vous l'avez remarqué, on a rajouté un nouvel attribut au vaisseau que l'on va initialiser dans Landing.load.
Je vous laisse le faire.

 

L'étape suivante est de définir les touches d'action pour lancer le décollage dans Landing.load :

Dina:setActionKeys(Move, "takeOff", "released", {"Keyboard", "space"}, {"Gamepad", "a"})

 

Ensuite, on a une modification importante à faire dans Landing:update.

Au tout début de la fonction (1ère ligne), on va rajouter le test ci-dessous :

if not Ship.takeOff then

Puis, à la fin de la fonction, on va rajouter :

else -- if not Ship.takeOff
end --if not Ship.takeOff

Les commentaires sont là pour me rappeller quel est le test qui lui est associé.

 

Tout ce qu'il nous reste à faire, c'est de faire déplacer la fusée vers le haut.

Le code ci-dessous est à placer entre le else -- if not Ship.takeOff et le end --if not Ship.takeOff :

--Animation de décollage
Engine:setVisible(true)
Ship.vy = Ship.vy - (0.8 * dt)
--Déplacement du vaisseau
MoveShip()

local sx, sy = Ship:getPosition()
local ew, eh = Engine:getDimensions()
if sy < 0 - eh then
  Dina:setState("game")
end

 

Si on ne laisait que ça, lorsqu'on lancerait de nouveau le mini-jeu, il y aurait de nombreux problèmes.

Juste avant le changement d'état, on va remettre à zéro certaines données.
Pour cela, nous allons créer  la fonction ResetDatas comme suit :

local function ResetDatas()
  Ship = {}
  Engine = {}
  LifeGroup = {}
  Terrain = {}
  SpeedBar = {}
end

Et enfin, nous allons l'ajouter avant le passage à l'état game :

if sy < 0 - eh then
  ResetDatas()
  Dina:setState("game")
end

On va également l'appliquer lors du passage vers le gameover que l'on avait déjà modifié :

if nbLifes < 1 then
  ResetDatas()
  Dina:setState("gameover")
end

 

Ajout du score

Pour le calcul du score, on va faire ça simple :

Comme il y a 16 astéroïdes qui apparaissent, cela fera un total de 1600 points.

La première difficulté est de transférer (ou récupérer) le score du jeu vers le mini-jeu. Pour cela, nous sommes aidés par le moteur Dina.

Créons d'abord une variable qui va permettre de contenir (et afficher) le score :

local Score = {}

Nous allons utiliser un composant Text qui est initialisé comme suit :

Score = Dina("Text", Dina:getGlobalValue("score") or 0, Dina.width/2, 20)
Score:setAlignments("right", "up")
Score:setDimensions(Dina.width/2 - 10, 15)

Nous alignons le texte à droite de l'écran, juste en dessous de la barre de progression.

Un ajustement des dimensions du composant est fait lors de la première insertion de texte (c'est notre cas) ou après avoir effacé le texte.
C'est pour cela que nous devons redéfinir les dimensions après la création du composant.

Il ne nous reste plus qu'à comptabiliser le score :

-- Si l'astéroïde est en dessous de l'écran, on le supprime
if (ay - ah) > Dina.height then
  Score:setContent(Score:getContent() + 100)
  table.remove(Asteroids, i)
end

 

Ensuite, on doit calculer le score si on a atterri dans le mini-jeu.

Comme tout à l'heure; on doit d'abord créer une variable qui va permettre de contenir (et afficher) le score :

local Score = {}

Et on utilise un composant Text qui est initialisé comme suit :

Score = Dina("Text", Dina:getGlobalValue("score") or 0, Dina.width/2, 5) --ici, on le positionne tout en haut
Score:setAlignments("right", "up")
Score:setDimensions(Dina.width/2 - 10, 15)

On comptabilise le score si on a atterrit et on met à jour la donnée globale :

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)
  SpeedBar:setVisible(false)
  --Comptabilisation du score
  Score:setContent(Score:getContent() + (200 * LifeGroup:nbComponents()))
  --Mise à jour de la donnée globale
  Dina:setGlobalValue("score", Score:getContent())
else -- if Ship.vy < MAX_LANDING_SPEED

On va en profiter pour changer la condition de départ de la fonction Landing:update :

function Landing:update(dt)
  if not Ship.landed then --Remplace if not Ship.takeOff then
    -- On masque le moteur
    Engine:setVisible(false)

Et on remplace également le else correspondant :

      end -- if Ship.vy < MAX_LANDING_SPEED
    end -- if (GroundY - (sy + sh/2)) < MIN_GROUND_LENGTH
  elseif Ship.takeOff then --remplace else --if not Ship.takeOff
    -- Animation de décollage
    Engine:setVisible(true)

Sans ces changements le score s'accumulerait tant que le joueur n'a pas décollé.

 

La dernière chose à faire est d'afficher le score lors du GameOver.

Pour cela, rien de plus simple : on ajoute un nouvel élément dans le menu qui contiendra le score.

function GameOver.load()
  local menu = Dina("MenuManager", 50) --on réduit l'écart entre les items du menu passant de 75 à 50
  menu:setTitle("Jeu terminé", Dina.height/4, "datas/font/Righteous-Regular.ttf", 60, Colors.WHITE, true, Colors.SILVER, 5, 5)

  -- Ajout de l'image de fond
  local rnd = love.math.random(2)
  menu:addImage("datas/images/gameover/blackhole_"..rnd..".jpg", Dina.width/2, Dina.height/2, true)

  -- Ajout du texte à afficher à l'écran
  menu:addItem("Score final : "..Dina:getGlobalValue("score"), "datas/font/Righteous-Regular.ttf", 40, ReturnToMenu)
  menu:addItem("La civilisation s'est éteinte avec vous...", "datas/font/Exo2-Regular.ttf", 40)
  menu:addItem("Appuyer sur 'A' pour revenir au menu principal", "datas/font/Exo2-Regular.ttf", 16)

  -- Définition des touches
  menu:setNextKeys({"Keyboard", "all"}, {"Gamepad", "a"})
end

 

Puis, nous effaçons le score quand on quitte le GameOver :

local function ReturnToMenu()
  Dina:setGlobalValue("score", nil)
  Dina:setState("logo")
end

 

Voilà, nous avons un jeu avec un gameplay complet.

 

Ecran des crédits

Finissons le dernier écran du menu : les crédits.

On crée un fichier credits.lua dans le répertoire menus et on reprend la structure du Logo pour la coller dans le nouveau fichier en renommant Logo par Credits.

 

Comme toujours, on va commencer par créer les variables dont nous avons besoin :

local CreditGroup = {}

local delay
local timer
local activeKey

local Speed = 75

On a besoin en plus d'une fonction qui permet de lire un fichier. Je vous fournie le code complet ci-dessous :

local function ReadFile(File, SkipLineChar)
  local toSkip = false
  local str = {}
  for line in love.filesystem.lines(File) do
    toSkip = IsStringValid(SkipLineChar) and string.sub(line, 1, 1) == SkipLineChar
    if not toSkip then
      table.insert(str, line)
    end
  end
  return str
end

Passons à l'initialisation :

function Credits.load()
  --Création du groupe
  CreditGroup = Dina("Group")

  --Lecture du fichier des crédits sans les liens
  local Lignes = ReadFile("datas/credits.txt", "[")

  --Parcours du contenu du fichier
  for _,ligne in pairs(Lignes) do
    local gw, gh = CreditGroup:getDimensions()
    --Création d'un composant Text par ligne
    local text = Dina("Text", ligne,0,gh + 5)
    --On l'affiche centré sur toute la largeur de l'écran
    text:setDimensions(Dina.width, text:getHeight() + 3)
    text:setAlignments("center", "center")
    CreditGroup:add(text)
  end

  --Positionnement du groupe en dessous de l'écran
  CreditGroup:setPosition(0, Dina.height + 1)

  --Définition les touches d'actions
  Dina:setActionKeys(Credits, "quit", "pressed", {"Keyboard", "all"}, {"Gamepad", "all"})

  --Initialisation des variables
  delay = 2
  timer = 0
  activeKey = false
end

Puis, lors de l'exécution de Credits:update :

function Credits:update(dt)
  if not activeKey then
    timer = timer + dt
    if timer > delay then
      activeKey = true
    end
  end
  
  local gx, gy = CreditGroup:getPosition()
  gy = gy - Speed * dt
  CreditGroup:setPosition(gx, gy)
  
  local gw, gh = CreditGroup:getDimensions()
  if gy + gh < Dina.height/2 then
    Credits:quit()
  end
  
  Dina:update(dt, false)
end

Et pour finir, on crée la fonction qui permet de revenir au menu principal :

function Credits:quit()
  if activeKey then
    Dina:setState("menu")
  end
end

 

Voilà, notre série de tutoriels pour la création d'un jeu d'exploration spatiale est terminée.

J'espère que vous l'avez apprécié.

N'hésitez pas à me transmettre vos remarques et/ou suggestions.

 

A bientôt pour de nouvelles aventures !

 

Le code de ce tutoriel se trouve ici.



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

Tutoriels