Entendiendo los comportamientos de la dirección Cola

Imagine una escena de juego en la que una sala esté llena de entidades controladas por la inteligencia artificial. Por alguna razón, deben abandonar la habitación y pasar por una puerta. En lugar de hacer que caminen uno sobre el otro en un flujo caótico, enséñeles cómo irse cortésmente mientras hacen cola. Este tutorial presenta el cola Conducta de dirección con diferentes enfoques para hacer que una multitud se mueva mientras se forman filas de entidades..

Nota: Aunque este tutorial está escrito con AS3 y Flash, debería poder utilizar las mismas técnicas y conceptos en casi cualquier entorno de desarrollo de juegos. Debes tener una comprensión básica de vectores matemáticos.


Introducción

Hacer cola, En el contexto de este tutorial, es el proceso de pararse en línea, formando una fila de personajes que esperan pacientemente llegar a algún lugar. A medida que el primero en la línea se mueve, el resto lo sigue, creando un patrón que parece un tren tirando de vagones. Al esperar, un personaje nunca debe abandonar la línea..

Para ilustrar el comportamiento de la cola y mostrar las diferentes implementaciones, una demostración que muestre una "escena de colas" es la mejor manera de hacerlo. Un buen ejemplo es una habitación llena de entidades controladas por la IA, todas tratando de salir de la habitación y pasar por la puerta:


Boids saliendo de la habitación y pasando por la puerta sin el comportamiento de la cola. Haga clic para mostrar fuerzas.

Esta escena se realizó utilizando dos comportamientos descritos anteriormente: búsqueda y evitación de colisiones.

La puerta está hecha de dos obstáculos rectangulares colocados lado a lado con un espacio entre ellos (la puerta). Los personajes buscan un punto ubicado detrás de eso. Cuando están allí, los personajes se vuelven a colocar en la parte inferior de la pantalla.

En este momento, sin el comportamiento de la cola, la escena parece una horda de salvajes pisando las cabezas de los demás para llegar al destino. Cuando hayamos terminado, la multitud abandonará el lugar suavemente, formando filas.


Viendo adelante

La primera habilidad que un personaje debe obtener para hacer una fila es averiguar si hay alguien delante de ellos. Con base en esa información, puede decidir si continuar o dejar de moverse.

A pesar de la existencia de formas más sofisticadas de verificar a los vecinos por delante, usaré un método simplificado basado en la distancia entre un punto y un personaje. Este enfoque se utilizó en el comportamiento de evitación de colisiones para verificar si hay obstáculos por delante:


Prueba para vecinos usando el punto de adelante..

Un punto llamado adelante Se proyecta frente al personaje. Si la distancia entre ese punto y un carácter vecino es menor o igual que MAX_QUEUE_RADIUS, Significa que hay alguien adelante y el personaje debe dejar de moverse..

los adelante el punto se calcula de la siguiente manera (pseudo-código):

 // Tanto qa como adelante son vectores matemáticos qa = normalizar (velocidad) * MAX_QUEUE_AHEAD; adelante = qa + posición;

La velocidad, que también da la dirección del personaje, está normalizada y escalada por MAX_QUEUE_AHEAD para producir un nuevo vector llamado qa. Cuando qa se agrega a la posición vector, el resultado es un punto por delante del personaje, y una distancia de MAX_QUEUE_AHEAD unidades lejos de ella.

Todo esto puede ser envuelto en el getNeighborAhead () método:

 función privada getNeighborAhead (): Boid var i: int; var ret: Boid = nulo; var qa: Vector3D = velocity.clone (); qa.normalize (); qa.scaleBy (MAX_QUEUE_AHEAD); ahead = position.clone (). add (qa); para (i = 0; i < Game.instance.boids.length; i++)  var neighbor :Boid = Game.instance.boids[i]; var d :Number = distance(ahead, neighbor.position); if (neighbour != this && d <= MAX_QUEUE_RADIUS)  ret = neighbor; break;   return ret; 

El método verifica la distancia entre el adelante y todos los demás caracteres, devolviendo el primer carácter cuya distancia es menor o igual a MAX_QUEUE_AHEAD. Si no se encuentra ningún carácter, el método devuelve nulo.


Creación del método de espera

Como con todos los otros comportamientos, la fuerza de espera se calcula por un método llamado cola():

 función privada queue (): Vector3D var neighbor: Boid = getNeighborAhead (); if (neighbor! = null) // TODO: toma acción porque el vecino está adelante devuelve nuevo Vector3D (0, 0); 

El resultado de getNeighborAhead () en almacenado en la variable vecino. Si vecino! = nulo significa que hay alguien por delante; De lo contrario, el camino es claro..

