Necrown - Devlog #07 [Engine Dev] : Settings et Input Handler
Splix Il y a 11 mois Premium Pro - Adhésion à vie0

Salut les gamecodeurs!!!

Aujourd’hui nous allons nous occuper de la gestion des contrôles, des paramètres audio et vidéo du jeu, et de la gestion des entrées en général… Tout ceci va nous permettre de préparer le terrain pour continuer l’élaboration de notre moteur dans les prochains devlogs. Commençons déjà par créer un fichier settings.lua dans le dossier Core de l’engine, qui comme son nom l’indique contiendra les paramètres du jeu, et préparons la gestion de nos contrôles en nous créant un petit objet control:

local control = {}
control.__index = control

function control:new(p_key)
  local obj = {}
  obj.key = p_key
  obj.isUp = false
  obj.isPressed = false
  obj.isDown = false
  obj.isReleased = false
  
  setmetatable(obj, control)
  
  return obj
end

function control:get()
  return self.key
end

function control:set(p_key)
  self.key = p_key
end

On suit ici le principe des objets de notre système POO, même si cet objet est à part. Les valeurs isUp, isPressed, isDow et isReleased vont nous donner des indication sur différents états de nos contrôles. La valeur key correspond aux valeurs des touches prises en compte par love2d (cf: https://love2d.org/wiki/KeyConstant) ou au numéro d’un des boutons de la souris (On ne prendra pas en charge d’autres périphériques, en tout cas pour le moment). Ainsi on pourra gérer précisément chaque contrôle grâce à sa fonction update:

function control:update()
  if not textInput.inWriting and inputs.areEnabled then
    local isDown = false
    if type(self.key) == "string" then
      isDown = love.keyboard.isDown(self.key)
    else
      isDown = love.mouse.isDown(self.key)
    end
    
    if isDown then
      if self.isUp then self.isUp = false end
      if self.isReleased then self.isReleased = false end
      if self.isPressed and not self.isDown then
        self.isPressed = false
        self.isDown = true
      elseif not self.isPressed and not self.isDown then
        self.isPressed = true
      end
    else
      if self.isDown then self.isDown = false end
      if self.isPressed then self.isPressed = false end
      if self.isReleased and not self.isUp then
        self.isReleased = false
        self.isUp = true
      elseif not self.isReleased and not self.isUp then
        self.isReleased = true
      end
    end
  else
    if self.isUp then self.isUp = false end
    if self.isPressed then self.isPressed = false end
    if self.isDown then self.isDown = false end
    if self.isReleased then self.isReleased = false end
  end
end

Comme vous le voyez, on peut maintenant savoir si un contrôle est juste pressé, enfoncé, relâché… Et ce qu’il s’agisse d’une touche du clavier ou d’un bouton de la souris! Mais notre module control ne s’occupe que d’un contrôle. Il faut maintenant créer notre système de gestion des contrôles:

controls = {}

local defaultControls = {}

function controls.add(p_key, p_val)
  assert(controls[p_key] == nil, "Duplicate key in controls : "..p_key)
  defaultControls[p_key] = p_val
  controls[p_key] = control:new(p_val)
end

function controls.reset()
  for k, v in pairs(defaultControls) do
    controls[k]:set(v)
  end
end

function controls.update()
  for _, v in pairs(controls) do
    if type(v) ~= "function" then
      v:update()
    end
  end
end

Notre système est très simple: Il créé le contrôle via la fonction controls.add, qui l’ajoute directement à controls (comme une sorte d’extension), et enregistre sa valeur dans un tableau defaultControls, ce qui permettra réattribuer les valeurs par défaut dans une fonction controls.reset. Ensuite il ne nous reste qu’à créer une fonction pour sauvegarder nos contrôles, et une autre pour les charger, en utilisant le module fileIO que nous avons créé dans un précédent devlog:

function controls.load()
  if fileIO.exists("Settings/controls") then
    local data = fileIO.load("Settings/controls")
    for k, _ in pairs(controls) do
      if data[k] ~= nil then (controls[k]):set(data[k]) end
    end
  end
  controls.save()
end

function controls.save()
  local data = {}
  for k, v in pairs(controls) do
    if type(v) ~= "function" then data[k] = v:get() end
  end
  fileIO.save("Settings/controls", data)
end

Nous appellerons la fonction controls.load dans love.load, et la fonction controls.update dans love.update, et nous avons maintenant un système fonctionnel pour gérer nos contrôles, qui en plus nous permet de les sauvegarder et de les charger simplement. J’en profite pour créer les contrôles fullscreen, qui nous permettra de basculer entre fullscreen et windowed, et debug qui nous permettra d’activer le mode debug lorsque l’on est en inDev.

controls.add("debug", "f3")
controls.add("fullscreen", "f11")

Je vous passe les paramètres audio et vidéo, car le principe reste globalement le même. Précisons juste que l’on créé pour l’instant trois paramètres audio (master, music et sfx, tous trois compris entre 0 et 1, et qui permettrons de gérer le volume sonore), et un paramètre vidéo (fullscreen, un booléen qui enregistrera l’état de la fenêtre).

Nous allons continuer ce devlog avec un nouveau fichier, que l’on nommera inputHandler.lua. Ce fichier va gérer les entrées clavier et souris indépendamment des contrôles, mais de la même manière. Imaginons que l’on assigne la touche « entrée » à un contrôle, on pourrait avoir envie de détecter que l’on appuis sur la touche indépendamment du contrôle à un moment donné. On créera donc un objet keyboard, qui fonctionnera au final comme controls (hormis que l’on aura pas besoin de le sauvegarder), et on pourra ainsi surveiller les touches qu’on lui indiquera. Idem pour l’objet mouse, mais ce dernier fera un peut plus que surveiller les boutons de la souris:

mouse = {}
mouse.x = 0
mouse.y = 0
mouse.wheel = 0

mouse.button = {}
mouse.button.left = mouseButton:new(1)
mouse.button.right = mouseButton:new(2)
mouse.button.middle = mouseButton:new(3)

function mouse.update()
  mouse.x = love.mouse.getX() / display.s
  mouse.y = love.mouse.getY() / display.s
  
  for _, v in pairs(mouse.button) do
    v:update()
  end
end

function mouse.postUpdate()
  mouse.wheel = 0
end

function love.wheelmoved(p_x, p_y)
  mouse.wheel = p_y
end

En effet, mouse va aussi enregistrer les entrées de la molette de la souris, et sa position (remarquez que l’on a réalise une division des position en x et y par display.s, une variable qui enregistre l’échelle d’affichage de notre fenêtre, qui avait été créée lorsque nous réalisions la configuration du projet). Pour continuer sur ce fichier inputHandler, nous allons créer un objet textInput:

textInput = {}
textInput.entry = ""
textInput.inWriting = false
textInput.limit = nil

function textInput.start()
  textInput.inWriting = true
  textInput.entry = ""
end

function textInput.pause()
  textInput.inWriting = false
end

function textInput.resume()
  textInput.inWriting = true
end

function textInput.stop()
  textInput.inWriting = false
  textInput.entry = ""
end

function textInput.erase()
  textInput.entry = ""
end

function textInput.get()
  return textInput.entry
end

Cet objet qui va permettre d’enregistrer le texte saisi au clavier quand on le lui demandera, par exemple lorsque l’on créera un champ texte pour l’utilisateur. On voit que l’on peut activer ou désactiver cette détection quand on le souhaite, mais c’est surtout au niveau de la fonction love.textInput que tout va se passer:

function textInput.restrict(p_limit)
  textInput.limit = p_limit
end

function textInput.isWrinting()
  return textInput.inWriting
end

function textInput.backspace()
  local byteOffs = utf8.offset(textInput.entry, -1)
  if byteOffs then
    textInput.entry = string.sub(textInput.entry, 1, byteOffs - 1)
  end
end

function textInput.lineBreak()
  textInput.entry = textInput.entry.."\n"
end

function love.textinput(p_txt)
  if textInput.inWriting then
    if not textInput.limit or utf8.len(textInput.entry) <= textInput.limit then
      textInput.entry = textInput.entry..p_txt
    end
  end
end

Vous remarquez que l’on utilise l’objet utf8. Je l’avais importé lorsque nous avons créé le fichier stdLib, c’est un module standard du lua qui permet l’encodage des caractères spéciaux (vous trouverez de la documentation sur celui-ci sur internet, par exemple ici). Son intérêt ici est de permettre à l’utilisateur de saisir des caractères spéciaux. J’avais juste ajouté à cet objet une fonction, que je vous donne maintenant:

utf8 = require("utf8")

function utf8.sub(p_string, p_start, p_end)
  p_start = utf8.offset(p_string, p_start)
  p_end = utf8.offset(p_string, p_end + 1) - 1
  return string.sub(p_string, p_start, p_end)
end

Cette fonction va nous permettre de récupérer une sous-chaîne en utf8. Nous l’utilisons dans la fonction backspace, au cas où l’on voudrait supprimer un caractère spécial (sans cela le programme planterait à ce moment). Pour finir ce devlog, nous allons créer un dernier objet dans ce fichier, que nous appellerons inputs:

inputs = {}
inputs.areEnabled = true
inputs.anyKeyIsPressed = false
inputs.lastKey = nil

function inputs.enable()
  inputs.areEnabled = true
end

function inputs.disable()
  inputs.areEnabled = false
end

function inputs.postUpdate()
  inputs.anyKeyIsPressed = false
end

function love.keypressed(p_key)
--  print(p_key)
  if textInput.inWriting then
    if p_key == "backspace" then textInput.backspace() end
  else
    if inputs.areEnabled then
      inputs.lastKey = p_key
      inputs.anyKeyIsPressed = true
    end
  end
end

Cet objet va véritablement gérer globalement les inputs. On y enregistrera le fait qu’une touche (n’importe laquelle) est pressée, ce qui pourrait être pratique. On y créé une variable inputs.areEnabled, et on appellera la fonction update des contrôles (et autres entrées du clavier ou de la souris) que quand cette variable est à true, ce qui nous donnera la possibilité d’activer / désactiver les inputs quand on le souhaite. Tout se passe ensuite dans love.keypressed…

Il ne nous restera finalement qu’à appeler les fonctions d’update de nos objets dans la fonction love.update, et notre programme sera enfin « interactif »!!! Ou du moins il sera prêt à gérer les interactions. Mais nous verrons tout cela dans un prochain devlog…

En attendant, bon code à tous!!!

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.