Les classes avec love2d

Les classes sont comme les plans d’une maison. Vous pouvez en créer plusieurs à partir d’un plan. De la même manière, nous pouvons créer plusieurs objets à partir d’une classe.

Images représentant une classe
Images représentant une classe

Pour les classes, nous allons utiliser la lib: classic.

Cliquez sur classic.lua puis sur Raw et copiez le code

Allez dans votre éditeur de texte, créez un nouveau fichier classic.lua et coller le code.

Maintenant, nous avons besoin de l’appeler :

1
2
3
function love.load()
  Object = require "classic"
end

Nous sommes prêt pour créer des classes. Créez un nouveau fichier rectangle.lua et mettez-y le code suivant :

1
2
3
4
5
6
7
8
--! file: rectangle.lua
-- Notez comment il utilise : et non .

Rectangle = Object:extend()

function Rectangle.new(self)
  self.test = math.random(1, 1000)
end

Tout sera expliqué ultérieurement. Mettez ce code dans votre main.lua

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
--! file: main.lua

function love.load()
  Object = require "classic"
  -- N'oublions pas de charger le fichier
  require "rectangle"

  r1 = Rectangle()
  r2 = Rectangle()
  print(r1.test, r2.test)
end

Quand vous lancez le jeu, vous devriez voir 2 nombres aléatoires s’afficher.

Examinons ce code étape par étape :

  1. Nous créons une nouvelle classe avec Rectangle = Object:extend(). Rectangle devient une classe. Ce sera notre plan/modèle. Contrairement aux propriétés, les classes sont généralement écrites avec un caractère majuscule sur la première lettre.
  2. Dans main.lua nous disons r1 = Rectangle(). Même si Rectangle est une table, nous pouvons encore l’appeler comme si c’était une fonction. Son fonctionnement sera pour un autre chapitre mais en appelant Rectangle(), cela crée une nouvelle instance. Cela signifie que notre plan est pris pour créer un nouvel objet avec toutes ses classes. Chaque nouvelle instance est unique.
  3. Pour prouver que r1 est unique, nous avons créer une autre instance appelé r2. Toutes les 2 ont la propriété test mais avec des valeurs différentes.
  4. Quand nous appelons Rectangle(), cela exécute Rectangle.new. C’est ce que l’on appelle un constructeur.
  5. Le paramètre self est l’instance que nous sommes en train de modifier. Si nous devions mettre Rectangle.test = math.random(0, 1000), nous donnerions la propriété au plan et non à une instance faite avec le plan. Faisons quelques changements à notre classe :
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
--! file: rectangle.lua

function Rectangle.new(self)
  self.x = 100
  self.y = 100
  self.width = 200
  self.height = 150
  self.speed = 100
end

function Rectangle.update(self, dt)
  self.x = self.x + self.speed * dt
end

function Rectangle.draw(self)
  love.graphics.rectangle("line", self.x, self.y, self.width, self.height)
end

C’est comme l’objet rectangulaire en mouvement que nous avons fait au chapitre 8 sauf que cette fois nous mettons le code du mouvement et de la partie du dessin dans l’objet. Maintenant, il suffit d’appeler la mise à jour et de dessiner dans main.lua :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
--! file: main.lua

function love.load()
  require "rectangle"
  r1 = Rectangle()
  r2 = Rectangle()
end

function love.update(dt)
  r1.update(r1, dt)
end

function love.draw()
  r1.draw(r1)
end

Quand vous lancez le jeu, vous devriez voir un rectangle bouger !

En fait, nous avons créé une classe appelée Rectangle. Nous avons créé une instance de cette classe appelé r1. Maintenant r1 a une fonction qui le met à jour (update) et qui le dessine (draw). Nous appelons ces fonctions et comme premier argument nous passons l’instance r1. C’est ce que le self devient dans les fonctions.

Cependant il est gênant de voir comment nous passons r1 à chaque appel d’une de ces fonctions. Heureusement, Lua a des raccourcis pour ça. Quand nous utilisons deux points (:), l’appel de fonction passera automatiquement l’objet à gauche des « : » comme premier argument :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
--! file: main.lua

function love.update(dt)
  -- Lua converti ceci en r1.update(r1, dt)
  r1:update(dt)
end

function love.draw()
  -- Lua converti cela en r1.draw(r1)
  r1:draw()
end

Et nous pouvons le faire avec toutes les fonctions aussi :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
--! file: rectangle.lua

