On entend souvent parler de la boucle de jeu. Il s’agit en
fait d’une boucle quasi-infinie au cours de laquelle s’effectuent généralement
trois tâches essentielles:
Dans cette première partie de tutoriel consacré au développement de jeux sur Android, mon objectif est de vous montrer comment réaliser une animation simple en respectant l’architecture d’un jeu. Vous allez voir qu’au fil du temps, cette architecture facilite beaucoup la réalisation de n’importe quelle application interactive sur Android.
Si vous êtes débutant en Android et que vous aurez des
doutes sur les notions de bases (Activité, View), référez-vous à ce site.
- Traitement des entrées (input)
- Mis à jour (update)
- Affichage (render)
Dans cette première partie de tutoriel consacré au développement de jeux sur Android, mon objectif est de vous montrer comment réaliser une animation simple en respectant l’architecture d’un jeu. Vous allez voir qu’au fil du temps, cette architecture facilite beaucoup la réalisation de n’importe quelle application interactive sur Android.
Nous aurons besoin de deux classes seulement, en plus le point d’entrée qui est l’Activité:
Le composant GameView
Nous allons définir une vue spécifique pour notre activité
en héritant de la classe SurfaceView. Cette dernière nous donne accès à son
buffer (mémoire tampon pour dessiner nos objets) et aussi à des événements du
type MotionEvent (toucher l’écran).
package com.creativegames; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Bitmap.Config; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceHolder.Callback; import android.view.SurfaceView; public class GameView extends SurfaceView implements Callback{ public int width; //largeur de l'écran public int height; //hauteur de l'écran public Canvas canvas; //outil pour dessiner sur l'écran private Bitmap buffer; // pixel buffer private SurfaceHolder holder; private GameLoop game; //pointeur vers la boucle de jeu public GameView(Context context, GameLoop game) { super(context); this.holder = getHolder(); this.holder.addCallback(this); this.game = game; } /** Rafraichir l'écran*/ @Override public void invalidate() { if (holder != null) { Canvas c = holder.lockCanvas(); if (c != null) { c.drawBitmap(buffer, 0, 0, null); holder.unlockCanvasAndPost(c); } } } /**callback lorsque l'écran est touché * on stocke l'événement pour être ensuite traité dans la boucle de jeu*/ @Override public boolean onTouchEvent(MotionEvent event) { this.game.lastEvent = event; return true; } /** callback lorsque la surface est chargée, * donc démarrer la boucle de jeu*/ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { this.width = width; this.height = height; this.buffer = Bitmap.createBitmap(width, height, Config.ARGB_8888); this.canvas = new Canvas(buffer); this.game.start(); } public void surfaceCreated(SurfaceHolder holder) { } public void surfaceDestroyed(SurfaceHolder holder) { } }
La classe GameLoop
Comme la boucle de jeu doit tourner indépendamment de la
boucle d’affichage principale de l’activité, il faut lancer GameLoop comme un
thread. Elle sera démarrée dès que la vue (GameView) sera prête.
package com.creativegames; import com.creativegame.R; 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 * du jeu pour frame per second FPS=10 * on a sleepTime=100 */ private long sleepTime = 100; /** Ecran de jeu */ public GameView screen; /** le dernier évenement enregistré sur l'écran*/ public MotionEvent lastEvent; /** Position de l'image que nous dessions sur l'écran */ private int x, y; /** vitesse de l'image : nombre de pixel parcouru à chaque boucle de jeu */ private int vx; /** image que nous allons dessiner */ private Bitmap img; /** contexte de l'application */ private Context context; /** activer ou désactiver l'animation*/ private boolean animate; public void initGame(Context context) { this.context = context; img = ((BitmapDrawable) context.getResources().getDrawable( R.drawable.ic_launcher)).getBitmap(); x = 0; y = 10; vx = 2; animate = true; running = true; this.screen = new GameView(context, this); } /** la boucle de jeu */ @Override public void run() { while (this.running) { this.processEvents(); this.update(); this.render(); try { Thread.sleep(sleepTime); } catch (InterruptedException e) { } } } /** Dessiner les composant du jeu sur le buffer de l'écran*/ public void render() { Paint paint = new Paint(); paint.setColor(0xFF000000); //effacer l'écran avec la couleur noire this.screen.canvas.drawPaint(paint); this.screen.canvas.drawBitmap(img, x, y, null); this.screen.invalidate(); } /** 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() { if(this.animate==false) return; int oldX = x; x = x + vx; if (x < 0 || x > screen.width - img.getWidth()) { x = oldX; vx = -vx; } } /** Ici on va faire en sorte que lorsqu'on clique sur l'écran, * L'animation s'arrête/redémarre * */ public void processEvents() { if (lastEvent != null && lastEvent.getAction() == MotionEvent.ACTION_DOWN) { this.animate = ! this.animate; } lastEvent = null; } }
Le programme
principal
Dans le point d’entrée de l’application qui est l’activité,
il nous reste à instancier un objet GameLoop et affecter sa gameView à
l’activité.
N’oubliez pas d’arrêter proprement la boucle de jeu lorsque
l’application est fermée (onDestroy()).
package com.creativegames; import android.app.Activity; import android.os.Bundle; public class Main extends Activity { private GameLoop game; /** A la création de l'activité * 1. on initialise le jeu et affecter la vue à l'activité * */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); game = new GameLoop(); game.initGame(this); setContentView(game.screen); } /** Lorsque l'application s'arrête, il faut arrêter proprement la boucle de jeu*/ @Override protected void onDestroy() { game.running = false; super.onDestroy(); } }Voici me résultat de l'exécution du code: une image qui se déplace en va et vient dans l'écran, cadencé par le sleepTime. Au clic, l'animation s'arrête/redémarre.
Faiblesses de cette méthode
La fait de fixer le sleepTime suppose que la frame du jeu s’exécute
pendant 0 ms. Ce qui n’est pratiquement pas
le cas. En effet si nous ajoutons beaucoup de traitements dans GameLoop.update(),
le FPS va varier et notre jeu deviendra moins performant. La solution consiste
donc à ajuster le sleepTime à chaque
frame.
Proposition de solution.
@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)); } }
Si vous avez des remarques ou erreurs à corriger ne manquez
pas de m’informer via un commentaire.
4 commentaires:
Merci pour ces tutoriels. C'est vraiment très intéressant.
Quand est ce que la méthode run() est elle appelée ? Je ne trouve pas...
Merci d'avance ;)
Ah okay c'est bon j'ai pigé comme c'est hérité de Thread c'est appelé automatiquement ! Cimer albert :)
Voici un excellent tuto pour mettre le pied à l'étrier... je vais tenter de m'y mettre aussi.
Btw as tu produit d'autres jeux dispo sur playstore ?
Enregistrer un commentaire