Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Glisser-Lâcher (drag and drop)
• Collisions et superpositions (ici)

Tester les superpositions (hitTestObject, hitTestPoint)

Par Nataly, le 29 octobre 2012

Savoir si deux instances de symbole se superposent ou si une instance recouvre un point précis, sont deux opérations dont il n'y a pas grand chose à dire en théorie.
Ce tuto s'attachera donc principalement à la mise en œuvre du principe. Je vous proposerai de vous y essayer via cet exemple :





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

Pour suivre ce tuto vous devrez être au point de la gestion de la liste d'affichage, de l'écoute d'événements et du flux de propagation ainsi que des méthodes startDrag et stopDrag.

Méthode hitTestObject

Hit, comme toucher, test comme… tiens je le dis pas :-P, object comme objet (trop gentille).
C'est donc une méthode qui permet de savoir si deux displayObjects (objets d'affichage) se touchent, comprendre se superposent au moins partiellement.

On invoque la méthode sur une instance et on lui en passe une autre, elle renvoie vrai si il y a superposition, faux dans le cas contraire.

trace(leDisque.hitTestObject(leRectangle))// true si superposition, false sinon

On peut difficilement faire plus simple ! \o/

Si vous venez du chapitre précédent, c'est le moment d'utiliser vos fraiches connaissances du glisser-lâcher pour vous fabriquer cette démo rapide, et constater la toute petite subtilité (ô combien handicapante) à garder en tête :


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

Eh oui !… Ce n'est pas la surface dessinée qui est prise en considération, mais la zone d'encombrement (ou cadre de sélection) du clip ; toujours rectangulaire1).
pastille.addEventListener(MouseEvent.MOUSE_DOWN, qdEnfonce);
pastille.buttonMode=true
pastille.addEventListener(MouseEvent.MOUSE_UP,qdLache);
 
function qdEnfonce(me:MouseEvent):void {
	trace("dd")
	me.currentTarget.startDrag();
}
 
function qdLache(me:MouseEvent):void {
	stopDrag();
	txtSortie.text = "Superposition ?\n" + pastille.hitTestObject(croissant);
}

Méthode hitTestPoint

• Tout comme pour sa copine, on l'invoque sur un displayObject et on lui passe, non pas un objet, mais le x et le y d'un point.

txtSortie.text = "Superposition ?\n" + croissant.hitTestPoint(mouseX,mouseY);

• Tout comme sa copine elle renverra vrai si le point est contenu dans la zone d'encombrement, false dans le cas contraire.

• En revanche… tada !… on peut préciser qu'on souhaite tester précisément la superposition du point spécifié avec un pixel dessiné du clip.
On utilisera le troisième paramètre, un booléen qui vaut false par défaut, en le valorisant à true.

ici on teste qu'il y a quelque chose sous la souris quand on clique :

stage.addEventListener(MouseEvent.CLICK,qdClic);
function qdClic(me:MouseEvent):void {
		txtSortie.text = "Superposition ?\n" + croissant.hitTestPoint(mouseX,mouseY,true);
}

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

Que le clip auquel vous appliquez hitTestPoint soit sur la racine ou dans un autre les coordonnées du point testées sont repérées sur la racine.

Mise en œuvre

Avant de nous lancer dans les grandes manœuvres, à l'attention des plus novices, quelques cas de figure courants, sans trop se compliquer :

Reconnaître la cible


Le hasard qui s'obstine à ne pas exister, vient de se manifester sur le forum sous forme d'une question qui nous fournit un excellent exo d'entrainement :

Objectif 1 :

Glisser un clip sur “sa” poubelle le supprime.
Rien de plus, sachant que j'ai utilisé plusieurs instances des trois mêmes symboles pour aller vite mais que ça pourrait être, tout pareil, une flopée de symboles différents.

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


Objectif 2 :

Ne pas se compliquer ! :mrgreen:
Aussi bien à la construction que dans l'écriture du code : toutes les instances sont déjà sur la scène, et moins on nomme, mieux on se porte :-P




Je nomme les trois contenants puisqu'il faudra leur associer un écouteur. Dedans, je dispose les clips à l'inspiration.
Il faut aussi nommer les poubelles, pour les identifier au test de collision.

C'est tout.



