Animation avec love2d

Images

Fabriquons une image animée. Premièrement, vous aurez besoins de ces images :

Chargez ces images et mettez-les dans une table.

1
2
3
4
5
6
7
8
function love.load()
  frames = {}
  table.insert(frames, love.graphics.newImage("jump1.png"))
  table.insert(frames, love.graphics.newImage("jump2.png"))
  table.insert(frames, love.graphics.newImage("jump3.png"))
  table.insert(frames, love.graphics.newImage("jump4.png"))
  table.insert(frames, love.graphics.newImage("jump5.png"))
end

Attendez, nous pouvons faire ça beaucoup plus efficacement.

1
2
3
4
5
6
7
function love.load()
  frames = {}

  for i=1,5 do
    table.insert(frames, love.graphics.newImage("jump" .. i .. ".png"))
  end
end

C’est mieux! Nous devons maintenant créer une animation. Comment allons-nous faire cela ?

Une boucle for ?

Non. Une boucle for nous permettrait de dessiner toutes les images en même temps, mais nous voulons dessiner une image différente chaque seconde. Nous avons besoin d’une variable qui augmente de 1 chaque seconde. Et bien c’est facile !

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
function love.load()
  frames = {}

  for i=1,5 do
    table.insert(frames, love.graphics.newImage("jump" .. i .. ".png"))
  end

  --I use a long name to avoid confusion with the variable named frames
  currentFrame = 1
end

function love.update(dt)
  currentFrame = currentFrame + dt
end

Maintenant, nous avons la variable currentFrame qui augmente de 1 chaque seconde, utilisons cette variable pour dessiner les images.

1
2
3
function love.draw()
  love.graphics.draw(frames[currentFrame])
end

Si vous lancez le jeu, vous obtiendrez une erreur : bad argument #1 to ‘draw’ (Drawable expected, got nil)

C’est parce que notre variable currentFrame a des décimales. Après la première mise à jour, currentFrame est quelque chose comme 1.016, et bien que notre table ait quelque chose sur les positions 1 et 2, il n’y a rien sur la position 1.016.

Pour résoudre ce problème, nous arrondissons le nombre avec math.floor. Donc 1.016 deviendra 1.

Exécutez le jeu et vous verrez que notre animation fonctionne, mais vous obtiendrez finalement une erreur. C’est parce que currentFrame est devenu supérieur (ou égal à) 6. Et nous n’avons que 5 images. Pour résoudre ce problème, nous réinitialisons currentFrame s’il est supérieur (ou égal à) 6. Et pendant que nous y sommes, accélérons notre animation.

1
2
3
4
5
6
function love.update(dt)
  currentFrame = currentFrame + 10 * dt
  if currentFrame >= 6 then
    currentFrame = 1
  end
end

Regardez-le sauter !

Résultat des images du bonhomme qui saute
Résultat des images du bonhomme qui saute

Quads

Cela fonctionne donc, mais ce n’est pas très efficace. Avec de grandes animations, nous aurons besoin de beaucoup de fichiers images. Et si nous mettions tous ces images dans une seule, puis dessinions une partie de l’image. Nous pouvons le faire avec grâce aux quads.

Tout d’abord, téléchargez cette image :

Image unique pour faire une animation via un quad
Image unique pour faire une animation via un quad

Nous allons refaire la fonction love.load (vous pouvez garder love.update et love.draw tels quels)

1
2
3
function love.load()
  image = love.graphics.newImage("jump.png")
end

Imaginez des quads comme un rectangle que nous découpons de notre image. Nous disons au jeu « Nous voulons cette partie de l’image ». Nous allons faire un quad de la première image. Vous pouvez faire un quad avec love.graphics.newQuad (wiki).

Les premiers arguments sont la position x et y de notre quad. Eh bien, puisque nous voulons la première image, nous prenons le coin supérieur gauche de notre image, donc 0,0.

1
2
3
4
5
function love.load()
  image = love.graphics.newImage("jump.png")
  frames = {}
  table.insert(frames, love.graphics.newQuad(0, 0))
end
Explication d’un quad
Explication d’un quad

Les 2 arguments suivants sont la largeur et la hauteur de notre quad. La largeur d’un cadre dans notre image est 117 et la hauteur est 233. Les 2 derniers arguments sont la largeur et la hauteur de l’image complète. Nous pouvons les obtenir avec image:getWidth() et image:getHeight().

