20 janv. 2012

Sprites sous Android

Tutoriel Développement Jeux sur Android Part 2 : les sprites sous Android
Dans les jeux 2D, un sprite est un élément qui peut se déplacer, s’animer. Il possède théoriquement une certaine couleur de transparence ou bien carrément un canal alpha. En fait il existe bien différentes manières de faire une animation: soit en faisant défiler des séquences successives d’images comme dans les gifs animés, soit en faisant des transformations squelettiques sur un objet.
Dans ce tutoriel, nous allons remplacer notre image du premier tutoriel par un personnage qui est animée en fonction de sa direction.




Le corps du jeu (GameLoop)
Par rapport au premier tutoriel, on a juste pris en considération le sprite: création avec mario=new Sprite(...), la mis à jour avec mario.update() puis l'affichage avec mario.render(screen.canvas) 

package com.creativegames;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.view.MotionEvent;

public class GameLoop extends Thread {

 /** Variable booléenne pour arrêter le jeu */
 public boolean running;
 /** durée de la pause entre chaque frame (FPS=10) */
 private long sleepTime = 100;
 /** Ecran de jeu */
 public GameView screen;
 /** le dernier évenement enregistré */
 public MotionEvent lastEvent;
 /** Personnage animé */
 private Sprite mario;
 /** Valeur du FPS (nombre de frames par secondes) */
 private int fps;

 public void initGame(Context context) {
  // créer la vue du jeu
  this.screen = new GameView(context, this);
  Bitmap img = ((BitmapDrawable) context.getResources().getDrawable(
    R.drawable.bad1)).getBitmap();
  // créer un sprite en spécifiant la position et la vitesse puis l'écran
  mario = new Sprite(img, 0, 0, 2, 3, screen);
  running = true;
 }

 /**
  * Ici on va faire en sorte que lorsqu'on clique sur l'écran, le sprite va
  * se rendre à la position cliquée
  * */
 public void processEvents() {
  if (lastEvent != null && lastEvent.getAction() == MotionEvent.ACTION_DOWN) {
   int xTouch = (int)lastEvent.getX();
   int yTouch = (int)lastEvent.getY();
   mario.moveTo(xTouch, yTouch);
  }
  lastEvent = null;
 }

 /**
  * Mise à jour des composants du jeu Ici nous déplaçon le personnage avec la
  * vitesse vx S'il sort de l'écran, on le fait changer de direction
  * */
 public void update() {
  mario.update();
 }

 /** Dessiner les composant du jeu sur le buffer de l'écran */
 public void render() {
  Paint paint = new Paint();
  // effacer l'écran avec la couleur verte
  paint.setColor(0xFF008000);
  this.screen.canvas.drawPaint(paint);
  // dessiner le sprite
  this.mario.render(screen.canvas);
  // ecrire le fps
  paint.setColor(0xFFFFFFFF);
  this.screen.canvas.drawText("FPS : " + fps, 10, 10, paint);
  // appliquer le buffer à l'écran
  this.screen.invalidate();
 }

 @Override
 public void run() {
  long startTime;
  long elapsedTime; // durée de (update()+render())
  long sleepCorrected; // sleeptime corrigé
  while (this.running) {
   startTime = System.currentTimeMillis();
   this.processEvents();
   this.update();
   this.render();
   elapsedTime = System.currentTimeMillis() - startTime;
   sleepCorrected = sleepTime - elapsedTime;
   // si jamais sleepCorrected<0 alors faire une pause de 1 ms
   if (sleepCorrected < 0) {
    sleepCorrected = 1;
   }
   try {
    Thread.sleep(sleepCorrected > 0 ? sleepCorrected : 1);
   } catch (InterruptedException e) {
   }
   // calculer le FSP
   fps = (int) (1000/(System.currentTimeMillis() - startTime));
  }
 }
}





