Lunar Lander - Devlog#3 - L'école en ligne des programmeurs de jeux vidéo

Lunar Lander – Devlog#3

Lunar Lander - Devlog#3
Gamabunta57 Il y a 3 semaines Premium Infinity2

Bien le bonjour ! Mon 4ème devlog déjà lol 😀 !

Quel était l’objectif de cette 3ème session ?

Dans cette session de développement j’avais prévu de m’attaquer aux différents écrans de base du jeu, à savoir: les écrans de fin (victoire et défaite) ainsi que le menu de pause. Pour les écrans de fin, j’ai hésité entre avoir un seul écran (un module si vous préférez) qui gère les 2 résultats possibles ou bien avoir 2 écrans/modules séparés.

Solution mise en place

J’ai opté pour la seconde solution où j’ai 2 modules séparés. Pour le moment la seule différence entre les 2 écrans se résume à une chaine de caractère. Ceci dit, c’était un peu plus simple de mettre 2 écrans en place plutôt qu’un seul (en tout cas, c’était mon ressenti à ce moment).
De plus, je souhaite ajouter un système de scoring en fonction de la performance du joueur, système pour lequel on devrait rentrer son nom. Et ceci n’apparaîtrait que lors d’une victoire (honnêtement, je suis toujours pas certain que la séparation en 2 modules est hyper pertinente mais bon).

De plus, chose que j’avais pas prévue du tout au départ, c’est un système de langue. J’en ai mis un assez rapidement. J’étais un peu perturbé par l’idée d’avoir des chaines de caractères en dur qui se baladent partout dans le code. J’ai donc voulu les centraliser en un endroit et j’en ai profité pour avoir la possibilité d’avoir des traductions. L’idée est très simple, il s’agit d’une table pour laquelle chaque champ représente une phrase traductible. Chaque phrase traductible est une table avec les différentes langues en propriété.

En code ça donne ça :

Lang = {
newGame = {
fr = "Nouvelle partie"
en = "New game
}
}
CurrentLang = "fr"
print(Lang.newGame[CurrentLang])

J’aime pas trop le fait de devoir préciser à chaque fois le [CurrentLang]. Je refactoriserai ça plus tard (d’ailleurs en parlant de refactoring, mon code est parsemé de “copié-collé” et je pense de plus en plus à la fin de ce projet à réécrire le code pour être plus propre et avoir quelque chose de plus structuré).

Entre la seconde séance et la troisième, il s’est passé des choses 😀

Ouais, j’ai pas hyper avancé dans le projet (quoique, techniquement là, j’ai tous les enchainements principaux y a plus qu’à embellir) et j’ai passé un peu de temps sur d’autres choses. Lors du premier devlog, @Lysenti m’a contacté en m’indiquant que concernant les problèmes de surcharge d’opérateur, l’atelier math pourrait m’aider.
Après cette petite conversation, j’ai réfléchi à pourquoi ça ne devait pas marcher dans mon cas. Là, je réalise que je m’étais probablement entêté à utiliser une syntaxe particulière que j’avais mal comprise et qui fait que je n’ai pas pu la faire fonctionner et que l’autre syntaxe devait fonctionner (en plus c’était comme ça dans la doc officielle, c’est dire que quand je m’entête …).
J’ai suivi l’atelier mathématique (très bien d’ailleurs) et ça a confirmé ce que je pensais au niveau de la syntaxe. En fait à un caractère près, mon code aurait fonctionné …

De quoi parle-je ?
J’ai fait ceci pour ma librairie de vecteur :


Vector = {}
function Vector:new(x, y)
local vector = {
x = x or 0,
y = y or 0
}
self.__index = self
setmetatable(vector, Vector)
return vector
end
function Vector:__add(otherVector)
return Vector:new(self.x + otherVector.x, self.y + otherVector.y)
end

Sauf qu’officiellement la syntaxe indique qu’on utilise un “.” au lieu de “:” pour la fonction “__add” (et “__mul” et “__div” etc).

Il y a une différence entre les 2 notations “.” et “:” que je croyais être:

  • “.” on n’est pas en “objet”
  • “:” on est en “objet”

C’est FAUX.

