Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Exercice pratique : le SNAKE

Compatible ActionScript 3. Cliquer pour en savoir plus sur les compatibilités.Par Monsieur Spi, le 26 novembre 2012

Bonjour,

Je vous propose de réaliser un petit jeu de SNAKE, cependant, comme dans le sujet précédent (le Taquin), je ne vais pas créer un tutorial, dans sa définition stricte, mais un exercice, la différence est notable, puisqu'un tutorial vous prendra par la main là où un exercice vous laissera réfléchir, tout en vous donnant les bases nécessaire à la résolution du problème. Bien sur nous étudierons ensemble les différentes étapes de la création, cependant c'est à vous de vous armer du minimum requis pour réussir à suivre. Cette méthode évite de créer des tutoriaux trop longs et d'expliquer de nouveau tout le détail à chaque nouveau jeu.

Voyons avant tout à quoi cela va ressembler :

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

Utilisez les flèches de votre clavier.

Etude préliminaire

Tout d'abord le SNAKE c'est quoi ? (merci Wikipedia)

Snake, de l'anglais signifiant « serpent », est un jeu vidéo populaire créé au milieu des années 1970, disponible de par sa simplicité sur l'ensemble des plate-formes de jeu existantes sous des noms de clone. Il s'est de nouveau fait connaître dans les années 1990 avec l'émergence du nouveau support de jeu qu'est le téléphone portable. Aujourd'hui, il est toujours aussi populaire et est devenu un classique dans les jeux vidéo.

Le joueur contrôle une longue et fine créature semblable à un serpent, qui doit slalomer entre les bords de l'écran et les obstacles qui parsèment le niveau. Pour gagner chacun des niveaux, le joueur doit faire manger à son serpent un certain nombre de pastilles ou de fruits (de la nourriture en général), allongeant à chaque fois la taille de la bestiole. Alors que le serpent avance inexorablement, le joueur ne peut que lui indiquer une direction à suivre (en haut, en bas, à gauche, à droite) afin d'éviter que la tête du serpent ne touche les murs ou son propre corps, dans ce cas il risque de mourir.

Le niveau de difficulté est contrôlé par l'aspect du niveau (simple ou labyrinthique), le nombre de pastilles à manger, l'allongement du serpent et sa vitesse.

Dans la version que nous allons réaliser, nous allons tenir compte de certains facteurs, comme l'allongement du serpent lorsqu'il mange, la perte de la partie si il touche un bord de l'écran ou un de ses propres anneaux, mais nous allons modifier la jouabilité en proposant une approche basée sur la trigonométrie. L'aspect labyrinthe des niveaux sera laissé à votre libre appréaciation.

Les pré-requis

Pour prendre en main ce programme vous devez connaître :

Si vous souhaitez plus de précisions sur ces points, je vous encourage à parcourir le Wiki de Mediabox où vous trouverez de nombreux tutoriaux détaillés.

Le code

On commence par vous donner tout le code d'un coup, histoire que vous ayez une vue d'ensemble.

// Objets 
var snake:Snake = 			new Snake();
var head:Head = 			new Head();
var pomme:Pomme = 			new Pomme();
var panneaux:Panneaux = 		new Panneaux();
var manger:SonSnake = 			new SonSnake();
 
// Tableaux
var posx:Array = [];
var posy:Array = [];
var stockAnneaux:Array = [];
 
// variables 
var longueur:int;
var angle:Number;
var compteur:int;
var ajouteAnneaux:int;
var victoire:int;
var vitesse:int;
var droite:Boolean;
var gauche:Boolean;
var W:int = stage.stageWidth;
var H:int = stage.stageHeight;
 
// actions des panneaux
panneaux.buttonMode = true;
panneaux.addEventListener(MouseEvent.CLICK,commencer); 
 
// Evenements
stage.addEventListener(KeyboardEvent.KEY_DOWN, appuie);
stage.addEventListener(KeyboardEvent.KEY_UP, relache);
var temp:Timer = new Timer(60);
temp.addEventListener("timer", deplaceSnake);
 
