TP 1 : Mini jeu de tir

Je vous propose de réaliser un petit jeu de tir, sans grande difficulté je vous rassure. Cela va être l’occasion de mettre en pratique tout ce qui a été dit lors des derniers tutoriels, à savoir : création graphique et événements, je vais introduire des nouvelles notions comme le chargement d’image (embarquée dans le fichier SWF) et de son. Le but du TP est d’être simple et rapide, vous ne mettrez pas 1 heure pour le réaliser et le code est au final très cours. Voici une capture d’écran du résultat :

Un petit jeu de tir sans prétention

Cahier des charges

Le joueur contrôle un viseur à la souris et lorsqu’il vise un smiley et qu’il clique, cela tue le smiley ce qui le fait disparaître. Un son de tir est joué à chaque fois que le joueur tir (donc quand il clique sur le stage).

Les smiley apparaissent avec une position aléatoire toutes les 3 secondes, de plus la taille d’un smiley est aussi aléatoire, elle est comprise entre 50% et 150% de la taille d’un smiley de base (la taille de son image en réalité).

Je n’ajoute aucune autre règle, libre à vous de modifier le décor, les ennemies, les sons, etc..

Charger des ressources embarquées

Vous allez devoir charger trois images (le smiley, le viseur et le fond d’écran) et un son (le son qui est joué quand on tire), pour cela vous avez plusieurs possibilités et je vais vous proposer d’utiliser la solution la plus simple (mais la moins souple) : Le chargement de fichier embarqué. Voici le code qu’il faut utiliser :

public class Main extends Sprite
{
        [Embed("../assets/background.png")] public var backgroundImage:Class;
	[Embed("../assets/viseur.png")] public var viseurImage:Class;
	[Embed("../assets/gunshot2.mp3")] public var sonMp3:Class;
        public var sonFusil:Sound;
        // du code d'initialisation

        sonFusil = new sonMp3();
}

Le code est assez simple et vous remarquerez qu’on utilise une balise meta pour indiquer au compilateur qu’il faut charger une ressource à l’adresse passée en paramètre, cette ressource (que ce soit une image, un son ou autre chose) est ensuite associée à une variable de type Class. Pour cette exemple je charge 3 ressources qui sont stockée dans le dossier assets, si les ressources étaient dans le dossier qui contient les fichiers source (le dossier src si vous suivez mon architecture) vous pourriez directement écrire le nom de la ressource, mais comme ici les ressources sont dans un dossier externe il faut indiquer au compilateur de revenir en arrière dans la hiérarchie des dossiers et aller dans le dossier assets.

On initialise ensuite directement l’objet de type Sound avec la variable associée à la ressource du son, le cast est automatique. Il faut quand même faire attention avec ce genre de chargement car tout ce que vous chargerez sera au final de type Class donc n’affectez pas une ressource sonore à une image ou à un fichier XML.

Les images et les sons

L’objet Bitmap permet d’afficher une image, soit en provenance d’un BitmapData soit depuis une ressource externe (c’est notre cas ici), sur la documentation on voit que cet objet dérive de DisplayObject, on peut donc aisément l’ajouter au stage pour qu’il soit représenté à l’écran 😉 Une fois initialisé votre objet Bitmap vous donnera accès à plusieurs méthodes définies dans DisplayObjet et à 3 propriétés, nous n’utiliserons pas ces propriétés pour le TP mais je vous invite à y jeter un œil.

La classe Sound est complexe et permet de faire beaucoup de choses avec un son ou une musique, on peut par exemple extraire les données d’un MP3 (auteur, durée, etc..), connaître le temps de lecture, etc… Un article entier devrait être consacré à cette classe pour en parler correctement. Dans le TP nous n’utiliserons que la méthode play() qui permet de lire le son.

Les nombres pseudo aléatoires