La syntaxe avec “:” est ce qu’on appelle un “sucre syntaxique” (j’aime pas cette expression mais soit). Ça signifie qu’il s’agit d’une syntaxe qui simplifie l’écriture de code… c’est tout, y’a rien de plus, pas de magie, pas plus d’objet qu’avant etc.

En gros les 2 fonctions “display” et “display2” dans le code suivant sont équivalentes (notez la différence avec “:” et “.” par contre) :


Vector = {
x = 0,
y = 0
}
function Vector:display()
print(self.x, self.y)
end
function Vector.display2(self)
print(self.x, self.y)
end

En fait “:” ajoute de manière implicite un argument dans la fonction. L’argument en question est la table à partir de laquelle on fait l’appel.
Cet argument implicite peut être accédé via le mot-clé “self”. Dans display2, j’ai appelé une variable local self pour l’exemple (qui écrase le mot-clé self lui-même de ce fait), c’est à ne pas faire mais ça fonctionne (à moins que vous codiez en Python mais c’est un autre sujet).

Et le code suivant fonctionne parfaitement et fait toujours appel à la même fonction :


local v = Vector:new(1, 3)
v.display(v) // affichera "1 3"
v:display() // affichera "1 3"
Vector.display(v) // affichera "1 3"

Idem pour display2 :


local v = Vector:new(2, 5)
v.display2(v) // affichera "2 5"
v:display2() // affichera "2 5"
Vector.display2(v) // affichera "2 5"

Dans le cadre d’une surcharge d’opérateur la syntaxe “:” n’est pas disponible car techniquement (je pense) on ne fait pas appel à l’opérateur à partir d’une table.
En fait, le code que j’avais écrit et qui ne fonctionne pas, n’a simplement pas de sens.
Il faut donc absolument passer par la syntaxe avec le “.” dans ce cas-ci et indiquer explicitement les 2 arguments.

J’aurai donc dû écrire :


function Vector.__add(vector1, vector2)
return Vector.new(vector1.x + vector2.x, vector1.y + vector2.y)
end

Voilà voilà, j’aurai passé pas mal de temps pour trouver ce problème. Au passage, merci à toi @Lysenti pour la petite discussion qui m’y a fait réflechir à nouveau et mis sur la piste.

Pour ceux qui veulent en savoir un peu plus sur les fonctions (ou méthode) spéciales qui commencent par “__” en Lua (appelé aussi metamethod), en voici une liste http://wxlua.free.fr/Tutoriel_Lua/Tuto/Metatables/metatables3.php
Je n’ai pas testé la totalité, mais je pense qu’aucune des metamethods ne fonctionnent avec la syntaxe “:”.

Quoi d’autre ?

En y réfléchissant, y a pas grand-chose d’autres que je pourrais indiquer mis à part peut-être la découverte d’une fonction Love2D bien pratique.
Vous la connaissez peut-être déjà, il s’agit de “love.keypressed(key)”. Cette fonction est appelée lorsqu’on appuie sur une touche, mais n’est pas rappelée si on maintient la touche appuyée.
Lors de mon premier devlog, j’ai implémenté un menu ou je devais détecter un keypress.
Sans mentir, J’avais fait ça des dizaines de fois auparavant mais là … c’était pas mon jour. J’y suis tout de même arrivé mais j’ai un peu galéré pour des bêtises. Au final, là, j’ai réécrit cette partie du code en utilisant love.keypressed cette fois-ci.
C’est beaucoup plus simple, plus fiable et j’ai eu le réel plaisir de supprimer une partie de mon code pour quelque chose qui fonctionne mieux.

[Edit]

Ouais j’ai oublié un petit truc. J’avais tenté de faire un héritage en Lua la dernière fois. J’avais pas réussi. Là j’ai retenté avec de meilleurs connaissances et une autre idée.
Le but, n’est pas d’avoir forcément un système d’héritage tel qu’on l’entend dans la POO “traditionnelle (je veux dire, celle où on déclare des classes etc avec des héritages explicites).
En fait, j’ai plusieurs écrans dans mon jeu (écran-titre, écran de jeu, écran de victoire, écran d’echec) et depuis mon “main.lua” j’appelle, sur l’écran en cours, les fonctions “update” et “draw” (ainsi que d’autres). Le tout sans savoir (depuis “main.lua”) quel écran est en cours d’utilisation ni si les méthodes “update” et “draw” sont disponibles pour l’écran.
En utilisant un système “d’héritage” qui fournit des fonctions “update” et “draw” par défaut,  j’ai pas besoin de vérifier si elles existent.
J’ai donc fait ceci :