// panneau de départ
addChild(panneaux);
 
// Appuyer sur une touche
function appuie(e:KeyboardEvent):void {
	if(e.keyCode==39) droite = true;
	if(e.keyCode==37) gauche = true;
}
 
// Relâcher une touche
function relache(e:KeyboardEvent):void {
	if(e.keyCode==39) droite = false;
	if(e.keyCode==37) gauche = false;
}
 
// Lancement de la partie
function commencer(e:Event):void{
	longueur = 5;
	angle = 0;
	compteur = 0;
	ajouteAnneaux = 4;
	victoire = 140;
	vitesse = 10;
	droite = false;
	gauche = false;
	posx = [];
	posy = [];
	stockAnneaux = [];
	for (var i:int=0; i<longueur; i++) {
		var a:Anneau = new Anneau();
		addChild(a);		
		stockAnneaux.push(a)
	}
	addChild(snake);
	addChild(pomme);
	addChild(head);
	snake.x = W*.5;
	snake.y = H*.5;
	pomme.x = 30+Math.random()*(W-60);
	pomme.y = 30+Math.random()*(H-60);
	removeChild(panneaux);
	temp.start(); 
	stage.focus = stage;
}
 
// Deplacement du Snake
function deplaceSnake(e:TimerEvent):void {
 
		var i:int;
		var j:Object;
 
		// Direction
		angle += (int(droite)-int(gauche))*20;
 
		// Enregistre la position de la tête
		posx[0] = snake.x;
		posy[0] = snake.y;
 
		// Deplace la tête
		snake.x += Math.cos(angle*Math.PI/180)*vitesse;
		snake.y += Math.sin(angle*Math.PI/180)*vitesse;
 
		// Envoye le dernier anneau en premier 
		var dernier:int = longueur-1-compteur++;
		if (dernier == 0) compteur = 0;
 
		var A:Object = stockAnneaux[dernier];
		A.x = posx[0];
		A.y = posy[0];
 
		// Déplace les anneaux
		for (i=longueur-1; i>0; i--) {
			posx[i] = posx[i-1];
			posy[i] = posy[i-1];
		}
 
		// si la tête touche de la nourriture
		if (snake.hitTestObject(pomme)) {
 
			// ajoute des anneaux
			for (i=0; i<ajouteAnneaux; i++) {
				var anneau:Anneau = new Anneau();
				addChild(anneau);
				posx[longueur] = posx[longueur-1];
				posy[longueur] = posy[longueur-1];
				longueur++;
				stockAnneaux.push(anneau);
			}
 
			pomme.x = 30+Math.random()*(W-60);
			pomme.y = 30+Math.random()*(H-60);
 
			// Repositionne le Snake complet (assure la cohésion)
			for (i=0; i<longueur; i++) {
				stockAnneaux[i].x = posx[i];
				stockAnneaux[i].y = posy[i];
			}
			compteur = 0;
			manger.play();
		}
 
		// Vérifie si la tête touche un bord
		if (snake.x<0 || snake.x>W || snake.y<0 || snake.y>H) finPartie(2);
 
		// Vérifie si la tête touche un anneau
		for each(j in stockAnneaux){
			if(snake.hitTestPoint(j.x,j.y)) finPartie(2);
		}
 
		// vérifie si le joueur gagne
		if (longueur>=victoire) finPartie(3);
 
		// effet pour la tête
		head.x = snake.x;
		head.y = snake.y;
		head.rotation = angle;
 
		// effet de la queue
		var id:int;
		for (i=longueur; i>0; i--) {
			id = longueur-i-compteur;
			if (id<0) id += longueur;
			with (stockAnneaux[id]) scaleX=scaleY = i/longueur;
		}
}
 
