Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Exercice pratique : AUDITORIUM

Compatible ActionScript 3. Cliquer pour en savoir plus sur les compatibilités.Par Monsieur Spi, le 27 avril 2013

Bonjour,

Voici un petit exercice dérivé du jeu Auditorium.

Tout d'abord le résultat :

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Les sources sont disponibles en fin d'exercice

Les pré-requis

Etude préliminaire

Pour ceux qui ne connaissent pas le jeu dont s'inspire cet exercice, je vous encourage à aller faire un tour ici : http://www.cipherprime.com/games/auditorium/

Nous n'allons pas reproduire le gameplay exact du jeu, juste refaire une base que vous serez libre de faire évoluer comme bon vous semble.

L'intérêt de ce jeu réside dans le fait de modifier le comportement d'un flux de particules à l'aide d'objets qui influent sur chaque particule individuellement. Bien que ça puisse se faire à l'IDE, je pense qu'il est temps pour ceux qui suivent mes exercices depuis le début de passer en douceur à la POO, c'est pourquoi cet exercice est le premier à introduire des Classes, mais on va conserver au moins une des petites roues du vélo, je conserve donc une base au sein de l'IDE, afin que vous ne perdiez pas totalement pied.

Je ne peux pas me permettre de vous faire un cours complet sur la POO au sein d'un exercice, c'est pourquoi je vous conseille fortement d'aller faire un tour sur les tutoriels qui traitent de la POO sur le wiki de Mediabox pour en apprendre d'avantage, notamment celui de Nataly si vous venez de l'environnement auteur (IDE).

http://forums.mediabox.fr/wiki/tutoriaux/flashplatform/programmation/poo_bases/classesqqc
http://forums.mediabox.fr/wiki/tutoriaux/flashplatform/programmation/poo_bases

Ce tutoriel est un pré-requis, je ne répéterai pas ce qui y est expliqué, je considère donc que vous savez ce qu'est une classe ou un package, que vous avez des notions d'héritage et que vous faites la différence entre classe liée et classe de base, toutefois je ne vous demande pas d'être des pros de la POO, connaître la base sera suffisant.

Cool, me voici débarrassé de la lourde charge de vous initier à la POO, merci Nataly, je vais pouvoir aller droit au but.

Préparation

Réfléchissons deux minutes à ce que nous voulons faire, notre programme va comporter quatre parties :

  • un programme principal qui assure la coordination générale, un peu comme un chef d'orchestre.
  • des particules qui savent se déplacer toutes seules, les notes de la symphonie.
  • des outils qui vont influencer le comportement des particules, les musiciens.
  • un rendu graphique qui permet d'ajouter des effets à l'ensemble, la salle de concert.

On commence par préparer le travail, ouvrez un nouveau projet Flash, créez un MovieClip “Particule” et un MovieClip “Outil”, ce sont les représentations graphiques de nos objets. Pour les outils, placez un outil par frame dans le MovieClip, pour les particules placez une particule de couleur blanche sur la première frame et des particules de couleurs différentes sur les frames suivantes (à volonté).

Ne les exportez pas tout de suite pour ActionScript comme on avait l'habitude de le faire dans les exercices précédents, ça ne servirait à rien, on va se charger de ça plus tard, quand le moment sera venu. Par contre supprimez-les de la scène, ils n'ont rien à y faire, ils sont au chaud dans la bibliothèque.

Pour la préparation c'est tout, passons au code.

Programme principal, le chef d'orchestre

Son rôle est de coordonner l'ensemble de l'orchestre, c'est lui qui va dire combien de musiciens il faut, où ils doivent se placer et quelle est la partition à jouer. Comme tout bon chef d'orchestre il va également vérifier que l'ensemble fonctionne harmonieusement.

Sur la première frame de votre projet tapez le code suivant.

var W:int;
var H:int;
var i:int;
var ecran:Sprite;
var rendu:Rendu;
var p:Particules;
var outil:Outils;
var stock:Array;
var outils:Array = [new Outils(1),new Outils(2),new Outils(3),new Outils(4),new Outils(5), new Outils(6),new Outils(7),new Outils(8),new Outils(9),new Outils(10), new Outils(11),new Outils(12)];
 
init();
 
function init():void{
	W = 		stage.stageWidth;
	H = 		stage.stageHeight;
	ecran = 	new Sprite();
	rendu = 	new Rendu(W,H);
	stock = 	[];
	for (i=0; i<outils.length; i++){
		with(outils[i]) {
			init((i-2)*width+width*.5,H-width*.5);
			if(categorie==1) init(width,H*.5);
			if(categorie==2) init(W-width,H*.5);		
		}
		addChild(outils[i]);
	}
	addChild(rendu);
	addEventListener(Event.ENTER_FRAME, main);
}
 