los cola(), como todos los otros métodos de comportamiento, debe devolver una fuerza que es la fuerza de dirección relacionada con el método mismo. cola() devolverá una fuerza sin magnitud por ahora, por lo que no producirá efectos.

los actualizar() El método de todos los personajes en la escena de la puerta, hasta ahora, es (pseudo-código):

 actualización de la función pública (): void var doorway: Vector3D = getDoorwayPosition (); dirección = buscar (puerta) // buscar la dirección de la puerta = dirección + colisiónAvivir (); // evitar obstáculos dirección = dirección + cola (); // hacer cola en el camino direction = truncate (direction, MAX_FORCE); dirección = dirección / masa; velocidad = truncar (velocidad + dirección, MAX_SPEED); posición = posición + velocidad;

Ya que cola() devuelve una fuerza nula, los personajes continuarán moviéndose sin formar filas. Es hora de hacer que tomen alguna acción cuando se detecte un vecino justo delante..


Algunas palabras acerca de detener el movimiento

Los comportamientos de dirección se basan en fuerzas que cambian constantemente, por lo que todo el sistema se vuelve muy dinámico. Dependiendo de la implementación, cuantas más fuerzas estén involucradas, más difícil será identificar y cancelar un vector de fuerza específico..

La implementación utilizada en esta serie de comportamiento de dirección suma todas las fuerzas. Como consecuencia, para cancelar una fuerza, se debe volver a calcular, invertir y agregar nuevamente al vector de fuerza de dirección actual..

Eso es más o menos lo que sucede en el comportamiento de llegada, donde la velocidad se cancela para que el personaje deje de moverse. Pero, ¿qué sucede cuando hay más fuerzas actuando juntas, como evitar colisiones, huir y más??

Las siguientes secciones presentan dos ideas para hacer que un personaje deje de moverse. El primero utiliza un enfoque de "parada dura" que actúa directamente sobre el vector de velocidad, ignorando todas las demás fuerzas de dirección. El segundo usa un vector de fuerza, llamado freno, para cancelar con gracia todas las demás fuerzas de dirección, haciendo que el personaje deje de moverse.


Movimiento de parada: "Parada dura"

Varias fuerzas de dirección se basan en el vector de velocidad del personaje. Si ese vector cambia, todas las demás fuerzas se verán afectadas cuando se vuelvan a calcular. La idea de "parada difícil" es bastante simple: si hay un personaje adelante, "encogemos" el vector de velocidad:

 función privada queue (): Vector3D var neighbor: Boid = getNeighborAhead (); if (neighbor! = null) velocity.scaleBy (0.3);  devolver nuevo Vector3D (0, 0); 

En el código de arriba, el velocidad vector se escala a 30% de su magnitud actual (longitud) mientras un personaje está adelante. Como consecuencia, el movimiento se reduce drásticamente, pero eventualmente regresará a su magnitud normal cuando el personaje que está bloqueando el camino se mueva..

Es más fácil de entender al analizar cómo se calcula el movimiento. cada actualización:

 velocidad = truncar (velocidad + dirección, MAX_SPEED); posición = posición + velocidad;

Si el velocidad la fuerza sigue disminuyendo, también lo hace el gobierno fuerza, porque se basa en la velocidad fuerza. Crea un círculo vicioso que terminará con un valor extremadamente bajo para velocidad. Ahí es cuando el personaje deja de moverse..

Cuando finalice el proceso de reducción, cada actualización del juego aumentará el velocidad Vector un poco, afectando a la gobierno fuerza tambien Eventualmente varias actualizaciones después traerán ambas velocidad y gobierno vector de nuevo a sus magnitudes normales.

El enfoque de "parada dura" produce el siguiente resultado:


El comportamiento de la cola con el enfoque de "parada dura". Haga clic para mostrar fuerzas.

Aunque este resultado es bastante convincente, se siente como un resultado "robótico". Una multitud real generalmente no tiene espacios vacíos entre sus miembros..


Movimiento de parada: fuerza de frenado

El segundo enfoque para detener el movimiento intenta crear un resultado menos "robótico" al cancelar todas las fuerzas de dirección activas utilizando un freno fuerza:

 función privada queue (): Vector3D var v: Vector3D = velocity.clone (); var brake: Vector3D = nuevo Vector3D (); vecino var: Boid = getNeighborAhead (); si (vecino! = nulo) brake.x = -eringing.x * 0.8; brake.y = -eringing.y * 0.8; v.scaleBy (-1); brake = brake.add (v); freno de retorno; 

En lugar de crear el freno Fuerza, volviendo a calcular e invirtiendo cada una de las fuerzas de dirección activas., freno se calcula en base a la corriente gobierno Vector, que contiene todas las fuerzas de dirección añadidas al momento:


Representación de la fuerza de frenado..

los freno la fuerza recibe tanto su X y y componentes de la gobierno Fuerza, pero invertida y con una escala de 0.8. Esto significa que freno tiene el 80% de la magnitud de gobierno y puntos en la dirección opuesta.

Propina: Utilizando la gobierno La fuerza directamente es peligrosa. Si cola() Es el primer comportamiento que se aplica a un personaje, el gobierno La fuerza estará "vacía". Como consecuencia, cola() debe ser invocado después de todos los otros métodos de dirección, Para que pueda acceder a la completa y definitiva. gobierno fuerza.

los freno La fuerza también necesita cancelar la velocidad del personaje. Eso se hace agregando -velocidad al freno fuerza. Después de eso, el método. cola() puede devolver la final freno fuerza.

El resultado de utilizar la fuerza de frenado es el siguiente:


Comportamiento de la cola mediante la aproximación de la fuerza de frenado. Haga clic para mostrar fuerzas.

Mitigación de la superposición de personajes

El enfoque de frenado produce un resultado más natural en comparación con el antiguo "robótico", porque todos los personajes están tratando de llenar los espacios vacíos. Sin embargo, introduce un nuevo problema: los caracteres se superponen.

Para solucionarlo, se puede mejorar el enfoque del freno con una versión ligeramente modificada del enfoque de "parada dura":

 función privada queue (): Vector3D var v: Vector3D = velocity.clone (); var brake: Vector3D = nuevo Vector3D (); vecino var: Boid = getNeighborAhead (); si (vecino! = nulo) brake.x = -eringing.x * 0.8; brake.y = -eringing.y * 0.8; v.scaleBy (-1); brake = brake.add (v); si (distancia (posicion, vecino.posicion) <= MAX_QUEUE_RADIUS)  velocity.scaleBy(0.3);   return brake; 

Se usa una nueva prueba para verificar vecinos cercanos. Esta vez en lugar de usar el adelante Para medir la distancia, la nueva prueba verifica la distancia entre los caracteres. posición vector:


Compruebe los vecinos cercanos dentro del radio MAX_QUEUE_RADIUS centrado en la posición en lugar del punto delantero.

Esta nueva prueba verifica si hay algún personaje cercano dentro del MAX_QUEUE_RADIUS radio, pero ahora está centrado en el posición vector. Si alguien está dentro del rango, significa que el área circundante se está llenando de gente y que los personajes probablemente se están superponiendo..

La superposición se mitiga al escalar la velocidad Vector al 30% de su magnitud actual cada actualización. Al igual que en el enfoque de "parada dura", encogiendo el velocidad vector reduce drásticamente el movimiento.

El resultado parece menos "robótico", pero no es ideal, ya que los personajes todavía se superponen en la puerta:


Comportamiento de cola con "tope duro" y fuerza de frenado combinados. Haga clic para mostrar fuerzas.

Añadiendo Separación

Aunque los personajes intentan llegar a la puerta de una manera convincente, llenando todos los espacios vacíos cuando el camino se estrecha, se están acercando demasiado en la puerta..

Esto se puede resolver agregando una fuerza de separación:

 función privada queue (): Vector3D var v: Vector3D = velocity.clone (); var brake: Vector3D = nuevo Vector3D (); vecino var: Boid = getNeighborAhead (); si (vecino! = nulo) brake.x = -eringing.x * 0.8; brake.y = -eringing.y * 0.8; v.scaleBy (-1); brake = brake.add (v); brake = brake.add (separación ()); si (distancia (posicion, vecino.posicion) <= MAX_QUEUE_RADIUS)  velocity.scaleBy(0.3);   return brake; 

Anteriormente utilizado en el comportamiento de líder del líder, la fuerza de separación agregada al freno la fuerza hará que los personajes dejen de moverse al mismo tiempo que intentan mantenerse alejados el uno del otro.

El resultado es una multitud convincente que intenta llegar a la puerta:


Comportamiento de la cola con "tope duro", fuerza de frenado y separación combinadas. Haga clic para mostrar fuerzas.

Conclusión

El comportamiento de la cola permite que los personajes se paren en línea y esperen pacientemente para llegar al destino. Una vez en línea, un personaje no intentará "engañar" saltando posiciones; solo se moverá cuando el personaje que está justo delante se mueva.

La escena de la puerta utilizada en este tutorial presentó lo versátil y modificable que puede ser este comportamiento. Algunos cambios producen resultados diferentes, que pueden ajustarse bien a una amplia variedad de situaciones. El comportamiento también se puede combinar con otros, como evitar colisiones.

Espero que te haya gustado este nuevo comportamiento y comience a usarlo para agregar multitudes a tu juego.!