// Fin de partie
function finPartie(F:int):void {
	addChild(panneaux);
	panneaux.gotoAndStop(F);
	temp.stop();
	for each(var i in stockAnneaux) removeChild(i);
	removeChild(snake);
	removeChild(pomme);
	removeChild(head);
}

Et voilà, moins de 200 lignes pour un jeu presque complet. Je dis “presque” car pour bien faire il faudrait ajouter le comptage des points, la durée de jeu, des obstacles, des niveaux différents avec une progression, etc… mais je vous laisse gérer tout ça, l'important c'est d'avoir la base pour commencer.

Etude du code

Et nous-nous lançons dans l'étude du code par portions.

// Objets 
var snake:Snake = 	new Snake();	
var head:Head = 	new Head();	
var pomme:Pomme = 	new Pomme();	
var panneaux:Panneaux = new Panneaux();
var manger:SonSnake = 	new SonSnake();
 
// Tableaux
var posx:Array = [];
var posy:Array = [];
var stockAnneaux:Array = [];
 
// variables 
var longueur:int;
var angle:Number;
var compteur:int;
var ajouteAnneaux:int;
var victoire:int;
var vitesse:int;
var droite:Boolean;
var gauche:Boolean;
var W:int = stage.stageWidth;
var H:int = stage.stageHeight;

Rien de bien compliqué à ce stade, on crée les variables globales et les objets du jeu.

Comme à chaque nouvel exercice de ce type, la construction est toujours la même, on commence par déclarer en début de code tous les objets, variables et tableaux utiles au cours du jeu.

Snake = la tête du serpent, c'est elle qui réagit aux collisions et qui dirige l'ensemble
Head = un petit effet spécial pour afficher le graphisme de la tête indépendamment du reste
Pomme = la nourriture
Panneaux = tous les panneaux d'interface du jeu (regroupés en un seul clip)
Manger = le bruitage pour indiquer qu'on a mangé la pomme

posx = un tableau pour stocker la position x des anneaux
posy = un tableau pour stocker la position y des anneaux
stockAnneaux = un tableau pour stocker les anneaux

Notez qu'ici on pourrait utiliser un seul tableau avec des points (x,y) plutôt que deux séparés, mais c'est plus simple à travailler ainsi pour un début.

Les variables sont assez explicites pour ne pas avoir besoin de vous les détailler, si la signification de l'une d'elle vous échappe vous la comprendrez mieux lorsque nous allons étudier les fonctions. Notez cependant que dans l'ensemble on ne fait que les déclarer, on ne leur attribue pas de valeur, c'est inutile car nous le ferons au lancement de la partie.

Passons à l'interactivité :

// actions des panneaux
panneaux.buttonMode = true;
panneaux.addEventListener(MouseEvent.CLICK,commencer); 
 
// Evenements
stage.addEventListener(KeyboardEvent.KEY_DOWN, appuie);
stage.addEventListener(KeyboardEvent.KEY_UP, relache);
var temp:Timer = new Timer(60);
temp.addEventListener("timer", deplaceSnake);
 
// panneau de départ
addChild(panneaux);

Tous les panneaux sont cliquables et déclenchent le lancement de la partie, que l'on commence, que l'on gagne ou que l'on perde, c'est pourquoi je les ai tous regroupés dans un seul clip (un panneau par frame) et que je passe juste un écouteur au conteneur.

Le code des touches est classique, on n'utilisera que deux touches au total, droite et gauche.

Je vais utiliser un timer pour cadencer le jeu et non un écouteur calé sur un ENTER_FRAME, ceci pour nous permettre plus de souplesse et surtout de modifier la vitesse du jeu indépendamment des objets qui le composent.

Et on ajoute le panneau de départ afin d'inciter l'utilisateur à cliquer pour démarrer la partie. Rien de complexe si vous avez les connaissances demandées dans les pré-requis.

// Appuyer sur une touche
function appuie(e:KeyboardEvent):void {
	if(e.keyCode==39) droite = true;
	if(e.keyCode==37) gauche = true;
}
 