Les smiley qui vont être affichés à l’écran vont apparaître à une position aléatoire à un intervalle de temps donné, ce qui veut dire qu’il faut que l’on génère des coordonnées x et y pour les positionner à un endroit de l’écran. On utilisera la classe Math qui contient la méthode statique random, cette dernière permet de générer un nombre pseudo aléatoire compris entre 0 et 1. Pour avoir une position aléatoire on multipliera le résultat de retour de la méthode random par la longueur et la largeur du stage ce qui nous donnera des coordonnées X et Y différentes à chaque fois.

var randX:Number = Math.random() * stage.stageWidth;
var randY:Number = Math.random() * stage.stageHeight;

Ce code permet d’initialiser deux variables de type Number (des nombres réels), comme la méthode Math.random() renvoie un nombre compris entre 0 et 1, le fait de multiplier ce nombre par la taille du stage nous permet de récupérer des coordonnées adaptées à la taille de la fenêtre. En fait pour être encore plus précis il faudrait soustraire la taille d’un smiley pour être sûr qu’ils ne sortent pas du tout de l’écran.

Gérer le temps

Lorsque l’on développe un jeu, la gestion du temps est un facteur essentiel car le temps va nous permettre de gérer les animations, les différentes actions du jeux qui devront se déclencher à un moment donnée, etc…Nos smiley vont apparaître toutes les 5 secondes ce qui veut dire que toutes les 5 secondes, une méthode devra être appelée pour créer, positionner et afficher un smiley sur le stage. Il y a deux méthodes pour réaliser cela :

  1. Utiliser un timer via la classe flash.utils.Timer ;
  2. Détecter le temps qui s’écoule dans la boucle de jeu (ENTER_FRAME) et agir en conséquence.

J’ai choisis d’utiliser la deuxième solution car c’est la méthode qui est utilisée dans beaucoup de Framework de jeux (Comme dans XNA par exemple). La méthode getTimer() du package flash.utils permet de récupérer le nombre de millisecondes qui se sont écoulées depuis le lancement de l’application. Il est donc facile de déterminer le temps qui s’est écoulé entre deux appels de fonction. Voici un extrait du code que nous allons utiliser :

var elapsedTime:Number = getTimer() / 1000;
timeCount += elapsedTime - previousTime;
previousTime = elapsedTime;
if (timeCount > 2)
{
    spawnSmiley();
    timeCount = 0;
}

Ce code est utilisé dans la fonction appelée par l’événement ENTER_FRAME du stage, concrètement à chaque fois qu’on passera dans cette méthode (donc à chaque fois que fois que l’affichage sera mis à jour), nous récupérerons le temps qui s’est écoulé et on agira en conséquence en créant un nouveau smiley.

La variable elapsedTime contient le nombre de secondes écoulée depuis le lancement de l’application, on divise le résultat de getTimer par 1000 pour récupérer des secondes et pas des milliseconde (ce que renvoie geTimer).

PreviousTime contient l’ancien nombre de seconde écoulé. Nous en avons besoin pour connaitre le temps qui s’est écoulé entre deux appel de fonction.

Enfin timeCount est le nombre de secondes écoulée depuis le dernier appel à la fonction, c’est avec cette variable que l’on travaillera, vous pouvez d’ailleurs voir qu’on l’utilise dans une condition permettant d’afficher un nouveau smiley.

Code source

Je vous propose l’architecture suivante :

  1. Une classe Smiley qui détermine le comportement d’un smiley ;
  2. Une classe Main qui va s’occuper de la logique et de l’affichage.

Bien sur la classe smiley n’était pas obligatoire car vue sa taille, on pourrait prendre le code de son constructeur et le mettre dans la méthode spawnSmiley() , de même la création d’une classe Viseur aurait été justifiée si nous avions plusieurs comportements pour le viseur. Dans un soucis de simplicité je vous propose donc un mini jeu en 2 classes 🙂 à vous de l’améliorer.

la classe Smiley

package  

{

	import flash.display.Sprite;
	import flash.display.Bitmap;

	public class Smiley extends Sprite
	{
		[Embed("../assets/smiley.png")] public var SpriteImage:Class;

		public function Smiley(posX:int, posY:int)
		{
			var image:Bitmap = new SpriteImage();
			var spriteScale:Number = Math.random() * 2;

			// Lissage de l'image
			image.smoothing = true;

			// Position
			this.x = posX;
			this.y = posY;

			// Echelle
			this.scaleX = spriteScale;
			this.scaleY = spriteScale;

			// Création du sprite final
			this.addChild(image);
		}
	}
}