function main(e:Event):void {
	p = new Particules(outils[0], outils[1], W, H, 10);
	stock.push(p);
	ecran.addChild(p);
	for (i=0; i<stock.length; i++)	{
		for each (outil in outils) outil.influence(stock[i]);
		if (stock[i].kill()) stock.splice(i,1);
	}
	rendu.update(ecran);
}

Si vous avez fait les exercices précédent (pré-requis) vous devriez retrouver vos marques.

var W:int;
var H:int;
var i:int;
var ecran:Sprite;
var rendu:Rendu;
var p:Particules;
var outil:Outils;
var stock:Array;

Je déclare mes variables et mes objets.

Attention, pour le moment nous n'avons pas exporté les clips qui se trouvent en bibliothèque pour une utilisation pour AS, ce n'est donc pas le clip “Particule” qui se trouve dans la bibliothèque que je souhaite utiliser mais la classe “Particules” (avec un S), le moule que je vais devoir écrire plus tard, pareil pour les outils et le rendu. Si vous arrivez à faire cette différence c'est gagné pour la suite.

Si on reprend la parabole de l'orchestre, chaque musicien est pour le moment une silhouette (le MovieClip) à laquelle correspondra un métier (la Classe) que nous ne lui avons pas encore appris. Chaque note (particule) est vierge, il faudra lui apprendre son rôle (la Classe) dans la partition.

Notez que même si je n'ai pas encore écrit mes Classes je suis en mesure de leur donner un nom, et de typer mes objets avec. Si je teste mon programme à ce stade le compilateur va hurler qu'il ne connaît pas ce type d'objet mais ça n'a pas d'importance car je ne compte pas compiler avant d'avoir au moins écrit la structure de base du programme.

var outils:Array = [new Outils(1),new Outils(2),new Outils(3),new Outils(4),new Outils(5), new Outils(6),new Outils(7),new Outils(8),new Outils(9),new Outils(10), new Outils(11),new Outils(12)];

Je crée une liste d'outils, le chef d'orchestre dresse la liste des musiciens dont il a besoin pour jouer la symphonie. Je passe un paramètre à chaque outil, il s'agit de son type, autrement dit “violoncelliste”, “pianiste”, “trompettiste”, etc…, chacun jouant le même genre de notes mais avec un rendu différent.

init();
 
function init():void{
	W = 		stage.stageWidth;
	H = 		stage.stageHeight;
	ecran = 	new Sprite();
	rendu = 	new Rendu(W,H);
	stock = 	[];
	for (i=0; i<outils.length; i++){
		with(outils[i]) {
			init((i-2)*width+width*.5,H-width*.5);
			if(categorie==1) init(width,H*.5);
			if(categorie==2) init(W-width,H*.5);		
		}
		addChild(outils[i]);
	}
	addChild(rendu);
	addEventListener(Event.ENTER_FRAME, main);
}

L'initialisation du programme, je valorise mes variables, je crée les objets utiles et je les place.

Je prend le parti de placer mes objets ici avec une boucle, ainsi j'aurai la liberté de les disposer comme je l'entend à chaque niveau. Ne confondez pas la fonction “init()” du programme principal et la méthode “init()” de la classe “Outils”, là encore il faut être vigilant.

Le chef d'orchestre dit à chaque musicien où il doit se placer afin d'obtenir le son général qu'il souhaite (fonction “init” du programme principal), mais chaque musicien va se placer lui même à l'endroit qu'on lui indique (méthode “init” de chaque outil).

function main(e:Event):void {
	p = new Particules(outils[0], outils[1], W, H, 10);
	stock.push(p);
	ecran.addChild(p);
	for (i=0; i<stock.length; i++)	{
		for each (outil in outils) outil.influence(stock[i]);
		if (stock[i].kill()) stock.splice(i,1);
	}
	rendu.update(ecran);
}

Cette fonction s'exécute tout le temps, elle génère des particules à l'infini, vérifie pour chacune d'elle si elle est influencée par un outil ou non et si elle est détruite, et enfin elle met à jour l'affichage.

Lorsque le concert aura commencé, le chef d'orchestre lira la partition, un des musiciens émettra en permanence des notes identiques et chaque musicien de l'orchestre va la modifier (ou pas) selon son propre instrument. Oui, techniquement ce devraient être les musiciens qui émettent les notes (du moins leurs instruments) mais dans notre jeu le flux de particules part d'un unique point et non de chaque outil, la parabole a ses limites il faut donc s'adapter un peu et faire un petit effort d'imagination ;-)