// Relâcher une touche
function relache(e:KeyboardEvent):void {
	if(e.keyCode==39) droite = false;
	if(e.keyCode==37) gauche = false;
}

Là encore quelque chose de très classique pour la gestion du clavier, on utilise deux variables pour savoir lorsqu'une touche est enfoncée ou relâchée.

// Lancement de la partie
function commencer(e:Event):void{
	longueur = 5;
	angle = 0;
	compteur = 0;
	ajouteAnneaux = 4;
	victoire = 140;
	vitesse = 10;
	droite = false;
	gauche = false;
	posx = [];
	posy = [];
	stockAnneaux = [];
	for (var i:int=0; i<longueur; i++) {
		var a:Anneau = new Anneau();
		addChild(a);		
		stockAnneaux.push(a)
	}
	addChild(snake);
	addChild(pomme);
	addChild(head);
	snake.x = W*.5;
	snake.y = H*.5;
	pomme.x = 30+Math.random()*(W-60);
	pomme.y = 30+Math.random()*(H-60);
	removeChild(panneaux);
	temp.start(); 
	stage.focus = stage;
}

On commence par initialiser toutes les variables en leur attribuant les valeurs de départ, ceci va nous permettre de réinitialiser le jeu à chaque début de partie. On effectue ensuite une petite boucle pour créer les premier anneaux du serpent que l'on pousse dans le tableau de stockage. Puis on affiche les objets utiles que l'on positionne. Notez au passage que la pomme est placée de manière à ce qu'elle ne puisse pas être collée à un bord du jeu, ce qui la rendrait très difficile à attraper. Et pour finir, une fois tout le jeu paramétré, on lance le timer qui permet de déplacer le snake.

CORRECTION : le fait d'avoir lié les événements clavier au stage et d'avoir placé par dessus un panneau cliquable entraîne un problème de focus, autrement dit, c'est le dernier objet cliqué qui récupére le focus, ce qui oblige à cliquer deux fois sur le jeu (une fois sur le panneau pour lancer le jeu, et une fois sur le stage pour récupérer le focus). Pour éviter ça il suffit de redonner le focus au stage lorsque la partie commence avec la ligne suivante :

stage.focus = stage;

Attention cette correction n'est pas présente dans les sources, à vous de l'ajouter ;-)

// Deplacement du Snake
function deplaceSnake(e:TimerEvent):void {
 
		var i:int;
		var j:Object;
 
		// Direction
		angle += (int(droite)-int(gauche))*20;
 
		// Enregistre la position de la tête
		posx[0] = snake.x;
		posy[0] = snake.y;
 
		// Deplace la tête
		snake.x += Math.cos(angle*Math.PI/180)*vitesse;
		snake.y += Math.sin(angle*Math.PI/180)*vitesse;
 
		// Envoye le dernier anneau en premier 
		var dernier:int = longueur-1-compteur++;
		if (dernier == 0) compteur = 0;
 
		var A:Object = stockAnneaux[dernier];
		A.x = posx[0];
		A.y = posy[0];
 
		// Déplace les anneaux
		for (i=longueur-1; i>0; i--) {
			posx[i] = posx[i-1];
			posy[i] = posy[i-1];
		}
 
		// si la tête touche de la nourriture
		if (snake.hitTestObject(pomme)) {
 
			// ajoute des anneaux
			for (i=0; i<ajouteAnneaux; i++) {
				var anneau:Anneau = new Anneau();
				addChild(anneau);
				posx[longueur] = posx[longueur-1];
				posy[longueur] = posy[longueur-1];
				longueur++;
				stockAnneaux.push(anneau);
			}
 
			pomme.x = 30+Math.random()*(W-60);
			pomme.y = 30+Math.random()*(H-60);
 
			// Repositionne le Snake complet (assure la cohésion)
			for (i=0; i<longueur; i++) {
				stockAnneaux[i].x = posx[i];
				stockAnneaux[i].y = posy[i];
			}
			compteur = 0;
			manger.play();
		}
 
		// Vérifie si la tête touche un bord
		if (snake.x<0 || snake.x>W || snake.y<0 || snake.y>H) finPartie(2);
 
		// Vérifie si la tête touche un anneau
		for each(j in stockAnneaux){
			if(snake.hitTestPoint(j.x,j.y)) finPartie(2);
		}
 
		// vérifie si le joueur gagne
		if (longueur>=victoire) finPartie(3);
 
		// effet pour la tête
		head.x = snake.x;
		head.y = snake.y;
		head.rotation = angle;
 
		// effet de la queue
		var id:int;
		for (i=longueur; i>0; i--) {
			id = longueur-i-compteur;
			if (id<0) id += longueur;
			with (stockAnneaux[id]) scaleX=scaleY = i/longueur;
		}
}

