SoundFactory

J’ai eu besoin pour Atomic Boarder d’ajouter du son et d’avoir une facade qui encapsule toute la gestion sonore.

Je vous propose de partager le noyau de cette classe qui peut servir sur n’importe quel projet (application ou jeu) et peut-être permettre à certains d’entre vous de comprendre les mécanismes et avantages offerts par un design de ce type.

Comme je l’avais déjà illustré à l’époque, l’objectif est simple et le principe efficace.
Il s’agit de créer une classe avec des membres statiques qui s’occupera d’instancier, de stocker et gérer un parc d’objets.

Au chapitre des avantages :
- Le fait de gérer l’instanciation et la destruction des objets au même endroit et d’offrir pour unique référence aux classes qui les utilisent un numéro d’id permet de bénéficier d’une propreté maximale. Les références sont virtuelles et coupées de l’objet lui-même au niveau du bytecode
- Offrir une une interface unique et bien encapsulée pour gérer un même type d’objets nous permet de pouvoir modifier à n’importe quel moment le fonctionnement interne de celle-ci pour ajouter ou enlever des fonctionnalités.
- Permettre tout ce qui est batch process, appliquer une même méthode à tous les objets du même type (ex : muteAllSounds() ). Réaliser des tris, des sous-groupes …
- Dernier point, ce type de classe peut être une manière habile pour éviter l’héritage abusif dans bien des cas. Rien de plus facile par exemple d’ajouter une méthode fade à un notre objet Sound en implémentant une méthode statique supplémentaire (à notre facade) du nom de makeFade qui prendra pour paramètre le numéro d’id du son concerné.

Assez parlé, voici une illustration basique :

class com.petepx.SoundFactory
{
 	private static var _bIsInitialized:Boolean = false;
 	private static var _oSounds:Object = new Object();
 	private static var _nCount:Number = 1;
 	private static var _oContainer:MovieClip;
 	private static var _bIsOn:Boolean = true;
 	private static var _noSound:Sound = _makeSound("", 0);

 	private function SoundFactory() {}

 	private static function _init(mcTarget:MovieClip) : Void
 	{
  		mcTarget == undefined ?_oContainer =
  			_level0.createEmptyMovieClip("__snd__", 65536) : _oContainer = mcTarget;
  		_bIsInitialized = true;
  	}

 	private static function _makeSound(linkageID:String, soundID:Number) : Sound
 	{
  		var container = _oContainer.createEmptyMovieClip("sound" + soundID, soundID);
  		var sound = new Sound(container);
  		sound.attachSound(linkageID);
  		return sound;
  	}

 	/* -------------------- */

 	public static function init(mcTarget:MovieClip) : Void { _init(mcTarget); }

 	public static function getSound(id:Number) : Sound
 	{
  		return _bIsOn ? _oSounds[id] : _noSound;
  	}

 	public static function removeSound(id:Number) : Void
 	{
  		_oContainer["sound" + id ].removeMovieClip();
  		delete _oSounds[id];
  	}

 	public static function addSound(linkageID:String) : Number
 	{
  		if (!_bIsInitialized) _init();
  		_oSounds[_nCount] = _makeSound(linkageID, _nCount);
  		_nCount++;
  		return _nCount - 1;
  	}

 	public static function addSounds(a:Array) : Void
 	{
  		for (var c=0; c< a.length; c++) addSound(a[c]);
  	}

 	public static function toggleOnOff() : Void { _bIsOn ? goOff() : goOn(); }
 	public static function get isOn() : Boolean { return _bIsOn; }
 	public static function goOn() : Void { _bIsOn = true; }
 	public static function goOff() : Void
 	{
  		_bIsOn = false;
  		stopAllSounds();
  	}
}
// Exemple d'utilisation
import com.petepx.SoundFactory;

// initialisation, purement optionnelle
SoundFactory.init(myMcLib);

// Ajouter une liste de sons en fournissant leurs linkages
SoundFactory.addSounds(['boom', 'crack', 'clang', 'dring']);

// On joue le son n°3 et on l'efface
SoundFactory.getSound(3).start();
SoundFactory.removeSound(3);

// On ajoute un son en récupérant son numéro d'id
var n:Number = SoundFactory.addSound('bong');

// On utilise l'id stocké pour pouvoir jouer le son associé
SoundFactory.getSound(n).start();

// On coupe le son de la factory
SoundFactory.goOff();

drowning in Neverneverland

Je vous propose de partager ce soir un extrait de mon troisième maxi Drowning in Neverneverland, un morceau très sombre. Dialogue obscur et schizoprhène entre un pauvre hére et sa conscience, fantasme ou apologie ambiguë du suicide ?

