Difference between revisions of "Tutorial:Animation (Français)"

m
(further translation)
Line 1: Line 1:
Before we begin. This is a tutorial for semi advanced users. You are expected to know about tables, loops and the basics of drawing in Löve2D. And of course how to run a Löve2D game.
+
Avant de commencer, ceci est un tutoriel pour les utilisateurs semi-avancés. Vous êtes censé connaître les tableaux, les boucles et les bases du dessin dans Löve2D. Et bien sûr, comment lancer un jeu Löve2D.
  
Alright. Let's get to it shall we?
+
Bon, vous êtes prêt pour y aller ?
  
I'm going to cover the basics of sprite based animation. This means you have a series of images which are displayed one after another. If you intent to create your own spritesheet make sure to leave at least 1px of pure transparency between the individual sprites. Otherwise you might see artifacts from the next or previous image and nobody wants that!
+
Je vais couvrir les bases de l'animation basée sur les sprites. Cela signifie que vous avez une série d'images qui sont affichées les unes après les autres. Si vous avez l'intention de créer votre propre spritesheet, veillez à laisser, au moins, 1px de transparence pure entre les sprites individuels sinon, vous pourriez voir des artefacts sur l'image suivante ou précédente et personne ne le souhaite!
  
== Loading animations ==
+
== Chargement de l'animation ==
  