Ok, voici le coeur du jeu ;-)

Voyons le détail des choses qui pourraient vous poser problème, à commencer par la rotation du serpent :

angle += (int(droite)-int(gauche))*20;

L'angle correspond à l'angle de rotation de la tête du serpent, j'utilise une petite astuce pour m'éviter plein de lignes de code avec des if et compagnie, il suffit de prendre la valeur entière d'un boolean.

Stop !!!! Comment ?

Oui, c'est en fait une conversion qui est effectuée par le programme, un boolean prend comme valeur “vrai” ou “faux”, converti en un chiffre entier on obtient 1 ou 0, pour connaître le sens dans lequel tourne mon serpent je peux donc utiliser une simple soustraction entre les valeurs des touches enfoncées(1) ou relâchées (0), prenons un exemple :

Droite enfoncée et gauche relâchée : 1-0 = 1
Droite relâchée et gauche enfoncée : 0-1 = -1
Droite relâchée et gauche relâchée : 0-0 = 0
Droite enfoncée et gauche enfoncée : 1-1 = 0

Une fois qu'on à le sens il reste simplement à le multiplier par la vitesse de rotation (ici 20) pour connaître le nouvel angle utile ;-)

// Enregistre la position de la tête
posx[0] = snake.x;
posy[0] = snake.y;
 
// Deplace la tête
snake.x += Math.cos(angle*Math.PI/180)*vitesse;
snake.y += Math.sin(angle*Math.PI/180)*vitesse;

Pas grand chose à dire à ce niveau, on stocke la position de la tête avant de la déplacer, puis on la déplace selon les règles de trigonométrie classiques avec sinus et cosinus (que vous connaissez, pré-requis).

// Envoyer le dernier anneau en premier 
var dernier:int = longueur-1-compteur++;
if (dernier == 0) compteur = 0;
 
var A:Object = stockAnneaux[dernier];
A.x = posx[0];
A.y = posy[0];

Voici la principale astuce pour faire un snake, replacer le dernier anneau de la queue en première position. Attardons-nous un petit moment là dessus, pour déplacer tout le serpent, c'est à dire la tête et les anneaux, nous avions le choix de déplacer tous les anneaux en permanence, mais c'est très lourd surtout dès que le serpent atteint une certaine longueur. En réfléchissant un peu on peut trouver une petite astuce très simple qui suit le raisonnement suivant : tous les anneaux sont identiques, la tête du serpent avance, plutôt que de déplacer tous les anneaux je prend le dernier et je viens le replacer à la position qu'occupait la tête juste avant son déplacement.

Voilà pourquoi on à enregistré la position de la tête dans les tableaux de position juste avant de la déplacer, car c'est cette position que doit occuper le dernier anneau de la file afin de faire avancer tout le serpent.

Résumons :
- lorsque le snake avance on enregistre la position de la tête
- on déplace la tête en fonction de sa rotation et de sa vitesse
- on prend le dernier anneau de la queue et on le repositionne à l'endroit qu'occupait la tête avant son déplacement.

Vous allez me dire, ok c'est très simple, mais alors pourquoi juste après on déplace tous les anneaux ?