1
2
3
4
5
6
7
8
9
function love.load()
  image = love.graphics.newImage("jump.png")
  frames = {}
  local frame_width = 117
  local frame_height = 233
  table.insert(frames, love.graphics.newQuad(0, 0, frame_width, frame_height, image:getWidth(), image:getHeight()))

  currentFrame = 1
end

Maintenant, testons notre quad en le dessinant. Vous dessinez un quad en le passant comme deuxième argument dans love.graphics.draw

1
2
3
function love.draw()
  love.graphics.draw(image, frames[1], 100, 100)
end

Comme vous pouvez le voir, il dessine notre première image. Génial, faisons maintenant le deuxième quad.

Pour dessiner la deuxième image, il suffit de déplacer le rectangle vers la droite. Puisque chaque image a une largeur de 117, tout ce que nous devons faire est de déplacer notre x vers la droite avec 117.

1
2
3
4
5
6
7
8
function love.load()
  image = love.graphics.newImage("jump.png")
  frames = {}
  local frame_width = 117
  local frame_height = 233
  table.insert(frames, love.graphics.newQuad(0, 0, frame_width, frame_height, image:getWidth(), image:getHeight()))
  table.insert(frames, love.graphics.newQuad(frame_width, 0, frame_width, frame_height, image:getWidth(), image:getHeight()))
end

Nous pouvons faire de même pour le 3e quad.

Représentation du découpage pour être utilisé via un quad
Représentation du découpage pour être utilisé via un quad

Attendez, répétons-nous la même action ? N’avons-nous pas quelque chose pour ça ? Pour les boucles !
De plus, nous pouvons empêcher d’appeler :getWidth et :getHeight plusieurs fois en stockant les valeurs dans une variable.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
function love.load()
  image = love.graphics.newImage("jump.png")
  local width = image:getWidth()
  local height = image:getHeight() 

  frames = {}

  local frame_width = 117
  local frame_height = 233

  for i=0,4 do
    table.insert(frames, love.graphics.newQuad(i * frame_width, 0, frame_width, frame_height, width, height))
  end

  currentFrame = 1
end

Remarquez comment nous commençons notre boucle for sur 0 et la terminons sur 4, au lieu de 1 à 5. En effet, notre premier quad est en position 0 et $0\times 177=0$.

Il ne reste plus qu’à utiliser currentFrame pour le quad que nous voulons dessiner.

1
2
3
function love.draw()
  love.graphics.draw(image, frames[math.floor(currentFrame)], 100, 100)
end

Images sur plusieurs rangées

Nous pouvons donc maintenant transformer une rangée d’images en animation, mais que faire si elle s’étale sur plusieurs rangées ?

Images sur plusieurs rangées
Images sur plusieurs rangées

Facile, il suffit de répéter la même chose avec une valeur y différente.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
function love.load()
  image = love.graphics.newImage("jump_2.png")
  local width = image:getWidth()
  local height = image:getHeight() 

  frames = {}

  local frame_width = 117
  local frame_height = 233

  for i=0,2 do
    table.insert(frames, love.graphics.newQuad(i * frame_width, 0, frame_width, frame_height, width, height))
  end

  for i=0,1 do
    table.insert(frames, love.graphics.newQuad(i * frame_width, frame_height, frame_width, frame_height, width, height))
  end

  currentFrame = 1
end

Mais attendez, ça y est: la répétition !
Et que faisons-nous quand nous voyons la répétition ?
Nous utilisons une boucle for.

Exactement !
Nous allons cependant devoir faire quelques changements.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
function love.load()
  image = love.graphics.newImage("jump_2.png")
  local width = image:getWidth()
  local height = image:getHeight() 

  frames = {}

  local frame_width = 117
  local frame_height = 233

  for i=0,1 do
    --I changed i to j in the inner for-loop
    for j=0,2 do
      --Meaning you also need to change it here
      table.insert(frames, love.graphics.newQuad(j * frame_width, i * frame_height, frame_width, frame_height, width, height))
    end
  end

  currentFrame = 1
end