-- Lua converti en Rectangle.new(self)
function Rectangle:new()
  self.x = 100
  self.y = 100
  self.width = 200
  self.height = 150
  self.speed = 100
end

-- Lua converti en Rectangle.update(self, dt)
function Rectangle:update(dt)
  self.x = self.x + self.speed * dt
end

-- Lua converti en Rectangle.update(self, dt)
function Rectangle:draw()
  love.graphics.rectangle("line", self.x, self.y, self.width, self.height)
end

Ajoutons quelques paramètres à Rectangle:new() :

1
2
3
4
5
6
7
8
9
--! file: rectangle.lua

function Rectangle:new(x, y, width, height)
  self.x = x
  self.y = y
  self.width = width
  self.height = height
  self.speed = 100
end

Avec ça nous pouvons donner à r1 et r2 leur propre position et leur propre taille :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
--! file: main.lua

function love.load()
  r1 = Rectangle(100, 100, 200, 50)
  r2 = Rectangle(180, 300, 25, 140)
end

function love.update(dt)
  r1:update(dt)
  r2:update(dt)
end

function love.draw()
  r1:draw()
  r2:draw()
end

Donc maintenant, nous avons 2 rectangles en mouvement. C’est ce qui rend les classes géniales. r1 et r2 sont les mêmes mais ils sont uniques.

Exemple de classe avec 2 rectangles en mouvement
Exemple de classe avec 2 rectangles en mouvement

Une autre chose qui rend les classes géniales est l’héritage.

L’héritage

Avec l’héritage, nous pouvons étendre nos classes. En d’autres thermes, nous faisons une copie de notre plan et nous y ajoutons des fonctionnalités sans modifier l’original.

Explication de l’héritage en image
Explication de l’héritage en image

Faisons un jeu avec des monstres. Chaque monstre a sa propre attaque et se déplace différemment mais ils peuvent subir des dommages et peuvent mourir. Les fonctionnalités se chevauchant devraient être placées dans une super classe ou une classe de base. Elles fournissent les fonctionnalités que tous les monstres ont en commun. Ensuite la classe de chaque monstre peut étendre la superclasse et y ajouter ses propres caractéristiques.

Créons une nouvelle forme mobile, le cercle. Qu’auront en commun notre rectangle et notre cercle ?
Hé bien, ils se déplaceront tous les deux donc faisons une superclasse pour nos 2 formes.

Créez un nouveau fichier appelé shape.lua et mettez-y le code suivant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
--! file: shape.lua
Shape = Object:extend()

function Shape:new(x, y)
  self.x = x
  self.y = y
  self.speed = 100
end

function Shape:update(dt)
  self.x = self.x + self.speed * dt
end

Notre superclasse Shape gère maintenant le mouvement. Je devrais souligner que notre superclasse est juste un terme. “A est une superclasse de B”. Une superclasse est toujours la même chose qu’une autre classe. C’est juste la manière de l’utiliser qui est différente.

Quoi qu’il en soit, nous avons maintenant une superclasse qui gère nos mouvements. Nous pouvons faire de Rectangle une extension de Shape et supprimer sa mise à jour :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
--! file: rectangle.lua

Rectangle = Shape:extend()

function Rectangle:new(x, y, width, height)
  Rectangle.super.new(self, x, y)
  self.width = width
  self.height = height
end

function Rectangle:draw()
  love.graphics.rectangle("line", self.x, self.y, self.width, self.height)
end

Avec Rectangle = Shape:extend() nous avons créé Rectangle qui est une extension de la superclasse Shape.

Shape a sa propre fonction appelée :new(). En créant Rectangle:new(), nous annulons la fonction d’origine. Cela veut dire que quand nous appelons Rectangle(), cela n’exécutera pas Shape:new() mais plutôt Rectangle:new().

Mais rectangle a la propriété super, donc la classe Rectangle est étendu. Avec Rectangle.super nous pouvons accéder aux fonctions de la superclasse et nous l’utilisons pour appeler Shape:new().

Nous devons nous même lui passer self comme premier argument et ne pas laisser Lua le gérer avec les deux points (:) parce que nous n’appelons pas la fonction comme une instance.

Maintenant, nous devons créer une classe cercle. Créez un nouveau fichier appelé circle.lua et mettez-y le code suivant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
--! file: circle.lua

Circle = Shape:extend()

function Circle:new(x, y, radius)
  Circle.super.new(self, x, y)
  -- A circle doesn't have a width or height. It has a radius.
  self.radius = radius
end

function Circle:draw()
  love.graphics.circle("line", self.x, self.y, self.radius)
