Capítulo 4 Implementación

Sin valoraciones

En el Capítulo 3 se comentaba de dónde surge la herramienta, se definieron los com­portamientos que iba a ofrecer el catálogo de componentes y, finalmente, se definieron unos enemigos comunes. En este capítulo se va a tratar en profundidad la implementación de los componentes diseñados, entrando en detalles acerca de las formas de personalizar cada componente y las distintas combinaciones de comportamientos que ofrece este catálogo.

4.1 Tecnología utilizada

Este proyecto se ha desarrollado de forma íntegra con el motor de videojuegos Unity, una herramienta multiplataforma creada por la empresa Unity technologies en el año 2005. La versión escogida para desarrollar la herramienta es la 2019.2.1f, y por tanto no se garantiza que la herramienta funcione en una versión anterior, pero si debería funcionar sin problemas en versiones posteriores.

Unity es una herramienta muy versátil, moderna, y potente que es capaz de rende- rizar desde entornos simples en dos dimensiones hasta entornos de gran tamaño en tres dimensiones, pasando por la realidad virtual y la realidad aumentada. Este motor de video­juegos surge con la idea de hacer el desarrollo de videojuegos más accesible a todo tipo de desarrolladores, tanto profesionales en la industria como amateurs autodidactas, ofreciendo soporte para varios lenguajes de programación en forma de plugins a pesar de contar con el lenguaje C sharp y Javascript como principales lenguajes de programación de scripts.

Unity resulta muy atractivo de utilizar porque cuenta con muchas facilidades a la hora de desarrollar aplicaciones de todo tipo, no solamente sirve para videojuegos sino también es muy utilizado en simuladores, para cine o para arquitectura. Unity cuenta con una interfaz de usuario muy gráfica, intuitiva dentro de su complejidad, y fácilmente personalizable al gusto de cada uno. Además, este motor de videojuegos tiene integrado un sistema de arrastrar y soltar para construir las escenas de juego, mover a voluntad los objetos dentro de ella y asignar scripts a las entidades de juego para darles vida. Este sistema ya aparecía en los motores de videojuegos que se han comentado en el Capítulo 2, y es lo que permite que Unity sea empleado por un gran número de personas. La curva de aprendizaje no es muy pronunciada ya que los creadores han proporcionado una documentación muy extensa, tutoriales para aprender a utilizar el motor así como unos foros donde la comunidad puede preguntar, y responder dudas a otros usuarios de la comunidad.

Figura 4.1: Interfaz de Unity (2 by 3), 2020

El motivo por el que se escogió Unity frente a otros motores de videojuegos es pre­cisamente por la accesibilidad y lo sencillo que resulta de utilizar por cualquier persona. La herramienta que se ha desarrollado se fundamenta en Unity porque a diario la utilizan muchas personas de distintos niveles de conocimiento y la idea del catálogo de enemigos es que llegue al mayor número de personas posibles, especialmente a gente que no necesaria­mente sepa programar o no tenga los recursos necesarios para construir toda la inteligencia artificial de los enemigos desde cero.

El sistema de arrastrar y soltar con el que cuenta el motor es muy útil para el uso de la herramienta porque hace que añadir y combinar los diferentes scripts de movimiento sea una tarea sencilla, al alcance de cualquier usuario del motor con un mínimo conocimiento. Además, Unity cuenta con una gran cantidad de elementos que han ayudado a hacer la herramienta, como por ejemplo su motor de físicas 2D integrado o las herramientas que tiene para ayudar visualmente al usuario a hacerse una idea de lo que va a pasar al añadir un script específico gracias a sus gizmos.

 

Figura 4.2: Ejemplo de gizmos

Otro punto a favor para utilizar este motor de videojuegos frente a otros motores previa­mente comentados es que Unity funciona con una arquitectura de código por componentes lo que lo hace perfecto para integrar esta herramienta. El estilo de arquitectura basado en componentes pone énfasis en la descomposición de un sistema en elementos más peque­ños de código para fomentar la modularidad del programa. Los componentes programados deben ser funcionales y presentar una interfaz que sirva para abstraer la implementación privada de la parte personalizable de un componente, que será pública. De esta manera, es posible definir en Unity un apartado de componentes personalizado (Figura 4.3) donde guardar los componentes propios de la herramienta de tal manera que quede totalmente aislado del código fuente que hace que funcione, además de que así están en un lugar que resulta intuitivo de encontrar.

