Está en la página 1de 8

Detección y manejo de colisiones en Haxeflixel 

En  Haxeflixel,  para  detectar  si  dos  entidades  colisionan  se  utilizan  las  funciones  ​overlap()  o 
collide()​, ambas son accesibles a través de la clase ​FlxG​. 
 
Tanto  ​overlap()  como  ​collide()  se  utilizan  de  la  misma  manera:  reciben  como  argumentos  las 
dos  entidades  para  las  cuales  necesitamos  saber  si  existe  colisión  y  una  función  callback  que 
será invocada ante un resultado positivo. 
 
class​ ​PlayState​ ​extends​ ​FlxState
{
​var​ sprite1​:​ ​FlxSprite;
​var​ sprite2​:​ ​FlxSprite;

​function​ onCollision​(​sp1​:​ ​FlxSprite​,​ sp2​:​ ​FlxSprite​){


​// codificar aquí lo que pasa cuando sprite1 (sp1)
​// colisiona con sprite2 (sp2)
}

​override​ ​public​ ​function​ update​(​elapsed​:​ ​Float​):​ ​Void


{
​super​.​update​(​elapsed​);
​FlxG​.​collide​(​sprite1​,​ sprite2​,​ onCollision​);
}

 
En  el  ejemplo,  cuando  sprite1  y  sprite2 se solapen se llamará a la función callback ​onCollision() 
definida  dentro  de  la  escena.  La  función  recibirá  como  argumentos  ambos  sprites  que 
chocaron (más adelante veremos la utilidad de recibirlos como argumentos). 
 
Las  llamadas  a  ​overlap()​/c
​ ollide()  deben realizarse (generalmente) dentro de ​update() ya que es 
necesario comprobar colisiones en cada nueva actualización. 
 
Tanto  ​collide()  como  ​overlap()  funcionan  de  la  misma  manera,  la  única  diferencia  es  que  la 
primera,  al  detectar  una  colisión,  además  de  invocar  a  la  función  callback  separa  los  objetos 
entre  sí  (invoca a ​FlxObject.separate() para hacerlo). Entonces, ​collide() resulta útil, por ejemplo, 
cuando  por  ejemplo  queremos  que  un  personaje  choque  con  las  paredes  (nos  interesa 
separarlo  de  la  pared  para  que  no  quede  “metido”  dentro  de  ella).  Por  otro  lado,  ​overlap()  es 
adecuado,  por  ejemplo,  cuando  por  ejemplo  un  proyectil  impacta  contra  un  enemigo.  En  este 
caso  no  nos  interesa  corregir las posiciones, ya que seguramente eliminaremos al proyectil o al 
enemigo. 

   
Propiedades físicas de los objetos 
Al  igual  que  existen  propiedades  para  modificar  la  posición  y  movimiento  y  de  un  sprite 
(velocity,  acceleration,  drag,  etc).  Existen  también  otras  propiedades  que  determinan  el 
comportamiento  del  objeto  ante  las  colisiones.  En  la  siguiente  tabla  se  muestran  y  explican 
algunas de esas propiedades.  
 
Propiedad  Tipo  Descripción 

width  Float  Ancho de la caja de colisión (AABB) del objeto (notar que el 
tamaño de la caja de colisión puede no coincidir con el 
tamaño del sprite). 

height  Float  Alto de la caja de colisión (AABB) del objeto (notar que el 
tamaño de la caja de colisión puede no coincidir con el 
tamaño del sprite). 

offset  FlxPoint  Desplazamiento entre la posición de la caja de colisión 


(AABB) del objeto y el sprite 

allowCollisions  Int  Indica sobre cuáles de los flancos es posible colisionar con 
el objeto. Los valores posibles son: ANY, CEILING, DOWN, 
FLOOR, LEFT, NONE, RIGHT (definidos en F ​ lxObject​). Los 
valores se pueden combinar con el operador |. Por ejemplo, 
la siguiente línea hará que sprite1 sólo pueda colisionar con 
otros objetos por la derecha y por arriba: 
 
sprite1​.​allowCollisions ​=​ ​FlxObject​.​CEILING ​|
FlxObject​.​RIGHT; 

 
Resulta útil, por ejemplo, para definir plataformas con las 
que se puede colisionar sólo desde arriba. 

immovable  Bool  Si el objeto es o no estático. Por defecto es False. Si es 


verdadero el objeto es inamovible, es decir, su posición, 
velocidad, etc no es afectada por las colisiones. Esto es útil, 
por ejemplo, para las paredes.  

solid  Bool  Si el objeto puede o no colisionar con otros. Por defecto es 
True. Si su valor es falso las colisiones no son procesadas 
para este objeto. 

mass  Float  Masa virtual del objeto. El valor por defecto es 1. Es 
utilizado en conjunto con elasticity en la resolución de 
colisiones. 

elasticity  Float  La elasticidad del objeto. Sólo afecta la resolución de 


colisiones, no el movimiento. El valor por defecto es 0, que 
significa 100% de rigidez (no hay elasticidad). 
Grupos 
Un  grupo  es  un  tipo  específico  de  entidad  que  puede  contener  a  otras  entidades.  Los  grupos 
permiten  organizar  jerárquicamente  la  escena  y  agrupar  objetos  relacionados  entre  sí. 
Podemos  pensar  en  un  grupo  como  un actor que es una especie de arreglo o lista que contiene 
a otros actores. 
 
Los  objetos  de  tipo  grupo  no  tienen  propiedades  como  posición  o  velocidad,  sino  que  esas 
propiedades  son  de  cada  uno  de  sus  miembros. Actualizar un grupo implica recorrer todos sus 
miembros y actualizarlos uno por uno. Los mismo ocurre con el dibujado. 
 
Los  objetos  se  pueden  agregar  a  un  grupo  de  la  misma  forma  que  a  la  escena.  A  su  vez,  el 
grupo  puede  agregarse  a  la  escena  y  todos  sus  miembros  serán  (indirectamente)  también 
agregados a la escena. 
 
Por  ejemplo,  el  siguiente  ejemplo  crea  un  grupo  para  agrupar  tres  sprites,  agrega  los  sprites al 
grupo  y  luego  el  grupo  a  la  escena. Al estar agregados a la escena, los sprites se actualizarán y 
dibujarán automáticamente. 
 
wall1 ​=​ ​new​ ​FlxSprite​();
wall2 ​=​ ​new​ ​FlxSprite​();
wall3 ​=​ ​new​ ​FlxSprite​();
var​ walls ​=​ ​new​ ​FlxGroup​();​ ​// crear grupo
walls​.​add​(​wall1​);
// agregar entidades al grupo
walls​.​add​(​wall2​);
walls​.​add​(​wall3​);
add​(​walls​); 

 
Un grupo puede incluso contener a otros grupos, aunque es mejor, siempre que sea posible, que 
un  grupo  contenga  objetos  de  una  misma  clase (puede ayudar a simplificar algunas cosas). Un 
objeto puede estar en más de un grupo a la vez (aunque se debe tener cuidado de no agregar un 
objeto  más  de  una  vez  a  la escena, ya que se actualizará y dibujará varias veces) y no todos los 
grupos  tienen  que  ser  agregados  a  la  escena  (algunos  solo  se  arman  para  simplificar  la 
detección de colisiones) 

Algunas operaciones sobre grupos 


Una de las ventajas de utilziar grupos es poder dar un tratamiento unificado a un conjunto de 
objetos que comparten las mismas caracterísicas. 
Se pueden realizar varias operaciones sobre un grupo, como por ejemplo ​obtener la cantidad de 
elementos vivos​. 
Es importante recalcar que un grupo no tiene posición, velocidad o una imagen asociada como 
un sprite, sino que es, más bien, como un arreglo o lista de elementos. 
 
Una  operación bastante común es recorrer el grupo y realizar una misma operación sobre todos 
sus  miembros.  Una  manera  de  hacer  ésto  es  utilizando  la  función  miembro  ​forEach()​.  La 
función  recibe  como  argumento  una  función  callback  que  será  invocada  para  cada  objeto 
dentro  del  grupo.  La  función  callback  recibirá  como  argumento  el  elemento  del  grupo  sobre  el 
cual se debe realizar la operación. Por ejemplo: 
 
var​ ​group​ ​=​ ​new​ ​FlxGroup​();
group​.​forEach​(​function​(​obj​:​ ​FlxBasic​){
var​ enemy ​=​ cast​(​obj​,​ ​Enemy​);
enemy​.​getDamagePoints​();
}) 

 
En  el  código  anterior,  se  invoca  a  ​forEach()  pasando  como  argumento  una  función  callback 
inline  o  anónima.  Esa  función  anónima  se  ejecutará  una  vez  por  cada  elemento  del  grupo.  En 
cada  ejecución  recibirá  como  argumento  uno  de  los  elementos  del  grupo.  Es  necesario  notar 
que  ​FlxGroup  almacena  los  elementos  que  agrupa  como  referencias al tipo ​FlxBasic (el tipo de 
objeto  más  básico  que  puede  existir  dentro  de  una  escena).  Es  bastante  probable  que  lo  que 
almacenemos  dentro  de  un  grupo  no  sean  objetos  de tipo ​FlxBasic​, sino de algún tipo derivado 
como  ​FlxSprite  o  un  tipo  propio  (en  el  caso  del  ejemplo  anterior,  el  tipo  ​Enemy​,  definido  por 
nosotros).  Si  necesitamos  invocar  a  alguna  función  miembro  que  se  encuentre  definida  en  la 
clase  del  objeto  (en  éste  caso  la  función  ​getDamagePoints()  se  encuentra  en  ​Enemy  no  en 
FlxBasic​)  será  necesario  convertir  la referencia de ​FlxBasic al tipo correspondiente utilizando la 
orden ​cast()​. 
 
Profundizaremos  más  adelante  estas  cuestiones  relacionadas  con  clases  propias  y 
conversiones  de  tipos.  Por  ahora  sólo  es  importante  saber  que,  entre  otras  cosas,  existe  una 
función para recorrer los miembros de un grupo. 

Manejo de colisiones con grupos 


Se  explicó  anteriormente  que  ​overlap()  y  ​collide()  reciben  como  argumentos  dos  objetos  para 
los  cuales  se  desea  saber  si  existe  colisión  y  una  función  callback  que  será  invocada  ante  un 
resultado positivo. 
 
Tanto ​overlap()​ y ​collide()​ como collide permiten trabajar con grupos, permitiendo recibir dos 
objetos (o FlxSprite), un objeto y un grupo, o dos grupos. Por ejemplo, en el siguiente código la 
función recibirá un ​FlxSprite​ (hero) y un ​FlxGroup​ (walls): 
   
 
function​ onHeroWalls​(​obj1​:​ ​FlxObject​,​ obj2​:​ ​FlxObject​){
// escribir aquí el código que
// se ejecutará cuando haya colision
}

function​ update​(​elapsed​:​ ​Float​){


​super​.​update​(​elapsed​);
​FlxG​.​overlap​(​hero​,​ walls​,​ onHeroWalls​);

 
Nuevamente,  no  se  puede  colisionar contra un grupo, sino contra algunos de sus elementos. La 
función  callback  siempre  recibirá  los  objetos  que  colisionaron  (y  no  el  grupo).  Si  existiera  una 
colisión  simultánea  entre  ​hero  ​y  más  de  un  objeto  del  grupo  ​walls  a  la  vez,  la  función 
onHeroWalls()​ será invocada varias veces, por cada par de objetos que colisionan. 
 
Entonces,  la  manera  más  eficiente  de  manejar  colisiones  es  agrupar  objetos (se pueden incluir 
grupos  dentro  de  otros  grupos)  de  manera  de  realizar  la  menor  cantidad  de  llamadas  a 
collide()​/​overlap()​.  El  siguiente  ejemplo  muestra  cómo  agrupar  una  serie  de  elementos  que 
pueden colisionar con el personaje principal. 
 
// crear al personaje
var​ hero ​=​ ​new​ ​FlxSprite​();

// crear el grupo para los items


var​ items ​=​ ​new​ ​FlxGroup​();

// crear los items


var​ item1 ​=​ ​new​ ​Item​();
var​ item2 ​=​ ​new​ ​Item​();
var​ item3 ​=​ ​new​ ​Item​();

// agregar los items al grupo


items​.​add​(​item1​);
items​.​add​(​item2​);
items​.​add​(​item3​);

// agregar el grupo de items a la escena


// (todos los items serán agregados a la escena)
add​(​items​);

// crear el grupo para las paredes


var​ walls ​=​ ​new​ ​FlxGroup​();

// crear las paredes


var​ w1 ​=​ ​new​ ​Wall​();
var​ w2 ​=​ ​new​ ​Wall​();
var​ w3 ​=​ ​new​ ​Wall​();

// agregar las paredes al grupo


walls​.​add​(​w1​);
walls​.​add​(​w2​);
walls​.​add​(​w3​);

// agregar el grupo de paredes a la escena


// (todas las paredes serán agregados a la escena)
add​(​walls​);

// crear un supergrupo con todas las cosas


// con las que el personaje puede colisionar
var​ heroCollideables ​=​ ​new​ ​FlxGroup​();

// agregar los grupos de paredes e items al supergrupo


// (no es necesario agregar al supergrupo a la escena,
// ya que todos sus elementos fueron agregados)
heroCollideables​.​add​(​items​);
heroCollideables​.​add​(​walls​);

// comprobar colisiones
FlxG​.​overlap​(​hero​,​ heroCollideables​,​ onHeroCollide​); 

 
En  el  ejemplo,  la  función  ​overlap()  se invoca una única vez con el personaje y un grupo que a su 
vez  contiene  otros  grupos  que  contienen  todas  las  cosas  con  las  que  el  personaje  puede 
colisionar. 
 
La  función  ​onHeroCollide()  será  invocada  con  el  personaje  (hero)  y  el  elemento  del  grupo 
heroCollideables  que  haya  chocado  con  él.  Si  bien  esto  resulta  muy  eficiente  surge  otro 
problema:  la  función ​onHeroCollide() recibirá como segundo parámetro un objeto que puede ser 
un  item  o  una  pared.  Como claramente el manejo de la colisión es muy distinto para cada caso, 
es  necesario  disponer  de  alguna  forma  de  averiguar  el  tipo  del  objeto.  Esto  puede  lograrse 
mediante la función g ​ etClass()​ como muestra el siguiente ejemplo: 
 
function​ onHeroCollide​(​hero​:​ ​Hero​,​ ​object​:​ ​FlxObject​){
if​(​Type​.​getClass​(​object​)​ ​==​ ​Wall​){
FlxObject​.​separate​(​hero​,​ ​object​);
}​else​ ​if​(​Type​.​getClass​(​object​)​ ​==​ ​Item​){
var​ item​:​ ​Item​ ​=​ cast​(​object​,​ ​Item​);
points ​=​ points ​+​ item​.​getPoints​();
item​.​kill​();
}

 
En  primer  lugar,  cabe  mencionar  que  siempre  conviene  crear  una  clase  específica  para  cada 
tipo  de  entidad  del  juego  (ya  veremos  más  adelante  cómo  hacerlo).  De  esta  manera  no  sólo 
queda  más  ordenado  el  código,  sino  que  se  puede  utilizar  ​getClass()  para  distinguir  entre 
distintos tipos de objetos. 
Cuando  un  objeto  recibido  por  la  función  callback  puede  ser  de  varios  tipos  distintos  el 
parámetro  debería  ser  de  un  tipo más general (en este caso ​FlxObject​, ya que todos los objetos 
derivan de esa clase). 
Una  vez  dentro  de  la  función,  se  puede  utilizar  ​getClass()  para  saber  cuál  es  el  verdadero  tipo 
del  objeto  (el  objeto  hereda  de ​FlxObject pero su tipo verdadero es otro más específico, en este 
caso  ​Wall  o  ​Item​).  Si  necesitamos  invocar  a  alguna  función  específica  de  una  clase  será 
necesario  hacer  una  conversión  con  ​cast() (en este caso ​getPoints() pertenece a la clase ​Item y 
no  está  disponible  en  ​FlxObject​).  En  el  caso  de  que  el  objeto  sea  una  pared,  simplemente 
separamos al personaje de la misma invocando a s ​ eparate()​ con ambos objetos.  

   
Depurador visual 
Haxeflixel incorpora un potente depurador gráfico. El mismo puede abrirse utilizando las teclas 
F2 y ‘\’. Alternativamente, la interfaz del depurador se puede mostrar/ocultar desde código 
haciendo: 
 
FlxG​.​debugger​.​visible ​=​ ​true; 

 
 
Es  importante  notar  que,  para  que el depurador pueda habilitarse, es necesario que el comando 
utilizado para construir el ejecutable incorpore el flag ​-debug​. Por ejemplo: 
 
lime test neko -debug
 
El  depurador posee muchas funcionalidades. Una que nos resultará particularmente útil es la de 
visualizar  las  cajas  de  colisión de los objetos, la cual se puede activar presionando el botón con 
forma  de  cubo  ubicado  en la esquina superior derecha de la interfaz. Alternativamente, también 
se puede activar por código escribiendo: 
 
FlxG​.​debugger​.​drawDebug ​=​ ​true; 

 
 
 
 
Se puede consultar acerca de éstas y otras funcionalidades en la d
​ ocumentación de Haxeflixel​. 
 
 

Descargar el código 
completo del ejemplo 

Descargar el código 
completo del ejemplo 

 
 

También podría gustarte