For this tutorial we'll be using a unnamed old hero. Generously made available for everyone by GrafxKid on [https://opengameart.org/content/classic-hero OpenGameArt.org]. Download the image below and place it next to your main.lua in the folder.
+
Pour ce tutoriel, nous utiliserons un vieux héros anonyme. Généralement mis à la disposition de tous par GrafxKid sur [https://opengameart.org/content/classic-hero OpenGameArt.org]. Téléchargez l'image ci-dessous et placez-la dans le même dossier que votre main.lua.
  
 
[[File:oldHero.png|200px|thumb|center|oldHero.png]]
 
[[File:oldHero.png|200px|thumb|center|oldHero.png]]
  
To be able to create multiple animations we want a reusable function that provides us with a table that we can use for everything related to the animation.
+
Pour pouvoir créer plusieurs animations, nous voulons une fonction réutilisable qui nous fournit une table que nous pouvons utiliser pour tout ce qui concerne l'animation.
We start this by defining a new function, storing our image in a variable and creating a new local table (the return value will be our table. We don't want some random animation table be globally defined or worse yet overwrite other data!)
+
Nous commençons ceci en définissant une nouvelle fonction, en stockant notre image dans une variable et en créant une nouvelle table locale (la valeur de retour sera notre table. Nous ne voulons pas qu'une table d'animation aléatoire soit définie globalement ou pire écrase d'autres données!)
  
The following parameters are going to be needed:
+
Les paramètres suivants seront nécessaires:
  
 
* image     
 
* image     
An image object created with <code>love.graphics.newImage(filepath)</code>
+
Un objet image créé avec <code>love.graphics.newImage(filepath)</code>
 
* width     
 
* width     
The width of each individual sprite. We are going to assume all sprites have the same size.
+
La largeur de chaque sprite individuel. Nous allons supposer que tous les sprites ont la même taille.
 
* height   
 
* height   
The height of each individual sprite.
+
La hauteur de chaque sprite individuel.
 
* duration  
 
* duration  
How long the animation takes before it loops back to the first frame.
+
Combien de temps dure l'animation avant de reboucler sur la première image.
  
 
<source lang="lua">
 
<source lang="lua">
Line 34: Line 34:
 
</source>
 
</source>
  
You can try to just draw this directly with <code>love.graphics.draw(animation.spriteSheet)</code>. However. Then we'd just have all the images drawn next to each other. Certainly not what we want. Which is where [[Quad|Quads]] come in very handy!  
+
Vous pouvez essayer de dessiner cela directement avec <code>love.graphics.draw(animation.spriteSheet)</code>. Toutefois, nous aurions toutes les images dessinées les unes à côté des autres. Ce n'est certainement pas ce que nous voulons. C'est là où les [Quad | Quads]] sont très pratique!
  
They define a part of the image which will be drawn instead of the whole image. Exactly what we need!
+
Ils définissent une partie de l'image qui sera dessinée à la place de l'image entière. C'est exactement ce dont nous avons besoin!
  
Now we could define each quad individually. But that's going to be very annoying if we have several animations and hundreds of sprites! So instead we will load it within a loop that detects how many sprites are within an image. For this setup there must not be empty spaces and the image must contain exactly one animation.
+
Nous pouvons maintenant définir chaque quad individuellement. Mais cela va être très ennuyeux si nous avons plusieurs animations et des centaines de sprites! Nous allons donc le charger dans une boucle qui détecte le nombre de sprites dans une image. Pour cette configuration, il ne doit pas y avoir d'espaces vides et l'image doit contenir, exactement, une animation.
  
 
<source lang="lua">
 
<source lang="lua">
Line 56: Line 56:
 
</source>
 
</source>
  
The 3 parameters to the for loop mean the following:
+
Les 3 paramètres de la boucle for sont les suivants:
  
1. The starting value for y or x.
 
  
2. The maximum value (in this case the total width or height of the reference image)
+
1. La valeur de départ pour y ou x.
  
3. How much should be added per step. In our case the size of the sprite.
+
2. La valeur maximale (dans ce cas la largeur ou la hauteur totale de l'image de référence)
  
With this we get the location on the sprite sheet from y and x!
+
3. Combien doit-on ajouté à chaque étape. Dans notre cas, la taille du sprite.
  
You might wonder why we do "image:getHeight() - height" instead of just testing against the height of the image. This is because we want to make sure another sprite still fits on the sprite sheet. The sheet itself might not have the exact size we want it to. If it were one pixel too large. We do not want to add another quad (which would render as nothing) but instead ignore it.
+
Avec cela, nous obtenons l'emplacement sur la feuille de sprite à partir de y et x!
  
Now we have 6 Quads ranging from index 1 - 6 in the table. Awesome!
+
Vous pourriez vous demander pourquoi faisons nous "image: getHeight () - height" au lieu de simplement tester la hauteur de l'image. C'est parce que nous voulons nous assurer qu'un autre sprite tient toujours sur la feuille de sprite. La feuille elle-même pourrait ne pas avoir la taille exacte souhaitée. Si elle était trop grande d'un pixel nous ne voudrions pas ajouter un autre quad (ce qui ne se traduirait par rien) mais plutôt l’ignorer.
  
But we have the issue that we have essentially 6 images that we can draw individually. But we need to draw them one after another over time.
+
Maintenant, nous avons 6 Quads allant de 1 à 6 dans le tableau. Impressionnant!
Also we don't just want to play this animation. We might want to change the speed at which it plays.
 
  
To cover this we add two more variables to our animation table.
+
Cependant, nous avons un autre problème avec ces 6 images que nous pouvons dessiner individuellement. Dans l'état actuel des choses, nous allons les dessiner les unes après les autres au fil du temps.
Duration (which we expect as parameter) and currentTime which is used to measure how much time has passed.
+
Mais, nous ne voulons pas forcément jouer cette animation aussi vite que possible et nous pourrions vouloir changer la vitesse de défilement.
 +
 
 +
Pour corriger ceci, nous ajoutons deux variables supplémentaires à notre table d'animation.
 +
Duration (que nous attendons en tant que paramètre) et currentTime qui sert à mesurer le temps écoulé.
  
 
<source lang="lua">
 
<source lang="lua">
Line 95: Line 96:
 
</source>
 
</source>
  
Which concludes our animation creation function already!
+
Ce qui conclut notre fonction de création d'animation!
  
== Updating the animation ==  
+
== Mise à jour de l'animation ==  
  
Next up we need to load our animation table (call our newly created function) and update our current time.
+
Nous devons charger notre table d'animation (appelez notre nouvelle fonction) et mettre à jour notre heure actuelle.
  
 
<source lang="lua">
 
<source lang="lua">
Line 114: Line 115:
 
</source>
 
</source>
  
The demo sprite sheet has a sprite size of 16 pixels wide and 18 pixels high. We intent to play all images over the course of 1 second.
+
La feuille de sprite de démonstration a une taille de sprite de 16 pixels de largeur et de 18 pixels de hauteur. Nous avons l'intention de jouer toutes les images sur une seconde.
  
In <code>love.update</code> we simply add dt (delta time aka the time since the last frame) to our current time. We now count upwards continuously!
+
Dans <code>love.update</code>, nous ajoutons simplement dt (delta time aka le temps écoulé depuis la dernière image) à notre heure actuelle. Maintenant, nous comptons continuellement!
  
But we will use the current time to determine which frame should be shown. As such we will want it to be between 0 and the value of "duration". The if simply checks if "currentTime" is more than "duration" in which case we subtract "duration". You could just set "currentTime" to 0 instead of subtracting "duration". However this will result in fractions of a second being lost every time the animation finishes and therefore slow down the animation playtime ever so slightly. Which can easily be avoided and should be avoided!
+
Mais nous utiliserons l'heure actuelle pour déterminer quelle image doit être affichée. En tant que tel, nous voulons qu'il soit compris entre 0 et la valeur de "duration". Le if vérifie simplement si "currentTime" est plus grand que "duration", auquel cas on soustrait "duration". Vous pouvez simplement définir "currentTime" sur 0 au lieu de soustraire "duration". Cependant, des fractions de seconde sont perdues chaque fois que l'animation se termine et ralentissent donc légèrement la durée de l'animation. Ceci est facile à évité et doit l'être!
  
Now for the really interesting part!
+
Maintenant, la partie vraiment intéressante!
  
== Drawing the animation ==  
+
== Dessiner l'animation ==  
  
How do we draw this?
+
Comment pouvons-nous dessiner cela?
  
Well. We have the duration and current time. With this info we can calculate a percentage! How much of the animation has passed so far?
+
Bon. Nous avons la durée et l'heure actuelle. Avec cette information, nous pouvons calculer un pourcentage! Combien de l'animation est passée jusqu'à présent?
  
If you've followed this tutorial correctly so far ''"currentTime / duration"'' will provide you with a number between 0 and 1. Which represents the percentage. 0.25 means 25% of the animation has passed.
+
Si vous avez suivi ce tutoriel correctement jusqu'à présent, ''"currentTime / duration"'' vous fournira un nombre compris entre 0 et 1 ce qui représente le pourcentage. 0,25 signifie que 25% de l'animation est passée.
  
Considering this we can search for the correct image to use! Since we already have a number between 0 and 1 we can simply multiply this percentage with our total amount of images and get a number between 0 and 6!
+
Compte tenu de cela, nous pouvons rechercher la bonne image à utiliser! Comme nous avons déjà un nombre entre 0 et 1, nous pouvons simplement multiplier ce pourcentage avec notre nombre total d'images et obtenir un nombre entre 0 et 6!
  
 
''currentTime / duration * #quads''
 
''currentTime / duration * #quads''
  
However. If we try to get this from our table we will run into the issue that this is not a whole number. But our images are stored with whole numbers! So attempting to get the image at index "4.75" will give us nothing. Bummer!  
+
Toutefois, le résultat de l'opération va nous poser un problème car ce ne sera pas un entier hors, nos images sont stockées dans la table avec des entiers! Donc, essayer d'obtenir l'image à l'index "4.75" ne nous donnera rien. Mince!
 +
 
 +
Pas de panique, la solution n'est pas trop difficile.
  
Fear not. The solution is not too difficult.
 
  
"currentTime" will be a number between 0 and just below "duration" (because we reduce "currentTime" if it is larger or ''equal'' "duration"). Which would result in a number between 0 and 5.
+
"currentTime" sera un nombre compris entre 0 et juste en dessous de "duration" (car nous réduisons "currentTime" s'il est plus grand ou ''égal'' à "duration"). Ce qui donnerait un nombre entre 0 et 5.
  
To transform this value from our decimal point value to a whole number between 1 and 6 we do the following:
+
Pour transformer cette valeur de notre valeur décimale à un nombre entier compris entre 1 et 6, il faut procéder comme suit:
  
 
<source lang="lua">
 
<source lang="lua">
Line 146: Line 148:
 
</source>
 
</source>
  
"math.floor" provides us with the next lower number. Which means in our case a number between 0 and 5. We add one pushing it to a number between 1 and 6. All the sprites we have!
+
"math.floor" nous fournit le prochain chiffre inférieur, ce qui signifie dans notre cas, un nombre compris entre 0 et 5. On y rajoute un et nous obtenons un nombre compris entre 1 et 6 ce qui correspond à ce au nombre de sprite que nous avons!
  
Lövely!
+
Charmant!
  
 
Alright. So all that's left is to draw the appropriate quad!
 
Alright. So all that's left is to draw the appropriate quad!
Line 215: Line 217:
 
{{#set:LOVE Version=}}
 
{{#set:LOVE Version=}}
 
{{#set:Description=Basics of sprite based animation.}}
 
{{#set:Description=Basics of sprite based animation.}}
== Autres langues ==
+
== Other Languages ==
 
{{i18n|Tutorial:Animation}}
 
{{i18n|Tutorial:Animation}}

Revision as of 17:08, 17 August 2018

Avant de commencer, ceci est un tutoriel pour les utilisateurs semi-avancés. Vous êtes censé connaître les tableaux, les boucles et les bases du dessin dans Löve2D. Et bien sûr, comment lancer un jeu Löve2D.

Bon, vous êtes prêt pour y aller ?

Je vais couvrir les bases de l'animation basée sur les sprites. Cela signifie que vous avez une série d'images qui sont affichées les unes après les autres. Si vous avez l'intention de créer votre propre spritesheet, veillez à laisser, au moins, 1px de transparence pure entre les sprites individuels sinon, vous pourriez voir des artefacts sur l'image suivante ou précédente et personne ne le souhaite!

Chargement de l'animation

Pour ce tutoriel, nous utiliserons un vieux héros anonyme. Généralement mis à la disposition de tous par GrafxKid sur OpenGameArt.org. Téléchargez l'image ci-dessous et placez-la dans le même dossier que votre main.lua.

oldHero.png

Pour pouvoir créer plusieurs animations, nous voulons une fonction réutilisable qui nous fournit une table que nous pouvons utiliser pour tout ce qui concerne l'animation. Nous commençons ceci en définissant une nouvelle fonction, en stockant notre image dans une variable et en créant une nouvelle table locale (la valeur de retour sera notre table. Nous ne voulons pas qu'une table d'animation aléatoire soit définie globalement ou pire écrase d'autres données!)

Les paramètres suivants seront nécessaires:

  • image

Un objet image créé avec love.graphics.newImage(filepath)

  • width

La largeur de chaque sprite individuel. Nous allons supposer que tous les sprites ont la même taille.

  • height

La hauteur de chaque sprite individuel.

  • duration

Combien de temps dure l'animation avant de reboucler sur la première image.

function newAnimation(image, width, height, duration)
    local animation = {}
    animation.spriteSheet = image;

    return animation
end

Vous pouvez essayer de dessiner cela directement avec love.graphics.draw(animation.spriteSheet). Toutefois, nous aurions toutes les images dessinées les unes à côté des autres. Ce n'est certainement pas ce que nous voulons. C'est là où les [Quad | Quads]] sont très pratique!

Ils définissent une partie de l'image qui sera dessinée à la place de l'image entière. C'est exactement ce dont nous avons besoin!

Nous pouvons maintenant définir chaque quad individuellement. Mais cela va être très ennuyeux si nous avons plusieurs animations et des centaines de sprites! Nous allons donc le charger dans une boucle qui détecte le nombre de sprites dans une image. Pour cette configuration, il ne doit pas y avoir d'espaces vides et l'image doit contenir, exactement, une animation.

function newAnimation(image, width, height, duration)
    local animation = {}
    animation.spriteSheet = image;
    animation.quads = {};

    for y = 0, image:getHeight() - height, height do
        for x = 0, image:getWidth() - width, width do
            table.insert(animation.quads, love.graphics.newQuad(x, y, width, height, image:getDimensions()))
        end
    end

    return animation
end

Les 3 paramètres de la boucle for sont les suivants:


1. La valeur de départ pour y ou x.

2. La valeur maximale (dans ce cas la largeur ou la hauteur totale de l'image de référence)

3. Combien doit-on ajouté à chaque étape. Dans notre cas, la taille du sprite.

Avec cela, nous obtenons l'emplacement sur la feuille de sprite à partir de y et x!

Vous pourriez vous demander pourquoi faisons nous "image: getHeight () - height" au lieu de simplement tester la hauteur de l'image. C'est parce que nous voulons nous assurer qu'un autre sprite tient toujours sur la feuille de sprite. La feuille elle-même pourrait ne pas avoir la taille exacte souhaitée. Si elle était trop grande d'un pixel nous ne voudrions pas ajouter un autre quad (ce qui ne se traduirait par rien) mais plutôt l’ignorer.

Maintenant, nous avons 6 Quads allant de 1 à 6 dans le tableau. Impressionnant!

Cependant, nous avons un autre problème avec ces 6 images que nous pouvons dessiner individuellement. Dans l'état actuel des choses, nous allons les dessiner les unes après les autres au fil du temps. Mais, nous ne voulons pas forcément jouer cette animation aussi vite que possible et nous pourrions vouloir changer la vitesse de défilement.

Pour corriger ceci, nous ajoutons deux variables supplémentaires à notre table d'animation. Duration (que nous attendons en tant que paramètre) et currentTime qui sert à mesurer le temps écoulé.

function newAnimation(image, width, height, duration)
    local animation = {}
    animation.spriteSheet = image;
    animation.quads = {};

    for y = 0, image:getHeight() - height, height do
        for x = 0, image:getWidth() - width, width do
            table.insert(animation.quads, love.graphics.newQuad(x, y, width, height, image:getDimensions()))
        end
    end

    animation.duration = duration or 1
    animation.currentTime = 0

    return animation
end

Ce qui conclut notre fonction de création d'animation!

Mise à jour de l'animation

Nous devons charger notre table d'animation (appelez notre nouvelle fonction) et mettre à jour notre heure actuelle.

function love.load()
    animation = newAnimation(love.graphics.newImage("oldHero.png"), 16, 18, 1)
end

function love.update(dt)
    animation.currentTime = animation.currentTime + dt
    if animation.currentTime >= animation.duration then
        animation.currentTime = animation.currentTime - animation.duration
    end
end

La feuille de sprite de démonstration a une taille de sprite de 16 pixels de largeur et de 18 pixels de hauteur. Nous avons l'intention de jouer toutes les images sur une seconde.

Dans love.update, nous ajoutons simplement dt (delta time aka le temps écoulé depuis la dernière image) à notre heure actuelle. Maintenant, nous comptons continuellement!

Mais nous utiliserons l'heure actuelle pour déterminer quelle image doit être affichée. En tant que tel, nous voulons qu'il soit compris entre 0 et la valeur de "duration". Le if vérifie simplement si "currentTime" est plus grand que "duration", auquel cas on soustrait "duration". Vous pouvez simplement définir "currentTime" sur 0 au lieu de soustraire "duration". Cependant, des fractions de seconde sont perdues chaque fois que l'animation se termine et ralentissent donc légèrement la durée de l'animation. Ceci est facile à évité et doit l'être!

Maintenant, la partie vraiment intéressante!

Dessiner l'animation

Comment pouvons-nous dessiner cela?

Bon. Nous avons la durée et l'heure actuelle. Avec cette information, nous pouvons calculer un pourcentage! Combien de l'animation est passée jusqu'à présent?

Si vous avez suivi ce tutoriel correctement jusqu'à présent, "currentTime / duration" vous fournira un nombre compris entre 0 et 1 ce qui représente le pourcentage. 0,25 signifie que 25% de l'animation est passée.

Compte tenu de cela, nous pouvons rechercher la bonne image à utiliser! Comme nous avons déjà un nombre entre 0 et 1, nous pouvons simplement multiplier ce pourcentage avec notre nombre total d'images et obtenir un nombre entre 0 et 6!

currentTime / duration * #quads

Toutefois, le résultat de l'opération va nous poser un problème car ce ne sera pas un entier hors, nos images sont stockées dans la table avec des entiers! Donc, essayer d'obtenir l'image à l'index "4.75" ne nous donnera rien. Mince!

Pas de panique, la solution n'est pas trop difficile.


"currentTime" sera un nombre compris entre 0 et juste en dessous de "duration" (car nous réduisons "currentTime" s'il est plus grand ou égal à "duration"). Ce qui donnerait un nombre entre 0 et 5.

Pour transformer cette valeur de notre valeur décimale à un nombre entier compris entre 1 et 6, il faut procéder comme suit:

math.floor(currentTime / duration * #quads) + 1

"math.floor" nous fournit le prochain chiffre inférieur, ce qui signifie dans notre cas, un nombre compris entre 0 et 5. On y rajoute un et nous obtenons un nombre compris entre 1 et 6 ce qui correspond à ce au nombre de sprite que nous avons!

Charmant!

Alright. So all that's left is to draw the appropriate quad!

This simply requires us to provide love.graphics.draw with the image reference (our spriteSheet) and the quad we want to use. Simple enough!

    local spriteNum = math.floor(animation.currentTime / animation.duration * #animation.quads) + 1
    love.graphics.draw(animation.spriteSheet, animation.quads[spriteNum])

And we are done! You should have a walking dude in the top left corner of your window when you execute this code!

You can change where and how it is drawn by providing more parameters to the draw function.

Disclaimer: This code is not game ready! It is meant to explain the basic logic behind animations. If you are searching for game ready code take a look at the list of Libraries available!

Task

To improve this code. Try to rewrite the update and draw function to be able to handle multiple animations.

You can load the same animation multiple times and store them in a special table to get started right away!

As hint as to how it could be done: You could put the drawing logic in the animation table as well. And build it so you can call "animation:draw(x, y, r, sx, sy, [...])" to draw it on the screen.

Good luck and have fun!

Full Sourcecode

function love.load()
    animation = newAnimation(love.graphics.newImage("oldHero.png"), 16, 18, 1)
end

function love.update(dt)
    animation.currentTime = animation.currentTime + dt
    if animation.currentTime >= animation.duration then
        animation.currentTime = animation.currentTime - animation.duration
    end
end

function love.draw()
    local spriteNum = math.floor(animation.currentTime / animation.duration * #animation.quads) + 1
    love.graphics.draw(animation.spriteSheet, animation.quads[spriteNum], 0, 0, 0, 4)
end

function newAnimation(image, width, height, duration)
    local animation = {}
    animation.spriteSheet = image;
    animation.quads = {};

    for y = 0, image:getHeight() - height, height do
        for x = 0, image:getWidth() - width, width do
            table.insert(animation.quads, love.graphics.newQuad(x, y, width, height, image:getDimensions()))
        end
    end

    animation.duration = duration or 1
    animation.currentTime = 0

    return animation
end


Other Languages