pp.jpg

Don’t !
Chant : Rosyflower (ma compagne de tous les jours depuis bientôt quatre ans).
Bugle : Joël Laforet.
Claviers et machines : Francis Bourre.

[audio:DrowningInNeverLand.mp3]

underground movie

Je profite de ce Dimanche un peu calme pour vous glisser un de mes morceaux inédits nommé Underground Movie. Il a été enregistré en 2001, à l’époque où j’étais encore plein d’illusions sur le monde du disque et de la musique. ^^

J’ai une série de titres inédits qui traînent sur mon HD, je vais donc sûrement pendant le mois de Février tenter de décrocher une signature avec un nouveau label indépendant. Sinon pour l’anecdote j’ai offert l’utilisation d’un de mes titres à Joey Lott pour un CdRom pédagogique sur MX2004 (à paraître prochainement).

Je vous laisse donc en compagnie de ce track aux influences jazz et cinématographiques (on trouve dedans plusieurs samples de voix de Clint Eastwood et un sample de cuivres de Lalo Shifrin issu de Bullit).

Basse : Philippe Dirienzo
Guitare : Thierry Dissard
Vibraphone : Bruno Rousset
Platines : Dr Vince
Piano et machines : Francis Bourre

[audio:UndergroundMovie.mp3]

flashpoll

Je viens de finir de développer une petite application bien pratique qui utilise en noyau dur ma classe XmlToObject.

Il s’agit de créer dynamiquement un sondage à partir (comme vous pouvez vous en douter) d’un simple fichier XML, l’application se charge de gérer tout le reste, création du client Flash, création de la base de données, gestion des résultats …

Voici le format du fichier XML, je l’ai réduit au strict minimum.

flashpoll.jpg

On liste les questions et réponses, on choisit le texte du bouton valider et on spécifie (si on le désire) une redirection ainsi que l’emplacement du script php.

Du coup, n’importe quel webmaster, étranger à toute forme de langage peut créer et updater des sondages à volonté juste en éditant ce simple fichier.
C’est une application que j’ai développé à la base pour Atari, elle m’a permis d’utiliser XmlToObject une fois de plus. Je me sers de cette classe pour tout style de projets, des plus vastes (Atomic Boarder) aux plus modestes (une gestion de pop-ups paramétrable).

atomic bubbles

Voici en beta test publique le dernier jeu que j’ai développé. Il s’agit d’un puzzle game inspiré de Diamonds avec un paquet de features en plus : Niveaux bonus, niveaux réflexion, color combos, couleurs en plus tous les dix niveaux …

Je vous présente une version quasi-finale, il ne lui manque plus que l’intégration du son ainsi que quelques interactions graphiques avec le décor. Je pense finir pendant le week-end.

atomicbubbles.jpg

Ca se passe ici, il suffit de se connecter et cliquer sur play atomic bubbles.

Que le meilleur gagne !

Tip : Vous pouvez utiliser les flèches gauche et droite pour faire pivoter l’aire de jeu, cela peut s’avéver bien pratique dans les niveaux de réflexion

le chant des sirènes

J’avais promis de mettre en ligne quelques unes de mes musiques. Voici donc à nouveau un de mes tracks, cette fois-ci enregistré en live lors d’un concert en première partie de Natacha Atlas.

C’est avec un réel plaisir symbolique que je prends le droit (sans l’accord de mon éditeur) de vous faire partager ce titre non libre de droits. Pour la petite histoire il avait à l’époque pris le droit lui aussi sans mon accord et sans même m’en informer de le vendre à Wagram pour figurer sur la compilation Maxim’s Café. Le milieu du disque est un monde assez étrange … Encore un ! ^^

pp.jpg

Morceau issu de mon deuxième maxi vinyl, le chant des sirènes raconte ma longue dérive cérébrale sur les flots matriciels de la vie, en proie au naufrage et happé fiévreusement par de douces complaintes subversives.

Sur scène on retrouve :
Barbara Zins : Vocals
Philippe Dirienzo : Basse
Gabriel Fernandez : Sax soprano
Peter : Claviers et machines

[audio:ChantDesSirenes.mp3]

mr bricolage

Cette brève du soir pour vous présenter un des derniers outils que j’ai développé. Il s’agit d’un éditeur que j’ai construit pour aller de paire avec la dernière version du moteur 3D isométrique que je développe depuis plus d’un mois.