Le code va se contenter d'associer à chaque contenant la bonne poubelle2).
Une même fonction de rappel souscrite au Mouse_Down des trois contenants ajoute une variable dynamique au clip saisi (cible).
Quand on lâche, il suffit de tester si le clip trainé se superpose à se cible et le tour est joué ;)


contCercles.addEventListener(MouseEvent.MOUSE_DOWN,qdEnfonce);
contCarres.addEventListener(MouseEvent.MOUSE_DOWN,qdEnfonce);
contEtoiles.addEventListener(MouseEvent.MOUSE_DOWN,qdEnfonce);
 
contCarres.poubelle=poubCarre;
contCercles.poubelle=poubCercle;
contEtoiles.poubelle=poubEtoile;
 
poubCercle.mouseEnabled=false
poubEtoile.mouseEnabled=false
poubCarre.mouseEnabled=false
 
stage.addEventListener(MouseEvent.MOUSE_UP, qdLache,true);
 
function qdLache(me:MouseEvent) {
	stopDrag();
	if (me.target.hitTestObject(me.target.cible)) {
		var conteneur:MovieClip=MovieClip(me.target.parent);
		conteneur.removeChild(MovieClip(me.target));
	}
}
 
function qdEnfonce(me:MouseEvent) {
	me.target.startDrag();
	me.target.cible=me.currentTarget.poubelle;// attribuer une poubelle cible au clip saisi
}

Intervertir des clips


En pleine forme, il est le hasard, ce soir : encore une question qu'elle est bien comme tout :)

Voilà la consigne d'origine :
L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.
Mais pourquoi se restreindre à quatre clips ? D'autant que plus on en aura, moins d'aucuns seront tentés de régler l'histoire à coup de cibles et de if - je n'ose même pas réfléchir comment - ;)


Et puis ça va plus vite à construire : on dessine vite fait une poignées de formes qu'on transforme en autant de symbole (F8), on sélectionne l'ensemble des instances qu'on transforme en symbole et on nomme l'instance, contenant.

Voilà yapuka -_-

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

La vérité c'est que sur le coup, je n'avais pas vu qu'il y avait une combinaison à trouver.
Ça tombe bien, ce sont deux choses différentes. Je vous propose de régler d'abord le principe de l'interversion. Et de ne vous préoccuper de la combinaison qu'ensuite.




On pourrait manipuler les clips depuis le conteneur à coup de getChild…, mais ce n'est pas une bonne idée, un tableau c'est toujours plus pratique et moins gourmand.

Ensuite le principe est simple : on initie le déplacement via startDrag comme d'habitude. On écoute le Mouse_Up, comme d'habitude encore, et là on fait quoi ?
En parcourant le tableau on vérifie si le clip lâché se superpose à chacun des autres.
• Si oui, on intervertit les coordonnées. Du coup il faut avoir pris soin de mémoriser les coordonnées d'origine du clip courant en l'attrapant (qdEnfonce).
• Sinon, on renvoie le clip courant à ses coordonnées d'origines.

Voilà c'est tout :)

Les pièges

Bon oui ! Je n'ai pas tout dit3), c'est plus drôle de s'y essayer.

• si les hitTest font un peu n'importe quoi, vérifiez donc qui diffuse le qdLache
• attention à ce que les formes du clip modèle (petit noir) ne soient pas trainables. Une pov' ligne y suffit…

//un tableau des enfants du clip 'contenant'
var _tbFormes:Array=new Array();
var _max:int=contenant.numChildren;
for (var i:int=0; i<_max; i++) {
	_tbFormes.push(contenant.getChildAt(i));
}
 
contenant.addEventListener(MouseEvent.MOUSE_DOWN,qdEnfonce);
stage.addEventListener(MouseEvent.MOUSE_UP, qdLache,true);
 
function qdLache(me:MouseEvent) {
	stopDrag();
	for (var i:int=0; i<_max; i++) {
		if (_tbFormes[i]!=me.target && _tbFormes[i].hitTestObject(me.target)) {
			//trace("placer courant aux coord de _tbFormes[i]");
			me.target.x=_tbFormes[i].x;
			me.target.y=_tbFormes[i].y;
			//trace("placer _tbFormes[i] aux coord de courant ");
			_tbFormes[i].x=me.target.xDep;
			_tbFormes[i].y=me.target.yDep;
			return;
		}
	}
//	trace("pas de superposition");
	me.target.x=me.target.xDep;
	me.target.y=me.target.yDep;
}
 