Notez que le chef d'orchestre n'a pas besoin de connaître en détail le métier de chaque musicien ou le comportement des notes qui composent la partition, son rôle est juste de dire que les notes doivent être émises en permanence depuis un musicien précis et de vérifier si un autre musicien influence une note. On peut donc écrire la structure du programme principal sans forcément connaître le détail des Classes de chaque objet, il suffit d'inventer par anticipation les méthodes utiles que nous écrirons plus tard.

Bien, nous avons créé notre programme principal, le chef d'orchestre connaît son propre rôle. A présent nous allons devoir instruire chaque participant, c'est à dire apprendre leur métier au musiciens (les outils) et décrire le comportement de base des notes (les particules).

Particules, les notes de musique

Créez un nouveau document AS au même endroit où vous avez créé votre projet (FLA) et écrivez :

package {
 
	import flash.display.MovieClip;	
	import flash.events.Event;
	import flash.events.MouseEvent;
 
	public class Particules extends MovieClip { 
 
		private var _W:uint;
		private var _H:uint;
		private var _alpha:Number;
		private var _angle:Number;
		private var _cible:Outils;
		public var V:int;
		public var vX:Number;
		public var vY:Number;
 
		public function Particules (A:Outils, B:Outils, W:int, H:int, vitesse:int) {
			_W = W;
			_H = H;
			_alpha = 0.005;
			_cible = B;
			V = vitesse;
 
			x = A.x+Math.random()*10-5;
			y = A.y+Math.random()*10-5;
 
			_angle = Math.atan2(B.y - y, B.x - x);
			vX = Math.cos(_angle)*V;
			vY = Math.sin(_angle)*V;
 
			x += Math.cos(_angle)*A.width*.5;
			y += Math.sin(_angle)*A.height*.5;
 
			addEventListener(Event.ENTER_FRAME, update);
		}
 
		private function update(e:Event):void {
			alpha -= _alpha;
			x +=  vX;
			y +=  vY;
			if(x>=_W || x<=0) vX = -vX;
			if (y >= _H || y <= 0) vY = -vY;
		}
 
		public function kill():Boolean {
			if (this.hitTestObject(_cible) || alpha <= 0) {
				this.parent.removeChild(this);
				removeEventListener(Event.ENTER_FRAME, update);
				_cible = null;
				delete(this);
				return true;
			}
			return false;
		}
	}
}

Chaque particule est un objet indépendant ayant ses propres caractéristiques et son propre comportement, chacune à une position de départ, une direction, se déplace toute seule, connaît les limites du terrain de jeu, peut être créée ou détruite et à une durée de vie limitée. Tout comme le son d'une note de musique, une fois émit à l'intérieur d'une salle de concert.

import flash.display.MovieClip;	
import flash.events.Event;
import flash.events.MouseEvent;

J'importe toutes les classes qui vont me servir à créer mon moule à particules.