// Déplace les anneaux
for (i=longueur-1; i>0; i--) {
	posx[i] = posx[i-1];
	posy[i] = posy[i-1];
}

En fait il ne s'agit pas de les déplacer, mais plutôt de les replacer à la bonne position, chaque anneau doit prendre la position de celui qui le précède dans la liste, ce qui assure la cohésion de l'ensemble. Ce n'est pas visible tant que le snake conserve sa taille de départ, il y a toujours le même nombre d'anneaux et on replace le dernier en premier pour assurer le déplacement, mais dès que le serpent mange une pomme on lui ajoute une flopé de nouveaux anneaux, si on les repositionne pas tout de suite comme il faut on va se retrouver avec des anneaux superposés ou avec un trou dans le serpent car n'oubliez pas que le dernier anneau se place en premier et que les nouveaux anneaux ne sont pas encore positionnés à la suite de la queue du serpent. On fait donc une petite boucle sur tous les anneaux pour s'assurer que chacun est à sa place même si leur nombre augmente, et en parlant d'augmenter le nombre des anneaux, voyons la suite.

// si la tête touche de la nourriture
if (snake.hitTestObject(pomme)) {
 
	// ajoute des anneaux
	for (i=0; i<ajouteAnneaux; i++) {
		var anneau:Anneau = new Anneau();
		addChild(anneau);
		posx[longueur] = posx[longueur-1];
		posy[longueur] = posy[longueur-1];
		longueur++;
		stockAnneaux.push(anneau);
	}
 
	pomme.x = 30+Math.random()*(W-60);
	pomme.y = 30+Math.random()*(H-60);
 
	// Repositionne le Snake complet (assure la cohésion)
	for (i=0; i<longueur; i++) {
		stockAnneaux[i].x = posx[i];
		stockAnneaux[i].y = posy[i];
	}
	compteur = 0;
	manger.play();
}

Vu la taille des objets du jeu, je peux utiliser un simple hitTestObject, bien que cela me répugne dans le fond, j'ai horreur de cette commande très peu précise et peu flexible, mais pour ce que nous voulons faire cela conviendra.

Quand la tête du serpent touche une pomme, on fait une boucle pour ajouter les anneaux, que l'on ajoute immédiatement au tableau de stockage. Vous noterez que si l'on renseigne bien les tableaux de position, la valeur qui y est passée est identique pour tous les anneaux ajoutés, reprenons :

- la tete du snake avance
- le dernier anneau est repositionné en premier
- tous les anneaux sont replacés
- la tête du snake touche une pomme
- on ajoute le nombre d'anneaux convenu
- on place tous ces nouveaux anneaux à la position de l'anneau qui est parti en tête

Pourquoi ?
Tout simplement pour éviter que le joueur perde la partie bêtement alors qu'on ajoute un certain nombre d'anneaux. Tous les anneaux sont là, superposés, et lorsque le snake va avancer chacun de ces anneaux partira en tête à son tour et viendra agrandir la taille du serpent au fur et à mesure qu'il avance, c'est beaucoup plus jouable ainsi surtout si vous ajoutez beaucoup d'anneaux d'un coup.

Vous noterez qu'on utilise une seule pomme, on ne va pas donc pas la détruire et la réafficher, il suffit de changer aléatoirement sa position lorsque le serpent la mange ;-)

Notez également qu'après avoir ajouté de nouveaux anneaux, on refait une petite boucle pour assurer la cohésion de l'ensemble du snake, ce qui nous permet de nous assurer qu'il ne va pas rester d'anneau à la traîne

// Vérifie si la tête touche un bord
if (snake.x<0 || snake.x>W || snake.y<0 || snake.y>H) finPartie(2);
 
// Vérifie si la tête touche un anneau
for each(j in stockAnneaux){
	if(snake.hitTestPoint(j.x,j.y)) finPartie(2);
}