La grande nouveauté, c’est l’apparition de triggers éditables, qui permettent d’associer la case d’une map à une action prédéfinie à l’intérieur du moteur. Je trouvais plus intéressant à long terme de privilégier un système d’actions qui soit totalement indépendant des tiles (graphismes). La position des triggers est placée dans une matrice séparée qui est interrogée à chaque fois qu’un personnage fait halte sur une case. L’intérêt c’est de pouvoir trigger par exemple une action asseoir à partir de n’importe quel modèle de tile (graphisme), que cela soit une chaise, un fauteuil, un canapé, une table … Deuxième avantage et non des moindres, les tiles ne sont pas des objets customs mais se contentent juste d’être de vulgaires symboles destinés à l’affichage. Ce n’est donc pas l’objet que l’on interroge pour pouvoir déclencher une action mais une matrice dédiée qui broadcastera (si une action lui a été assignée aux coordonnées x,y) un message aux éventuels intéressés. Pas besoin de switch pour identifier l’action à appeler, ce serait une perte de process, j’utilise simplement des nombres (identifiants) qui sont concaténés au moment du broadcast à la chaîne Trigger.

atomiceditor1.jpg

Pour l’instant seulement quatre triggers sont disponibles : Trigger1, Trigger2, Trigger3 et Trigger4 qui permettent de sortir d’une map pour aller sur une autre. La classe MapFactory sait que si le Trigger1 est appelé, elle devra charger la map qui se trouve au Nord de celle en cours d’affichage, si c’est le Trigger2 celle à l’Est , le 3 celle au Sud… Et ainsi de suite … Elle calculera dés lors dynamiquement le nom de la prochaine map. Je vous passe outre la nomenclature que j’utilise pour les identifier. Une fois le document XML chargé et parsé elle recherchera un Trigger correspondant pour déposer le personnage en se remémorant la direction d’où il venait. Si le personnage allait au Nord, il sera placé à la sortie Sud de la map suivante dos à celle-ci.

Deuxième nouveauté, le système d’export des données, je n’exporte plus que les cases occupées avec un système de compression en base32, et celles qui sont libres et interdites avec une conversion binaire sur 5 bits passée elle aussi dans la moulinette base32. J’obtiens du coup en sortie un format hyper léger qui brutalise beaucoup moins la bande passante et le processeur pendant les temps de chargement. (C’est fou d’ailleurs comme l’objet XML est gourmand en process au moment du parsing !). Ma classe XmlToObject me sert d’interface pour reconstruire de façon intuitive l’objet map une fois loadé.

Pour résumer, j’ai choisi au niveau de mon moteur un système avec trois matrices par map. Une matrice pour les objets graphiques, une autre pour les triggers et une dernière constituée uniquement de booléens pour accélérer le pathfinding. J’utilise le pathfinder de Grant Skinner que j’ai converti en AS2.0 pour avoir les temps de calculs les plus courts et ne pas surcharger le système ou freezer l’affichage.

Dernier point, chaque objet graphique existe dans deux orientations différentes, les deux dernières sont générées dynamiquement en passant la propriété _xscale en négatif pour un gain de poids évident.

atomiceditor2.jpg

C’est marrant, mais plus le temps passe et plus je m’aperçois que le développement d’Atomic Boarder ressemble au développement d’un vrai jeu à petite échelle, avec ses outils, son moteur, ses retards au niveau des délais qu’on s’était fixé, ses bugs, ses différentes versions, orientations et évolutions … J’ai aussi développé récemment un player pour accompagner l’éditeur. Il permet de tester en deux temps trois mouvements une map à l’aide d’un personnage de son choix. Pour le graphiste qui design les maps, c’est assez pratique.

On devrait ouvrir demain avec une version alpha du moteur d’affichage et quatre maps disponibles. La première version multi-joueurs ne devrait pas tarder non plus, un bon mois (maxi) devrait suffire si je ne m’attarde pas trop à fignoler et enrichir le moteur isométrique de nouvelles features.

De nouvelles perspectives graphiques sont envisagées pour plus tard, on projette de faire une version avec des personnages 3D exportés en bitmap. On a déjà commencé à faire des tests et ça rend plutôt bien.

Je vous tiens au courant…

mon temple invisible

J’avais promis hier à Joey Lott que je mettrai un de mes titres en ligne, c’est chose faite …

Voici une de mes dernières compositions que j’ai enregistré avec Arnaud Delbos aux percussions et Jerome Auguste Charlery à la contrebasse + archet. Je joue du Fender Rhodes sur ce titre et m’occupe des claviers et machines. C’est un mélange entre electro-jazz et indy avec un clin d’oeil à Henry Miller pour le titre.

J’en dis pas plus, je vous laisse écouter.

[audio:MonTemplInvisible.mp3]

isométrie et z-sorting

En m’intéressant de près à l’isométrie, je me suis aperçu que les méthodes pronées par Jobe Makar (Flash MX Games Design Demystified) et consorts au sujet du z-sorting n’étaient pas du tout adaptées pour un moteur 3D isométrique dont l’objectif final serait d’afficher des animations.