public class Particules extends MovieClip {

La classe hérite de la classe mère MovieClip, techniquement on étend donc les fonctionnalités d'un objet MovieClip, dit autrement la classe Particule surcharge la classe MovieClip en lui ajoutant des fonctionalités. Appeler une particule sera donc comme appeler un MovieClip amélioré, et pour l'améliorer on va tout simplement lui ajouter du code…

private var _W:uint;
private var _H:uint;
private var _alpha:Number;
private var _angle:Number;
private var _cible:Outils;
public var V:int;
public var vX:Number;
public var vY:Number;

Je déclare toutes les variables utiles, attention ici j'ai des variables privées (accessibles uniquement au sein de la classe) et des variables publiques (accessibles en dehors de la classe). Les variables publiques comme la vitesse initiale (V) et la vitesse sur chaque axe (vX et vY) sont des paramètres utiles pour modifier le comportement de la particule.

Les musiciens ne peuvent modifier que la vitesse et la position de la note, pour le reste la note se débrouille toute seule.

public function Particules (A:Outils, B:Outils, W:int, H:int, vitesse:int=10) {

Voici le constructeur de la classe, c'est lui qui est appelé dès qu'on crée une nouvelle particule. Comme vous pouvez le constater je peux passer des paramètres à mon constructeur, attention ceci n'est possible que dans le cas d'une classe liée et non d'une classe de base.

Nataly vous en dit plus à propos des classes liées, ici : http://forums.mediabox.fr/wiki/tutoriaux/flashplatform/programmation/poo_bases/classesqqc/bibliotheque2

Au moment de sa création la particule accepte donc 5 paramètres, A et B sont deux outils, il s'agit du générateur, celui qui émet les particules, et de la cible, celui qui attire les particules. Chaque particule utilise ces deux objets pour se positionner et se déplacer. W et H sont la largeur et la hauteur de la zone de jeu, la particule en a besoin pour rebondir sur les bords de l'écran. Et enfin “vitesse” est un paramètre par défaut qui permet de donner une vitesse initiale différente à chaque particule si on le souhaite, ce n'est pas très utile ici puisque je considère qu'au départ toutes mes particules ont la même vitesse, mais selon les cas on peut avoir besoin de la faire varier.

_W = W;
_H = H;
_alpha = 0.005;
_cible = B;
V = vitesse;
 
x = A.x+Math.random()*10-5;
y = A.y+Math.random()*10-5;
 
_angle = Math.atan2(B.y - y, B.x - x);
vX = Math.cos(_angle)*V;
vY = Math.sin(_angle)*V;
 
x += Math.cos(_angle)*A.width*.5;
y += Math.sin(_angle)*A.height*.5;
 
addEventListener(Event.ENTER_FRAME, update);

Je renseigne mes variables, je positionne la particule, je trouve l'angle entre le point de départ et celui d'arrivée et j'initialise la vitesse sur les deux axes. Je modifie ensuite la position de la particule pour qu'elle semble apparaître sur le bord de mon générateur et non au centre et enfin j'ajoute un écouteur qui sert à mettre à jour la particule à chaque frame.

private function update(e:Event):void {
	alpha -= _alpha;
	x +=  vX;
	y +=  vY;
	if(x>=_W || x<=0) vX = -vX;
	if(y>=_H || y<=0) vY = -vY;
}

La fonction “update” est privée, c'est de la tambouille interne à la particule. On y met à jour la position sur les deux axes et l'opacité, qui décroît en fonction du temps, c'est elle qui détermine la durée de vie de la particule. Les deux dernières lignes servent à faire rebondir la particule sur les bords de la zone de jeu en inversant la vitesse sur l'axe concerné.

public function kill():Boolean {
	if (this.hitTestObject(_cible) || alpha <= 0) {
		this.parent.removeChild(this);
		removeEventListener(Event.ENTER_FRAME, update);
		_cible = null;
		delete(this);
		return true;
	}
	return false;
}

Cette méthode permet de savoir quand une particule doit être détruite, elle est publique car le chef d'orchestre en a besoin pour savoir quand retirer la note de la partition (le tableau de stockage). Si la particule à une opacité égale à zéro ou qu'elle touche la cible (l'outil qui l'attire), elle est détruite, dans ce cas on la retire de l'affichage, on retire son écouteur, on vide les références, on supprime l'objet et on renvoie un signal au programme principal qui se charge de la supprimer du stock.

Notez que vous pourriez passer par un événement personnalisé pour signaler au programme que la particule doit être retirée.

Voilà pour le rôle des particules, rien de bien compliqué, reste à assigner ce rôle à la représentation graphique des particules, autrement dit le MovieClip qui se trouve dans la bibliothèque.

Revenez dans votre FLA et allez dans les propriétés du MovieClip “Particule”, là cochez la case “exporter pour ActionScript”, supprimez ce qui se trouve dans le champ “Classe de base” et remplacez ce qui se trouve dans le champ “Classe” par “Particules”. Nous venons de lier notre symbole de la bibliothèque à notre moule, à chaque fois que je vais ajouter une occurrence du clip “Particule” son graphisme sera celui du MovieClip et son code interne sera celui de la classe “Particules”.

Notez que j'emploie ici des MovieClip afin de profiter des propriétés de ce type d'objet comme le fait d'avoir une timeline sur laquelle je pose les différents états de mes particules, mais un MovieClip est aussi un objet lourd, lorsque cela est possible préférez utiliser des Sprites comme objet d'affichage, votre programme ne s'en portera que mieux. Notez également que je me sert de l'IDE de Flash pour créer mes graphismes, vous pouvez vous en passer si vous choisissez d'utiliser une feuille de sprites par exemple, rien ne vous oblige à utiliser un clip de la bibliothèque, c'est juste plus simple pour ceux qui viennent de l'environnement auteur de Flash.

Si vous avez compris le principe, on va faire la même chose pour les outils.

Outils, les musiciens

Les outils sont des objets dont le rôle est d'influencer le comportement des particules, oui je sais, je me répète, mais cette définition entraîne à se poser la question suivante : “est-ce la particule qui doit reconnaître son environnement et réagir en conséquence, comme on le fait pour la faire rebondir sur les bords de la zone de jeu, ou est-ce l'environnement (donc les outils) qui doit modifier le comportement de la particule ?”.

Si on reprend l'image de l'orchestre, est-ce une note qui se modifie d'elle même quand elle touche un musicien, ou le musicien qui modifie le comportement de la note quand elle arrive à sa portée ?

Si on fait le choix de créer des particules réellement autonomes alors il faudrait leur permettre de détecter leur environnement et de réagir en conséquence. Dans notre cas cela reviendrait à apprendre aux notes à reconnaître les musiciens, cela n'a pas de sens.

En autorisant les outils à modifier certains paramètres des particules, on peut en ajouter autant que l'on souhaite sans avoir besoin de toucher au code des particules. Dans notre cas cela semble pertinent, ce n'est pas la musique qui se modifie en présence d'un musicien, mais le musicien qui modifie la musique.

Ainsi, on va considérer que la particule est une entité ayant son propre comportement de base et que les outils sont là pour modifier ce comportement en jouant sur certains paramètres.

Allez jetons un oeil au code des outils, créez une classe “Outils” à la racine du projet et écrivez :

package {
 
	import flash.display.MovieClip;	
	import flash.events.MouseEvent;
 
	public class Outils extends MovieClip { 
 
		private var _T:uint;
		private var _W:uint;
		private var _H:uint;
		private var _D:int;
		private var _A:Number;
		private var _Dx:Number;
		private var _Dy:Number;
		public static var _cibleX:Number;
		public static var _cibleY:Number;
 
		public function Outils (type:int):void {
			_T = type;
			_W = width;
			_H = height;
			gotoAndStop(_T);
			if (_T == 4) _A = Math.PI;
			if (_T == 5) _A = 0;
			if (_T == 6) _A = -Math.PI/2;
			if (_T == 7) _A = Math.PI/2;
			addEventListener(MouseEvent.MOUSE_DOWN, attrape);
			addEventListener(MouseEvent.MOUSE_UP, relache);
			buttonMode = true;
			super();
		}
 
		public function init(X:Number,Y:Number):void {
			x = X;
			y = Y;
		}
 
		public function influence(p:Object):void {
 
			// distance des particules
			_Dx = p.x-x
			_Dy = p.y-y
			_D = _Dx * _Dx + _Dy * _Dy;
 
			// teleporeur sortie
			if (_T == 9) {
				_cibleX = x;
				_cibleY = y;
			}
 
			if (_D <= 1000) {
 
				with (p) {
 
					// bouclier (force = masse x accélération)
					if (_T==3) {
						x +=  _W*(_Dx-width*vX)/_D;
						y +=  _H*(_Dy-width*vY)/_D;	
					}
 
					// aiguillages
					if(_T>3 && _T<8) {
						vX = Math.cos(_A)*V;
						vY = Math.sin(_A)*V;
					}
 
					// teleporteur entrée
					if(_T==8) {
						x = _cibleX-5+Math.random()*10;
						y = _cibleY-5+Math.random()*10;
					}
 
					// disperseur
					if(_T==10) {
						vX = _W*(_Dx-width*vX)/_D;
						vY = _H*(_Dy-width*vY)/_D;
					}
 
					// coloriseur
					if(_T==11) {
						p.gotoAndStop(int(Math.random()*4)+1);
					}
 
					// separateur
					if (_T == 12) {
						_A = Math.random() * Math.PI*2;
						vX = Math.cos(_A)*V;
						vY = Math.sin(_A)*V;
					}
				}
			}
		}
 
 
		private function attrape(e:MouseEvent):void {this.startDrag()};
		private function relache(e:MouseEvent):void {stopDrag()};
 
		public function get categorie():int { return _T };
	}
}

Voyons cela dans le détail.

import flash.display.MovieClip;	
import flash.events.MouseEvent;

J'importe les classes utiles.

public class Outils extends MovieClip {

Les outils héritent de la classe mère MovieClip.

private var _T:uint;
private var _W:uint;
private var _H:uint;
private var _D:int;
private var _A:Number;
private var _Dx:Number;
private var _Dy:Number;
public static var _cibleX:Number;
public static var _cibleY:Number;

Les variables, toutes privées sauf deux qui sont publiques et statiques (on y reviendra).

public function Outils (type:int):void {
	_T = type;
	_W = width;
	_H = height;
	gotoAndStop(_T);
	if (_T == 4) _A = Math.PI;
	if (_T == 5) _A = 0;
	if (_T == 6) _A = -Math.PI/2;
	if (_T == 7) _A = Math.PI/2;
	addEventListener(MouseEvent.MOUSE_DOWN, attrape);
	addEventListener(MouseEvent.MOUSE_UP, relache);
	buttonMode = true;
	super();
}

Le constructeur de la classe, je lui passe un paramètre “type” pour indiquer le type d'outil que je souhaite créer.

Quand je crée un musicien il a donc un rôle de base (jouer de la musique…), mais aussi une spécialité (…avec tel instrument), c'est son type.

Je valorise les variables, en fonction du type j'affiche la bonne frame dans le clip, pour les outils de 4 à 7 je détermine un angle qui servira à fixer la direction de la particule, je crée deux écouteurs pour permettre au joueur de déplacer l'outil.

public function init(X:Number,Y:Number):void {
	x = X;
	y = Y;
}

Rappelez-vous, dans le programme principal je place mes outils à l'aide d'une boucle, pour cela je fais appel à la méthode “init” de l'outil, la voilà, elle permet simplement de positionner l'outil où je le souhaite.

public function influence(p:Object):void {
 
	// distance des particules
	_Dx = p.x-x
	_Dy = p.y-y
	_D = _Dx * _Dx + _Dy * _Dy;
 
	// teleporteur sortie
	if (_T == 9) {
		_cibleX = x;
		_cibleY = y;
	}
 
	if (_D <= 1000) {
 
		with (p) {
 
			// bouclier (force = masse x accélération)
			if (_T==3) {
				x +=  _W*(_Dx-width*vX)/_D;
				y +=  _H*(_Dy-width*vY)/_D;	
			}
 
			// aiguillages
			if(_T>3 && _T<8) {
				vX = Math.cos(_A)*V;
				vY = Math.sin(_A)*V;
			}
 
			// teleporteur entrée
			if(_T==8) {
				x = _cibleX-5+Math.random()*10;
				y = _cibleY-5+Math.random()*10;
			}
 
			// disperseur
			if(_T==10) {
				vX = _W*(_Dx-width*vX)/_D;
				vY = _H*(_Dy-width*vY)/_D;
			}
 
			// coloriseur
			if(_T==11) {
				p.gotoAndStop(int(Math.random()*4)+1);
			}
 
			// separateur
			if (_T == 12) {
				_A = Math.random() * Math.PI*2;
				vX = Math.cos(_A)*V;
				vY = Math.sin(_A)*V;
			}
		}
	}
}

Une méthode publique qui gère l'influence qu'a l'outil sur la particule, pour que cela fonctionne je passe en paramètre la particule sur laquelle l'outil doit agir. Je calcule ensuite la distance qui sépare l'outil de la particule à l'aide de la position de la particule sur la scène et celle de l'outil.

Lorsqu'une note vient toucher le musicien celui-ci en modifie le son avant de la laisser poursuivre sa route.

Si l'outil correspond à la sortie du téléporteur (outil 9), j'enregistre sa nouvelle position, notez que les variables de position (_cibleX et _cibleY) sont publiques pour être lues de l'extérieur et statiques car leur valeur doit être conservée peut importe le nombre d'appel à la Classe.

Pour chaque outil je vérifie si la distance qui le sépare de la particule correspond à son rayon, pour faire simple j'ai écrit cette valeur à la main car tous mes outils ont la même taille, vous pouvez bien sur formuler ça pour l'adapter à la taille de l'outil au besoin ou remplacer le tout par un simple test de collision.

Le musicien regarde si la note entre dans sa zone d'influence.

Chaque outil, selon son type, modifie le comportement de la particule, cela peut être sa position (x et y) ou sa vitesse sur chaque axe (vX et vY), ou bien encore la frame affichée dans le clip de la particule, ce qui permet d'en changer la couleur ou la forme à volonté.

Le musicien se spécialise dans un instrument.

Si vous souhaitez ajouter de nouveaux outils il vous suffit de rajouter quelques lignes dans cette fonction tout simplement ;-)

Notez que je n'ai créé que des outils basiques, à vous de vous plonger dans vos livres de physique pour jouer avec les forces et manipuler les particules à votre convenance.

private function attrape(e:MouseEvent):void {this.startDrag()};
private function relache(e:MouseEvent):void {stopDrag()};

Le drag&drop de chaque outil.

Le joueur peut déplacer le musicien à volonté.

public function get categorie():int { return _T };

Voici un accesseur, grâce à la syntaxe “get” je peux récupérer facilement le type de l'outil même si la variable est privée, il me suffit pour cela d'appeler la méthode “categorie” de l'outil. Cela est particulièrement utile par exemple lorsque je place mes outils au début du programme, si je tombe sur l'outil 1 c'est le générateur, si je tombe sur l'outil 2 c'est la cible, par défaut je considère que ces deux outils sont uniques.

Le musicien peut signaler à qui le souhaite le type d'instrument dont il joue.

C'est tout pour les outils, revenez dans votre FLA et modifiez les propriétés du MovieClip “Outil”, faites la même chose que pour les particules, c'est à dire exportez-le pour AS, supprimez tout ce qui se trouve dans le champ “Classe de base”, et dans le champ “Classe” remplacez “Outil” par “Outils”.

Techniquement le jeu est prêt, mais je souhaite m'amuser encore un peu…

Rendu, la salle de concert

Histoire de pimenter un peu le tout, je vais ajouter un effet de traînée lumineuse,aussi appelé “ghost effect”, à tous mes objets. Un peu comme si je choisissait la salle de concert pour sa acoustique particulière.

Attention ! Cet effet est très gourmand en ressources, d'autant que je vais en abuser et faire des boucles pour renforcer le rendu. Là ça ira car ce n'est qu'un exercice, mais regardez la consommation CPU avec et sans l'effet et vous verrez que c'est lui qui bouffe toutes les ressources du programme, usez-en avec parcimonie.

Créez une nouvelle classe “Rendu” à la racine de votre projet et écrivez :

package {
 
	import flash.display.Sprite;	
	import flash.events.Event;
	import flash.display.Bitmap
	import flash.display.BitmapData;
	import flash.geom.Point;
	import flash.geom.ColorTransform;
	import flash.filters.BlurFilter;
 
	public class Rendu extends Sprite { 
 
		private var texture:BitmapData;
		private var dessin:Bitmap;
		private var blur:BlurFilter;
		private var couleur:ColorTransform;
		private var depart:Point;
 
		public function Rendu (W:int,H:int) {
			texture = 		new BitmapData(W,H,true,0x00000000);
			dessin = 		new Bitmap(texture);
			blur = 			new BlurFilter(2,2);
			couleur = 		new ColorTransform(0.999,0.95,0.9,1);
			depart =		new Point(0, 0);
			this.addChild(dessin);
			this.mouseEnabled = false;
		}
 
		public function update(ecran:Sprite):void {
			texture.applyFilter(texture,texture.rect,depart,blur);
			texture.colorTransform(texture.rect, couleur);
			texture.draw(this.parent);
			texture.draw(ecran);
		}
	}
}

Détaillons tout ça :

import flash.display.Sprite;	
import flash.events.Event;
import flash.display.Bitmap
import flash.display.BitmapData;
import flash.geom.Point;
import flash.geom.ColorTransform;
import flash.filters.BlurFilter;

Mes imports.

public class Rendu extends Sprite {

Le rendu hérite de la classe mère “Sprite”.

private var texture:BitmapData;
private var dessin:Bitmap;
private var blur:BlurFilter;
private var couleur:ColorTransform;
private var depart:Point;

Les variables et objets utiles, j'ai besoin d'un bitmap qui servira à l'affichage, d'un filtre blur, d'une couleur et d'un point de départ pour la texture.

public function Rendu (W:int,H:int) {
	texture = 		new BitmapData(W,H,true,0x00000000);
	dessin = 		new Bitmap(texture);
	blur = 			new BlurFilter(2,2);
	couleur = 		new ColorTransform(0.999,0.95,0.9,1);
	depart =		new Point(0, 0);
	this.addChild(dessin);
	this.mouseEnabled = false;
}

Le constructeur accepte deux paramètres qui sont la largeur et la hauteur de la zone de jeu. Je crée d'abord une texture vierge de la taille de la zone de jeu, puis un dessin que je remplis avec la texture, puis je paramètre le filtre, spécifie la couleur et le point de départ, enfin j'ajoute mon dessin à la liste d'affichage de mon Sprite et lui interdit les interactions avec la souris.

public function update(ecran:Sprite):void {
	texture.applyFilter(texture,texture.rect,depart,blur);
	texture.colorTransform(texture.rect, couleur);
	texture.draw(this.parent);
	texture.draw(ecran);
}

Méthode publique qui permet de mettre à jour l'affichage, elle prend comme paramètre le Sprite “ecran” déclaré dans le programme principal, c'est dans cet objet qu'on stocke les particules à afficher.

C'est comme si on enregistrait la symphonie sur bande (l'écran) pour la restituer ensuite au public dotée d'effets supplémentaires.

J'applique le filtre à la texture, je transforme sa couleur, et…. je dessine dans ma texture le contenu du parent de mon Sprite “Rendu”, autrement dit mes boutons et tout ce qui se trouve sur ma scène à ce moment là, ce qui va entraîner un effet de larsen volontaire (et bouffer une bonne partie des ressources). Enfin je fait un second passage où je dessine cette fois les particules dans ma texture.

Et c'est tout.

Relecture

Maintenant que nous avons créé toutes nos classes, relisons ensemble le code du programme principal pour voir si on comprend un peu mieux son fonctionnement.

var W:int;
var H:int;
var i:int;
var ecran:Sprite;
var rendu:Rendu;
var stock:Array;
var p:Particules;
var outil:Outils;
var outils:Array = [new Outils(1),new Outils(2),new Outils(3),new Outils(4),new Outils(5), new Outils(6),new Outils(7),new Outils(8),new Outils(9),new Outils(10), new Outils(11),new Outils(12)];

On crée l'écran, un Sprite qui sert juste de conteneur aux particules, et le rendu, qui est le Sprite affiché après ajout de l'effet. On crée un tableau de stockage pour ranger nos particules et un objet “outil” de base qui servira à manipuler les différents outils. Enfin on crée une liste d'outils (un nouveau tableau de stockage) dans laquelle on crée autant d'outils dont on a besoin pour le level du jeu, chaque outil acceptant un paramètre définissant son type.

init();
 
function init():void{
	W = 		stage.stageWidth;
	H = 		stage.stageHeight;
	ecran = 	new Sprite();
	rendu = 	new Rendu(W,H);
	stock = 	[];
	for (i=0; i<outils.length; i++){
		with(outils[i]) {
			init((i-2)*width+width*.5,H-width*.5);
			if(categorie==1) init(width,H*.5);
			if(categorie==2) init(W-width,H*.5);		
		}
		addChild(outils[i]);
	}
	addChild(rendu);
	addEventListener(Event.ENTER_FRAME, main);
}

L'initialisation du programme permet de créer l'écran et le rendu avec en paramètre la taille de la zone de jeu, on initialise le stock des particules, puis on place chaque outil de la liste, si on tombe sur l'outil de type 1 ou 2 on le place au centre de la zone de jeu en hauteur et proche d'un bord.

J'ajoute ensuite mes outils sur la scène, c'est important car le joueur doit pouvoir saisir un outil pour le déplacer et pour cela l'outil doit être affiché, mais je souhaite également ajouter un ghost effect sur mes outils et pour cela le me sert d'un bitmap que je retrace par dessus tout le reste, c'est pourquoi dans le rendu je trace d'abord ce qui se trouve sur le parent (la scène) avant de tracer mes particules, ainsi j'inclus les outils dans l'effet.

J'ajoute enfin le rendu sur la scène, rappelez-vous il s'agit d'un Sprite dans lequel j'ai dessiné une texture, pas besoin donc de le lier à un quelconque symbole de la bibliothèque. Et pour fini je lance mon écouteur.

function main(e:Event):void {
	p = new Particules(outils[0], outils[1], W, H);
	stock.push(p);
	ecran.addChild(p);
	for (i=0; i<stock.length; i++)	{
		for each (outil in outils) outil.influence(stock[i]);
		if (stock[i].kill()) stock.splice(i,1);
	}
	rendu.update(ecran);
}

A chaque frame je crée une nouvelle particule, le générateur est le premier outil de la liste et la cible est le second outil de la liste, je passe également la taille de la scène (pour les rebonds sur les bords) à la particule.

J'ajoute la particule au tableau de stockage des particules et à l'“ecran” afin de pouvoir les dessiner ensuite avec le ghost effect. Puis je fais une boucle sur toutes les particules affichées, pour chaque particule je vérifie tous les outils de la liste et je regarde si l'un d'eux influence le comportement de la particule. Enfin je regarde si la particule à une raison d'être détruite et si c'est le cas je la retire du stock.

Dernière opération, mettre à jour le rendu avec comme paramètre le conteneur où se trouvent les particules.

Conclusion

Voilà pour ce premier pas vers la POO, on peut bien sur faire beaucoup plus propre, ce n'est qu'un exercice, mais comme il s'inscrit dans une série d'exercices commencés à l'IDE j'ai essayé d'effectuer un passage en douceur. A partir d'ici la plupart des prochains exercices utiliseront la POO, d'une part parce que les exercices seront de plus en plus long et que ça permet de mieux organiser le code, mais aussi parce que la POO est un passage obligatoire si vous souhaitez vous lancer dans le développement de jeux vidéos.

Les sources

auditorium_mb.zip version CS6
auditorium_mb_cs5.zip version CS5