function qdEnfonce(me:MouseEvent) {
	contenant.addChild(MovieClip(me.target));
	me.target.startDrag();
	me.target.xDep=me.target.x;
	me.target.yDep=me.target.y;
}

La combinaison


Je suis partie du principe que la combinaison c'est la disposition des clips dans le symbole conteneur.
Toujours sur le mode on ne se complique pas, une autre instance rétrécie et colorée (teinte) posée sur la scène fera office de modèle à trouver.

L'idée c'est de mémoriser les positions x d'origine de chacun des clips puis de les “mélanger”.
Il suffit alors d'une fonction de validation - booléenne - qui vérifie pour chaque clip qu'il est à sa place. On l'appelle dans qdLache.


Mélanger les clips du contenant


Bon alors ça… ça vaut ce que ça vaut, pour ce que c'est faire… 8-) j'intervertis chaque clip avec n'importe lequel des autres - lui compris ça changera rien -

// mélanger les formes
for (i=0; i<_max; i++) {
	var aa:int=Math.random()*_max;
	var clipAlea:MovieClip=_tbFormes[aa];
	var x_c:int =_tbFormes[i].x;
	var y_c:int=_tbFormes[i].y;
	_tbFormes[i].x=_tbFormes[aa].x;
	_tbFormes[i].y=_tbFormes[aa].y;
	_tbFormes[aa].x=x_c;
	_tbFormes[aa].y=y_c;
}

Mémoriser les positions initiales

for (var i:int=0; i<_max; i++) {
	_tbFormes.push(contenant.getChildAt(i));
--> ici _tbFormes[i].xRef=_tbFormes[i].x;// la position x de référence
}

Valider

function valid():Boolean {
	for (i=0; i<_max; i++) {
		trace("xRef "+_tbFormes[i].xRef+" x "+_tbFormes[i].x);
		if (_tbFormes[i].xRef!=_tbFormes[i].x) {
			return false;
		}
	}
	return true;
}


