Programmation graphique
Chapitre 6 : Les Sprites : Animation et accélération
Tutoriel présenté par : Robert Gillard (Gondulzak)
Publication : 01 février 2014
Dernière mise à jour : 22 novembre 2015
Concept d'Accélération appliqué à l'Animation
Dès que vous pressez les touches gauche/droite de votre clavier/gamepad, vous voyez notre motocycliste avancer dans la direction que vous choisissez mais ce, à une vitesse constante dès l'affichage de la première frame !
Si un avion ou un mobile quelconque traverse votre écran d'un côté à l'autre et disparaît vers le côté opposé de celui duquel il était apparu, cela n'a pas grande importance. Mais si nous démarrons une animation à l'intérieur de la fenêtre de jeu, celà ne semblera pas naturel. Un bon exemple est de vous demander d'essayer de courir un 100 mètres en ayant atteint votre vitesse maximum dès le départ... Et bien cela est la même chose sur l'écran lorsque vous démarrez une animation dont la vitesse est constante et dans certains cas, nous devrons effectuer une accélération / décélération sur notre animation.
Mais je vous rassure, point n'est besoin ici d'appliquer des formules de cinématique relatives aux mouvements uniformément accélérés ou retardés. Alors, comment procéder ?
Nous allons voir le code dans quelques instants. Nous pourrions intégrer celui-ci dans la fonction UpdateHero() de notre projet précedent mais ne tenant pas à trop modifier nos classes actuelles en vue de les réutiliser dans notre jeu à venir, nous allons créer un nouveau projet, très court comme vous allez le voir, mais qui nous permettra d'imprimer une réelle accélération à notre sprite ainsi qu'une décélération remarquée dès que nous relâcherons la touche de direction.
Ne voulant pas vous faire attendre plus longtemps, je vous demanderai de bien vouloir créer un nouveau projet que vous nommerez « DriveIn2 ». Nous reprendrons bien entendu le background ainsi que le sprite du projet DriveIn (que vous retrouverez ci-dessous par commodité ) :
Dans la classe Game1, juste en dessous de SpriteBatch spriteBatch;, ajoutez les variables suivantes :
//Données source private Texture2D background; private Texture2D animationTexture; private Rectangle currentFrameLocation; //Données de destination private Vector2 spritePosition; private Vector2 spriteVelocity; private Vector2 maxSpriteVelocity; //Données pour l'animation private int currentFrame; private int numberOfFrames; private int timeUntilNextFrame; // toujours en milisecondes private int millisecondsPerFrame; // toujours en milisecondes private SpriteEffects spriteEffet;
Certaines de ces variables demandent une explication :
Nous déclarons une variable spriteVelocity qui gérera l'accélération ainsi que maxSpriteVelocity qui sera la vitesse maximale que pourra atteindre notre motocycliste, timeUntilNextFrame étant le temps restant jusqu'à l'affichage de la frame suivante.
Inutile de toucher à notre constructeur, occupons-nous de la fonction Initialize(). Supprimez celle-ci et remplacez-la par la suivante:
//Fonction d'initialisation protected override void Initialize() { // TODO: Add your initialization logic here numberOfFrames = 4; currentFrame = 0; millisecondsPerFrame = 20; timeUntilNextFrame = millisecondsPerFrame; currentFrameLocation.X = 0; currentFrameLocation.Y = 0; currentFrameLocation.Width = 80; //largeur du sprite currentFrameLocation.Height = 55; //hauteur du sprite spritePosition = new Vector2(150, 390); spriteVelocity = new Vector2(0, 0); maxSpriteVelocity = new Vector2(8, 0); spriteEffet = SpriteEffects.FlipHorizontally; base.Initialize(); }
Dans cette fonction, je vous ferai juste remarquer la variable représentant l'accélération de type Vector2 qui bien entendu a une valeur nulle au départ (0, 0). Nous donnons de même une valeur à la vitesse maximale du sprite maxSpriteVelocity (8, 0), représentée elle aussi par une variable de type Vector2. Et pour terminer nous donnons un effet renversé horizontal à notre variable spriteEffect.
Dans la fonction LoadContent, vous ajouterez simplements nos assets nécessaires à l'animation et à l'affichage. Entrez les deux lignes suivantes en dessous de spriteBatch = new SpriteBatch(GraphicsDevice);
animationTexture = Content.Load<Texture2D>("moto");
background = Content.Load<Texture2D>("Background22bis");
|
Et comme vous vous en doutez, toute la logique de cette animation va se faire dans la fonction Update(). Supprimez cette fonction et remplacez-la par celle-ci :
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
timeUntilNextFrame -= gameTime.ElapsedGameTime.Milliseconds;
if (Keyboard.GetState().IsKeyDown(Keys.Left))
{
if (spriteVelocity.X > -maxSpriteVelocity.X)
{
spriteVelocity.X -= 0.03f;
spriteEffet = SpriteEffects.None;
}
}
else if (Keyboard.GetState().IsKeyDown(Keys.Right))
{
if (spriteVelocity.X < maxSpriteVelocity.X)
{
spriteVelocity.X += 0.03f;
spriteEffet = SpriteEffects.FlipHorizontally;
}
}
else
{
spriteVelocity *= 0.97f;
}
spritePosition += spriteVelocity;
float relativeVelocity = Math.Abs(spriteVelocity.X / maxSpriteVelocity.X);
if (relativeVelocity > 0.03f)
{
if (timeUntilNextFrame <= 0)
{
currentFrame++;
timeUntilNextFrame = (int)(millisecondsPerFrame * (2.0f -
relativeVelocity));
}
}
if (currentFrame >= numberOfFrames)
currentFrame = 0;
currentFrameLocation.X = currentFrameLocation.Width * currentFrame;
base.Update(gameTime);
}
|
Voyons maintenant tout ce que ceci signifie.
Nous avons besoin de faire un lien entre millisecondsPerFrame et la vitesse du sprite. Nous allons donc supposer que la plus petite valeur de timeUntilNextFrame est la valeur définie auparavant dans millisecondsPerFrame et que la plus grande valeur de timeUntilNextFrame est disons, deux fois cette valeur.
Nous devons donc modifier la valeur que nous avons assignée à timeUntilNextFrame basée sur la vitesse relative.
Mais qu'entendons-nous par vitesse relative ?
La vitesse relative est déterminée comme étant la valeur absolue du quotient de la vitesse actuelle / la vitesse maximum et qui détermine le nombre de millisecondes jusqu'à la prochaine frame.
TimeUntilNextFrame = millisecondsPerFrame x (1.0 + ( 1.0 – (vitesse actuelle/vitesse maximum)))
Ceci est valable tant que le sprite est en mouvement, mais dès l'instant que vous relâchez la touche directionnelle, vous voudriez que l'animation s'arrête également. A cet effet nous calculons la vitesse relative un peu plus tôt (spriteVelocity *= 0.97f ) en utilisant une instruction conditionnelle.
Il ne nous reste plus qu'à réecrire la fonction Draw() qui n'a plus de secrets pour vous maintenant (cf. chapitre précédent)...
Supprimez cette fonction et remplacez-la par celle-ci :
protected override void Draw(GameTime gameTime) { //GraphicsDevice.Clear(Color.CornflowerBlue); // TODO: Ajoutez le code du dessin ici spriteBatch.Begin(); spriteBatch.Draw(background, Vector2.Zero, Color.White); spriteBatch.Draw(animationTexture, spritePosition, currentFrameLocation, Color.White, 0.0f, //Rotation Vector2.Zero, //Origine 1.0f, //Echelle spriteEffet, //Effet 1.0f); //Profondeur spriteBatch.End(); base.Draw(gameTime); }
Il m'est bien entendu impossible de vous montrer cette animation à l'intérieur de ce tutoriel mais je vous laisse regarder ce bel effet d'accélération et de décélération sur votre propre écran. A ce sujet je vous invite à faire quelques modifications sur les valeurs des variables concernant la mise à jour de la fonction Update() afin de trouver l'animation qui vous convient le mieux pour un projet éventuel.
Afin de voir l'importance de cette animation, je vous suggère de faire défiler le sprite animé d'un côté ou l'autre de la fenêtre de votre écran, après avoir doublé sa vitesse par exemple, (16,0) et en le laissant sortir pendant quelques secondes de cette même fenêtre. Faites-le alors revenir en maintenant la touche opposée jusqu'à ce que vous aperceviez le sprite apparaitre à nouveau. Lâchez aussitôt la touche directionnelle sur laquelle vous êtes en train de presser et vous pourrez alors voir le sprite animé s'arrêter après avoir encore parcouru pratiquement toute la largeur de la fenêtre et ce, avec un ralentissement significatif.
En outre, en guise d'exercice, vous pourriez essayer de modifier la fonction UpdateHero() de la classe Game1 du précédent projet DriveIn afin d' y intégrer ce concept d'accélération.
Les différents effets d'accélération/décélération que vous pourriez implémenter dans vos propres projets sont relativement nombreux. Je pense bien entendu au démarrage et à l'arrêt d'une voiture ou d'une moto mais aussi d'un personnage qui court. D'autres effets très intéressants pourraient être également le décollage ou l'atterrissage d'un hélicoptère ainsi que la chute d'un personnage en parachute, etc...
Le prochain chapître portera sur une conception un peu plus élaborée des effets de profondeur, sur la mise en place d'une simple caméra et sur les effets de zoom.
Je vous demanderai de bien vouloir me présenter vos remarques éventuelles ainsi que toutes autres considérations sur le forum Programmation C#/Xna, dans le sujet des «Tutos Xna en français»
Je vous souhaite une bonne lecture et à bientôt pour de nouveaux tutoriels Xna.