Donc, dans la première itération de la boucle for externe, i est égal à 0, et j est égal à 0, puis 1, puis 2 et enfin 3. Dans la deuxième itération, i est égal à 1, et j est à nouveau égal à 0, puis 1, puis 2 et enfin 3.

Vous remarquerez peut-être que nous avons un quad vide supplémentaire. Ce n’est pas vraiment un gros problème puisque nous ne dessinons que les 5 premiers quads, mais nous pouvons faire quelque chose comme ça pour l’empêcher :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
maxFrames = 5
for i=0,1 do
  for j=0,2 do
    table.insert(frames, love.graphics.newQuad(j * frame_width, i * frame_height, frame_width, frame_height, width, height))
    if #frames == maxFrames then
      break
    end 
  end
  print("I don't break!")
end

Avec break, nous pouvons mettre fin à une boucle for. Cela l’empêchera d’ajouter ce dernier quad.

Notez comment « Je ne casse pas » est imprimé. En effet, break ne rompt que la boucle dans laquelle vous l’utilisez, la boucle externe continue toujours. Il pourrait être corrigé en ajoutant la même instruction if dans notre boucle externe, mais dans notre cas, cela n’a pas d’importance car la boucle à ce point est déjà à sa dernière itération.

Bleeding

Lors de la rotation et / ou de la mise à l’échelle d’une image lors de l’utilisation de quads, un effet peut apparaître appelé bleeding (saignement). Ce qui se passe, c’est qu’une partie de l’image à l’extérieur du quad est dessinée.

Donc, si c’était notre feuille de sprites :

Images permettant d’expliquer ce qu’est le bleeding
Images permettant d’expliquer ce qu’est le bleeding

Notre première image pourrait être dessinée comme ça :

Images représentant ce qu’est le bleeding
Images représentant ce qu’est le bleeding

C’est un peu technique pourquoi cela se produit, mais le fait est que cela se produit. Heureusement, nous pouvons résoudre ce problème en ajoutant une bordure de 1 pixel autour de notre image. Soit de la même couleur que la bordure actuelle, soit avec transparence.

Image avec une bordure pour supprimer le bleeding
Image avec une bordure pour supprimer le bleeding

Du coup, nous n’incluons pas cette bordure à l’intérieur du quad.

J’ai ajouté une bordure à notre personnage de saut. Au lieu de transparent, je l’ai rendu violet pour que nous puissions voir si nous ne dessinons pas accidentellement une partie de la bordure.

Image de notre bonhomme qui saute avec une bordure
Image de notre bonhomme qui saute avec une bordure

Faisons ça par étape.

Tout d’abord, nous ne voulons pas que le premier pixel soit dessiné, donc notre quad commence à 1 (au lieu de 0).

1
newQuad(1, 1, frame_width, frame_height, width, height)

D’accord, cela fonctionne donc pour la première image, mais quelle partie de l’image suivante voulons-nous dessiner? Ajoutez simplement la largeur / hauteur du cadre ?

1
newQuad(1 + j * frame_width, 1 + i * frame_height, frame_width, frame_height, width, height)

Presque. Il nous manque quelque chose.

Image représentant le petit problème qu’on a avec le code ci-dessus
Image représentant le petit problème qu’on a avec le code ci-dessus

La ligne bleue est notre quad. Comme vous pouvez le voir, le quad est à 2 pixels à gauche de l’endroit où il est censé se trouver. Ajoutons donc 2 à combien nous déplaçons chaque itération.

1
newQuad(1 + j * (frame_width + 2), 1 + i * (frame_height + 2), frame_width, frame_height, width, height)

Et maintenant, nos quads sont dans la bonne position. Voici une image montrant comment nous positionnons le quad. Nous ajoutons donc 1, puis ajoutez frame_width + 2, multiplié par i. De cette façon, nous positionnons correctement le quad pour chaque image.

Image pour représenter ce que l’on a exactement
Image pour représenter ce que l’on a exactement

Résumé

Avec les quads, nous pouvons dessiner une partie d’une image. nous pouvons l’utiliser pour transformer une feuille de sprites en une animation. En cas de plusieurs lignes, nous pouvons utiliser une boucle for à l’intérieur d’une boucle for pour couvrir la feuille entière. Nous pouvons utiliser break pour terminer une boucle. Nous ajoutons une bordure de 1 pixel à nos sprites pour éviter l’effet de bleeding (saignement).