Lunar Lander - Devlog #1
Gamabunta57 Il y a 2 ans Expiré - En attente d'adhésion Premium12

Hello la team !

Avant-propos

Voilà, ça fait déjà quelques jours que j’ai débuté mon projet et plus particulièrement ma première session de code de ce projet. J’ai un peu de retard si on peut dire du fait que j’ai voulu appliquer la méthode du streaming pour me pousser à ne pas être « lazy ».

Ceci dit mon upload étant trop faible, je peux pas partir en live (pour le streaming j’entends) et j’ai opté pour une méthode alternative qui consiste à m’enregistrer puis à uploader la vidéo. C’est la toute première fois que je fais ça donc j’ai pris du temps pour le montage (y’a pas grand-chose à faire mais j’avais jamais fait).

Ce qui s’est passé durant la session

L’approche orientée objet

Avant tout, j’ai voulu tenter l’approche objet en Lua. Celle telle que décrite dans la documentation (https://www.lua.org/pil/16.html).

J’ai jamais fait de Lua auparavant et sa syntaxe légèrement différente de celles dont j’ai l’habitude (C#, Java, PHP .. ) a fait que j’ai eu du mal à démarrer tout de même (pas mal de recherche sur le langage lui-même plutôt que trouver des solutions pour mes algos). Une fois la syntaxe en tête, c’est plus vraiment un problème. Il reste cependant les « bizarretés » de l’approche objet de LUA. En voyons les vidéos de David, j’avais flairé un langage objet par prototype (comme Javascript). Comme j’avais déjà expérimenté un peu cette approche (que j’aime moyen faut avouer) au final ça a été. J’ai tout de même trouvé des choses que je ne comprends pas

Dans la documentation on trouve (https://www.lua.org/pil/16.1.html):

function Account:new (o) o = o or {} -- create object if user does not provide one setmetatable(o, self) self.__index = self return o end

Tout d’abord, y avait la première ligne où je me suis dit « la variable `o` est globale, qu’est ce qu’ils foutent là ? ». En écrivant ce devlog, je me rend compte de ma connerie. La variable n’est pas globale, elle est bien locale car déclarée en tant qu’argument de la méthode `new`.

Second point, la création d’un objet en passant un même objet, je comprenais pas (jusqu’à écrire ce devlog, décidément ..). Maintenant, j’entrevois l’intérêt car ici on peut passer un autre objet/table pour en faire un `Account` (dans notre exemple), ou rien et avoir un objet `Account` « pure ».

Par contre (même en écrivant ce devlog), je pige pas le self.__index = self. Du coup, je viens de regarder sur le net et je tombe sur cette réponse https://stackoverflow.com/a/2767751. En gros, pour essayer de la reformuler en français, si on souhaite accéder à une propriété/méthode d’un objet et que celle-ci n’existe pas, alors Lua va chercher et « remonter » dans __index et vérifier si la propriété/méthode existe. En fait, je pensais qu’il y avait un champ « metatable » sur les tables et que c’était celui-ci qui était utilisé pour « remonter » dans les propriété au besoin.
Pour ceux qui connaissent, __index est à Lua ce que prototype est à Javascript (je n’aurai pas imaginé sur le coup, mais rédiger un devlog … on en apprend des choses ^^).

Ceci dit, il y avait une partie de la documentation qui m’a induit en erreur sur ce point car ils disent que si __index est une fonction, alors elle peut prendre de 0 à 2 arguments et est utilisée lors d’un accès à une propriété non existante. Sauf que j’ai mal lu un détail: « SI, c’est une fonction »; on n’est donc pas obligé de passer une fonction à __index pour que ça fonctionne.

Bref du coup, en rédigeant ceci, je comprends tout à fait ce bout de code. Et d’ailleurs je pense que je vais plutôt utiliser la solution proposée dans la réponse sous stackoverflow (Account.__index = Account).

L’écriture d’une petite libraire

La création d’une petite librairie d’outil est de l’ordre du réflexe pour moi, donc c’est ce que j’ai voulu faire. Je souhaitais coder des types d’objets que je pourrais réutiliser plus tard dans d’autres projets. J’en ai profité pour faire de recherche sur la possibilité de surcharger les opérateurs. Le but était pour moi d’avoir la possibilité d’additionner 2 vecteurs entre eux (via simplement un truc du genre vector1 + vector2) sans avoir à additionner les champs 1 à 1. J’ai tenté différentes choses, j’ai pas réussi à le faire fonctionner (la surcharge d’opérateur existe bien en Lua) et au final je suis revenu à la méthode de traiter les champs 1 à 1. J’aurais pu faire une méthode à la place mais bon, j’ai pas fait.

La stupidité humaine

Ma défiance envers les langages objets par prototype m’a souvent fait penser qu’une erreur se cachait dans mon manque de compréhension de cette approche plutôt que par mes fautes de frappe et étourderies …
Au final, j’ai passé pas mal de temps à débugger des choses qui fonctionnaient jusqu’à me rendre compte que l’erreur était entre la chaise et le clavier.

La gestion du projet

J’ai mis un peu plus de temps que ce que j’imaginais à la base; faute à mon manque d’aisance avec Lua et de tenter de corriger des problèmes là où ils ne sont pas. Ceci dit, ce n’était pas catastrophique. J’ai pu mettre en oeuvre le Trello et voir comment ça se passe. En général, c’était ok; au final, tu n’es pas tout le temps dessus en train de le consulter, il permet juste de faire un pas en avant plus rapidement lorsqu’on termine un point.

P.S.: en « montant » la vidéo, j’ai voulu avoir un « thanks for watching » à la fin. Je savais pas comment faire pour l’animer avec les outils de montage que j’avais. Du coup, je l’ai fait en Lua, et j’ai filmé mon écran comme pour le reste de la session de dev. C’est peut-être bête à dire, mais je m’étais jamais penché sur la question de faire apparaître le texte lettre par lettre.
Maintenant, je sais comment le faire (bon c’est pas propre mais ça marche) et en plus, j’en sais un peu plus sur la gestion des font en Lua :D.

[Edit]
J’ajoute le lien vers les sources du projet (en l’état lors de la rédaction de ce devlog):
https://github.com/Gamabunta57/GC-LunarLander/tree/511f79b51b6f07499eee6e058ab9b1fc355915f6

Comments (12)

C’est avec beaucoup de plaisir que je découvre et lis ce nouveau devlog, merci beaucoup de nous partager ton avancée et tes connaissances. Je vais suivre avec intérêt !

Tout comme Lost… super devlog très instructif ! Merci beaucoup. 🙂

Je ne veux pas te dire de bêtise, mais pour additionner 2 vecteurs, en fait tu peux résumer le souci à ça : comment additionner 2 tables (tout est tables en lua). Pour ça il y a une métaméthodes __add qui te permet de faire ton table1 + table2 (transposable aux vecteurs donc) via les métatables donc.

Tu trouveras plus d’infos dans les ateliers maths, notamment celui dédié aux vecteurs justement. 🙂
Je ne sais pas si ça t’aidera ou si tu cherchais une autre façon !

Hâte de voir tes prochains devlogs en tout cas.

Merci à toi aussi :D, c’est vraiment encourageant.
J’ai pas hyper l’habitude de faire ce genre d’exercice à la base (les devlog). Du coup, si ça intéresse les gens c’est d’autant plus motivant à continuer.

Effectivement, c’est bien ce que j’avais trouvé. Il en existe également d’autre pour la multiplication, la division, la conversion en string etc..
Mais quand j’ai essayé, ça plantait. Je suis donc revenu en arrière. Du coup, j’ai envie de jeter un oeil à l’atelier math. Je dois pas être loin d’y arriver mais avec la « pression » de l’enregistrement j’ai préféré passer à autre chose.

Salut ! On reconnait de suite les pros du code ^^

Quand j’ai cherché à apprendre les métatables je suis tombé là dessus :
http://wxlua.free.fr/Tutoriel_Lua/Tuto/Metatables/metatables2.php
avec la fonction :
maMetaTable.__index(la_table, la_key)

et ils m’ont perdu avec cette phrase du coup :
« la_table » est l’argument de la table qui a été indexé et « la_key » est la clé qui va essayer de lire la table.
C’est probablement à cause de mon manque d’expérience et je ne comprends pas ce qu’ils veulent dire par « table indexée », ni ce que c’est la « clé qui va lire ». C’est pas très débutant friendly
Du coup j’avais pas du tout compris à quoi servait __index. Maintenant que tu dis que c’est comme le prototype en JS ça me parle déjà un peu plus…

Mais en fait je ne comprends toujours pas à quoi ça sert ^^’ : Account.__index = Account
Si la propriété à laquelle on souhaite accéder n’existe pas, on remonte dans __index, mais qu’est-ce qui se passe ensuite du coup ? On peut mettre des propriétés dans __index ?
Mais pareil du coup en quoi __index sert à faire de la POO ?
Sur le tutoriel Lua c’est juste écrit: « utilisée lorsqu’on tente d’accéder à un élément de la variable inexistant. Sera exploité pour la création d’une classe dans la partie POO »

Oui désolé pour moi c’est loin d’être clair 🙁
En fait il me manque beaucoup d’informations, même dans les explications du Tutoriel Lua. Sûrement un manque d’expérience en POO

Salut !

Ouais en effet, c’est pas évident à comprendre au départ. Perso, j’y arrive parce que j’arrive à faire le lien avec des choses que je connais (et c’est surement pour pouvoir faire ce genre de lien plus facilement que David enseigne plusieurs langage, framework et outil).
De ce que je comprends, en Lua on parle de table, mais on pourrait changer le terme table par le terme « objet » en Lua, le résultat serait le même.
Ce que je veux dire c’est que la programmation orientée objet en Lua utilise ce qu’ils appellent des tables.

Pour être honnête, la phrase que tu cite, il faut savoir comment ça marche avant de pouvoir la comprendre (ce qui n’est pas très utile pour une explication).

Je vais essayer d’être plus explicite avec un example (attention j’ai pas testé le code, la plupart sont des illustrations de mes propos).

Si je prend un bout de mon code, j’ai :


local hero = {
position = Vector:new(),
angle = 0,
origin = Vector:new(),
velocity = Vector:new(),
angularSpeed = 3,
thrustPower = 5,
state = "flying",
sprite = love.graphics.newImage("assets/images/ship.png"),
flameSprite = love.graphics.newImage("assets/images/engine.png"),
maxSpeed = 5
}

D’un point de vue POO, j’ai un objet avec des propriété (« position », « angle » etc). D’un point de vue Lua, on dira que j’ai une table indexée. ça veut simplement dire que c’est une table et qu’elle fonctionne avec des index (ici les index sont « position », « angle » etc).

En lua si je fait ensuite :


print(hero.angle)

J’aurai un affichage de l’angle du vaisseau.

Si je fait :


print(hero.blabla)

On s’attend à avoir un affichage « nil » étant que la propriété n’existe pas.
Cependant, avant de voir si Lua doit afficher « nil », Lua va vérifier s’il n’existe pas une « solution de secours » dans la metatable. Avec le bout de code en l’étant, il n’y a pas de metatable, donc j’aurai un nil.

Si maintenant on considère le code suivant :


Spacecraft = {
blabla = "trop cool"
}

function Spacecraft:new()
local hero = {
position = Vector:new(),
angle = 0,
origin = Vector:new(),
velocity = Vector:new(),
angularSpeed = 3,
thrustPower = 5,
state = "flying",
sprite = love.graphics.newImage("assets/images/ship.png"),
flameSprite = love.graphics.newImage("assets/images/engine.png"),
maxSpeed = 5
}

setmetatable(hero, self);
self.__index = self;

print(hero.blabla)

return hero;
end

Le résultat du print(hero.blable) ne sera pas « nil ». La raison est que à hero j’ai défini comme metatable « Spacecraft ». Ce qu’il va se passer lors du print, c’est que Lua va chercher dans hero si l’index (ou la clé, c’est un synonyme ici) existe dans hero. Elle n’existe toujours pas dans hero, mais il y a une metatable! Donc Lua va fouiller dans la metatable s’il n’y a pas un « __index » qui traine et en fonction de ce qu’est __index va l’utiliser pour savoir comment trouver la propriété « blabla ».
Si __index est une table, il va surement regarder si cette table possède « blabla ». C’est justement le rôle de « self.__index = self; » (self fait référence à Spacecraft ici).

En gros si j’affiche mon objet/table héro au complet j’aurai quelque chose comme (après la ligne avec le setmetatable):

hero = {
position = Vector:new(),
angle = 0,
origin = Vector:new(),
velocity = Vector:new(),
angularSpeed = 3,
thrustPower = 5,
state = "flying",
sprite = love.graphics.newImage("assets/images/ship.png"),
flameSprite = love.graphics.newImage("assets/images/engine.png"),
maxSpeed = 5,
[[metatable]] = {
__index = {
blabla = "trop cool"
}
}
}

(C’est juste une vision mentale ici, je sais pas si [[metatable]] est un index de la table hero, mais c’est pour illustrer que hero possède une metatable maintenant).

Donc Lua trouvera dans hero, une metatable qui possède un __index et __index possède la propriété recherchée.

///BEBIN WARNING///

__index étant une table, __index peut lui même posséder une metatable

et cette metatable peut elle même posséder un __index

Je pourrai avoir un truc du genre

hero = {
[[metatable]] = {
__index = {
blabla = "trop cool",
[[metatable]] = {
__index = {
blabla = "trop cool"
mega_blabla = "trop cool plus fort"
}
}
}
}
}
print(hero.blabla) -- affichera "trop cool"
print(hero.mega_blabla) -- affichera "trop cool plus fort"
print(hero.blabla_pas_top) -- affichera "nil"

///END WARNING///

Et Lua va (ce que j’appelle) « remonter » dans les __index jusqu’à trouver ce qu’il cherche et donner le résultat; ou bien jusqu’à ce qu’il n’y ait plus d’__index et renvoyer « nil ».

Par contre on est en Lua, et on est un peu libre de faire « kesskon veu ». __index pourrait tout à fait être une fonction et Lua le permet.
De la même manière qu’avant, si Lua ne trouve pas un index dans la table, il va chercher dans la metatable et voir que __index existe mais il s’agit d’une fonction cette fois.
Dans ce cas là, il va appeller la fonction en passant en paramètre la table (ici ce serait donc hero) et le nom de la propriété (ou index, ou clé, tu l’appelles comme tu préfères).
Donc là, Lua appelerait la fonction un peu de la manière suivante (c’est encore une expérience de pensée):


hero.metatable.__index(hero, "blabla") -- là index peut être appeler comme une fonction car c'est une fonction

Ensuite c’est à la fonction qui a été placé dans __index de savoir comment traiter le problème.

Pour avoir le même résultat que print blabla de tout à l’heure mais en utilisant une fonction:


Spacecraft = {}
Spacecraft.__index = function(table, index) {
if (index == "blabla") then
return "trop cool"
end

return nil
}

function Spacecraft:new()
local hero = {
position = Vector:new(),
angle = 0,
origin = Vector:new(),
velocity = Vector:new(),
angularSpeed = 3,
thrustPower = 5,
state = "flying",
sprite = love.graphics.newImage("assets/images/ship.png"),
flameSprite = love.graphics.newImage("assets/images/engine.png"),
maxSpeed = 5
}

setmetatable(hero, self);
self.__index = self;

print(hero.blabla)

return hero;
end

Je sais pas si je suis hyper clair. En tout cas, c’est comme ça que je comprends les choses à l’heure actuelle.

Pour ta question du « Account.__index = Account ». ça n’a d’interêt que lorsque t’utilises « Account » en tant que metatable via « setmetatable ».
Comme ça, __index possédera toutes les propriétés de Account et donc Lua pourra les trouver en « remontant » les __index.

Pour répondre à la question de comment faire de la POO avec ça:

La POO par définition est le fait d’avoir des objets (des tables ici) qu’on fait interagir entre eux. C’est pas une solution qui est meilleure que les autres, c’est juste une façon de penser. On peut faire de la programmation sans penser POO, ça marche aussi et les débuts de l’informatique on commencer sans POO (de ce que je sais).

Mais ici, je dirai que si on a différents « concept » comme un héro, ennemi et pnj. On pourrait créer un nouveau concept « Personnage » qui possède toutes les propriétés communes (et même des fonctions pourquoi pas) aux concepts héro, ennemi et pnj.
Par exemple, ils ont tous une position. Ben « position » pourrait être une propriété dans Personnage mais pas dans héro, ni de ennemi ni de pnj.
Mais héro, ennemi et pnj pourrait avoir dans metatable.__index une table Personnage. Comme ça on pourrait faire

print(hero.position)
print(ennemi.position)
print(pnj.position)

Et Lua trouvera la propriété position qui vient du concept « Personnage ». Le but ici est d’éviter de dupliquer du code (mais ça a certains désavantages aussi attention comme avoir des metatable dans des metatables dans des metatables dans des metatables … et on peut s’y perdre).

Pour aller plus loin, admettons que le héro et l’ennemi ont des points de vies. On pourrait définir un concept « PersonnageTuable » (le nom est pourri désole je suis pas inspiré).
Ce concept « PersonnageTuable » pourrait avoir en tant qu’__index une table Personnage (et donc une position) et les concept héro et ennemi pourrait avoir en tant qu’__index une table « PersonnageTuable ».
Du coup, héro et ennemi ont des points de vie ainsi qu’une position. Le pnj lui aura une position mais pas de point de vie (car il n’est pas un « PersonnageTuable »).

Encore une fois, c’est une façon de penser, on peut faire autrement.

Je me permets de te répondre JaDona, mais peut-être que Gamabunta pourra être plus précis que moi.

On est d’accord, c’est pas du tout débutant-friendly les métatables de toute façon…on comprend bien pourquoi David fait l’impasse de cette fonctionnalité de Lua dans les cours.

Quand tu demandes à Lua d’utiliser le contenu d’une table, si ce contenu n’existe pas il ne te renvoie pas immédiatement nil, il va voir s’il y a une métatable reliée à cette table, avec la métaméthode __index associée.

Basiquement, la métaméthode __index fonctionne comme une fonction qui prend 2 paramètres, la table de base et le contenu introuvable dans celle-ci (la clé), puis qui renvoie la valeur recherchée dans une autre table (que tu auras indiqué dans la fonction). Exemple :

entrepot = {pomme = 3, poire = 3, orange = 4}
panier = {pomme = 1, poire = 2}

mt = {}
mt.__index = function(table, key)
return entrepot[key] end

setmetatable(panier, mt)

print(panier.orange)

Le résultat du print sera : 4
NB : L’argument table doit rester « table », lua y met « panier » lorsque tu fais le print.

Lua permet aussi un raccourci plutôt que de créer une fonction, c’est de faire pointer directement le __index vers une autre table (la table indexée), dans laquel il ira piocher ce que tu cherches.

entrepot = {pomme = 3, poire = 3, orange = 4}
panier = {pomme = 1, poire = 2}

mt = {}
mt.__index = entrepot

setmetatable(panier, mt)

print(panier.orange)

Ca renvoie exactement le même résultat sans créer de fonction (plus optimisé donc).

En fait c’est une façon de faire de l’héritage en lua, et je n’ai jamais testé, mais le fait de passer par la fonction permet de faire de l’héritage multiple si j’ai bien compris, entre autre.

J’espère ne pas avoir empiré ton incompréhension. :p

Merci pour vos réponses Gamabunta et Lysenti !

Donc du coup en quelque sorte __index permet de désigner le « parent » de l’objet ?

En fait j’ai été pas mal perturbé par l’atelier Shoot’em up parce que ce qu’il fait « ressemble » à de la poo. Avec les fonctions CreateEnemy() par exemple, où chaque ennemi aura des propriétés distinctes etc..
Du coup j’ai du mal à différencier la pseudo-poo qu’on a vu à de la vraie poo. Ça se situerait dans l’héritage ? Ou peut être aussi dans le fait que ce ne sont peut être pas de vraies instances ?

Oui, perso ça m’a semblé super naturel parce que je ne connaissais rien à la poo justement.
Avec les fonctions d’usinage de type CreateSprite() etc, c’est une façon de faire des « classes » en lua en fait, et les instances sont les objets qui sortent de ces « usines ».

Je ne sais pas si on peut parler de parent en lua… en tout cas c’est une façon de ne pas dupliquer du code ça c’est sûr (sans toutefois passer par des fonctions). Ce qui est, il me semble, le but premier de la poo.

Après pour les différences entre la pseudo-poo en lua et la vraie poo, il faudrait demander à quelqu’un de plus expérimenté que moi… je crois par exemple qu’il y a la notion d’attribut privé ou public lorsque tu crées les constructeurs (les fonctions usines en lua), c’est-à-dire que tu peux modifier certaines variables héritées et pas d’autres. Et encore, il me semble avoir lu que la métaméthode __newindex permet d’empêcher la modification d’une variable…

En fait on dit que lua peut simuler de la poo, parce qu’il faut passer par certains artifices propres au langage pour en faire, là où le C# l’intègre naturellement par exemple. En définitive ça permet d’avoir une compréhension plus claire de ce qu’est un objet en passant par le lua… enfin de mon point de vue !

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.