Salut les gamecodeurs!!!
Dans le dernier devlog, nous avions créé un système orienté objet, censé nous permettre de standardiser la création de nos objets. Ainsi, tous les objets que nous créerons auront, via leur classe, une variable isObject, une variable type et une table prototype. Nous allons utiliser ces particularités aujourd’hui… Tout d’abord, nous allons créer un nouveau fichier: stdLib.lua, dans lequel nous allons ajouter ou remanier des fonctions standard du lua. Je ne vais pas tout détailler, mais on y importe le module utf8 de lua, on migre ici la fonction split que nous avions créé dans dataStorage et on l’ajoute au module string du lua, et on ajoute également tout un tas de fonctions utiles à différents modules (math, string, table…)
Ce qui va nous intéresser ici c’est d’utiliser les particularités de nos objets:
if not game.inDev then
print = function(...) end
end
local oldType = type
function type(p_val)
local old = oldType(p_val)
if old == "table" and p_val.isObject then
return p_val.type
else
return old
end
end
function hasType(p_val, p_type)
local old = oldType(p_val)
if old == "table" and p_val.isObject then
for _, v in pairs(p_val.prototype) do
if v == p_type then return true end
end
return false
else
return (old == p_type)
end
end
function isObject(p_val)
if oldType(p_val) ~= "table" then return false end
return (p_val.isObject == true)
end
Pour commencer, vous remarquerez que lorsque l’on n’est pas en version de développement, on réécrit print pour en faire une fonction vide (De toute manière la console ne s’ouvrira pas chez les joueurs, il n’y a donc pas besoin pour cette fonction d’effectuer un quelconque traitement). Ensuite, on enregistre en local la fonction type pour pouvoir continuer à l’utiliser ici, avant de la réécrire. Maintenant, quand on appellera la fonction type en lui envoyant un objet, elle nous renverra son type enregistré par nos soins, et non « table ». La fonction hasType va nous dire si l’objet qu’on lui envoie possède un certain type, et tout ça grâce à notre table prototype qui enregistre toute la chaîne d’héritage. La fonction isObject nous permet de nous assurer que l’élément qu’on lui envoie est bien une table avant de vérifier qu’elle possède la variable isObject.
Vous constatez maintenant l’intérêt d’avoir standardisé la création de nos objets: Cela nous permet d’ajouter tout un tas de fonctionnalités à notre code, et ce n’est que le début. Nous allons retourner dans le fichier dataStorage et modifier la fonction fileIO.save:
local function saveObject(p_obj)
if p_obj.onSave then p_obj:onSave() end
p_obj.address = nil
p_obj.typeObj = p_obj.type
end
local function preSave(p_table)
if isObject(p_table) then saveObject(p_table) end
for _, v in pairs(p_table) do
if hasType(v, "table") then
if isObject(v) then saveObject(v) end
preSave(v)
end
end
end
function fileIO.save(p_path, p_table)
if string.find(p_path, "/") ~= nil then createDir(p_path) end
preSave(p_table)
return love.filesystem.write(p_path, serial.pack(p_table, nil, game.inDev and 0 or 1))
end
Lorsque l’on sauvegarde une table, il faut vérifier si celle-ci est un objet, ou si elle contient des objets, car dans ce cas on va devoir effectuer quelques action avant d’enregistrer les données dans un fichier. En effet, lorsque l’on sauvegarde un objet, cela ne sert à rien d’enregistrer la variable hex, qui enregistre l’adresse mémoire de notre objet, puisque cette adresse sera différente lorsqu’on le chargera depuis le fichier de sauvegarde. Par contre, on aura besoin du type de l’objet pour le recréer, et type est une variable qui provient de sa métatable, elle ne sera donc pas sauvegardée, voilà pourquoi il faut qu’on l’enregistre dans une variable typeObj. On va aussi appeler la fonction onSave de nos objet lorsqu’elle existe, afin de régler au besoin certains détails avant la sauvegarde. Toutes ces actions sont réalisées par la fonction saveObject, elle même appelée par la fonction récursive preSave, qui se chargera de vérifier tout le contenu des tables pour préparer les objets à la sauvegarde. Voyons maintenant pour le chargement des objets:
function fileIO.restore(p_table, p_obj)
local obj = nil
if p_table.typeObj ~= nil then
obj = (classes[p_table.typeObj]):new()
else
obj = p_obj or {}
end
for k, v in pairs(p_table) do
if k == "typeObj" then goto continue end
if type(v) == "table" then
obj[k] = fileIO.restore(v, obj[k])
else
obj[k] = v
end
::continue::
end
if obj.onRestore then obj:onRestore() end
return obj
end
Nous pourrons envoyer à la fonction restore les données chargées par la fonction load, et elle nous permettra de charger tous les objets de façon récursive. Cette fonction va récupérer le type de l’objet via typeObj et créer un nouvel objet de ce type, avant de réattribuer les valeurs de ses variables. Il faut toutefois noter que l’on envoie pas de paramètres lors de la recréation de l’objet. Dans la plupart des cas cela ne devrait pas poser de problèmes, mais il faudra se souvenir de ce détail quand nous réaliserons nos classes. Une fois notre objet recréé, on appel sa fonction onRestore quand elle existe, qui nous permettra de finaliser le chargement d’un objet quand c’est nécessaire. A noter que l’on peut également envoyer une table contenant des objets à cette fonction, comme elle est récursive, elle se chargera de charger tous les objets…
Petite astuce que vous avez sans doute remarqués: l’utilisation de l’instruction goto. Vous connaissez peut-être l’instruction continue, que l’on trouve dans beaucoup de langages de programmation, et qui permet de sauter une itération dans une boucle. Cette instruction n’existe pas en lua, mais on se sert ici de l’instruction goto pour faire comme si: elle permet de forcer notre programme à faire un saut jusqu’à une étiquette qu’on lui indique (ici l’étiquette ::continue::). C’est une astuce qui peut s’avérer très pratique…
Ainsi tout commence à s’interconnecter dans notre moteur, tout est lié. Mais nous avons encore du travail, car même si nous avons déjà bien avancé, même si le terrain est bien préparé, notre programme n’ouvre toujours qu’une simple fenêtre et une console, et rien de plus… Il nous faudra encore plusieurs devlogs pour commencer à vraiment s’occuper de la création de notre jeu.
Mais en attendant, bon code à tous!!!