appelée ds qdLache :

 
function qdLache(me:MouseEvent) {
	stopDrag();
--> Ici	txtSortie.text="";
	for (var i:int=0; i<_max; i++) {
		if (_tbFormes[i]!=me.target && _tbFormes[i].hitTestObject(me.target)) {
			me.target.x=_tbFormes[i].x;
			me.target.y=_tbFormes[i].y;
			_tbFormes[i].x=me.target.xDep;
			_tbFormes[i].y=me.target.yDep;
			if (valid()) {
--> Ici			  txtSortie.text="OUI !";
			}
			return;
		}
[]

Un inventaire


Attention !
Il s'agit ici de s'entrainer à l'emploi des méthodes hitTest… et du glisser-lâcher. Je n'affirme en rien qu'il faille gérer des inventaires en y trainant des objets ;)


L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.
• On glisse les formes de la base vers l'inventaire, elles s'y rangent les unes à la suite des autres (notez que les formes ont des dimensions différentes).
• Si lâchée hors de l'inventaire la forme revient à sa place d'origine.
• Un simple clic sur les formes de l'inventaire les renvoie à la base.
• Les formes restantes sont redisposées les unes à la suite des autres (pas de “trou”).

Suggestions


• Dans un premier temps, ne vous préoccupez pas du changement de couleur quand on saisit un objet. C'est un détail qui n'a pas à voir avec le sujet premier du tuto. Disons que c'est un petit plus que vous réglerez en une ou deux lignes, pour faire bô à la fin, soit à l'aide d'un gotoAndStop, soit à l'aide de l'objet ColorTransform.

• Quand on lâche un objet en dehors de l'inventaire, il revient à ses coordonnées d'origines. Il faudra donc les mémoriser, le plus simple est d'utiliser la caractéristique dynamique de la classe MovieClip.

• Pour gérer la disposition des objets dans l'inventaire on pourrait être tenté de parcourir le contenu de l'instance concernée à coup de getChild…. Il est préférable d'avoir recours à un tableau : c'est plus facile/souple et moins gourmand qu'un getChildAt et infiniment mieux qu'un getChildByName qui n'est à utiliser qu'avec parcimonie, c'est la doc elle même qui l'affirme.

Ça marchait ça marche plus !


Ronchon a pris la (bonne !) habitude de procéder par étapes en testant en cours de route. Il avait donc brillamment franchit la première étape : glisser un objet de la base vers l'inventaire et se débrouiller des dimensions différentes :

mcBase.addEventListener(MouseEvent.MOUSE_DOWN,qdEnfonceBase,true);
stage.addEventListener(MouseEvent.MOUSE_UP,qdLache);
 
var clip_c:MovieClip;
var tbInventaire:Array=new Array();
 
function qdEnfonceBase(me:MouseEvent):void {
	clip_c = MovieClip(me.target);
	clip_c.startDrag();
	clip_c.xDep = me.target.x;
	clip_c.yDep = me.target.y;
}
 
function qdLache(me:MouseEvent):void {
	if (! clip_c) {
		return;
	}
 
	stopDrag();
	if (clip_c.hitTestObject(mcInventaire)) {
		ajouteInventaire();
	} else {
		clip_c.x = clip_c.xDep;
		clip_c.y = clip_c.yDep;
	}
}
 
 
function ajouteInventaire() {
	var max:int = tbInventaire.length;
	var posX:Number = 10;
	for (var i:int; i<max; i++) {
		posX +=  tbInventaire[i].width + 10;
	}
	mcInventaire.addChild(clip_c);
	clip_c.x = posX + clip_c.width / 2;
	clip_c.y = 0;
 
	tbInventaire.push(clip_c);
}

Pour coder le retour de l'inventaire à la base il souscrit un écouteur au clip lâché dans l'inventaire :

function ajouteInventaire() {
	var max:int = tbInventaire.length;
	var posX:Number = 10;
	for (var i:int; i<max; i++) {
		posX +=  tbInventaire[i].width + 10;
	}
	mcInventaire.addChild(clip_c);
	clip_c.x = posX + clip_c.width / 2;
	clip_c.y = 0;
// --------> ici, Ronchon souscrit un écouteur au clip courant
	clip_c.addEventListener(MouseEvent.CLICK,qdClicForme);
 
	tbInventaire.push(clip_c);
}
 
function qdClicForme(me:MouseEvent):void {
// Magnifique code dont il est si fier 
// qui renvoie le clip cliqué à la base
// […]
 
}


Et voilà, triple zut, que ça ne fonctionne plus !
Plus moyens de lâcher les formes sur l'inventaire, elle reviennent à la base O.O


Passer en commentaire la ligne qui souscrit l'écouteur au clip courant, suffit à rétablir le bon fonctionnement des choses…

C'est quoi l'embrouille ?!
Quelques trace judicieusement glissés ou quelques lignes de test synthétiques devraient vous suffire à élucider la chose :

leClip.addEventListener(MouseEvent.CLICK,clic)
leClip.addEventListener(MouseEvent.MOUSE_DOWN,down)
leClip.addEventListener(MouseEvent.MOUSE_UP,up)
stage.addEventListener(MouseEvent.MOUSE_UP,up)
 
function clic (me:MouseEvent) {
	trace("clic")
}
function down (me:MouseEvent) {
	trace("down")
}
function up (me:MouseEvent) {
	trace("up écouté sur "+me.currentTarget)
}
down
up écouté sur [object MovieClip]
up écouté sur [object Stage]
clic

Première conclusion sans surprise les événements “clic” sont entendus après les événements “up” (down puis up puis clic)

autre test :

var leClip:Mv_Demo=new Mv_Demo();
addChild(leClip);
 
stage.addEventListener(MouseEvent.MOUSE_UP,clicScene);
function clicScene(me:MouseEvent):void {
	trace("clicScene");
	leClip.addEventListener(MouseEvent.MOUSE_UP,qdUp);
	leClip.addEventListener(MouseEvent.CLICK,qdClic);
}
 
function qdClic(me:MouseEvent):void {
	trace("qd clic clip ");
}
function qdUp(me:MouseEvent):void {
	trace("up sur clip");
}

cliquer sur le clip - la première fois - donne ça :

clicScene
qd clic clip

les fois suivantes :

up sur clip
clicScene
qd clic clip

Le MOUSE_UP du clip est entendu avant les événements de la scène4). Le CLICK est entendu après.

Voilà pourquoi, dans le code de ronchon, écouter le MouseEvent.CLICK est une mauvaise idée puisqu'il est aussitôt entendu et fait son job : il renvoie l'instance considérée à la base.

Il suffit de souscrire un écouteur au MOUSE_UP pour régler le problème. \o/


Code complet :

mcBase.addEventListener(MouseEvent.MOUSE_DOWN,qdEnfonceBase,true);
stage.addEventListener(MouseEvent.MOUSE_UP,qdLache);
 
var _clip_c:MovieClip;
var _tbInventaire:Array=new Array();
var _coulVert:ColorTransform= new ColorTransform();
_coulVert.color = 0x00FF00;// on précise une couleur 
 
 
function qdEnfonceBase(me:MouseEvent):void {
	_clip_c = MovieClip(me.target);
	_clip_c.startDrag();
	_clip_c.xDep = me.target.x;
	_clip_c.yDep = me.target.y;
	// on applique la couleur
	_clip_c.transform.colorTransform = _coulVert;
}
 
function qdLache(me:MouseEvent):void {
	trace("lache");
	if (! _clip_c) {
		return;
	}
 
	stopDrag();
	if (_clip_c.hitTestObject(mcInventaire)) {
		ajouteInventaire();
	} else {
		_clip_c.x = _clip_c.xDep;
		_clip_c.y = _clip_c.yDep;
	}
	//sans couleur
	_clip_c.transform.colorTransform =new ColorTransform();
 
	_clip_c = null;
}
 
 
function ajouteInventaire() {
	trace("ajoute");
	var max:int = _tbInventaire.length;
	var posX:Number = 10;
	for (var i:int; i<max; i++) {
		posX +=  _tbInventaire[i].width + 10;
	}
	mcInventaire.addChild(_clip_c);
	_clip_c.x = posX + _clip_c.width / 2;
	_clip_c.y = 0;
	_clip_c.addEventListener(MouseEvent.MOUSE_UP,qdClicForme);
	_tbInventaire.push(_clip_c);
}
 
 
function qdClicForme(me:MouseEvent):void {
	trace("qdClicForme "+me.target);
	_clip_c = MovieClip(me.target);
	mcBase.addChild(_clip_c);
	_clip_c.x = _clip_c.xDep;
	_clip_c.y = _clip_c.yDep;
	_clip_c.removeEventListener(MouseEvent.MOUSE_UP,qdClicForme);
 
	_tbInventaire.splice(_tbInventaire.indexOf(_clip_c),1);
	var max:int = _tbInventaire.length;
	var posX:Number = 0;
	for (var i:int; i<max; i++) {
		_tbInventaire[i].x = posX + 10 + _tbInventaire[i].width / 2;
		posX +=  _tbInventaire[i].width + 10;
		trace(posX);
	}
 
}



Le sapin - étape 1 : Déplacer les objets à l'intérieur

Commençons par déplacer des instances déjà accrochées au sapin.

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

L'idée c'est de pouvoir accrocher jusqu'aux pointes des branches, mais pas dans le vide. Il va donc s'agir d'utiliser un hitTestPoint sans quoi à cause de ce fichu cadre de sélection (surface d'encombrement) on ne pourra pas “distinguer” les branches dessinées de la surface du clip.