Figura 4.3: Interfaz de la herramienta

4.2 Infraestructura básica

Para llevar a cabo el diseño de este catálogo de componentes e implementar los com­portamientos de movimiento y atributos sensoriales era indispensable poner la herramienta en el contexto de un videojuego 2D. Utilizando una serie de componentes y objetos se creó una simulación sencilla de un nivel donde había un enemigo, una plataforma y el jugador.

La clase Player Controller que sirve de controlador del jugador. Esta clase es la en­cargada de mover a la entidad jugador o hacer que salte cuando se invocan los métodos determinados, pero por si sola no es capaz de interpretar el input del jugador sino que sólo contiene los métodos que implementan las acciones de movimiento posibles para el juga­dor. La clase PlayerMovement es una clase que permite al usuario controlar el movimiento del jugador debido a que recoge el input del usuario, y utiliza los métodos definidos en Player Controller con la acción deseada. Permite, además, modificar ciertos valores como la velocidad de movimiento o la potencia del salto. Como no es una parte intrínseca de este catálogo he creado el código siguiendo un tutorial de un usuario de la comunidad de Unity, Brackeys.

La clase Camera Controller de la Figura 4.4 es una clase simple que, añadida a un objeto cámara hace que este objeto que mueva para mantener a una entidad objetivo siempre visible. El objeto al que la cámara debe seguir es personalizable, pero para esta implementación se ha establecido que el jugador sea el objeto al que siga la cámara.

Se ha implementado también una clase Health que gestiona la vida de la unidad a la que se le aplicase, y que permite eliminar entidades de la escena cuando su vida llegue a 0. Esto se crea para probar un componente de ataque sencillo que es el daño por contacto. La clase Contact Damage controla la colisión de la entidad con el entorno, y gestiona que si la entidad que colisiona con el jugador era un enemigo, le enviara un mensaje indicándole al componente Health del jugador que ha sido dañado como se ve en la Figura 4.4. Para el desarrollo del catálogo se ha decidido hacer más énfasis en los comportamientos de movimiento que en los posibles comportamientos de ataque, por lo que estas clases han sido relegadas a un segundo plano.

La clase Enemy Engine que aparece en la Figura 4.4 sirve como gestor de informa­ción importante de cada enemigo. Es una clase que se añade automáticamente al añadir cualquier comportamiento de movimiento y que sirve para guardar una referencia de la posición del jugador, constantemente actualizada y que puede ser requerida por cualquier componente de movimiento mediante la clase Movement Behaviour. La clase Enemy En- gine es capaz de buscar las capas concretas del suelo, o del jugador, dentro de las capas físicas del motor y devolver una referencia. Mediante el uso de estas capas, por ejemplo, los enemigos saben si han colisionado con el suelo y los sensores son capaces de detectar al jugador para activar los componentes. Actualmente las capas que sabe detectar esta clase son las capas físicas “Player“ para el jugador, y “ Ground“ para el suelo, porque son capas físicas muy comunes en cualquier videojuego.

Finalmente, en la Figura 4.4 aparecen dos módulos con los componentes de movimiento y de tipo sensor. Se crearán dos jerarquías correspondientes, con los componentes imple- mentados en cada una de ellas. Cada jerarquía tiene como raíz una clase que les aporta funcionalidades comunes, y de la que heredarán los componentes. Esto se tratará en detalle en las siguientes secciones.

4.3 Componentes de Movimiento

Los componentes de movimiento son scripts que, al aplicárselos a una entidad, generan un tipo de comportamiento. Los componentes están hechos cuidando la abstracción, te­niendo en cuenta que el usuario final no tiene por qué saber cómo están hechos los scripts. Funcionan de tal manera que no haga falta añadir ni configurar nada que no sean los atri­butos públicos que cada componente ofrece, por lo que basta con añadir el componente y ejecutar la aplicación para que la entidad empieze a moverse.

Como podemos ver en la Figura 4.5, todos los componentes de movimiento son hijos de la clase Movement Behaviour. Esta es una clase común a todos los comportamientos de movimiento, lo que quiere decir que cualquier clase que defina un movimiento es una clase hija de esta clase. Mediante esta herencia, las clases hijas son capaces de obtener atributos y métodos de las clases padre. Concretamente, la clase Movement Behaviour declara un método que heredan todos los componentes de movimiento, por el cual cada componente puede devolver su vector de movimiento si estuviera actuando por si mismo pero sin mover a la entidad. Esto es muy útil para las clases que combinan varios componentes de forma interna, como es el caso del Forward Jumper que se diseñó en el Capítulo 3.

Figura 4.4: Esquema general

La clase Movement Behaviour requiere un componente de tipo Enemy Engine para funcionar, de esta manera existe la garantía de que la entidad cuenta con esta clase porque al añadir cualquier componente de movimiento, se comprobará si la entidad tiene un Enemy Engine y en caso de no tenerlo lo añadirá automáticamente. Guardando la referencia al Enemy Engine, las clases de movimiento pueden preguntar por la posición del jugador o por una capa física concreta como se ha explicado previamente.

La estructura que siguen los componentes de Unity viene determinada por una serie de funciones que son llamadas automáticamente en lo que es llamado el “ciclo de vida“ del script. Los componentes de movimiento desarrollados para la herramienta, por tanto, siguen la misma estructura. Al iniciar la aplicación, se llamará al método Start que construye la parte interna del componente, gestionando las dependencias y montando el comportamiento interno con los valores públicos. No obstante, si el enemigo cuenta con algún tipo de sensor no se llamará a ese método al iniciarse sino cuando el sensor los active al detectar al jugador. Cada vez que se ejecuta un ciclo de juego, lo que se conoce como frame, se llama a un método llamado update que actualiza la lógica de cada componente de movimiento activo. Es en este método cuando los componentes llaman a sus funciones privadas de gestión de movimientos, y dependiendo de la estructura del enemigo moverán la entidad, o devolverán el movimiento generado a otro comportamiento si este se lo pidiera.

4.3.1 Floater

El componente Floater se caracteriza porque desplaza a la entidad entre dos puntos a lo largo de un eje, el horizontal o el vertical, siguiendo un patrón fijo de movimiento. El enemigo que tenga este comportamiento se moverá con una velocidad calculada en función de la distancia hacia el punto de destino y el tiempo total en completar el movimiento,

Figura 4.5: Esquema de los componentes de movimiento

de tal manera que va frenando a medida que se aproxima a los puntos finales. Además del tiempo total que tarda en completar el movimiento es posible definir un tiempo extra de retardo tras completar un sector del movimiento. Por tanto, si se desea la entidad se detendrá una cantidad fija de tiempo en esos límites antes de reanudar el movimiento.

Se desplaza, por tanto, entre dos puntos en el espacio que se definen como las dos mitades desde el punto del objeto, que deberá situarse en el punto medio de la ruta. Como esta condición puede resultar compleja de entender, se ha creado una ayuda visual para este componente como se ve en la Figura 4.6. Esta ayuda visual representa la amplitud del movimiento que va a seguir, trazando el recorrido total que va a efectuar la unidad y, además, se actualiza si se modifica el valor.

Atributos de configuración

  • (Float) Movement Amplitude: Amplitud de onda. Distancia total que quieres cubrir en unidades de Unity.
  • (Float) Time to Complete Movement: Tiempo que tarda en llegar al extremo al que se deba dirigir.
  • (Float) Delay Time: Tiempo que espera en los límites del movimiento.

Figura 4.6: Componente Floater

4.3.2 Faller

Un enemigo con el componente Faller tiene un movimiento vertical hacia abajo, impul­sado por la masa del enemigo y por la “escala de gravedad“ que se le aplicará. Si se desea, el efecto de caída libre no se activará inmediatamente, existe una opción para definir un retardo en la caída porque si cayera inmediatamente el jugador podría no ver al enemigo a tiempo y no tener tiempo a reaccionar. Una vez pasado ese tiempo, se dejará caer el objeto aplicándole una fuerza hacia abajo sin modificar el peso de la entidad.

No se debe confundir este comportamiento de caída libre con un comportamientos de habilidad de “lanzar elementos, porque se entiende el comportamiento Faller como que es la entidad la que se lanza con su propio cuerpo en un movimiento descendiente en lugar de generar objetos que después puedan caen por gravedad, utilizando el mismo comportamiento Faller.

Atributos de configuración

  • (Int) Gravity: Fuerza hacia abajo que se le aplicará a la entidad, resultando en la velocidad con la que cae.
  • (float) Time before fall: Tiempo, en segundos, que espera antes de caer.

4.3.3 Jumper

El comportamiento Jumper hace que la entidad salte, impulsándola hacia arriba con una altura máxima personalizable. El salto está programado mediante físicas: se le aplica un incremento de velocidad hacia arriba con una fuerza inicial calculada internamente con la altura máxima indicada en el editor. Se añadió posteriormente, ajustándome a los comentarios de los probadores, una variable que controla el tiempo que tarda la entidad en llegar al punto más alto.

Mediante la variable de la altura máxima, el diseñador puede hacerse a la idea de lo alto que va a saltar la entidad con los gizmos que posee el componente. Como se ve en la Figura 4.7, la línea indica tanto el punto más alto como el recorrido que va a hacer el enemigo. Además el Jumper cuenta con un atributo que controla el retardo que existe entre saltos, y que empezará a contarse cuando la entidad Jumper haya entrado en contacto con el suelo. Si el punto más bajo de la entidad Jumper no está en contacto directo con el suelo no saltará.

Atributos de configuración

  • (Float) Jump Height: Lo mucho que salta la entidad.
  • (Float) Jump Time: Tiempo que tarda la entidad en llegar al punto más alto.
  • (Float) Jump Delay: Tiempo que la entidad espera antes de saltar de nuevo.

4.3.4 Bullet

El comportamiento Bullet permite a una entidad avanzar en línea recta en una direc­ción. La dirección del movimiento viene dada por el vector Bullet Direction que, como

Figura 4.7: Componente Jumper

se ve en la Figura 4.8 se representa como una línea en la dirección elegida. La línea está normalizada por claridad, para no llenar la pantalla de gizmos, pero el movimiento será en línea recta de forma indefinida. Es posible seleccionar si se desea que el enemigo colisione con las paredes o que, por otro lado, las atraviese. Por último, se puede personalizar la velocidad a la que se mueve la entidad que posea este comportamiento.

Atributos de configuración

  • (Bool) Collide with walls: Determina si colisiona o no con el entorno.
  • (Vector 2) Bullet direction: Vector que define la dirección del movimiento.
  • (Float) Bullet speed: Velocidad a la que se mueve el objeto en unidades de Unity por segundo.

Figura 4.8: Componente Bullet

4.3.5 Forward Jumper

Este comportamiento es el resultado de combinar un Jumper con un Bullet de forma interna. Tiene todos los atributos con los que luego se construyen los componentes necesa­rios, sin que el diseñador tenga que montar la estructura en el editor. La combinación de Jumper con Bullet permite hacer un movimiento particular: la entidad salta en una direc­ción diagonal, dando la sensación de que salta avanzando hacia delante. El salto siempre se orienta hacia el jugador, y la entidad espera una breve cantidad de tiempo entre saltos, que al igual que pasaba con el Jumper sólo empezará a contar el tiempo cuando la parte más baja de la entidad esté en contacto con el suelo.

Atributos de configuración

  • Cuenta con los tributos de un Jumper y un Bullet.
  • (Float) Delay between jumps: Establece el retardo entre saltos, en segundos.

4.3.6 Liner