Je vous propose de partager aujourd’hui la solution que j’utilise. Elle vous permettra de gérer les profondeurs sans bug d’affichage dans un moteur 3d isométrique animé.

Le concept récurrent pour gérer la profondeur d’un objet dans un tileworld isométrique est d’assigner un z-sorting incrémenté de gauche à droite sur l’axe des x, puis de bas en haut sur l’axe des y comme indiqué sur le schéma ci-dessous :

Ce système fonctionne à merveille tant que l’on se contente du fait que chaque objet se trouvant à une position (x,y) se déplacera sur une case voisine d’un seul jet, sans interpolation de mouvement, autrement dit sans se trouver à un instant t à cheval entre deux cases.
Pourquoi me direz vous ?

Considérons l’exemple ci-dessous :

Un personnage se trouvant à la position 1,1 et voulant se rendre à la position 1,0 va devoir choisir son camp. S’il garde 4 comme profondeur le temps de l’animation pour se rendre sur la case destination, il va passer devant l’objet qui se trouve en 2,0.

S’il choisit de prendre directement sa profondeur d’arrivée (1), il sera le temps de son trajet affiché derrière l’objet se trouvant en 2,0.

On est confronté au même probléme dans le sens inverse comme vous pouvez le remarquer :

La plupart des moteurs utilisant ce système réussissent à tricher en jouant sur la taille des objets à l’intérieur des cases et sur l’instant ou la valeur z-sorting de l’objet en mouvement est modifiée. Mais ces solutions au bout du compte ne sont vraiment pas satisfaisantes pour gérer l’affichage de façon propre et logique.

D’où l’idée d’opter pour une organisation comme celle-ci, avec un z-sorting incrémenté en diagonales :

Cette fois-ci les animations sur l’axe des y peuvent-être gérées sans bug d’affichage :

Mais les esprits judicieux auront remarqué que le probléme est maintenant répercuté sur l’axe des x :

Sans plus attendre, voici la solution finale, le même tableau mais avec des intervalles en dizaine :

Hum … Quoi de mieux me direz-vous ?
L’idée est d’incrémenter de 15 la profondeur d’un personnage voulant se déplacer vers la droite sur l’axe des x (50 dans l’exemple devient 65) et de lui donner à la fin de son parcours la profondeur de la case d’arrivée (80 dans l’exemple ci-dessous) :

Même chose dans l’autre sens, on enléve 15 à la profondeur d’un personnage voulant se déplacer vers la gauche sur l’axe des x (50 devenant 35 dans le cas précis) et on lui attribue à la fin de son trajet la profondeur de la case destination (il passe donc de 35 à 20 en profondeur une fois arrivé) :

Dernier point, quelle méthode utiliser pour calculer la profondeur d’un objet en connaissant la largeur et la hauteur de la grille ainsi que sa position x et y ?
C’était le sujet d’un précédent thread, et je remercie Didier d’avoir trouvé une solution très élégante et rapide pour gérer cette problématique.
Voici le code que j’utilise :

 private function _sum(n:Number) : Number {

         return (n * (n + 1) / 2);
    }
 
   public function calculDepth(x:Number, y:Number) {

         var r:Number = _sum(x + y) + x + 1;
         r = ((x + y) > (grid.x-1)) ? r -= _sum(x + y - (grid.x-1)) : r;
         r = ((x + y) > grid.y) ? r -= _sum(x + y - grid.y) : r;
         return (r*10);
    }

Voila, j’espère que le concept que j’ai développé et utilise couramment pourra contribuer à remplir un peu le vide auquel je me suis confronté lors de mes investigations sur le sujet.

Je tiens à remercier tout particulièrement Joan pour ses idées et Didier pour son algorithme de calcul remarquable ainsi que Jean-Louis pour la conversion en AS.
Comme d’habitude, vos questions, commentaires ou critiques sont les bienvenue.

Crea04

LAlex m’a motivé hier soir pour m’inscrire au concours MediaBox qui aura lieu ce soir à 21H. J’ai motivé deux personnes de la boîte où je bosse pour concourir avec moi et j’ai commencé hier soir à développer une SoundFactory. J’aimerais bien si le sujet le permet pouvoir développer une illustration sonore interactive en AS et retrouver mes premiers amours : l’Audio et la musique en général.

La soirée ne va pas être triste … On devrait attaquer en dilettante à 20H par un repas arrosé chez Jérôme et continuer plus tard sur les PCs de la boîte (j’espère … ) avant qu’il ne soit trop tard.

Une bonne soirée en perspective très oldschool façon demo party !