end

Donc nous faisons de Circle une extension de Shape. Nous passons x et y à new(), la fonction de Shape avec Circle.super.new(self, x, y).

Nous donnons à notre classe Circle sa propre fonction de dessin. C’est ainsi qu’on dessine un cercle. Les cercles n’ont pas besoin de largeur ou de hauteur mais plutôt d’un rayon.

Et maintenant, dans main.lua chargeons shape.lua et circle.lua puis changeons r2 en un cercle :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
--! file: main.lua

function love.load()
  Object = require "classic"
  -- Ne pas oublier de charger le fichier
  require "shape"

  require "rectangle"

  -- Ne pas oublier de charger le fichier
  require "circle"

  r1 = Rectangle(100, 100, 200, 50)

  -- Nous transformons r2 en un cercle au lieu d'un rectangle
  r2 = Circle(180, 300, 50)
end

Quand vous lancez le jeu, vous voyez bouger un rectangle et un cercle.

L’héritage en mouvement avec un rectangle et un cercle
L’héritage en mouvement avec un rectangle et un cercle

Encore quelques choses

Examinons ce code encore une fois.

  1. Nous chargeons la lib classic avec require “classic”. Le chargement de cette lib retourne une table que nous stockons dans un objet. Il est essentiel de simuler une classe car Lua n’a pas de notion de classe mais en utilisant la lib classic nous obtenons une très belle imitation d’une classe.
  2. Ensuite nous chargeons shape.lua. Dans ce fichier, nous créons une nouvelle classe appelée Shape. Nous l’utiliserons comme superclasse pour Rectangle et Circle. Les 2 choses que ces classes ont en commun sont les propriétés x et y ainsi que le mouvement horizontal. Ces similitudes sont celles que nous mettons dans Shape.
  3. Ensuite, nous créons la classe Rectangle. Nous en faisons une extension de la superclasse Shape. Dans la fonction :new(), le constructeur, nous appelons le constructeur de notre superclasse avec Rectangle.super.new(self, x, y). Nous passons self comme premier argument de sorte que Shape utilisera l’instance de notre plan et non pas celui du plan lui-même. Nous donnons à notre rectangle des propriété de largeur (width) et de hauteur (height) ainsi qu’une fonction de dessin.
  4. Ensuite, nous répétons ce qui précède à l’exception du cercle. À la place de la largeur et de la hauteur nous donnons la propriété rayon.
  5. Maintenant que nous avons nos classes de prêtes, nous pouvons commencer à créer des instances de ces classes. Avec r1 = Rectangle(100, 100, 200, 50) nous créons une instance de notre classe Rectangle. C’est un objet créer à partir de notre plan et non le plan lui même. Tout changement sera vrai pour cette instances mais n’affectera pas la classe. Nous mettons à jour cette instance, la dessinons et pour ça nous utilisons les deux points (:). C’est parce que nous avons besoin de passer notre instance comme premier argument et les deux points disent à Lua de le faire pour nous.
  6. Finalement nous faisons de même pour r2 sauf que nous en faisons un cercle.

Confus ?

Ça fait beaucoup d’informations en un seul chapitre et je peux imaginer que vous puissiez avoir du mal à tout comprendre.
Mon conseil: continuez à suivre les tuto. Si vous êtes nouveau dans la programmation il vous faudra du temps avant d’intégrer ces nouveaux concepts. Je continuerai à ajouter des explications sur les anciens sujets sans oublier les récents.

Résumé

Les classes sont comme des plans. Nous pouvons créer de multiples objets à partir d’une classe. Pour simuler une classe, nous utilisons la lib classic.
Vous créez une classe avec le nom ClassName = Object:extend(). Vous créez une instance de la classe avec instanceName = ClasseName(). Cela appellera la fonction ClassName:new(). Ceci est appelé le constructeur. Chaque fonction de la classe devrait commencer avec le paramètre self de sorte que lorsque vous appelez la fonction, vous puissiez passez l’instance comme premier argument: instanceName.functionName(instanceName). Nous pouvons utiliser les deux points (:) pour dire à Lua de le faire à notre place.

Nous pouvons étendre une classe avec ExtensionName = ClassName:extend(). Cela fait de ExtensionName, une copie de ClassName à laquelle nous pouvons ajouter des propriétés sans modifier ClassName. Si nous donnons à ExtensionName une fonction que ClassName possède déjà, nous pouvons toujours appeler la fonction d’origine avec ExtensionName.super.functionName(self)