La gestion des animations
Notre sprite est une image qui contient 4 animations avec 3 frames chacunes.
Alors, quand on observe notre image, on a les correspondances lignes-animation suivantes  (ligne3=up, ligne1=left, ligne0=down, ligne2=right)
Maitenant si la vitesse en x est supérieure en valeur absolue à la vitesse en y, on considèrera que le personnage se déplace soit à gauche ou à droite. Dans le cas contraire, on considèrera qu’il se déplace soit vers le haut ou vers le bas.
Nous avons donc besoin d’une fonction qui retourne l’animation à utiliser en fonction des paramètres de vitesse (xSpeed, ySpeed).
         private int getAnimationRow() {
  // animation: 3 back, 1 left, 0 front, 2 right
  if (Math.abs(xSpeed) > Math.abs(ySpeed)) {
   if (xSpeed > 0)
    return 2; //right
   else
    return 1; //left
  } else {
   if(ySpeed>0) 
    return 0; //down
   else
    return 3; //up
  }
 }



Maintenant  pour sélectionner un morceau d’image et l’afficher, on utilise la fonction.
drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) de la classe Canvas.


        public void render(Canvas canvas) {
  int srcX = currentFrame * width;
  int srcY = getAnimationRow() * height;
  Rect src = new Rect(srcX, srcY, srcX + width, srcY + height);
  Rect dst = new Rect(x, y, x + width, y + height);
  canvas.drawBitmap(image, src, dst, null);
 }
Enfin le code source intégral de la classe Sprite
 
package com.creativegames;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;


public class Sprite {

 private static final int BMP_ROWS = 4;
 private static final int BMP_COLUMNS = 3;

 private GameView gameView;
 private Bitmap image;
 private int x;
 private int y;
 private int xSpeed;
 private int ySpeed;
 /** numéro de la frame courante */
 private int currentFrame;
 /** dimension du sprite */
 private int width;
 private int height;

 public Sprite(Bitmap bmp, int x, int y, int vx, int vy, GameView gameView) {
  this.width = bmp.getWidth() / BMP_COLUMNS;
  this.height = bmp.getHeight() / BMP_ROWS;
  this.gameView = gameView;
  this.image = bmp;
  this.x = x;
  this.y = y;
  this.xSpeed = vx;
  this.ySpeed = vy;
 }

 public void update() {
  if (x >= gameView.getWidth() - width - xSpeed || x + xSpeed <= 0) {
   xSpeed = -xSpeed;
  }
  x = x + xSpeed;
  if (y >= gameView.getHeight() - height - ySpeed || y + ySpeed <= 0) {
   ySpeed = -ySpeed;
  }
  y = y + ySpeed;
  currentFrame = (currentFrame + 1) % BMP_COLUMNS;
 }
 
 private int getAnimationRow() {
  // animation: 3 back, 1 left, 0 front, 2 right
  if (Math.abs(xSpeed) > Math.abs(ySpeed)) {
   if (xSpeed > 0)
    return 2; //right
   else
    return 1; //left
  } else {
   if(ySpeed>0) 
    return 0; //down
   else
    return 3; //up
  }
 }

 public void render(Canvas canvas) {
  int srcX = currentFrame * width;
  int srcY = getAnimationRow() * height;
  Rect src = new Rect(srcX, srcY, srcX + width, srcY + height);
  Rect dst = new Rect(x, y, x + width, y + height);
  canvas.drawBitmap(image, src, dst, null);
 }

 /** déterminer la vitesse vers le point cliqué */
 public void moveTo(int x2, int y2) {
  int dx = x2-this.x;
  int dy = y2-this.y;
  double r = Math.sqrt(dx*dx+dy*dy);
  xSpeed = (int) (3*dx/r);
  ySpeed = (int) (3*dy/r);
 }

}
Inconvénients de cette méthode !
Le problème survient ici c’est que le sprite est toujours animé à la cadence du FPS. C’est-à-dire si on augmente le FPS (nombre de frames par seondes), on vera le personnage se déplacer plus vite. Il faut chercher à isoler la rapidité des sprites avec la boucle du jeu.
Ce que l’on peut faire c’est de fournir à chaque update le temps écoulé depuis le dernier frame, par exemple GameLoop.update(long elapsedTime).
Ainsi on a la possibilité par exemple de dire au sprite de changer de direction après exactement 3 secondes.

Téléchargements

Aucun commentaire: