Entendiendo los comportamientos de la dirección Gestor de movimientos

Los comportamientos de dirección son excelentes para crear patrones de movimiento realistas, pero son aún mejores si puedes controlarlos, usarlos y combinarlos fácilmente. En este tutorial, discutiré y cubriré la implementación de un gestor de movimientos para todos nuestros comportamientos discutidos previamente.

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.


Combinando Fuerzas Directivas

Como se discutió anteriormente, cada comportamiento de dirección produce una fuerza resultante (llamada "fuerza de dirección") que se agrega al vector de velocidad. La dirección y la magnitud de esa fuerza impulsarán al personaje, haciéndolo moverse de acuerdo con un patrón (buscar, huir, vagar, etc.). El cálculo general es:

 dirección = buscar (); // esto puede ser cualquier comportamiento dirección = truncar (dirección, max_force) dirección = dirección / velocidad de la masa = truncar (velocidad + dirección, máxima velocidad) posición = posición + velocidad

Dado que la fuerza de dirección es un vector, se puede agregar a cualquier otro vector (al igual que la velocidad). Sin embargo, la verdadera "magia" radica en el hecho de que puede sumar varias fuerzas de dirección juntas, es tan simple como:

 dirección = nada (); // el vector nulo, que significa "magnitud de fuerza cero" dirección = dirección + buscar (); dirección = dirección + huir (); (…) Dirección = truncar (dirección, max_force) dirección = dirección / velocidad de la masa = truncar (velocidad + dirección, max_speed) posición = posición + velocidad

Las fuerzas de dirección combinadas resultarán en un vector que representa todos esas fuerzas En el fragmento de código de arriba, la fuerza de dirección resultante hará que el personaje busque algo mientras al mismo tiempo huirá de algo más.

Vea a continuación algunos ejemplos de fuerzas de dirección combinadas para producir una sola fuerza de dirección:


Fuerzas de dirección combinadas.

Patrones complejos sin esfuerzo

La combinación de fuerzas de dirección producirá patrones de movimiento extremadamente complejos sin esfuerzo. Imagina lo difícil que sería escribir código para hacer que un personaje busque algo, pero al mismo tiempo evita un área específica, sin usar vectores y fuerzas.?

Eso requeriría el cálculo de distancias, áreas, caminos, gráficos y similares. Si las cosas se mueven, todos esos cálculos deben repetirse de vez en cuando, porque el entorno cambia constantemente..

Con los comportamientos de dirección, todas las fuerzas son dinámica. Están destinados a ser calculados en cada actualización del juego, por lo que reaccionarán de forma natural y sin problemas a los cambios del entorno.

La siguiente demostración muestra los barcos que buscarán el cursor del mouse, pero huirán del centro de la pantalla, ambos al mismo tiempo:


Los barcos buscarán el cursor del mouse (gris), pero huirán del centro de la pantalla (naranja). Haga clic para mostrar fuerzas.

Gestor de movimientos

Para utilizar varios comportamientos de dirección al mismo tiempo de una manera simple y fácil, un gestor de movimientos Viene muy bien. La idea es crear una "caja negra" que se pueda conectar a cualquier entidad existente, para que pueda realizar esos comportamientos..

El administrador tiene una referencia a la entidad a la que está conectado (el "host"). El administrador proporcionará al host un conjunto de métodos, como buscar() y huir(). Cada vez que se invocan tales métodos, el administrador actualiza sus propiedades internas para producir un vector de fuerza de dirección.

Después de que el administrador procese todas las invocaciones, agregará la fuerza de dirección resultante al vector de velocidad del host. Eso cambiará la dirección y la magnitud del vector de velocidad del host de acuerdo con los comportamientos activos.

La siguiente figura muestra la arquitectura:


Gestor de movimientos: arquitectura plugin..

Haciendo las cosas genéricas

El administrador tiene un conjunto de métodos, cada uno de los cuales representa un comportamiento distinto. Cada comportamiento debe ser suministrado con diferentes piezas de información externa para poder trabajar..

El comportamiento de búsqueda, por ejemplo, necesita un punto en el espacio que se utiliza para calcular la fuerza de dirección hacia ese lugar; Persigue necesita varias piezas de información de su objetivo, como la posición actual y la velocidad. Un punto en el espacio se puede expresar como una instancia de Punto o Vector2D, Ambas clases bastante comunes en cualquier marco.

El objetivo utilizado en el comportamiento de persecución, sin embargo, puede ser cualquier cosa. Para que el gestor de movimientos sea lo suficientemente genérico, debe recibir un objetivo que, independientemente de su tipo, pueda responder algunas "preguntas", como "Cual es tu velocidad actual?". Utilizando algunos principios de programación orientada a objetos, se puede lograr con interfaces.

Asumiendo la interfaz IBoid describe una entidad capaz de ser manejada por el gestor de movimientos, cualquier clase en el juego puede usar comportamientos de dirección, siempre que se implemente IBoid. Esa interfaz tiene la siguiente estructura:

 interfaz pública IBoid function getVelocity (): Vector3D; función getMaxVelocity (): Number; función getPosition (): Vector3D; función getMass (): Number; 

Estructura del gestor de movimiento

Ahora que el gerente puede interactuar con todas las entidades del juego de una manera genérica, se puede crear su estructura básica. El administrador se compone de dos propiedades (la fuerza de dirección resultante y la referencia del host) y un conjunto de métodos públicos, uno para cada comportamiento:

 SteeringManager de clase pública var dirección pública: Vector3D; host var público: IBoid; // La función pública constructora SteeringManager (host: IBoid) this.host = host; this.steering = nuevo Vector3D (0, 0);  // La API pública (un método para cada comportamiento) búsqueda de función pública (target: Vector3D, slowingRadius: Number = 20): void  public function flee (target: Vector3D): void  public function wander (): void Evasión de la función pública  (target: IBoid): void  Búsqueda de la función pública (target: IBoid): void  // El método de actualización. // Debería llamarse después de invocar todos los comportamientos public function update (): void  // Restablecer la fuerza de dirección interna. public function reset (): void  // La función privada interna de la API doSeek (target: Vector3D, slowingRadius: Number = 0): Vector3D  private function doFlee (target: Vector3D): Vector3D  private function dowander (): Vector3D  función privada doEvade (target: IBoid): Vector3D  doPursuit función privada (target: IBoid): Vector3D 

Cuando se crea una instancia del administrador, debe recibir una referencia al host al que está conectado. Le permitirá al administrador cambiar el vector de velocidad del host de acuerdo con los comportamientos activos.

Cada comportamiento está representado por dos métodos, uno público y otro privado. Usando la búsqueda como ejemplo:

 búsqueda de función pública (target: Vector3D, slowingRadius: Number = 20): void  doSeek de la función privada (target: Vector3D, slowingRadius: Number = 0): Vector3D 

El público buscar() se invocará para decirle al administrador que aplique ese comportamiento específico. El método no tiene valor de retorno y sus parámetros están relacionados con el comportamiento en sí, como un punto en el espacio. Bajo el capó del método privado. hacer () se invocará y su valor de retorno, la fuerza de dirección calculada para ese comportamiento específico, se agregará a la dirección del gerente. gobierno propiedad.

El siguiente código demuestra la implementación de la búsqueda:

 // El método de publicación. // Recibe un objetivo para buscar y un slowingRadius (usado para realizar llegar). búsqueda de función pública (target: Vector3D, slowingRadius: Number = 20): void direction.incrementBy (doSeek (target, slowingRadius));  // La implementación real de la función privada de búsqueda (con el código de llegada incluido) doSeek (objetivo: Vector3D, desaceleración Radio: Número = 0): Vector3D var force: Vector3D; distancia var: Número; deseado = target.subtract (host.getPosition ()); distancia = longitud deseada; deseado.normalizar (); si (distancia) <= slowingRadius)  desired.scaleBy(host.getMaxVelocity() * distance/slowingRadius);  else  desired.scaleBy(host.getMaxVelocity());  force = desired.subtract(host.getVelocity()); return force; 

Todos los otros métodos de comportamiento se implementan de una manera muy similar. los búsqueda() El método, por ejemplo, se verá así:

 búsqueda de función pública (target: IBoid): void direction.incrementBy (doPursuit (target));  función privada doPursuit (target: IBoid): Vector3D distance = target.getPosition (). restar (host.getPosition ()); var updatesNeeded: Number = distance.length / host.getMaxVelocity (); var tv: Vector3D = target.getVelocity (). clone (); tv.scaleBy (updatesNeeded); targetFuturePosition = target.getPosition (). clone (). add (tv); return doSeek (targetFuturePosition); 

Usando el código de tutoriales anteriores, todo lo que tiene que hacer es adaptarlos en la forma de comportamiento() y comportamiento (), Para que puedan ser agregados al gestor de movimientos..


Aplicación y actualización de las fuerzas de dirección.

Cada vez que se invoca un método de comportamiento, la fuerza resultante que produce se agrega a los gestores. gobierno propiedad. Como consecuencia, esa propiedad acumulará todas las fuerzas de dirección..

Cuando se han invocado todos los comportamientos, el administrador debe aplicar la fuerza de dirección actual a la velocidad del host, para que se mueva de acuerdo con los comportamientos activos. Se realiza en el actualizar() Método del gestor de movimientos:

 actualización de la función pública (): void var speed: Vector3D = host.getVelocity (); posición var: Vector3D = host.getPosition (); truncado (dirección, MAX_FORCE); direction.scaleBy (1 / host.getMass ()); velocity.incrementBy (direction); truncar (velocidad, host.getMaxVelocity ()); position.incrementBy (velocidad); 

El método anterior debe ser invocado por el anfitrión (o cualquier otra entidad del juego) después de que se hayan invocado todos los comportamientos, de lo contrario el anfitrión nunca cambiará su vector de velocidad para que coincida con los comportamientos activos.


Uso

Asumamos una clase llamada Presa Debe moverse usando el comportamiento de la dirección, pero en este momento no tiene un código de dirección ni el administrador de movimientos. Su estructura se verá así:

 Clase pública Presa posición var pública: Vector3D; public var speed: Vector3D; misa de var. pública: Número; función pública Prey (posX: Number, posY: Number, totalMass: Number) position = new Vector3D (posX, posY); velocidad = nuevo Vector3D (-1, -2); masa = masa total; x = posición.x; y = posición.y;  actualización de la función pública (): void velocity.normalize (); velocity.scaleBy (MAX_VELOCITY); velocity.scaleBy (1 / mass); truncar (velocidad, MAX_VELOCITY); position = position.add (velocidad); x = posición.x; y = posición.y; 

Usando esa estructura, las instancias de la clase pueden moverse usando la integración de Euler, al igual que la primera demostración del tutorial de búsqueda. Para que sea capaz de usar al administrador, necesita una propiedad que haga referencia al administrador de movimientos y debe implementar el IBoid interfaz:

 público clase Prey implementa IBoid public var position: Vector3D; public var speed: Vector3D; misa de var. pública: Número; dirección var pública: SteeringManager; función pública Prey (posX: Number, posY: Number, totalMass: Number) position = new Vector3D (posX, posY); velocidad = nuevo Vector3D (-1, -2); masa = masa total; dirección = nuevo SteeringManager (este); x = posición.x; y = posición.y;  actualización de la función pública (): void velocity.normalize (); velocity.scaleBy (MAX_VELOCITY); velocity.scaleBy (1 / mass); truncar (velocidad, MAX_VELOCITY); position = position.add (velocidad); x = posición.x; y = posición.y;  // A continuación se muestran los métodos que requiere la interfaz IBoid. función pública getVelocity (): Vector3D velocidad de retorno;  public function getMaxVelocity (): Number return 3;  public function getPosition (): Vector3D return position;  función pública getMass (): Number return mass; 

los actualizar() El método debe cambiarse en consecuencia para que el administrador también pueda actualizarse:

 actualización de la función pública (): void // Hacer que la presa deambule por ... direction.wander (); // Actualizar el administrador para que cambie el vector de velocidad de presa. // El administrador también realizará la integración de Euler, cambiando // el vector de "posición". direction.update (); // Después de que el administrador haya actualizado sus estructuras internas, todo lo que debemos hacer // es actualizar nuestra posición de acuerdo con el vector "posición". x = posición.x; y = posición.y; 

Todos los comportamientos se pueden usar al mismo tiempo, siempre que todas las llamadas a métodos se realicen antes de que el gerente actualizar() invocación, que aplica la fuerza de dirección acumulada al vector de velocidad del host.

El siguiente código muestra otra versión de Prey's actualizar() Método, pero esta vez buscará una posición en el mapa y evadirá otro personaje (ambos al mismo tiempo):

 actualización de la función pública (): void var destination: Vector3D = getDestination (); // el lugar para buscar var hunter: IBoid = getHunter (); // obtener la entidad que nos está cazando // buscar el destino y evadir al cazador (¡al mismo tiempo!) direction.seek (destino); direction.evade (cazador); // Actualizar el administrador para que cambie el vector de velocidad de presa. // El administrador también realizará la integración de Euler, cambiando // el vector de "posición". direction.update (); // Después de que el administrador haya actualizado sus estructuras internas, todo lo que debemos hacer // es actualizar nuestra posición de acuerdo con el vector "posición". x = posición.x; y = posición.y; 

Manifestación

La siguiente demostración muestra un patrón de movimiento complejo donde se combinan varios comportamientos. Hay dos tipos de personajes en la escena: Cazador y el Presa.

El cazador perseguir una presa si se acerca lo suficiente; perseguirá mientras dure el suministro de resistencia; cuando se agota la resistencia, se interrumpe la persecución y el cazador lo hará. deambular Hasta que recupera sus niveles de resistencia..

Aquí está el cazador actualizar() método:

 actualización de la función pública (): void if (resting && stamina ++> = MAX_STAMINA) resting = false;  if (¡presa! = nulo &&! descansando) direction.pursuit (presa); aguante - = 2; si <= 0)  prey = null; resting = true;   else  steering.wander(); prey = getClosestPrey(position);  steering.update(); x = position.x; y = position.y; 

La presa sera deambular indefinidamente. Si el cazador se acerca demasiado, lo hará. evadir. Si el cursor del mouse está cerca y no hay ningún cazador alrededor, la presa lo hará. buscar el cursor del mouse.

Aquí está el Prey's actualizar() método:

 actualización de la función pública (): void var distance: Number = Vector3D.distance (position, Game.mouse); hunter = getHunterWithinRange (posición); if (hunter! = null) direction.evade (hunter);  si (distancia) <= 300 && hunter == null)  steering.seek(Game.mouse, 30);  else if(hunter == null) steering.wander();  steering.update(); x = position.x; y = position.y; 

El resultado final (el gris es vagar, el verde es buscar, el naranja es perseguir, el rojo es evadir):


La caza. Haga clic para mostrar fuerzas.

Conclusión

Un gestor de movimientos es muy útil para controlar varios comportamientos de dirección al mismo tiempo. La combinación de tales comportamientos puede producir patrones de movimiento muy complejos, permitiendo que una entidad del juego busque una cosa al mismo tiempo que evade otra..

Espero que te haya gustado el sistema de gestión discutido e implementado en este tutorial y lo uses en tus juegos. ¡Gracias por leer! No se olvide de mantenerse actualizado siguiéndonos en Twitter, Facebook o Google.+.