On voudrait aussi pouvoir ajouter des éléments les uns aux autres. Ce qui implique que chaque boule accrochée fasse partie du clip sapin (en soit un enfant). On va donc disposer les boules non pas sur la racine par dessus le clip sapin, mais bien dans le clip lui-même.


Et puis il y a le visuel
Déplacer un clip par glissé lâché, on sait faire : startDrag et stopDrag.
Ce qu'on ne sait pas faire, c'est appliquer startDrag à deux clips à la fois. D'ailleurs personne ne sait faire : on ne peut pas. Quand on applique la méthode startDrag, un stopDrag est automatiquement invoqué.
Il va falloir ruser en utilisant l'événement MouseMove pour suivre le mouvement du pointeur ; mais c'est pas le plus pressé.

Le principe en français :

• Quand on saisit un objet dans le sapin, il est déplacé sur la racine (liste d'affichage) ainsi, ses coordonnées sont celles à tester (trop facile).
• Quand on lâche si c'est bon, on remet l'objet dans le clip sapin, sinon on le renvoie à sa position d'origine (penser à mémoriser les coordonnées quand clic).
• En cours de déplacement un visuel ('non' dans les sources) suit le clip, il s'affiche (visible) selon que le test de superposition renvoie vrai ou faux (je le formule comme ça pour que vous évitiez un if inutile ;) ).


A vous de jouer !


stage.addEventListener(MouseEvent.MOUSE_UP,qdLache);
sapin.addEventListener(MouseEvent.MOUSE_DOWN, qdEnfonce,true);
 
var _clip_c:MovieClip;// le clip  en cours de déplacement
 
function qdEnfonce(me:MouseEvent) {
	_clip_c = MovieClip(me.target);
	_clip_c.xDep = _clip_c.x;// mémoriser ses coordonnées d'origine pour retour éventuel
	_clip_c.yDep = _clip_c.y;
	addChild(_clip_c);// sur la racine : ça évitera de convertir les coordonnées pour le hitTest
	addChild(non);//par dessus le visuel "interdit"
	// rétablir les coordonnées
	_clip_c.x = mouseX 
	_clip_c.y = mouseY
 
	_clip_c.startDrag();
// un visuel "dépot interdit" suivra le pointeur le clip déplacé
	stage.addEventListener(MouseEvent.MOUSE_MOVE,qdBouge);
}
 
 
function qdBouge(me:MouseEvent):void {
	non.x = mouseX;
	non.y = mouseY;
	non.visible = ! sapin.hitTestPoint(_clip_c.x,_clip_c.y,true);
 
}
 
function qdLache(me:MouseEvent):void {
	if (! _clip_c) {
// Up est écouté sur la scène, pourrait être diffusé par un clic intempestif
		return;
	}
 
	stage.removeEventListener(MouseEvent.MOUSE_MOVE,qdBouge);
	stopDrag();
	non.visible = false;
	if (sapin.hitTestPoint(_clip_c.x,_clip_c.y,true)) {
// on va ajouter l'objet au sapin, repérons les coordonées, ce sont celles du pointeur sur le clip sapin
		_clip_c.x = sapin.mouseX 
		_clip_c.y = sapin.mouseY
		//trace("oui");
	} else {
		//trace("non");
//lâché ds le vide, on replace à position d'origine
		_clip_c.x = _clip_c.xDep;
		_clip_c.y = _clip_c.yDep;
	}
	sapin.addChild(_clip_c);
	_clip_c = null;
 
}



Et c'est là que Ronchon ronchonne. Ça ne lui plait pas qu'on déplace les objets en les “tenant” par leur origine. Il veut qu'ils suivent le pointeur depuis le point de clic, comme ici :

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

Or il a beau passer false en toutes lettres à startDrag,

_clip_c.startDrag(false);


rien n'y fait !


C'est parce qu'on modifie les coordonnées du clip après l'avoir ajouté à la liste d'affichage pour que, visuellement, il reste en place

         addChild(_clip_c);// sur la racine : ça évitera de convertir les coordonnées pour le hitTest
// rétablir les coordonnées
	_clip_c.x = mouseX 
	_clip_c.y = mouseY



Il suffit de repérer la position du pointeur sur le clip lors du clic et de répercuter le décalage.

function qdEnfonce(me:MouseEvent) {
	_clip_c = MovieClip(me.target);
	// *-*-*-*-*-*- Repérer la position du pointeur
	_clip_c.xClic = _clip_c.mouseX;// mémoriser le déclage
	_clip_c.yClic = _clip_c.mouseY;
	// *-*-*-*-*-*-*--*-*--*-*-*-*-*-*-*-*-*-*-*--*-*
 
	_clip_c.xDep = _clip_c.x;
	_clip_c.yDep = _clip_c.y;
	addChild(_clip_c);// sur la racine : ça évitera de converir les coordonnées pour le hitTest
	addChild(non);//par dessus
	// rétablir les coordonnées
//  avant : 	_clip_c.x = mouseX;
//	        _clip_c.y = mouseY;
	// *-*-*-*-*-*- répercuter
	_clip_c.x = mouseX - _clip_c.xClic;
	_clip_c.y = mouseY - _clip_c.yClic;
	// *-*-*-*-*-*-*--*-*--*-*-*-*-*-*-*-*-*-*-*--*-*
       []

function qdLache(me:MouseEvent):void {
	[]
	if (sapin.hitTestPoint(_clip_c.x,_clip_c.y,true)) {
//		_clip_c.x = sapin.mouseX;
//		_clip_c.y = sapin.mouseY;
		// *-*-*-*-*-*- en conservant le point de saisie là où on a cliqué
		_clip_c.x = sapin.mouseX - _clip_c.xClic;
		_clip_c.y = sapin.mouseY - _clip_c.yClic;
		// *-*-*-*-*-*-*--*-*--*-*-*-*-*-*-*-*-*-*-*--*-*
       []

Le sapin - étape 2 : Ajouter des objets

Ce qui nous intéresse ici c'est :
• dupliquer l'instance sur laquelle on enfonce la souris,
• déplacer la nouvelle instance en la trainant (drag),
• l'ajouter au sapin ou la supprimer selon qu'on lâche ou non sur le sapin,
• faire en sorte de rendre “supprimables” les instance ajoutées, par Maj-Clic.

Dupliquer

On a vu le principe dans le tuto 'Ajouter et supprimer des clips dynamiquement' au chapitre 'dupliquer'.
Je n'ai pas plus à en dire ;)
… Si, peut être une chose :
Autant écouter le MouseDown sur le clip inventaire (on pourra ainsi ajouter autant d'objet qu'on le souhaite sans modifier le code) mais on veut aussi pouvoir déplacer l'inventaire par glissé-lâché. Il faut donc différencier à l'écoute, le fait d'avoir enfoncé la souris sur l'inventaire (le déplacer) ou sur un de ses enfants (le dupliquer). Une solution c'est eventPhase.

function qdEnfonceInventaire(me:MouseEvent):void {
	if (me.eventPhase == 2) {
		inventaire.startDrag();
	} else {
		var leClip:MovieClip = MovieClip(me.target);// le modèle
		var nomClasse:String = getQualifiedClassName(leClip);// le nom de la classe en chaine
		var classRef:Class = getDefinitionByName(nomClasse) as Class;// l'objet Classe
		_clip_c= new classRef();
		_clip_c.origine = "inventaire";
		// supprimer sur clic
		_clip_c.addEventListener(MouseEvent.CLICK,supClip);
		qdEnfonce();
	}
}

Déplacer l'objet dupliqué

Ah ah ! Voilà qui va aller vite : c'est déjà fait - ou presque :)
C'est la fonction de rappel qdEnfonce qui s'en charge. C'est la version qui ne se complique pas du code, que je me propose de modifier, celle-ci :

function qdEnfonce(me:MouseEvent) {
	_clip_c = MovieClip(me.target);
	_clip_c.xDep = _clip_c.x;// mémoriser ses coordonnées d'origine pour retour éventuel
	_clip_c.yDep = _clip_c.y;
	addChild(_clip_c);// sur la racine : ça évitera de convertir les coordonnées pour le hitTest
	addChild(non);//par dessus le visuel "interdit"
	// rétablir les coordonnées
	_clip_c.x = mouseX 
	_clip_c.y = mouseY
 
	_clip_c.startDrag();
// un visuel "dépot interdit" suivra le pointeur le clip déplacé
	stage.addEventListener(MouseEvent.MOUSE_MOVE,qdBouge);
}

Un écueil : erreur 1136

Après avoir dupliqué le clip on invoque qdEnfonce tout bonnement comme suit :

qdEnfonce();


…et l'erreur suivante est levée :

1136 : Nombre d'arguments incorrect. 1 attendus.

Eh oui, c'est une fonction de rappel qui attend donc un paramètre de type MouseEvent :

function qdEnfonce(me:MouseEvent) {

La solution c'est de décider d'une valeur par défaut, null. Ainsi, quand on ne passe rien, on passe null ;)

function qdEnfonce(me:MouseEvent=null):void {

Ne reste plus qu'à traiter ce cas spécifique.
Aux choix, soit on teste si me existe (if(me)) et c'est un clip dans le sapin sur lequel on a cliqué) ; même chose si clip courant n'est pas null (if (! _clip_c))

Anticipons le fait de lâcher l'objet en cours de déplacement en dehors du sapin. La conséquence sera différente selon que l'objet proviendra de l'inventaire ou du sapin. Je propose d'utiliser la caractéristique dynamique de la classe MovieClip pour créer à la volée une variable origine qui vaudra “inventaire” ou “sapin”.

function qdEnfonce(me:MouseEvent=null):void {
	// if (! _clip_c) {
	if (me) {
		// diffusé par le clip (dansle sapin)
		_clip_c = MovieClip(me.target);
		_clip_c.addEventListener(MouseEvent.CLICK,supClip);
		_clip_c.origine = "sapin";
	}
 
	// le clip vient de l'inventaire, _clip_c est déjà valorisé
	_clip_c.xDep = _clip_c.x;
	_clip_c.yDep = _clip_c.y;
	addChild(_clip_c);// sur la racine : mêmes repères que sapin
	addChild(non);//par dessus
	// rétablir les coordonnées
	_clip_c.x = mouseX;
	_clip_c.y = mouseY;
 
	_clip_c.startDrag();
	stage.addEventListener(MouseEvent.MOUSE_MOVE,qdBouge);
}
function qdLache(me:MouseEvent):void {
	stopDrag();
	if (! _clip_c) {
		//clic sur scène, "dans le vide" 
		return;
	}
 
	stage.removeEventListener(MouseEvent.MOUSE_MOVE,qdBouge);
	non.visible = false;
 
	// sur sapin
	if (sapin.hitTestPoint(_clip_c.x,_clip_c.y,true)) {
		_clip_c.x = sapin.mouseX;
		_clip_c.y = sapin.mouseY;
		sapin.addChild(_clip_c);
		trace("oui");
		_clip_c = null;
		return;
	}
	// pas sur sapin
	if (_clip_c.origine == "inventaire") {
// LA modification -----------------------------------
		removeChild(_clip_c);// on supprime
	} else {
		// position initiale
		_clip_c.x = _clip_c.xDep;
		_clip_c.y = _clip_c.yDep;
		sapin.addChild(_clip_c);
	}
 
	_clip_c = null;
 
}



rotation au clavier

Rien à voir avec le sujet du tuto, c'est pour le plaisir de faire des guirlandes, je vous renvoie donc au tuto de Billy _o_

stage.addEventListener(KeyboardEvent.KEY_UP, qdToucheClavier);
function qdToucheClavier(ke:KeyboardEvent):void {
	//trace("une touche a été relâchées - keyCode="+ke.keyCode+"  - charCode="+ke.charCode);
	if (! _clip_c) {
		//clic sur scène, "dans le vide" ou laché de l'inventaire après déplacement
		return;
	}
	var sens:int;
	switch (ke.keyCode) {
		case 65 :
			_clip_c.rotation +=  45;
			break;
		case 90 :
			_clip_c.rotation -=  45;
 
	}
 
}


Conclusion : des vertus comparées du hitTest et des calculs

Tous les exercice de cette page utilisent les méthodes hitTest…, et pour cause c'est le sujet du tuto :mrgreen: Il n'en reste pas moins que ce n'est pas toujours le meilleur réflexe.

Quand on travaille sur une grille, par exemple, on peut procéder par calcul. L'exercice d'interversion tel qu'il était proposé à l'origine : quatre clips sur deux lignes, pouvait être résolu sans recours aux tests de superposition. Monsieur Spi propose une solution toute mathématique ici.

Il souligne aussi que si on utilise le hitTestPoint sans troisième paramétre, s'il s'agit donc seulement de savoir si un point est dans un rectangle (le cadre de sélection du clip au hasard) alors la formule mathématique suivante fait le même boulot :


Ca parrait annecdotique mais en fait c'est pratique si on veux faire varier la détection (décalage vis à vis de la surface par exemple).

if (A.x<B.x+B.width && A.x>B.x && A.y<B.y+B.height && A.y>B.y)


5) A : le point, B la surface.

Et tant qu'à faire, rappelons que la méthode getBounds renvoie un rectangle aux coordonnée du cadre de sélection (zone d'encombrement) d'un objet d'affichage ;)

Les sources

1) voire carrée :-P
2) celle qui supprime les enfants du contenant considéré
3) et j'ai fait exprès de ne pas mettre d'ombre portée sur l'exemple
4) MOUSE_UP et CLICK
5) Où A vaut… 8-o Le félon ! Il aurait pu préciser