El comportamiento Liner se desplaza a la entidad a una velocidad definida hasta al­canzar un objetivo, orientando previamente su dirección hacia el mismo. La velocidad de movimiento o de aceleración vendrán definidas por el diseñador. Es importante destacar que el enemigo no se va a desplazar en esa dirección constantemente, como un Bullet, sino que va a moverse hacia un punto fijo en el espacio en línea recta y una vez llegue a ese punto el movimiento se detendrá. Además, se le ha dado al componente Liner la funcionalidad de rotar el objeto para orientarse hacia el objetivo, cosa que no sucede con el componente Bullet.

Originalmente este comportamiento viajaba hacia el jugador, no obstante siguiendo con los comentarios extraídos en las pruebas el diseñador tiene la opción de seleccionar un objetivo personalizado. En caso de que el diseñador no establezca un objetivo se establecerá de objetivo del movimiento al jugador por defecto.

Atributos de configuración

  • (Game Object) Target: Objetivo del movimiento. En caso de ser vacío al empezar la partida, se asignará el jugador.
  • (Float) Time to reach target: Tiempo en segundos que le tomará al entity llegar al punto objetivo.
  • (Bool) Rotate towards target: Si está activo, el Liner rotará su sprite para orientarlo hacia el punto objetivo.
  • (Enum) Tipo movimiento: Enumerado que determina si el tipo de movimiento es continuo o acelerado. En función de esta decisión se usará un gestor de movimiento u otro

4.3.7 Follower

El componente Follower permite al enemigo actualizar la posición de objetivo cada cierto tiempo, proporcionando así un nuevo punto objetivo al movimiento Liner interno y evitando así que se detenga. Con esta sencilla modificación se consigue que un enemigo persiga constantemente a su objetivo. De la misma manera que sucedía con el compor­tamiento Liner, si el usuario no asigna un objetivo a este componente se establecerá el jugador por defecto. Además, el usuario puede modificar el tiempo de refresco o renovación de la posición del jugador para que sea más o menos constante. Por ejemplo, con un valor bajo se consigue que la entidad se mueva de forma fluida hacia su objetivo mientras que con un valor más alto el efecto se asemeja más a que el enemigo carga hacia la posición del jugador.

Atributos de configuración

  • Como es una modificación del atributo Liner, cuenta con todos sus atributos.
  • (Float) Time to Refresh: Tiempo que el componente espera antes de actualizar la posición.

4.3.8 Wander

El comportamiento Wander hace que la entidad deambule sin rumbo fijo dentro de un área determinada. Este componente tiene de forma interna un Liner, pero no permite que se le asigne ningún objetivo. En su lugar, cada cierto tiempo configura su movimiento Liner interno con una nueva posición generada dentro de área circular de este componente. El usuario puede establecer tanto las dimensiones de ese área como el tiempo que tardará la entidad en elegir un nuevo objetivo.

Atributos de configuración

  • Como es una modificación del atributo Liner, cuenta con todos sus atributos salvo el target.
  • (Float) Area Radius: Radio de la esfera en la que se moverá.
  • (Float) Time to Refresh: Tiempo que el componente espera antes de actualizar la posición.

4.3.9 Pacer

El comportamiento Pacer hace que la entidad avance en línea recta, en la dirección hacia la que mire, a lo largo de una superficie. Cuando este componente detecta que delante de la entidad no hay suelo firme sobre el que caminar, el componente cambiará de sentido rotando a la entidad volviendo sobre sus pasos. Debido a esto, este comportamiento se suele utilizar en plataformas elevadas.

Para este movimiento, el componente cuenta con un sensor interno que consiste en un rayo físico que se lanza hacia debajo desde una posición ligeramente más adelantada que la de la entidad. Mientras que este sensor detecte que hay suelo delante la entidad seguirá avanzando pero si, por el contrario, el sensor no detectase suelo el enemigo se daría la vuelta.

Atributos de configuración

– (Float) Movement speed: Velocidad en unidades de Unity por segundo a la que se mueve la entidad en línea recta.

Compártelo en tus redes

Valore este curso

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Utilizamos cookies para asegurar que damos la mejor experiencia al usuario en nuestra web. Si sigues utilizando este sitio asumimos que estás de acuerdo. VER