Screen = {}
Screen.__index = Screen
function Screen:update()
end

function Screen:draw()
end

function Screen:keypressed(key)
end

etc.

Puis dans les écrans eux-même:

GameScreen = {}
GameScreen.__index = GameScreen
setmetatable(GameScreen, Screen)

En utilisant le principe des metatables de Lua, j’aurai toujours au moins les fonctions implémentées avec un comportement par défaut ce qui me permet de ne pas avoir à me soucier de leur existence avant de les appeler. Dans mon cas, j’avais pas toujours la fonction update d’implémentée car pour certains écrans, j’ai juste un menu et j’utilise seulement “keypressed” pour le gérer.

Bon, plus tard, j’ai trouvé d’autres exemples qui font différemment. Ces exemples ont probablement plus de sens que ce que j’ai fait mais ça fait ce que je recherche donc bon, je laisse comme ça.

En gros ils font quelque chose comme ça :

function GameScreen:new()
local screen = Screen.new()
setmetatable(screen, GameScreen)
return screen
end

On crée donc un “Screen” qu’on “spécialise” en “GameScreen”. Par rapport au principe d’héritage, ça a un peu plus de sens je trouve.

La prochaine étape

Bon, la prochaine étape est quelque chose dont je n’ai vraiment pas l’habitude, il s’agit de l’habillage du jeu ou “polishing”.
Pour le moment tout est très brut, tous les écrans sont composés d’un fond noir; l’écran-titre a un menu textuel basique, le jeu a un vaisseau et une plateforme rectangulaire blanche, les écrans de fin, un mot et un menu.
Bref, y a aucun polish quel qu’il soit pour le moment. Prochaine séance, ce sera donc ça (et j’ai aucune idée du temps que ça va me prendre si ce n’est beaucoup ..).

Les sources

Voici les sources du projet au moment de ce devlog#3 pour ceux qui veulent voir :
https://github.com/Gamabunta57/GC-LunarLander/tree/1cdf321072d82ff6ebe7e7d1c48822e342b5a8dc

P.S. : lors de l’atelier math j’ai écrit ma petite librairie “Vector” comme je le souhaitais et c’est comme ça que j’ai pu confirmer mon erreur de choix de syntaxe.
Ceci dit, j’ai pas pris le temps de l’implémenter dans Lunar Lander car je prévois le refactoring de code plus tard.

Comments (2)

macfly

Merci pour ton partage. J’ai trouvé cela très instructif.

Pour l’appel de fonction avec les 2 syntaxes possibles “.” ou “:”, j’ai pour ma part eu un déclic en manipulant les strings lorsque je me suis rendu compte qu’on pouvait faire les formules suivantes pour le même résultat :

local text = “texte”
print(string.sub(text,1,2))

ou bien

local text = “texte”
print(text:sub(1,2))

En tout cas j’ai appris beaucoup de choses sur la modularité et l’utilisation des meta tables en lisant ton code.
Par contre je ne sais pas si c’est normal, j’ai voulu lancer le lander pour l’essayer mais je n’ai pas pu car il n’y avait pas d’assets dans le gitHub.

Gamabunta57

Salut ! Merci pour ton commentaire, c’est super encourageant pour moi 😊.
Alors oui, j’ai pas mis les assets sur le gît, je savais pas si j’avais les droits pour faire ça ou non.
Il faudrait que je mette à jour le readme pour expliquer ça.

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.

Avatar
Salut c'est David. Est-ce que tu as une question sur la formation ?
Holler Box

Dialoguez avec les autres membres de la gamecodeur school.

Accédez maintenant à notre serveur Discord privé : Entraide, Game Jams, Partage de projets, etc.

Vous devez être membre de la Gamecodeur School Premium pour être autorisé à accéder au serveur.