Bon, ben là on s'assure bêtement que la tête du snake ne sort jamais des limites du terrain, sinon la partie est perdue, et une simple boucle nous assure que la tête ne touche pas non plus un des anneaux du serpent, sinon, c'est le serpent qui se mord la queue (jeu de mot à deux balles j'en convient….). Plutôt que d'utiliser un hitTestObject, qui prend en comte toute la surface des objets, j'utilise un hitTestPoint, techniquement cela revient à dire que ce qu'on teste c'est le point central de chaque anneau et non toute la surface de l'anneau, cela permet des collisions plus fines, et permet à la tête de frôler les anneaux sans pour autant les toucher.

On termine avec quelques astuces et effets :

// vérifie si le joueur gagne
if (longueur>=victoire) finPartie(3);
 
// effet pour la tête
head.x = snake.x;
head.y = snake.y;
head.rotation = angle;
 
// effet de la queue
var id:int;
for (i=longueur; i>0; i--) {
	id = longueur-i-compteur;
	if (id<0) id += longueur;
	with (stockAnneaux[id]) scaleX=scaleY = i/longueur;
}

Tout d'abord on vérifie si le joueur à atteint la taille max du serpent, dans ce cas il gagne.

Puis on replace la fausse tête sur la tête techniquement utile, rappelez-vous que nous avons déclaré cette fausse tête en début de programme, elle permet d'avoir une tête graphiquement libre, et d'une autre tête qui est chargée des collisions et des déplacements. Techniquement ça ne sert donc à rien, mais c'est plus joli ;-) Si on avait utilisé le leurre pour tester les collisions on aurait eu des problèmes car les anneaux sont tous collés les uns aux autres, et la tête aurait du coup touché en permanence les anneaux qui sont juste derrière elle.

Enfin, et toujours dans un soucis esthétique avant tout, je vous propose une petite astuce pour réduire la taille des anneaux en fonction de leur position dans la queue, ce qui donne un effet plus “serpent”. Pour cela c'est assez simple, il faut trouver la valeur inverse de la position dans le tableau de stockage divisée par la longueur du serpent, je vous laisse étudier ces 3 lignes par vous même.

// Fin de partie
function finPartie(F:int):void {
	addChild(panneaux);
	panneaux.gotoAndStop(F);
	temp.stop();
	for each(var i in stockAnneaux) removeChild(i);
	removeChild(snake);
	removeChild(pomme);
	removeChild(head);
}

On termine par une petite fonction que l'on appelle dès que la partie est terminée, qu'elle soit gagnée ou perdue (on affiche juste la frame correspondante au panneau souhaité). On en profite pour arrêter le timer (le snake ne bouge plus) et on supprime tous les objets du jeu, ils seront remis en place au démarrage de la partie.

Conclusion

Voilà c'est terminé, à vous de jouer à présent, faites évoluer cette base pour obtenir de vrais jeux complets. En comparaison du jeu original, cette version corse un peu la difficulté, bien qu'on réduise le nombre de commandes à deux touches, au lieu de quatre, car le serpent prend une certaine amplitude pour tourner sur lui même. Cela impose aussi de ne pas travailler avec des labyrinthes carrés, le joueur ne pourrait entrer ou sortir d'un couloir qu'avec une très grande agilité, cependant c'est bien plus réaliste qu'un jeu où le serpent tourne à angle droit.

Techniquement parlant il n'y a absolument rien de complexe dans cet exercice, tout repose sur des astuces, c'est pourquoi j'ai fait le choix de ne pas en faire un tutorial comme vous pouvez en trouver sur le Wiki de Mediabox, mais un exercice pratique pour vous permettre de vous entraîner à l'aide des connaissances requises. Ceci à également pour but de rappeler que construire un jeu c'est aussi trouver les bonnes astuces pour se simplifier la vie et parvenir à ses fins.

Merci de m'avoir lu et rendez-vous pour un prochain petit exercice.

Les sources

snake_mb_v2.fla version CS6
snake_mb_cs5_v2.fla version CS5