La classe Main

package
{
	import adobe.utils.CustomActions;
	import flash.display.DisplayObject;
	import flash.events.MouseEvent;
	import flash.display.Sprite;
	import flash.display.Bitmap;
	import flash.events.Event;
	import flash.utils.*;
	import flash.ui.Mouse;
	import flash.media.Sound;

	[SWF(width="640", height="480", backgroundColor="#000000", frameRate="60")]

	public class Main extends Sprite
	{
                // Chargement depuis l'exterieur
		[Embed("../assets/background.png")] public var backgroundImage:Class;
		[Embed("../assets/viseur.png")] public var viseurImage:Class;
		[Embed("../assets/gunshot2.mp3")] public var sonMp3:Class;

                // Ressources
		private var background:Bitmap;
		private var viseur:Bitmap;
		private var sonFusil:Sound;

                // Regroupement des sprite
		private var spriteGroup:Sprite;

                // Gestion du temps
		private var spawnTime:int = 5;
		private var previousTime:Number = 0;
		private var timeCount:Number = 0;

		public function Main()
		{
			Mouse.hide(); 

			background = new backgroundImage();
			addChild(background);

			spriteGroup = new Sprite();
			spriteGroup.addEventListener(MouseEvent.CLICK, spriteGroupClickHandler);
			addChild(spriteGroup);

			viseur = new viseurImage();
			viseur.x = stage.stageWidth / 2 - viseur.width / 2;
			viseur.y = stage.stageHeight / 2 - viseur.height / 2;
			addChild(viseur);

			sonFusil = new sonMp3();

			addEventListener(Event.ENTER_FRAME, onEnterFrame);
			stage.addEventListener(MouseEvent.CLICK, onMouseClickOnStage);
		}

		private function onMouseClickOnStage(evt:MouseEvent):void
		{
			sonFusil.play();
			trace(evt.target);	// Smiley
			trace(evt.currentTarget);	// Stage
		}

		private function spawnSmiley():void
		{
			var randX:Number = Math.random() * stage.stageWidth;
			var randY:Number = Math.random() * stage.stageHeight;
			var enemy:Smiley = new Smiley(randX, randY);
			enemy.addEventListener(Event.REMOVED_FROM_STAGE, onEnemyRemovedFromStage);
			spriteGroup.addChild(enemy);
		}

		public function spriteGroupClickHandler(evt:MouseEvent):void
		{
			var enemy:DisplayObject = spriteGroup.removeChildAt(spriteGroup.getChildIndex(DisplayObject(evt.target)));
			enemy = null;
		}

		public function onEnemyRemovedFromStage(evt:Event):void
		{
			var s:Smiley = Smiley(evt.target);
			s.removeEventListener(Event.REMOVED_FROM_STAGE, onEnemyRemovedFromStage);
			s = null;
		}

		public function onEnterFrame(evt:Event):void
		{
			var elapsedTime:Number = getTimer() / 1000;
			timeCount += elapsedTime - previousTime;
			previousTime = elapsedTime;

			if (timeCount > 2)
			{
				spawnSmiley();
				timeCount = 0;
			}
			viseur.x = mouseX - viseur.width / 2;
			viseur.y = mouseY - viseur.height / 2;
		}
	}
}

Et voici le résultat en vrai, ça ne paye pas de mine mais c’est amusant à réaliser :

[SWF]/wp-content/uploads/2011/06/Main.swf, 640, 480[/SWF]

Le premier TP est terminé et vous devez maintenant avoir une meilleur vision de la plateforme Flash et du langage ActionScript 3. Dans les prochains tutoriels nous utiliserons des Framework ou des Bibliothèques telles que As3IsoLib (style isométrique) et Flixel (style rétro 8/16 bit).