Introducción

En este artículo vamos a ver qué es una función cuadrática, su expresión matemática, sus características, cómo graficarla en el plano cartesiano y para qué nos puede ser útil en el desarrollo de juegos con ejemplos en Unity.

Expresión matemática de una función cuadrática

La expresión polinómica de una función cuadrática es:

Se lee comunmente «f de x», siendo X la variable independiente, a, b y c números reales constantes.

Características de una función cuadrática

Dominio

El dominio es el intervalo de valores admisibles para la variable independiente, comunmente denominada X.

En el caso de la función cuadrática el dominio es el conjunto de los números reales.

En otras palabras podemos elegir cualquier valor de X perteneciente al conjunto de los números reales y encontraremos su valor f (X) correspondiente.

Haz clic aquí para leer el artículo sobre conjuntos numéricos.

Gráfica en el plano cartesiano de una función cuadrática

La gráfica de f (X) en el plano cartesiano es una parábola. Puedes graficar una función cuadrática a partir de una tabla de valores, aquí tienes un ejemplo del procedimiento.

Fig. 1: Gráfica de una función cuadrática con el coeficiente a positivo.

Fig. 2: Gráfica de una función cuadrática con el coeficiente a negativo.

Si te interesa puedes leer un poco más sobre el sistema cartesiano en este artículo.

Raíces de la función

Las raíces de una función cuadrática son los valores de x para los cuales f (x) vale 0, es decir los puntos en los que la función toca al eje de las abscisas (comunmente el eje x).

Se pueden dar tres casos, la función tiene una raíz, es decir hay un único valor de x para el cuál la función vale 0.

La función puede tener dos raíces, es decir hay dos valores distintos de x para los cuales la función vale 0.

La función puede no tener raíces en el conjunto de los números reales pero si en el de los números complejos.

Para calcular las raíces de una función cuadrática usamos una fórmula que se conoce comunmente como fórmula de Bhaskara

Dada la siguiente ecuación:

Los dos valores de x que satisfacen esta ecuación están dados por:

El signo más menos ( ± ) quiere decir que son dos ecuaciones, en una usamos el signo más y obtenemos X1 y en otra usamos el signo menos y obtenemos X2.

Como observación, si lo que está dentro de la raíz cuadrada dá como resultado un número negativo quiere decir que la ecuación no tiene solución en el conjunto de los reales, en este caso gráficamente vemos que la parábola no corta el eje de las abscisas.

Algunos ejemplos en Unity de función cuadrática

Dibujar trayectorias parabólicas

Podríamos estar interesados en dibujar a través de código trayectorias con forma de parábolas, por ejemplo para sistemas de partículas, o para representar el posible trayecto que un proyectil pueda seguir al lanzarlo.

En el siguiente video muestro cómo un conjunto de GameObjects siguen una trayectoria parabólica descrita por dos funciones cuadráticas a las que se pueden ajustar los parámetros.

https://gamedevtraum.com/wp-content/uploads/2019/09/funcion-cuadratica-trayectorias-parabolicas-unity.mp4
Video 1: Las esferas siguen la trayectoria parabólica descrita por las funciones cuadráticas en rojo y verde.

Observación 1: las esferas del mismo color siguen la trayectoria definida por la misma ecuación solo que para una tomo la constante a como positiva y en la otra como negativa, por esta razón se reflejan respecto a los ejes.

Observación 2: Noten que al comienzo y al final del video el valor de a es 0, esto significa que se anula el término cuadrático y nos quedamos con una función lineal.

Artículo sobre función lineal

Implementar magnitudes cuadráticas

En la física existen muchas magnitudes cuadráticas que podríamos estar interesados en implementar en nuestro juego.

Un ejemplo de esto puede ser el movimiento con aceleración constante, es decir objetos que se mueven con velocidad que cambia de manera lineal. Por ejemplo cuerpos en caída libre por acción de la gravedad o vehículos que aceleran.

Algunos tipos de energía como la energía cinética o la energía potencial elástica son proporcionales al cuadrado de una magnitud.

Conclusión

La función cuadrática es una función polinómica de orden 2 cuya gráfica en el plano cartesiano es una parábola.

Las raíces de la función cuadrática son los valores de x en los que se cumple que f (x) = 0. Podemos utilizar la fórmula de Bhaskara para encontrarlas.

Una función cuadrática puede tener dos raíces distintas, una raiz o no tener raíces en el conjunto de los números reales. Si una función cuadrática no tiene raíces reales, gráficamente no intersecta el eje de abscisas.

Hay magnitudes físicas así como también trayectorias de cuerpos acelerados que pueden describirse por funciones cuadráticas, por eso es útil tener conocimiento sobre sus características y así poder implementarlas correctamente en Unity.

Introducción

Hoy les traigo una solución para que el jugador pueda activar botones, interruptores, palancas y más en Unity.

La solución consiste en tres Scripts que interactúan entre sí para resolver la detección de los elementos interactivos y su activación. Además se proveen dos Scripts extra a modo de ejemplo para lograr lo que se observa en el siguiente video:

Hay dos videos para esta solución, en la versión extensa hago la descarga de los archivos de esta página y configuro el sistema de interacción desde cero. En la versión corta explico de manera breve qué archivos contiene el paquete y cómo funciona el sistema.

VERSIÓN CORTA

VERSIÓN DETALLADA

Es importante mencionar que en esta solución no se resuelve ninguna accion en particular, si bien se proveen dos Scripts que ejemplifican la utilización, cada acción particular deberá ser programada.

Descripción de los archivos de descarga

Esta solución consiste en tres Scripts básicos: «CheckInteraction», «InteractionReceiver» y «IAction»

CheckInteraction se encarga de comprobar si se está observando algo con lo que se puede interactuar, se puede agregar a cualquier GameObject pero es coherente colocarlo en nuestro personaje.

InteractionReceiver se coloca en todos los objeto de nuestro mundo que el personaje pueda accionar, por ejemplo botones, palancas, puertas, etc. Los objetos que tengan asignado este Script debe tener también asignado un Collider para que el Script CheckInteraction pueda detecarlos.

IAction es una interfaz de programación, no se aplica sobre ningún GameObject pero es necesaria para la solución y debemos prestar atención a cómo usarla.

Se proveen dos Scripts que resuelven acciones a modo de ejemplo.

Extra: Interfaz gráfica

En el paquete también viene la interfaz gráfica simple que se ve en el video y que permite mostrar mensajes en pantalla.

Ejemplo de Aplicación – Activar botón en Unity

Ahora vamos a ver cómo usar esta solución para poder activar un botón en Unity y este abra un portón.

Para empezar partimos de una escena simple en la que hay un botón y un portón. Estos dos objetos pueden ser reemplazados simplemente con cubos.

Personaje

Colocamos en la escena el prefabricado FPSController de Standard Assets, si no tenés el paquete podés descargarlo haciendo clic acá.

A este prefabricado le vamos a asignar el Script ChequearInteraccion, el campo «minInteractionDistance» es la mínima distancia a la que tiene que estar el jugador para poder interactuar con el objeto, en mi caso pongo 4.

El campo «origenRayo» es el punto desde donde se origina el rayo para chequear la interacción, en este caso coloco la cámara del personaje, de esa forma el personaje podrá «ver» los objetos que estén en el centro de la pantalla.

Fig. 1: El Script ChequearInteraccion está asignado al control en primera persona.

Receptores – Botones o interruptores

Los objetos que se podrán accionar (como botones, interruptores, palancas, etc) tendrán asignado el Script «ReceptorInteraccion», esto hará que el Script «ChequearInteraccion» detecte estos objetos y permita accionarlos con la tecla E.

Podemos agregar un mensaje al receptor de interacción y luego hacer cosas con este mensaje, por ejemplo mostrarlo en un cartel en pantalla como se observa en el video, para esto hay una región marcado con comentarios en el Script ChequearInteraccion en la que se hace un Debug.Log mostrando el mensaje en consola.

Fig. 2: El Script ReceptorInteraccion está asignado al objeto con el que podemos interactuar. Este objeto también tiene asignado el Script switch del ejemplo.

Objetos con acción

Cuando pulsemos el botón van a ocurrir dos cosas, la primera es que el portón se abrirá o se cerrará dependiendo de su estado actual, la segunda acción es que el botón cambia a color verde cuando el portón está abierto y a rojo cuando está cerrado.

Estas dos acciones las programamos individualmente utilizando dos Scripts: Switch asignado al GameObject del botón (figura 2) y Porton asignado al GameObject del portón (figura 3).

Fig. 3: El portón tiene asignado el Script portón que viene en las descargas a modo de ejemplo.

Scripts que resuelven las acciones

Las acciones deberán ser programadas específicamente, es decir deberemos crear más Scripts que resuelvan la acción. Esto va a depender de las necesidades de cada uno.

En este artículo vamos a ver los dos Scripts que resuelven la acción del botón y del portón.

El requisito que deben tener los Scripts de acciones es que implementen la interfaz de programación «IAction» que viene en los archivos de descarga, esto hará que nuestro Script deba tener definido obligatoriamente un método llamado «Activate», dentro del cual haremos todo lo que tiene que hacer la acción al ser activada por el receptor.

En las figuras 4 y 5 vemos ejemplos para el botón y para el portón. Seguramente haga videos explicando cómo resolver más mecanismos utilizando esta solución y también es probable que mejoremos esta solución.

Fig. 4: El Script Switch que viene como ejemplo implementa la interfaz Iaction.

Fig. 5: El Script Porton que viene como ejemplo implementa la interfaz Iaction.

Asignar Acciones al receptor

Una vez que tenemos las acciones programadas, las asignamos a los GameObjects que correspondan y seleccionamos el objeto que usamos como activador de esas acciones (en este caso el botón).

En el Script ReceptorInteraccion tenemos un campo que se llama «ObjectsWithActions», el cual es un vector de GameObjects en el que colocaremos todos los objetos que serán activados al pulsar el botón.

Los objetos que coloquemos aquí deben tener asignado un Script que implemente IAction para funcionar, en el valor «Size» pongo 2 y asigno los GameObjects Porton y Boton, los cuales tienen asignados los Script Switch y Porton respectivamente (ver figuras 3 y 6).

Fig. 6: En el receptor configuramos las acciones.

Con esto logramos que al pulsar el botón el Script ReceptorAccion toma los objetos «Porton» y «Boton», obtenga sus Scripts que implementen la interfaz IAction y les ejecute a todos el método Activate.

Conclusión

En este artículo hemos visto cómo implementar la solución propuesta para resolver la activación de botones, interruptores o palancas en Unity.

La acción que realizarán los interruptores deberá ser resuelta de forma particular con otro Script, el único requisito es que implemente la interfaz IAction y en consecuencia se deba definir un método público llamado Activate.

Introducción

Blender es un programa Open Source que sirve para crear modelos 3D, texturizarlos, animarlos y más aplicaciones. En este artículo voy a mencionar algunos atajos y consejos útiles para empezar a modelar en 3D con Blender.

Luego de tres años de usar Blender para mis proyectos decidí hacer un video hablando sobre los problemas que tuve al principio y dando alguno consejos y atajos para facilitar el aprendizaje del programa.


Hoja con atajos útiles para Blender

En la figura 1 están listados los atajos que menciono en este video. Hay que tener en cuenta de que muchos de estos atajos se utilizan en distintas ventanas y en general producen efectos similares, pero en este caso todos fueron usados en la ventana Viewport.

Fig. 1: Hoja con algunos atajos para Blender 2.8.

También hay que presentar atención al puntero del mouse, si no lo tenemos sobre la ventana Viewport los atajos no funcionarán.

Ventanas en Blender

Ver esta parte del video

Al principio se nos puede hacer un lío con las ventanas en Blender, pero basta con entender cómo funcionan y podremos adaptarlas a nuestras necesidades.

Las ventanas en Blender se pueden ajustar en tamaño haciendo clic izquierdo en la línea que las separa y moviendo en la dirección que necesitemos escalar.

Podemos hacer que cualquier ventana de Blender muestre todo tipo de contenido utilizando el ícono que se encuentra en la esquina superior izquierda de cualquier ventana.

Podemos partir las ventanas haciendo clic en la esquina inferior izquierda y arrastrando en la dirección horizontal o vertical.

Manejo de la Vista 3D

Ver esta parte del video

Manejar la vista 3D era bastante difícil cuando empecé, espero que estos atajos que doy en esta parte te ayuden, eso sí, hay que practicar, no basta con saber la teoría.

El Modo Objeto

Ver esta parte del video

Trabajar con distintos objetos, moverlos, rotarlos y escalarlos en el espacio. Acá doy los atajos del teclado más importantes que en general se utilizan en varias partes, no sólo el modo objeto.

El Modo Edición

Ver esta parte del video

Trabajar con la geometría de un objeto, aprender a hacer extrusiones, crear cortes y trabajar con edge loops.

Origen de los objetos en Blender

Ver esta parte del video

Poner el origen de un objeto en donde queramos es importante para luego poder manipularlo en el espacio 3D.

Modelar a partir de imagen de referencia

Ver esta parte del video

Al principio esta es la forma más simple y divertida de crear modelos 3D, crear modelos 3d a partir de una imagen de referencia que coloquemos en el fondo es ideal para piezas de revolución como por ejemplo una botella, un reloj de arena, tuercas, etc.

Te invito a ver este artículo sobre un rostro 3D hecho en Blender utilizando imagen de referencia.

Conclusión

Blender es un Software versátil que nos sirve para crear Assets para nuestros proyectos, tener un conocimiento básico de Blender nos puede ayudar a prototipar escenarios, objetos y avanzar en aspectos fundamentales de nuestro proyecto, cuando la idea tome forma se pueden mejorar los modelos, comprarlos o delegar este trabajo a otra persona.

En este artículo se muestran los atajos y un poco de información, para que esto sea realmente de utilidad es necesario abrir Blender y poner en práctica lo que se ve en el video.


Introducción

En este artículo vamos a ver una forma simple de hacer un Mini Mapa en Unity que muestre una vista superior del escenario y siga al personaje. Para resolver esto no será necesario escribir código.

Fig. 1: Esto es lo que conseguimos al final del artículo y el video.

🟢 En el siguiente video explico cómo construir el minimapa para Unity y cómo funciona

Artículo: Cómo mostrar este Minimapa en un segundo monitor

Archivos de descarga:

A continuación puedes descargar un archivo para importar el paquete de Unity en tu proyecto.

Entre las descargas hay un par de archivos png para usar como máscaras, además hay un Script muy sencillo que hará que el objeto al que está asignado, siga la posición de otro objeto que queramos.

Si no tienes el paquete Standard Assets lo podés conseguir haciendo clic aquí.

Estado inicial del proyecto

Para comenzar hice un escenario simple y arrastré a la escena el prefabricado «FPSController» que es un controlador en primera persona, de esa forma al entrar en el modo juego ya tengo un prototipo armado.

Fig. 2: Antes de empezar armé un escenario simple para que se vea en el mini mapa.

Fig. 3: Coloqué en la escena el prefabricado FPSController de Standard Assets.

Vamos a importar los archivos que vienen dentro el ZIP de descarga, se trata de tres imágenes png para usar como máscara y un Script simple que vamos a aplicar al final como una segunda opción.

Fig. 4: En el paquete de descarga vienen estos archivos, los importamos a Unity.

Seleccionamos las tres imágenes y en el inspector las configuramos con el tipo Sprite, luego aplicamos los cambios.

Fig. 5: Seleccionamos las tres imágenes y en el inspector las configuramos como Sprites y aplicamos.

Esto permitirá utilizar estas imágenes en la interfaz gráfica y también hará que tengan transparencia, como observamos en la figura 6.

Fig. 6: Ahora las imágenes tienen transparencia.

Crear Mini Mapa en la interfaz gráfica

El mini mapa aparecerá como una imagen en la interfaz gráfica, esto significa que siempre estará visible por encima de la cámara del jugador.

Para empezar vamos a crear un objeto tipo Canvas y configurar el componente escalador de Canvas con la opción «Scale with Screen Size» y resolución 1920×1080. Esto hará que a distintas resoluciones el mini mapa tenga distintos tamaños.

Fig. 7: Creamos un GameObject tipo Canvas para los elementos de la interfaz gráfica.
Fig. 8: Configuramos el Canvas Scaler como se observa para que los elementos se escalen automáticamente.

Como hijo del Canvas vamos a crear un GameObject tipo Image que llamaremos «Fondo», este objeto actuará como un recuadro para el mini mapa.

Fig. 9: Creamos un GameObject tipo Image que va a actuar como el fondo del mini mapa.

En la figura 10 podemos ver la configuración del componente imagen del fondo.

Fig. 10: Componente Image del fondo del mini mapa.

Luego como hijo del fondo vamos a crear otro GameObject tipo Image que llamaremos «Mascara». A este objeto le vamos a asignar la componente «Mask» utilizando el botón Add Component en el inspector.

Fig. 11: Creamos otro GameObject tipo Image que va a servir de máscara para darle la forma al mini mapa.

Fig. 12: Componente Image y Mask para enmascarar el mini mapa.

Por último vamos a crear un GameObject tipo Raw Image que llamaremos MiniMapa, este objeto será el que finalmente muestre el mapa.

Fig. 13: Creamos un GameObject tipo Raw Image que va a ser el que muestre el mapa.

En la figura 14 vemos la componente Raw Image del GameObject MiniMapa.

Fig. 14: Componente Raw Image del objeto mini mapa.

Cámara del Mini Mapa

El mini mapa mostrará todo lo que esté viendo una segunda cámara que situaremos en lo alto mirando hacia abajo y configuraremos para que siga todo el tiempo los pasos del personaje.

Creamos el objeto Cámara y lo ponemos como hijo del GameObject FPSController que en mi caso es el personaje, dependiendo de tu proyecto deberías ponerla como hijo del objeto que la cámara tiene que seguir.

Es importante que sea hijo del personaje porque de esa forma seguirá todo el tiempo los movimientos del personaje. En la figura 15 se ve cómo debe quedar esta relación jerárquica

Fig. 15: Creamos una segunda cámara y la posicionamos arriba del personaje apuntando hacia abajo.

Ubicación y Forma del Mini Mapa

Ahora seleccionamos el GameObject Fondo de la figura 15 que contiene todos los elementos del mini mapa y lo situamos en donde nos guste dentro del Canvas.

Fig. 16: Acomodamos el fondo en donde nos guste dentro del Canvas.

Fig. 17: Configuración de la componente Rect Transform del fondo.

Dejo capturas de la configuración de las componentes RectTransform de los GameObjects «Fondo», «Mascara» y «MiniMapa». Lo que hice fue setear el ancho y el alto de las tres imágenes en 300 para el fondo y el mini mapa y 280 la máscara.

Fig. 18: Configuración de la componente Rect Transform de la máscara.

Fig. 19: Configuración de la componente Rect Transform del mapa.

Básicamente podemos hacer que nuestro Mini Mapa tenga la forma que queramos utilizando una máscara apropiada.

Fig. 20: Al colocar un Sprite en la máscara, las partes con transparencia son eliminadas mientras que las partes blancas de la imagen quedan visible.

Fig. 21: Podemos asignar el mismo u otro Sprite que queramos para el fondo.

Cámara Perspectiva y Ortográfica

Una alternativa interesante es configurar la cámara del mini mapa para que proyecte de manera ortográfica, como se ilustra en la figura 22, seteamos el modo Orthographic y en el valor Size ponemos 25 o el valor que queramos, este último parámetro hará que el mini mapa muestre más o menos área.

Fig. 22: Configuramos la cámara como ortográfica con valor 25 en el parámetro Size.

La diferencia que hay entre proyección perspectiva y ortográfica es que la primera tiene profundidad lo que nos permite percibir la tridimensionalidad en la imagen que vemos, esto hace que los objetos que estén más cercanos a la cámara se verán relativamente más grandes en comparación con los que estén más lejos.

En cambio en la perspectiva ortográfica todos los objetos se proyectan en el plano de la cámara.

En la figuras 23 y 24 vemos dos imágenes tomadas con la cámara y los objetos situados en la misma posición, pero en la primera la cámara es perspectiva y en la segunda ortográfica.

Fig. 23: En la cámara perspectiva los objetos más cercanos se verán más grandes.

Fig. 24: En la cámara ortográfica los objetos se proyectan sobre el plano y objetos del mismo tamaño se ven iguales.

Renderizar imagen sobre textura

Para mostrar lo que está viendo la cámara en la imagen del mini mapa usaremos un objeto llamado Render Texture.

Fig. 25: En la carpeta del proyecto creamos un objeto tipo Render Texture.

En el inspector de este objeto podemos setear el tamaño de la textura, mayor tamaño resultará en una mejor resolución para el mapa.

Fig. 26: Podemos ajustar el tamaño de la textura lo que mejorará la resolución del mini mapa.

Seleccionamos la cámara del mini mapa y en el campo «Target Texture» asignamos esta Render Texture, debería verse como en la figura 27.

Fig. 27: Asignamos la Render Texture al campo «Target Texture» de la cámara del mini mapa.

Luego vamos al GameObjec MiniMapa de la jerarquía (el que tenía asignada la componente Raw Image) y en el campo Texture de Raw Image asignamos la Render Texture.

Fig. 28: En el componente Raw Image del mini mapa asignamos en «Texture» la Render Texture.

En la figura 29 vemos que ahora el componente Raw Image muestra lo que está viendo la cámara del mini mapa.

Fig. 29: Vemos que ahora la silueta de Skinner muestra una vista superior del escenario, lo cual funciona a modo de mapa.

Carteles e Íconos en el mini mapa

Es común que los mini mapas nos muestre carteles o íconos con puntos de interés en el escenario.

Vamos a ver una forma de agregar estos elementos sin necesidad de programar.

Para empezar creamos un texto 3D y lo configuramos, en mi caso le escribo «Cuartel General».

Fig. 30: Creamos un 3D Text para colocar carteles en el mini mapa.

Fig. 31: Componente Text del texto 3D.

Colocamos el texto en algún lugar del escenario.

Fig. 32: Acomodamos el texto 3d en el escenario.

Fig. 33: La cámara del personaje puede ver el texto flotando.

El problema con esto es que el jugador también puede ver el texto 3D flotando en el aire.

Para solucionar esto vamos a hacer uso de las Layers o capas, en el inspector de cualquier GameObject hacemos clic en el menú desplegable Layer y luego en Add Layer.

Fig. 34: En el inspector hacemos clic en Layer y luego Add Layer para crear una nueva capa.

Vamos a crear una nueva capa llamada Mini Mapa, luego seleccionamos el texto 3D y le asignamos la capa recién creada.

Fig. 35: Creamos una nueva capa llamada Mini Mapa.

Fig. 36: Al texto 3D le asignamos la capa Mini Mapa recién creada.

Ahora vamos a la cámara del personaje o la cámara que estemos utilizando para mostrar el juego y en el campo Culling Mask quitamos de la lista la capa Mini Mapa.

Fig. 37: En la cámara del personaje, en el campo Culling Mask desmarcamos la capa Mini Mapa.

En las figuras 38 y 39 vemos que la cámara del juego no ve el texto 3d flotando, pero en la cámara del mini mapa si se puede ver.

Fig. 38: Al quitar la capa Mini Mapa de la lista el texto 3D ya no es visible para esa cámara.

Fig. 39: La cámara del mini mapa puede ver ese texto.

El mismo principio podemos usar para colocar íconos en el escenario, utilizamos el GameObject tipo Sprite y lo configuramos a gusto.

Fig. 40: Arrastramos la silueta de Skinner a la jerarquía para crear un Sprite y nos aseguramos que sea hijo del personaje.

Fig. 41: Configuramos el Sprite Renderer como nos guste.

Para el ícono del personaje no debemos olvidar ponerlo como hijo del GameObject, de esta forma el ícono seguirá los pasos del personaje.

Fig. 42: Acomodamos la silueta arriba del personaje.

Fig. 43: Ahora en donde está el personaje tendremos un ícono para identificarlo.

Script

Por último podemos cambiar la manera en la que la cámara del mini mapa sigue al personaje. Con un Script podemos tener un mayor control sobre el mini mapa, implementar un zoom por ejemplo.

En las descargas hay un Script que se llama SeguirObjeto que hará este trabajo.

Para utilizarlo debemos sacar la cámara del mini mapa de entre los hijos del objeto personaje. Luego asignar el Script a la cámara, como se muestra en la figura 45.

El Script tiene un campo llamado Target que permite poner el objetivo al que la cámara va a seguir, en mi caso pongo el prefabricado del personaje. El Vector Offset permite setear la distancia a la que la cámara seguirá al personaje.

Fig. 44: Para controlar la cámara con el Script que viene en la descarga la sacamos de entre los hijos del personaje.

Fig. 45: Agregamos a la cámara el Script y colocamos como objetivo el prefabricado del personaje y configuramos el offset.

Utilizando este Script evitamos que el mini mapa gire junto con el personaje, ya que la cámara sólo copia los movimientos del jugador pero no el ángulo.

Fig. 46: Con esto logramos que el mini mapa deje de girar junto con el personaje.

Conclusión

Probando descubrí varias formas de resolver este problema, pero esta solución me pareció la mas interesante porque no requería de programación para funcionar.

Podemos resolver el problema usando sólo lo que nos ofrece Unity, pero para darle una forma circular por ejemplo vamos a necesitar una máscara como la que viene en los archivos de descarga.

El componente Render Texture nos permite renderizar en tiempo real lo que está viendo una de las cámaras.

Podemos añadir elementos indicativos al mini mapa como textos 3D e íconos y hacer que sólo una de las cámaras pueda verlos, para esto utilizamos las Layers o capas.

Dependiendo de las necesidades de nuestro proyecto esta solución nos puede servir o no, si necesitamos mostrar información más compleja en el mini mapa como rutas o la posibilidad de hacer zoom, vamos a tener que crear un Script y programar una solución a medida.

Introducción

Este artículo funcionará como la parte práctica del artículo anterior sobre Clases en Programación. Vamos a resolver un ejercicio sobre estructura de clases y a implementarlo en C# en Unity.

Si no sabés qué es una clase en programación estaría bueno que veas el artículo que escribí sobre el tema antes de continuar.

🟢 VÍDEO: Acerca del concepto de «CLASE» en programación orientada a objetos


Planteo del ejercicio

Vamos a ver un ejemplo que se me ocurrió al momento de escribir este artículo. Se trata de modelar el comportamiento de una aldea que tiene habitantes y estos habitantes pueden ser aldeanos que trabajan o guerreros que luchan.

He resaltado lo que considero palabras claves en el planteo del problema, en principio parece que al menos vamos a necesitar dos clases para resolverlo, una para la aldea y otra para los habitantes, pero vamos a considerar el hecho de que tanto aldeanos como guerreros en sí son habitantes.

Aldeanos y Guerreros tendrán características y comportamientos comunes que estarán definidos en la clase Habitante y luego las clases Aldeano y Guerrero heredarán las características y comportamientos de Habitante.

Una aldea tiene habitantes por lo que la clase Aldea tendrá entre sus datos una o más instancias de la clase Habitante.

Quisiera mencionar que el hecho de conocer la diferencia entre clases e instancia de clases es importante muy importante, pero este artículo no trata sobre eso así como tampoco trata sobre herencia.

De momento la idea es que vamos a usar la clase Habitante para a crear todos los objetos Habitante que necesitemos, estos objetos tendrán sus propios valores (por ejemplo nombres o edad distintas) pero en sí respetarán la estructura definida en la clase.

Diagrama de Clases

Entonces dijimos que vamos a usar cuatro clases, Aldea, Habitante, Aldeano y Guerrero. La clase Aldea tiene entre sus datos una o más instancias de la clase Habitante. Aldeano y Guerrero son un tipo específico de Habitante por lo que se usará herencia.

El diagrama de clases de la figura 1 está basado en el párrafo anterior.

Fig. 1: Ejemplo de lo que podría ser un diagrama de clases en un proyecto

En el diagrama observamos un «1..*» encima de Habitante, esto quiere decir que Aldea tendrá uno o más objetos Habitante.

Además observamos que la herencia de clases se representa con una flecha ascendente hacia la clase padre.

Diagrama de Objetos

Una vez definida las clases que estarán presentes pensamos en sus posibles campos y métodos, en esta parte está bueno reflexionar sobre la naturaleza del objeto que queremos modelar, cuáles son sus características, qué información maneja y cuál puede ser su comportamiento.

Voy a inventar algunos campos y métodos para las clases porque esta práctica de clases de programación se trata más que nada sobre el planteo de estructura de clases y su posterior implementación en Unity en lenguaje C#, pero sin entrar en detalles de qué se va a hacer con esto.

En la figura 2 se ven los diagramas de objeto de las cuatro clases.

Fig. 2: Utilizando estas cajas indicamos los atributos y métodos, públicos y privados de las clases.

Ahora voy a pasar a explicar en detalle cómo se interpretan las cajas de la figura 2.

El objeto Aldea

Para este objeto consideré que toda aldea tiene un nombre y un grupo de habitantes, así que para sus campos voy a usar una colección de objetos Habitante llamada «habitantes», un string llamado «nombre» y un entero llamado «poblacion». Estos campos los coloco en la parte de arriba de la caja como vemos en la figura 2.1.

Algunos métodos del objeto Aldea pueden ser «NuevoHabitante» que se ejecutará cada vez que nazca un habitante. «ObtenerNombre» y «ObtenerPoblacion» que nos devolverán los datos correspondientes (se conocen como «Getters» y son útiles por ejemplo para hacer estadísticas de todas las aldeas que podamos tener).

El método «MuereHabitante» se puede encargar de modificar la población y eliminar el habitante muerto de la colección de habitantes.

Fig. 2.1: Diagrama de objetos para la clase Aldea, en él vemos los campos y métodos de la clase.

Por último el método «AlimentarHabitantes» que puede recorrer la colección de habitantes ejecutando métodos apropiados en cada uno de ellos, o chequear la reserva de comida y restar tantas unidades como población haya.

El objeto Habitante

Habitante va a ser la clase padre (o superclase) de Aldeano y Guerrero, en esta clase estará definido el comportamiento y los datos que tengan en común Aldeanos y Guerreros.

Todo habitante tiene un nombre representado por un String, un entero para la edad, otro para la salud y otro para la defensa.

Fig. 2.2: Diagrama de objetos para la clase Habitante, en él vemos los campos y métodos de la clase.

Entre los métodos de Habitante tenemos los «Getters» para obtener nombre y edad, un método privado para envejecer y un metodo público para que el habitante se mueva a una posición.

El objeto Aldeano

El objeto Aldeano es un caso más específico de la clase Habitante, entre sus campos tenemos un string que indica el nombre del trabajo y una variable booleana para saber si se encuentra trabajando o no.

Los métodos de Aldeano están relacionados a lo que podrían ser sus actividades, por ejemplo Recolectar, Reparar, Talar, Pescar y también un método privado para que entregue los recursos que haya conseguido. Este último método lo puede ejecutar internamente Aldeano por ejemplo cuando esté al límite de su capacidad de recolección.

Fig. 2.3: Diagrama de objetos para la clase Aldeano, en él vemos los campos y métodos de la clase.

El objeto Guerrero

La clase Guerrero es un caso más específico de la clase Habitante.

Un Guerrero puede estar atacando o no para lo que usamos la variable booleana «atacanado», tiene asignada un arma representada por un String y tiene ciertos puntos de ataque representados por una variable entera.

Entre sus métodos tenemos Atacar, Formacion, Defenderse y un método privado para entregar el botin que obtuvo después de salir exitoso de una batalla.

Fig. 2.4: Diagrama de objetos para la clase Guerrero, en él vemos los campos y métodos de la clase.

Implementación en Unity

Ahora pasamos a la parte buena, vamos a tomar el diagrama de clases y el diagrama de objetos de las figuras 1 y 2 y utilizarlos como referencia para crear las clases en Unity.

Antes de empezar tal vez necesitás saber cómo usar Unity, acá en la página tengo un par de series, una llamada «Fundamentos de Unity» y la otra «Juego del Laberinto«, la primera tiene temas específicos y la segunda es un mini proyecto. Podés encontrar todos los artículos usando el menú de navegación y también están los videos en el canal.

Sigamos, en un proyecto de Unity creamos las cuatro clases del diagrama de la figura 1.

Fig. 3: Creamos los Scripts C# necesarios acorde al diagrama de clases de la figura 8.

Por convención los nombres de las clases comienzan con mayúscula y se utiliza CamelCase si el nombre de la clase tiene más de una palabra. Por ejemplo la clase «Arma de Largo Alcance» podría escribirse «ArmaLargoAlcance», mejor aún si nos acostumbramos a usar nombre en inglés «LongRangeWeapon».

Una aclaración, como se vio en el artículo: «¿Qué es una Clase en Programación?», al crear una nueva clase en Unity, esta va a heredar de MonoBehaviour, lo que permitirá que Unity la considere en su ciclo interno. Vamos a dejar esta herencia así como está, aunque no lo hayamos explicitado en el diagrama de clases de la figura 1.

Si quisiéramos hacer un diagrama más acertado deberíamos colocar la clase MonoBehaviour y hacer que tanto Aldea como Habitante hereden de ella.

Como aclaración está bueno decir que nuestras clases van a ser MonoBehaviours, sin embargo no tiene sentido plantear un diagrama de clases extremadamente completo ya que MonoBehaviour hereda de la clase Behaviour, que a su vez hereda de la clase Component, quien finalmente hereda de la clase Object. No es necesario ir tan lejos, hay que enfocarnos en lo importante para resolver nuestro problema.

La clase Aldea

Para escribir el cógido correspondiente en la clase Aldea debemos mirar el diagrama de Objeto de Aldea y simplemente conocer la sintaxis del lenguaje orientado a objetos que vamos a utilizar, en este caso C#.

En la figura 4 vemos cómo se implementac lo especificado en el diagrama de la figura 3.1 utilizando lenguaje C# en Unity.

Noten cómo se respetan los nombres, los tipos de datos y la visibilidad indicada en el diagrama de objetos.

Fig. 3.1: Diagrama de objetos para la clase Aldea, en él vemos los campos y métodos de la clase.

Fig. 4: Definimos los atributos y métodos necesarios, teniendo en cuenta las cajas de la figura 9.

Esta es la idea de la práctica de Clases en programación, crear clases en Unity basándonos en un análisis del problema y los diagramas de clases y objetos.

No vamos a escribir nada en los métodos porque es algo que depende de cuál es la finalidad de la solución que estamos planteando.

La clase Habitante

Fig. 2.1: Diagrama de objetos para la clase Habitante, en él vemos los campos y métodos de la clase.

Fig. 5: Definimos los atributos y métodos necesarios, teniendo en cuenta las cajas de la figura 9.

La clase Aldeano

Fig. 2.1: Diagrama de objetos para la clase Aldeano, en él vemos los campos y métodos de la clase.

Fig. 6: Definimos los atributos y métodos necesarios, teniendo en cuenta las cajas de la figura 9.

La clase Guerrero

Fig. 2.1: Diagrama de objetos para la clase Guerrero, en él vemos los campos y métodos de la clase.

Fig. 7: Definimos los atributos y métodos necesarios, teniendo en cuenta las cajas de la figura 9.

Conclusión

En esta práctica de Clases en Programación hemos visto cómo a partir del planteo de un problema proponemos una estructura de clases para modelar el comportamiento de los elementos.

La estructura de clases la representamos utilizando un Diagrama de Clases en el que mostramos cuál es la relación que existe entre ellas.

Las Clases tendrán sus miembros, es decir Campos y Métodos, podemos representarlos utilizando Diagramas de Objetos, en los que mostramos nombres y tipos de datos para los Campos y los métodos que tendrá la clase. Además la visibilidad de cada uno.

Todo lo anterior es independiente del lenguaje de programación, se trata sobre modelar de manera intuitiva una solución orientada a objetos.

Para implementarlo en Unity en lenguaje C# deberemos conocer la sintaxis propia del lenguaje, sin embargo es sumamente interesante saber que esto mismo se podría reutilizar por ejemplo para una aplicación Android utilizando lenguaje Java en Android Studio.

Introducción

En este artículo vamos a ver qué es una CLASE en programación, cuáles son sus componentes, diagramas de clase y vamos a ver ejemplos de clases en lenguaje C#. Además podrás encontrar dos vídeos del canal que hablan sobre el concepto de clase en programación y otro que muestra el análisis de un diagrama de clases propuesto para resolver un trabajo.

Un vídeo hablando sobre diagramas de CLASE en programación

En el siguiente vídeo se hace el análisis de un diagrama de clases propuesto para resolver un trabajo final sobre programación orientada a objetos.


¿Qué es una CLASE en programación?

El concepto de Clase está ligado a la programación orientada a objetos.

Una clase es una herramienta que tenemos para modelar objetos de programación, lograr que se comporten como queremos y hacer tantas copias de estos objetos como necesitemos.

Por ahora simplemente pensemos que es código que está encapsulado en un Script de programación.

Cuando programamos en C# utilizando Unity, al crear un nuevo Script automáticamente creamos una clase cuyo nombre coincide con el nombre del archivo del Script.

La terminología que usamos al referirnos a una clase probablemente cambia dependiendo de la bibliografía o los profesores, pero si logramos captar la idea básica podremos usar este conocimiento para crear soluciones para nuestros proyectos.

¿Para qué sirven las Clases en programación?

Las clases en Programación se utilizan para crear modelos computacionales de objetos, estos objetos pueden ser modelos de cualquier cosa, desde objetos tangibles del mundo real hasta objetos abstractos que cumplan una determinada función. Por ejemplo podemos modelar un objeto de clase «Árbol», en el cual seleccionaremos las características y funciones más relevantes de los árboles, dependiendo del nivel de complejidad de nuestro modelo.

La información y funciones necesarias para llevar a cabo el comportamiento del objeto estarán definidas dentro de la clase de programación. De esta forma, utilizando la Clase como molde, podemos crear tantos objetos de la misma clase como necesitemos.

Cómo crear una CLASE en Programación

Dependiendo de lo que estemos haciendo, crear una clase se hará de una forma u otra. En general lo que haremos será crear un archivo de instrucciones con la extensión del lenguaje de programación que estemos usando y cuyo nombre coincidirá con el nombre que le demos a la clase en su declaración.

En general los entornos de desarrollo nos simplifican el proceso de creación de una clase. Por ejemplo en Unity desde la carpeta del proyecto podemos hacer clic derecho y usar los menúes para crear un nuevo Script C#, como se muestra en la figura 1.

Fig. 1: Desde la ventana proyecto se puede crear nuevos Script C#.

Para mostrar algunos ejemplos de clases en Unity, voy a crear los tres Scripts que se observan en la figura 2.

Fig. 2: Voy a crear tres Scripts para ejemplificar las clases en programación.

Ejemplo 1, una Clase de Programación genérica

Cuando creamos un nuevo Script, al abrirlo nos vamos a encontrar con algo de código ya escrito.

En la figura 3 podemos observar que en las tres primeras líneas se importan algunas librerías que nos permitirán acceder a la funcionalidad del motor Unity.

Luego se observa que se define una clase llamada «Clase1», la definición de la clase empieza en la línea 5 y termina en la línea 18 con el cierre de la llave.

Algo que también vemos es el «MonoBehaviour» a la derecha del nombre de la clase, esto quiere decir que nuestra «Clase1» va a ser una extensión de la clase MonoBehaviour, lo que se conoce como herencia.

Herencia es un tema genial y no vamos a profundizar en este artículo, por el momento pensemos que si una clase llamada Clase1 hereda de la clase MonoBehaviour, quiere decir que Clase1 es en sí un MonoBehaviour pero con funcionalidad más específica.

Es como pensar en perros y gatos, se trata de especies distintas pero comparten ciertas características como el número de patas y también tienen comportamientos similares como caminar, correr, comer y respirar. Esto hace que podamos agruparlos por ejemplo en una clase llamada SerVivo, Animal o Cuadrupedo (no se usa acentos en programación).

MonoBehaviour es como la clase básica de los GameObjects en Unity y permite que estos puedan ser inicializados automáticamente a través del método Start y también se ejecutan automáticamente los métodos Update (también definidos automáticamente al crear una clase, líneas 8 y 14 de la figura 3) y FixedUpdate por ejemplo. Para más detalles sobre la clase MonoBehaviour puedes echar un vistazo a la API de Unity.

Fig. 3: El primer ejemplo muestra cómo se ve un nuevo Script. Se trata de la clase Clase1 que hereda de la clase MonoBehaviour

Ejemplo 2, Subclase de Object

En el ejemplo de la figura 4 se ha removido la herencia de MonoBehaviour, esto implica que la Clase2 va a heredar implicitamente de la clase «Object», la cual probablemente es la madre de todas las clases que vamos a usar en Unity.

Al remover el MonoBehaviour los comentarios en verde en las líneas 7 y 13 de la figura 4 dejan de ser verdad. Estos métodos Start y Update no serán ejecutados automáticamente.

Normalmente no se suele utilizar en Unity este tipo de clases que deriven directamente de Object.

Fig. 4: En el segundo ejemplo eliminamos la herencia de MonoBehaviour, lo que quiere decir que la clase Clase2 hereda de Object.

Ejemplo 3, Subclase de otra clase

Este ejemplo es similar al ejemplo 1, podemos hacer clases que deriven de otras clases (herencia de clases), no necesariamente MonoBehaviour sino cualquier clase de UnityEngine o que hayamos creado.

En la figura 5 vemos que la clase Clase3 hereda de Clase2.

Fig. 5: La clase Clase3 hereda de la clase Clase2.

Ejemplo 4, Clases de Programación Java

Utilizando el IDE NetBeans hice un par de clases en Java para mostrar, si bien Unity no se utiliza Java, es interesante ver las similitudes y diferencias.

Las siguientes clases serán análogas a las creadas en los ejemplos 2 y 3.

La figura 6 muestra una clase llamada Clase1 que deriva de Object mientras que la figura 7 muestra una clase llamada Clase2 que deriva de Clase1.

Fig. 6: Definición de clase en Java utilizando el IDE NetBeans.

Fig. 7: Para indicar que la herencia se utiliza la palabra extends.

Componentes o miembros de una Clase de Programación

Las clases en programación las utilizamos para modelar objetos que queremos que cumplan una función.

Campos

Son los datos que tiene la clase, pueden ser variables o incluso instancias de otras clases.

Por ejemplo una clase llamada «Auto» podría tener una variable booleana para indicar si está encendido o no. Una variable float para indicar la velocidad y otra para el combustible restante.

Podríamos utilizar un vector de tres dimensiones (clase «Vector3» en Unity) para determinar la velocidad y dirección de movimiento.

También la clase «Auto» podría tener entre sus campos la instancia de otra clase llamada «Motor», la cual a su vez tendrá definido sus propios miembros.

Métodos

Los métodos son los que proveen funcionalidad a una clase, permitiéndole realizar tareas, manipular sus campos e interactuar con otras clases.

Para saber más sobre métodos te invito a leer este artículo sobre métodos en programación.

Volviendo al ejemplo de la clase «Auto» que tiene entre sus campos la clase «Motor».

Supongamos que Auto tiene un método llamado «Acelerar» que se encargue de aumentar progresivamente la velocidad del vehículo.

El diagrama de Clases

Una herramienta útil a la hora de reflejar la estructura de clases que haremos para plantear una solución es el diagrama de clases, en el mostraremos las distintas clases, la relación que existe entre ellas y mostraremos la herencia de clases que pueda haber.

En la figura 8 vemos un ejemplo de diagrama de clases basado en el ejercicio práctico de clases en programación.

Fig. 8: Ejemplo de lo que podría ser un diagrama de clases en un proyecto

Diagrama de Objetos

Una vez que tenemos definidas las clases que estarán presentes pensamos en sus posibles campos y métodos, en esta parte está bueno reflexionar sobre la naturaleza del objeto que queremos modelar, cuáles son sus características, qué información maneja y cuál puede ser su comportamiento.

Podemos representar los campos y métodos de una clase usando «diagramas de Objetos» que en general se dibujan con una caja por cada clase, indicando campos y métodos usando los nombres que les vamos a dar en el script y separados por una línea (en mi caso a los métodos les agrego los símbolos «( )» para indicar que se trata de métodos). Para los campos indicamos también el tipo de dato.

Otra cosa que podemos indicar es la visibilidad de los campos y métodos, utilizando un signo «+» para los campos y métodos públicos y el signo «-» para campos y métodos privados.

En la figura 9 se observan diagramas de Objetos para las 4 clases del diagrama de clases de la figura 8.

Fig. 9: Utilizando estas cajas indicamos los atributos y métodos, públicos y privados de las clases.

Conclusión

En este artículo hemos visto qué es una clase en programación, la cual es como una plantilla que nos permitirá crear todos los objetos que queramos y que tendrán los atributos y métodos que hemos definido dentro de la clase.

Las clases tienen definido en su interior campos y métodos que en conjunto definirán su naturaleza y comportamiento.

El diagrama de clases nos ayuda a representar la relación que existe entre nuestras clases.

El diagrama de objetos permite representar los campos y métodos de una clase junto con su visibilidad.

Información actualizada sobre esta entrada

Este artículo pertenece a una serie que consiste en hacer un juego simple en primera persona acerca de encontrar objetos dentro de un laberinto. Es una de mis primeras series de cuando empecé el canal, ahora he mejorado mucho este proyecto y puedes descargar el código fuente para importarlo en tu propio proyecto de Unity. Algún día vamos a hacer un remake de esta serie, suscríbete a mi canal para estar al tanto del nuevo contenido sobre Blender, Unity y programación.

Sígueme en itch.io y descarga el código fuente de este proyecto

PUEDES TESTEAR ESTE JUEGO AQUÍ. TAL VEZ TARDE UN POCO EN CARGAR
🔻

MOVEMENT: WASD CAMERA LOOK: MOUSE

Introducción al artículo original

En este artículo vamos a ver una forma de resolver las interacciones en Unity entre el jugador y objetos como la puerta o el pedestal que contiene la espada.

Para ello haremos uso del método RayCast que nos permitirá trazar rayos desde el jugador a la posición a donde está mirando y examinar contra qué chocan esos rayos.

Página principal del proyecto

Antes de leer el artículo aquí tienes el vídeo correspondiente a esta entrada. Interacciones en Unity entre el personaje y las puertas.


Actualmente he creado una mejor versión del sistema de interacción que está basada en la que hacemos aquí.

Ver el nuevo sistema de interacción

Archivos de descarga

¿Qué vamos a hacer en este artículo?

Vamos a comenzar colocando fuego sobre las antorchas de las puertas, para ello vamos a usar uno de los prefabricados de Standard Assets.

Fig. 1: Vamos a hacer que las antorchas estén encendidas.

El objetivo principal es hacer que el jugador sea capaz de interactuar con las puertas y con el pedestal, esto lo resolveremos utilizando trazado de rayos y analizar contra qué chocan esos rayos.

Fig. 2: Al acercarnos a la puerta nos informará si podemos irnos o aún debemos cumplir el objetivo.

Fig. 3: Al acercarnos el pedestal nos aparece un mensaje indicando que podemos interactuar.

Importar archivos y configuración

Comenzamos descargando y extrayendo el archivo proporcionado más arriba. En la figura 4 se observan los archivos que vienen en el paquete.

Fig. 4: Archivos que vienen en el ZIP.

Seleccionamos estos archivos y los llevamos arrastramos hacia la ventana de Unity.

Fig. 5: Llevamos los archivos extraidos a Unity.

Luego creamos todas las carpetas necesarias para organizarnos de acuerdo a nuestras necesidades.

Fig. 6: Vamos a crear una carpeta para las fuentes de texto.

Vamos a modificar el sonido de las pisadas del prefab first person controller de Standard Assets, reemplazando los sonidos con los que vienen en el paquete.

Fig. 7: Seleccionamos el prefab del personaje y vamos al final del Script donde están asignados los sonidos de pisadas.

Fig. 8: Buscamos los nuevos sonidos importados.

Los llamé de la misma forma que los sonidos de Standard Assets, pero en la figura 9 los sonidos que están aplicados son los que vienen en la descarga. Además eliminé los sonidos de salto y aterrizaje.

Fig. 9: Asignamos los nuevos sonidos y eliminamos los otros dos.

Fondo para los textos

Ahora vamos a aplicar la fuente que viene en el paquete a los textos de la interfaz gráfica. Seleccionamos el GameObject Timer que tiene asignado un componente Text, en la figura 11 vemos que la fuente asignada es Arial.

Fig. 10: Seleccionamos el gameobject Timer de la jerarquía, creado anteriormente.

Fig. 11: En el inspector vamos a modificar la fuente estándar por la que viene en el paquete.

Hacemos clic sobre el punto a la derecha de Arial para seleccionar una nueva fuente.

En mi caso voy a usar «akaPostley» que es la fuente que suelo utilizar cuando hago videillos para el canal. Pero si lo prefieren pueden buscar su propia fuente o incluso crear una personalizada.

Fig. 12: En la ventana emergente seleccionamos la fuente.

Dejo unos enlaces a páginas donde se pueden conseguir fuentes:

Da Font

1001 Fonts

Luego elegimos el color, en mi caso simplemente voy a elegir blanco.

Fig. 13: También elegimos un color para la fuente, en mi caso será blanco para que contraste con el fondo.

En la figura 14 vemos cómo se ve el timer con los cambios mencionados.

Fig. 14: Nueva fuente y color para el temporizador.

Lo siguiente que vamos a hacer es crear un fondo para el texto del temporizador. Seleccionamos el GameObject Timer y creamos un objeto imagen.

Fig. 15: Creamos una imagen para hacer el fondo del timer.

Fig. 16: Seleccionamos la imagen que importamos del zip.

Ubicamos la imagen de fondo que se observa en la figura 16, la seleccionamos y vamos a configurarla como una textura tipo Sprite.

Fig. 17: En el inspector vamos a seleccionar como tipo de textura Sprite, para que tenga transparencia.

Con esta configuración la imagen de fondo tiene transparencia. Arrastramos la textura al campo source image de la componente imagen.

Fig. 18: Ahora vemos que la imagen tiene transparencia.

Fig. 19: Aplicamos la textura a la imagen que usaremos de fondo del timer.

Modificamos el resto de parámetros a gusto, en la figura 20 se ve la configuración que elegí.

Fig. 20: Configuramos la imagen de fondo a gusto, estos son mis valores.

En la figura 21 se observa que las letras en lugar de ser blancas se ven teñidas con el color de la imagen de fondo. Esto se debe a que la imagen se está renderizando por encima del texto.

Para solucionar este problema debemos colocar la imagen de fondo más arriba en la jerarquía, en la figura 22 se ve el orden de los GameObjects y en la figura 23 el resultado del cambio.

Fig. 21: Los números no se ven blancos porque la imagen está por delante del texto.

Fig. 22: Para corregir el problema vamos a la jerarquía y cambiamos de orden los GameObjects.
Fig. 23: Ahora los números del Timer están por delante de la imagen de fondo.

Crear fuego para las antorchas

Hacemos doble clic sobre algún GameObject de la puerta en la jerarquía, con esto centraremos la cámara sobre una de ellas.

Fig. 24: Seleccionamos una de las puertas en la jerarquía.

Ubicamos el prefabricado FireMobile de Standard Assets, en la figura 25 se ve la ruta donde se encuentra.

Fig. 25: Vamos a la carpeta Particle Systems de Standard Assets y elegimos el prefab Fire Mobile.

Tomamos el prefabricado y lo arrastramos a la jerarquía como hijo de la puerta.

Fig. 26: Arrastramos el prefabricado Fire Mobile y lo hacemos hijo de la puerta.

Cuando seleccionamos el prefabricado en la jerarquía podemos ver que se simula el sistema de partículas en la ventana Scene.

Fig. 27: Así se ve el prefabricado Fire Mobile.

En la figura 28 vemos la configuración que utilicé para el sistea de partículas, básicamente disminuí un poco el tamaño.

Fig. 28: Configuramos los parámetros del sistema de partículas.

Utilizando las vistas colocamos el fuego en la punta de la antorcha.

Fig. 29: Posicionamos el fuego sobre la antorcha usando las vistas frontal y lateral.

Fig. 30: Posicionamos el fuego sobre la antorcha usando las vistas frontal y lateral.

Luego haremos dos copias del objeto, una para la antorcha que se encuentra en el lado derecho de la puerta y otro para la antorcha que se encuentra varios metros por encima de la puerta.

Fig. 31: Duplicamos el fuego y lo colocamos sobre la otra antorcha.

Fig. 32: Volvemos a duplicar el fuego y lo llevamos a la punta de la antorcha que está sobre la puerta.

El propósito de esta última antorcha es que se vea desde distintas partes del laberinto.

Fig. 33: Esta llama será visible desde distintos puntos del laberinto.

Cuando terminamos de trabajar en la puerta aplicamos los cambios para que se extiendan a las demás puertas.

Fig. 34: Volvemos a seleccionar el objeto puerta y aplicamos los cambios para que se extiendan a las demás puertas.

Programar comportamiento – Scripts

En esta ocasión vamos a crear tres Scripts que llamaremos: «Gate», «Pedestal» y «UIManager».

El Script Gate se encargará de modelar el comportamiento de la puerta, el Script Pedestal se encargará del pedestal que contiene la espada y nos permitirá removerla. Y el Script UIManager se encargará de modificar los elementos de la interfaz gráfica.

Fig. 35: Creamos tres nuevos Scripts llamados Gate, Pedestal y UIManager.

Seleccionamos una puerta y le asignamos el Script Gate, luego aplicamos los cambios para las demás puertas.

Fig. 36: El Script Gate lo asignamos al GameObject Gate y aplicamos los cambios.

Ubicamos el prefabricado del pedestal con la espada y lo seleccionamos.

Fig. 37: Seleccionamos el prefabricado del pedestal.

Asignamos el Script Pedestal a este GameObject. Si es necesario podemos bloquear la ventana inspector con el candadito que se observa en la esquina superior derecha en la figura 38. También podemos colocar el prefabricado en la jerarquía, asignarle el Script, aplicar los cambios y eliminar el GameObject de la jerarquía.

Fig. 38: Al pedestal le asignamos el Script Pedestal.

Seleccionamos el GameObject Control y le asignamos el Script UIManager.

Fig. 39: Seleccionamos el objeto control de la jerarquía.

Fig. 40: Al objeto Control le asignamos el Script UIManager.

Completar Scripts

Vamos a comenzar adaptando lo que teníamos resuelto de la interfaz gráfica al nuevo objeto UIManager que se va a encargar de controlar lo que se muestra en pantalla.

Entramos en el Script Timer y seleccionamos los métodos que se observan en la figura 41, luego vamos al Script UIManager y los pegamos.

Fig. 41: Abrimos el Script Timer, seleccionamos estas instrucciones y las cortamos.

Fig. 42: Abrimos el Script UIManager y pegamos las instrucciones cortadas del Timer.

También nos llevamos la definición de los campos necesarios, por ejemplo timerText que es la referencia del texto del temporizador.

Fig. 43: También nos llevamos de Timer los objetos necesarios.

Fig. 44: Pegamos los objetos cortados de Timer e importamos el namespace UnityEngine.UI.

Cuando colocamos el campo timerText en UIManager dejamos de tener el error en el método WriteTimer.

Fig. 45: Debemos hacer público el método WriteTimer porque lo ejecutaremos desde un contexto externo.

Ahora tenemos un error en el Script Timer porque intentamos ejecutar el método WriteTimer, el cual ya no está definido en este Script. Para resolver el problema vamos a definir un objeto tipo UIManager y encontrar la referencia en el método Start, como se observa en la figura 46.

Fig. 46: En el Script Timer definimos un objeto tipo UIManager y encontramos la referencia en el método Start.

Luego all llamado del método WriteTimer le anteponemos la referencia del objeto UIManager y el operador punto, indicando que estamos queriendo ejecutar el método WriteTimer definido en el objeto UIManager.

Fig. 47: Para ejecutar el método WriteTimer debemos anteponer la referencia del objeto UIManager.

No necesitamos que el Script Timer tenga el namespace UnityEngine.UI (previamente definido en la línea 4 del Script Timer, figura 48), así que eliminamos el «using UnityEngine.UI».

Fig. 48: Ya no se necesita el namespace UnityEngine.UI en el Script Timer. Además eliminamos los objetos innecesarios.

Cuando voy a la consola me encuentro con un error, figura 49 en rojo. Nos dice que el problema está en el Script Timer, en la línea 22.

Si observamos la figura 48 vemos que el problema está en la ejecución del método GetComponent<UIManager>, faltó abrir y cerrar paréntesis.

Fig. 49: En consola aparece un error en el Script Timer.

En la figura 50 hemos resuelto el problema.

Fig. 50: El problema era la ausencia de paréntesis en la ejecución del método GetComponent.

Lo siguiente que hacemos es asignar el texto del temporizador al campo TimerText en el inspector del GameObject Control.

Fig. 51: Asignamos el texto del timer en el UIManager.

Script GameControl

Para controlar las interacciones entre el personaje y los objetos del escenario vamos a definir algunos campos y métodos en el Script GameControl que se encargarán de hacer el trazado de rayos y analizar las colisiones.

En la figura 52 vemos los nuevos campos entre el comentario «//Video 12» y el método Start.

Fig. 52: Definimos estos campos en el Script GameControl

Vamos a definir el método CheckRaycast que ejecutaremos al final del método Update, como vemos en la figura 53.

Fig. 53: Definimos el método CheckRaycast y lo ejecutamos en Update.

Script Pedestal

En el Script Pedestal vamos a escribir lo que se ve en la figura 54.

Definiremos un GameObject Sword para la referencia de la espada que está en el pedestal y un AudioSource para que el pedestal emita sonido cuando retiramos la espada.

Luego tenemos el método público RemoveSword que ejecutaremos cuando el personaje esté lo suficientemente cerca del pedestal, esté apuntando hacia la espada y presione la tecla E.

Fig. 54: En el Script Pedestal definimos estos campos y métodos.

Seleccionamos el prefab del Pedestal y utilizando Add Component agregamos un componente AudioSource.

Fig. 55: Agregamos una fuente de audio al pedestal

Ubicamos el audio «Steel» que venía dentro del paquete y lo aplicamos al componente AudioSource del pedestal, también desmarcamos la casilla «Play On Awake» y movemos el Slider «Spacial Blend» a 3D.

Fig. 56: Seleccionamos el sonido Steel que viene en el zip descargado.

Fig. 57: Asignamos el sonido de acero en el componente AudioSource del pedestal.

Vamos a colocar el prefabricado del pedestal en la jerarquía.

Fig. 58: Colocamos el pedestal en la jerarquía.

Seleccionamos el GameObject Sword que se ve en la figura 58 (puede que tenga la nombre Espada) y lo llevamos al campo «Sword» del Script Pedestal. De esta forma cuando ejecutemos el método RemoveSword, este GameObject es el que se desactivará.

Fig. 59: Asignamos el GameObject de la espada al Script Pedestal.

Mensajes de interacciones del Jugador

Vamos al Script UIManager y definimos los campos necesarios para mostrar las posibles interacciones entre el personaje y los elementos del escenario.

Vamos a definir un GameObject que será el contenedor del mensaje, es decir cuando lo desactivemos no se mostrará en pantalla y cuando lo activemos si. Un Text que será el componente texto del mensaje.

Fig. 60: Definimos estos nuevos campos en el Script UIManager.

Definimos los métodos ShowMessage y ClearMessage, el primero tomará como parámetro un String que es el mensaje que se debe mostrar. El segundo no lleva parámetros y simplemente se encarga de ocultar el GameObject que contiene al mensaje.

Fig. 61: Definimos estos nuevos métodos en el Script UIManager.

Ahora vamos a crear el elemento de la interfaz gráfica que mostrará los mensajes de interacciones.

Comenzamos creando una imagen que actuará de fondo y luego, como hijo, un objeto texto. En la figura 62 vemos estos dos objetos llamados «MessageBackground» y «MessageText».

Fig. 62: Creamos una imagen para usar como fondo del mensaje de interacción. Luego un texto como hijo.

Configuramos todos los parámetros visuales hasta que quede como nos guste, en mi caso en la figura 63 vemos el resultado.

Fig. 63: Configuramos todo a gusto, este fue mi resultado.

Vamos al GameObject Control y arrastramos los objetos a los nuevos campos definidos en UIManager.

Fig. 64: Asignamos los nuevos objetos a la componente UIManager del objeto Control.

Luego me di cuenta de que es más directo serializar el componente Text en lugar del GameObject que contiene el Text, así que como se ve en la figura 65, comenté la línea en la que se define el GameObject «messageTextGameObject» y le agregué SerializeField al Text «messageText».

Fig. 65: Modificamos de esta forma los campos de UIManager.

Esto nos modifica los campos que se ven en el inspector, arrastramos nuevamente los objetos necesarios.

Fig. 66: Volvemos a asignar los objetos a UIManager.

También completamos los campos nuevos del componente GameControl.

La distancia mínima de interacción será 4 metros. El mensaje de interacción que se muestra cuando miramos una puerta pero aún no tenemos la espada será:

«This gate is currently locked. You have to find the sword first.»

El mensaje que nos indica que podemos interactuar dirá:

«Press E to interact.»

Fig. 67: Completamos los campos del componente GameControl.

Resolver Raycasting

Para identificar qué objetos tenemos en frente, lo que vamos a hacer es crear un rayo desde el centro de la cámara en la dirección en la que está viendo el personaje.

Este rayo puede chocar con un objeto que tenga Collider o irse al infinito. Si resulta que choca con algún objeto, vamos a fijarnos si la distancia que hay ente el personaje y el objeto es menor a la distancia de interacción. Si esto se cumple vamos a intentar obtener del objeto una componente Gate o una componente Pedestal. Si el objeto no tiene ninguna de estas dos componentes (por ejemplo un muro o el suelo), en las variables van a estar en null.

Si en cambio la variable que contiene el objeto Gate es distinta de null, quiere decir que estamos mirando a la puerta a una distancia menor que la de interacción, por lo tanto podríamos interactuar con la puerta.

Esta es la escencia de la solución escrita en la figura 68 para el método CheckRaycast.

Fig. 68: Método CheckRaycast para resolver la interacción del personaje con los objetos del escenario.

Si el personaje está mirando el pedestal a una distancia menor a la de interacción vamos a comprobar si presiona la tecla E, si esto pasa ejecutaremos el método removeSword de pedestal y pondremos en true la variable swordFound, para indicar que el personaje encontró la espada.

Como detalle final ponemos la variable swordFound en false en el método Start.

Fig. 69: En el método Start hacemos la falsa la variable swordFound.

En las figuras 70 y 71 podemos ver el resultado de lo que estuvimos haciendo a lo largo de ese artículo.

Fig. 70: Al acercarnos a la puerta nos aparece el mensaje indicado en el componente GameControl.

Fig. 71: Al acercarnos al pedestal y apuntar a la espada nos aparece el mensaje de interacción que indicamos.

Conclusión

En este artículo hemos visto cómo resolver interacciones entre el personaje y ciertos elementos del escenario.

Para esto utilizamos el método RayCast de la clase Physics, que se encarga de trazar un rayo desde donde le indiquemos y en la dirección que le demos y entrega una vairbale booleana si resulta que ese rayo ha chocado contra algo.

Cuando sabemos que el rayo ha chocado contra algo, verificamos que la distancia a ese punto sea una distancia razonable en la cual el jugador pueda interactuar con un objeto.

Luego fue necesario determinar si el objeto contra el que el rayo choca es una puerta, un pedestal u otro objeto. Esto lo logramos intentando obtener las referencias de alguno de los Scripts que nos interesan y que podrían estar asignadas al objeto contra el que el rayo chocó.

Esto es una forma que se me ocurrió en la que se puede resolver las interacciones entre el jugador y distintos elementos del escenario. Me gustaría crear una solución mejor y con más posibilidades, luego de escribir este artículo he ido pensando nuevas ideas, así que posiblemente haga otros artículos sobre esto más adelante.

Información actualizada sobre esta entrada

Este artículo pertenece a una serie que consiste en hacer un juego simple en primera persona acerca de encontrar objetos dentro de un laberinto. Es una de mis primeras series de cuando empecé el canal, ahora he mejorado mucho este proyecto y puedes descargar el código fuente para importarlo en tu propio proyecto de Unity. Algún día vamos a hacer un remake de esta serie, suscríbete a mi canal para estar al tanto del nuevo contenido sobre Blender, Unity y programación.

Sígueme en itch.io y descarga el código fuente de este proyecto



PUEDES TESTEAR ESTE JUEGO AQUÍ. TAL VEZ TARDE UN POCO EN CARGAR
🔻

MOVEMENT: WASD CAMERA LOOK: MOUSE



Introducción al artículo original

En este artículo vamos a ver una forma de colocar objetos colectables en Unity, es decir objetos que el personaje puede recolectar y estos tendrán un efecto en él. Estos objetos serán relojes que al recogerlos nos sumarán tiempo en la cuenta regresiva.

Vamos a utilizar el prefabricado del reloj que configuramos en el segundo artículo del proyecto, clic aquí para descargar los archivos y ver cómo configurar los prefabs.

Página principal del proyecto

Vídeo relacionado a este artículo


También te dejo este video en el que hablo sobre Generación de Datos Aleatorios en Unity, está dividido en dos partes, en la primera vemos cómo generar valores enteros y reales dentro de uno o más intervalos y en la segunda parte vemos cómo generar otras estructuras de datos como Vector2, Vector3, Quaternion y Color.

Descripción del objetivo

Debemos establecer reglas y hacer una descripción exhaustiva del comportamiento de los relojes, por ejemplo cómo será la interacción con el personaje, cómo aparecerán en el escenario, etc. Cuanto mejor sea la descripción, más fácil será crear una solución usando programación.

Lo que buscamos es que en el laberinto aparezca un determinado número de relojes. Debemos asegurarnos de que estos relojes no aparezcan dentro de los muros y no se superpongan entre si.

Para lograr esto voy a reutilizar la solución del artículo anterior para colocar el pedestal en una posición aleatoria, en la solución hacía que cada pieza del laberinto conozca su propia geometría y nos dé una posición de su interior si se lo pedimos.

Fig. 1: Los relojes aparecerán en posiciones aleatorias del laberinto.

Además voy a hacer que cada pieza del laberinto pueda contener un sólo reloj, de esta forma no corremos el riesgo de colocar dos relojes superpuestos.

Para lograr esto debemos llevar un registro de las piezas del laberinto que tienen un reloj en su interior, de esta manera cuando vamos a colocar un nuevo reloj, esas piezas no serán consideradas en la selección.

Cuando el personaje tome un reloj se agregará una determinada cantidad de segundos a la cuenta regresiva.

Cada vez que un reloj es destruido aparece uno nuevo en el escenario, de esa forma todo el tiempo tendremos la misma cantidad de relojes en el escenario.

Resolución

Pasos previos

Comenzamos seleccionando el GameObject Control de la jerarquía y asignándole el tag «GameController». Luego seleccionamos el GameObject FPSController y le asignamos el tag Player.

Fig. 2: Seleccionamos el objeto control y asignamos el tag «GameController».

Fig. 3: Seleccionamos el prefab del jugador y le asignamos el tag «Player».

A continuación vamos a crear un nuevo Script llamado Clock, el cual posteriormente asignaremos al prefabricado del reloj.

Fig. 4: Creamos un nuevo Script con nombre «Clock».

Este Script modelará el comportamiento de los relojes.

Campos de Script Clock

Vamos a definir un String serializado que llamaremos «playerTag», esta variable contendrá simplemente el nombre del tag que tenemos asignado en el jugador.

Luego definiremos una variable tipo float para indicar el tiempo de vida en segundos del reloj en el escenario.

Definimos un GameObject llamado labyrinthPiece con «get» y «set» como se indica en la figura 5, de esta forma será un parámetro que puede ser leído y escrito. Usualmente me gusta definir métodos públicos para acceder a las variables, pero esta es una forma muy práctica de hacerlo.

Por último definimos un objeto tipo GameControl ya que el reloj necesita informar al control lo que está ocurriendo.

Fig. 5: Definimos estos campos para utilizar en la solución.

Métodos de Script Clock

En primer lugar definimos el método público SetLifeTime con parámetro float que usaremos para que el objeto GameControl le asigne un determinado tiempo de vida al reloj.

Luego tenemos el método SelfDestruction que se ejecutará cuando se acabe el tiempo de vida del reloj o el personaje lo agarre.

El método Collect se ejecutará cuando el personaje tome el reloj, se encargará de avisar al GameControl y luego autodestruirse.

Por último el método OnTriggerEnter para detectar al personaje, cuando esto ocurre vamos a ejecutar el método Collect.

Fig. 6: Definimos estos métodos para utilizar en la solución,

Campos en GameControl

Vamos al Script GameControl y definimos cuatro campos serializados.

ClockPrefab contendrá el prefab del reloj que colocaremos en el escenario. El entero nClocks indicará cuántos relojes se deben colocar en el escenario. El float clockLifetime será el tiempo de vida medio de un reloj en el escenario. Por último el entero timePerClock indicará cuántos segundos se añaden al Timer luego de que el personaje toma un reloj.

Fig. 7: En GameControl vamos a definir algunos campos para resolver el problema.

Declaración de métodos del Script GameControl

El método PlaceAllTheClocks se encargará de asegurarse de que es posible poner la cantidad de relojes indicada y luego ejecutar el método PlaceAClock tantas veces como relojes haya que colocar.

ClockDestroyed será ejecutado por un reloj que acaba de autodestruirse, de esa forma el Script GameControl podrá volver a considerar la pieza del laberinto donde el reloj estaba y colocar un nuevo reloj en una posición aleatoria.

ClockCollected será ejecutado por un reloj cuando el personaje entra en contacto con él, de esta forma podremos agregar tiempo al temporizador.

Por último el método DestroyAll se encargará de destruir todo lo que deba ser destruido al finalizar una partida (pedestal, relojes, personaje, etc).

Fig. 8: Definimos algunos métodos en GameControl.

Instrucciones de los métodos de Clock

Para empezar en el método Start de Clock encontramos la referencia del objeto GameControl, luego invocamos el método «selfDestruction» en un tiempo que es el resultado de la suma del tiempo de vida del reloj con un valor aleatorio entre 0 y 1. De esta forma logramos que los relojes no se destruyan en el mismo frame.

Fig. 9: En el método Start de Clock vamos a encontrar la referencia de la componente GameControl e invocar la autodestrucción.

En el método SelfDestruction vamos a informar al objeto GameControl que un reloj fue destruido y pasamos como parámetro la pieza del laberinto asignada al reloj, de esa forma el objeto GameControl podrá sacarla de la lista de exclusión. Luego ejecutamos el método Destroy con parámetro «gameObject» para que el objeto se destruya a si mismo.

Fig. 10: En SelfDestruction vamos a comunicar al Control que el reloj fue destruido y luego ejecutar el método Destroy.

En el método Collect primero vamos a cancelar la invoación pendiente del método selfDestruction. Luego informamos al objeto GameControl que un reloj fue recolectado. Finalmente ejecutamos el método selfDestruction.

Fig. 11: El método Collect informa al control del evento y luego se autodestruye.

En el método OnTriggerEnter vamos a preguntar si el tag del Collider que tocó el reloj es el del jugador y si esto es verdadero vamos a ejecutar el método Collect.

Para ver en detalle cómo funciona el método OnTriggerEnter y OnTriggerEnter te invito a ver uno de los videos de la serie fundamental de Unity que hice hace un tiempo. Si prefieres información más detallada aquí está el artículo de ese video.

Fig. 12: Si el Collider que entra en contacto con el reloj tiene el tag «Player» ejecutaremos el método Collect.

Instrucciones de los métodos de GameControl

Método PlaceAllTheClocks

En el método PlaceAllTheClocks de GameControl vamos a leer la cantidad de piezas de laberinto que tenemos disponibles para colocar los relojes. Si resulta que se deben poner más relojes que la cantidad de piezas de laberinto, vamos a hacer que nClocks sea igual al número de piezas de laberinto menos uno, de esta forma evitaremos que el programa entre en un bucle infinito.

Luego haremos un loop ejecutando el método PlaceAClock para colocar un reloj en el escenario.

Fig. 13: En el método PlaceAllTheClocks pondremos todos los relojes en el escenario.

Método StartGame

En el método StartGame vamos a crear el objeto List de GameObjects (línea 182 de la figura 14).

Crear los objeto es muy importante, tanto así que en un examen de informática en el que había que escribir código en papel, se me olvidó colocar la instrucción new para crear el objeto y me restaron casi 40 puntos de 100. Cuando fui a defender mi examen consideraron que habían exagerado un poco dado que había sido mi único error y me perdonaron.

Fig. 14: En Start de GameControl creamos el objeto lista y luego ejecutamos el método PlaceAllTheClocks.

Método PlaceAClock

Volviendo al tema de los métodos, en PlaceAClock tenemos que elegir aleatoriamente una pieza del laberinto asegurándonos que la pieza no contiene ya un reloj, una vez que la conseguimos, le pedimos una posición aleatoria de su interior. Para resolver esto es necesario haber resuelto el ejercicio del artículo anterior, en el que creamos el Script Labyrinth Piece.

El algoritmo para colocar la pieza del reloj se puede ver en la figura 15.

Fig. 15: El método PlaceAClock se encargará de elegir una pieza del laberinto y colocar el reloj.

Método ClockDestroyed

En el método ClockDestroyed vamos a remover de la lista la pieza del laberinto que nos pasan como parámetro y luego ejecutar el método PlaceAClock para colocar un nuevo reloj en el escenario.

Fig. 16: En Clock Destroyed eliminamos la pieza del laberinto que contenía el reloj de la lista y colocamos un nuevo reloj.

Método AddSeconds de Timer

Necesitamos definir un método público dentro de Timer que nos permita añadir una cantidad de segundos al temporizador.

Al final del Script hacemos la declaración del método AddSeconds, lo completaremos al final.

Fig. 17: En el Script Timer vamos a agregar un método público para poder añadir segundos al timer.

Volviendo al Script GameControl, en el método ClockCollected hacemos la llamada al método AddSeconds de timer, pasando como parámetro el entero timePerClock.

Fig. 18: En el método Clock Collected vamos a añadir segundos a la cuenta regresiva.

Ahora vamos al método EndGame, en donde están las líneas Destroy vamos a ejecutar el método DestroyAll y cortar las dos instrucciones Destroy que habíamos colocado previamente en otros artículos.

Fig. 19: Vamos al método EndGame, cortamos las instrucciones de destrucción y ejecutamos el método DestroyAll.

Esas dos instrucciones las pegamos en el método DestroyAll y luego econtramos todos los relojes presentes en el escenario y los destruimos usando un bucle foreach.

Fig. 20: En DestroyAll pegamos las instrucciones cortadas anteriormente y eliminamos todos los relojes del escenario.

Configurar prefabricado del reloj

Ahora vamos a seleccionar el prefabricado del reloj de la carpeta del proyecto y arrastrarlo al escenario para configurarlo.

Fig. 21: Seleccionamos el prefab del reloj.

Creamos el tag Clock y se lo asignamos al GameObject del reloj.

Fig. 22: Creamos un nuevo Tag llamado Clock.

Fig. 23: Al prefab del reloj le asignamos el tag Clock.

Fig. 24: Reloj con el tag asignado.

Luego arrastramos el Script Clock hacia sus componentes o utilizamos el botón AddComponent. Luego introducimos «Player» en el campo del tag (figura 25).

Fig. 25: Asignamos el Script Clock al prefab del reloj e introducimos el Tag del jugador.

Vamos al GameObject Control e ingresamos los nuevos parámetros que habíamos definido previamente.

Fig. 26: Seleccionamos el objeto Control y en el inspector seteamos los nuevos parámetros.

Ahora vamos a completar el método AddSeconds del Script Timer que nos había quedado pendiente.

Dentro simplemente vamos a incrementar los segundos, ajustar los minutos y ejecutar el método WriteTimer para actualizar los valores.

Fig. 27: Volvemos al Script Timer y completamos el método AddSeconds.

Error de programación

En este punto apareció un error en la consola diciendo que no existe una versión del método PlaceAClock que no lleve parámetros.

Fig. 28: Surge un error que dice que no hay una definición del método que no lleve parámetros.

Voy a la línea 184 del Script GameControl donde apareció el error, en efecto vemos en la figura 29 que la ejecución del método PlaceAClock se hace sin parámetros.

Fig. 29: Voy a la instrucción en la que está el error.

Defino el entero nPieces con el valor de la cantidad de elementos en la lista de piezas del laberinto e ingreso este entero como parámetro del método.

Fig. 30: Defino un entero con la cantidad de piezas de laberinto y se lo paso como parámetro al método.

Corrección de Bug

Cuando probé el juego no parecían haber aparecido los relojes en el escenario, al pausar la simulación y buscar en la jerarquía descubrí que habían aparecido pero estaban boca abajo como se ve en la figura 31.

Fig. 31: Al correr el juego encontramos un bug, los relojes aparecen boca abajo.

Para corregir este bug voy al método donde se coloca un reloj en el escenario y busco la instrucción en la que hago el Instantiate, instrucción 173 en la figura 32.

En lugar de darle al nuevo GameObject la rotación del Quaternion identidad voy a darle la rotación que viene definida en el prefab del reloj, el cual cuando colocamos en el escenario aparece correctamente orientado.

Fig. 32: Voy al punto donde coloco los relojes en el escenario.

Fig. 33: En lugar de usar Quaternion.Identity utilizo la rotación que viene definida en el Prefab.

Últimos detalles y prueba

Al probar el juego noté que había pocos relojes y que estos entregaban muy poco tiempo al recogerlos.

Para balancear correctamente los elementos es necesario hacer varias pruebas y ver qué funciona mejor, además esto puede formar parte de un sistema de dificultad en el cual una dificultad alta implica relojes menos frecuentes que entregan poco tiempo al recogerlos.

Fig. 34: Ajusto los valores en GameControl para que me den más tiempo al recogerlos.

En la figura 35 tenemos dos relojes frente a nostors y el timer marca aproximadamente un minuto cincuenta, la figura 36 fue tomada momentos despues de agarrar los dos relojoes, vemos que el Timer ahora marca un poco más de dos minutos diez. Con esto damos por concluido el problema.

Fig. 35: En la escena se observan dos relojes frente al personaje y el tiempo indica 1:47.

Fig. 36: Luego de recoger ambos relojes, el tiempo es 2:13.

Conclusión

En este artículo hemos visto cómo colocar colectables aleatoriamente en Unity, estos colectables eran los relojes que al recogerlos debían sumar tiempo a la cuenta regresiva.

El objeto GameControl es el que se encarga de colocarlos en el escenario aleatoriamente haciendo uso de la solución creada en el artículo anterior para colocar el pedestal aleatoriamente en una pieza del laberinto.

Para resolver el problema hemos creado un Script que modelará el comportamiento del reloj y que intercambiará mensajes con los demás Scripts para informar de eventos como una autodestrucción o que el personaje ha recogido el reloj.

El efecto de elemento colectable lo hacemos simplemente ejecutando acciones apropiadas cuando el personaje pasa por encima de ellos.

Introducción

Un vector es una herramienta matemática que nos permite representar magnitudes en las que no sólo importa la intensidad (o módulo), sino también la dirección y el sentido en la que están aplicadas. Los vectores son muy útiles en el desarrollo de juegos, ya que nos permiten definir direcciones para el movimiento, hacer trazado de rayos, entre otras aplicaciones.

El ejemplo más simple y quizás más cotidiano que se me ocurre es el de una fuerza. Las fuerzas son magnitudes vectoriales, no sólo importa la intensidad de la fuerza que se aplica, sino también la dirección y el sentido.

Si por ejemplo cambiamos el sentido de la fuerza que le aplicamos a un objeto, el objeto se moverá en la dirección contraria o disminuirá su velocidad.

Otros ejemplos de magnitudes vectoriales pueden ser el torque, el momento angular, la velocidad. En electricidad y magenitsmo tenemos el campo eléctrico en un punto, el vector de Poynting.

Vectores en el plano y en el espacio

Por cuestiones prácticas vamos a ver el caso de vectores en el plano y en el espacio, es decir vectores de dos y tres componentes respectivamente.

Matemáticamente

Usualmente un vector se identifica usando una letra con una flecha en la parte superior.

Hay distintos tipos de notaciones, una de ellas es escribir las componentes del vector entre paréntesis y separadas con coma. En el plano usamos dos componentes y en el espacio tres.

Fig. 1: Ejemplos de vectores en el plano y en el espacio.

Representación gráfica de vectores

El valor del vector representa el punto final de una flecha que parte desde el origen de coordenadas (es decir (0,0) en el plano y (0,0,0) en el espacio), hasta la coordenada que indica el vector. Como se ilustra en la figura 2.

Hace un tiempo escribí un artículo sobre sistema cartesiano, puedes leerlo haciendo clic aquí.

Fig. 2: Representación gráfica de vectores en el plano y en el espacio.

Características de un vector

Componentes de un vector

Las componentes son los valores reales para cada eje del sistema de coordenadas.

En el plano un vector tiene dos componentes, generalmente x e y, en el espacio necesitamos tres componentes, en general se llaman x, y y z.

Módulo o Norma de un vector

El módulo o norma de un vector nos habla del tamaño del vector, la magnitud o intensidad que tiene, en otras palabras es cuánto mide el vector desde el origen hasta el punto final.

Para calcular el módulo se utiliza el teorema de Pitagoras sobre los triángulos rectángulos que forma el vector con los ejes coordenados. Esto suena algo complicado pero las fórmulas son simples, en la figura 3 vemos el cálculo de la norma de un vector en el plano y en el espacio. Fórmula con ejemplos.

Fig. 3: Cálculo de módulo o norma de un vector en el plano y en el espacio.

Dirección de un vector

Esto habla de la orientación del vector en el plano o en el espacio. Esto nos permite calcular ángulos respecto a los ejes coordenados utilizando triángulos rectángulos y senos y cosenos.

Dado un vector podemos encontrar una única recta que lo contiene.

Acá empezamos a ver la utilidad de saber sobre vectores para programar videojuegos, con los vectores podemos representar movimientos en una determina dirección o calcular la trayectoria de un proyectil por ejemplo.

Sentido de un vector

Dada una dirección para el vector, dijimos que existe una única recta que lo contiene, sin embargo el vector podría estar apuntando hacia un lado o hacia el otro de la recta. Con el sentido solucionamos esta ambigüedad.

Para cambiar el sentido de un vector basta con multiplicar por -1 todas sus componentes.

Vector en matemática vs Vector en programación

En programación también existe un tipo de datos que se conoce como vectores, arreglos o arrays. Sin embargo esto hace referencia a una estructura de programación en la que podemos ordenar datos del mismo tipo y utilizarlos para resolver algoritmos.

Podemos modelar vectores matemáticos utilizando arrays, sin embargo los arrays pueden contener cadenas de texto por ejemplo.

Vectores matemáticos en Unity

En Unity hay varios componentes que utilizan vectores, el más simple de todos es el componente Transform que determina la posición, rotación y escala de un GameObject en Unity.

Para esto utiliza tres vectores de tres dimensiones. En la figura 4 podemos visualizar estos vectores en el inspector. Unity le da a las componentes del vector el nombre x, y y z.

Fig. 4: Componente Transform que tiene todo GameObject en Unity.

Otros componentes como los Colliders utilizan vectores para el dibujado del Collider, definir su posición y su tamaño. En la figura 5 vemos un ejemplo de Collider en tres dimensiones en los que se usan vectores de tres componentes y en la figura 6 un ejemplo de Collider 2D en el que se utilizan vectores de dos componentes.

Fig. 5: Vectores de 3 componentes en para las diensiones de un Collider.

Fig. 6: Vectores de 2 componentes para las dimensiones de un Collider 2D.

Además de las componentes con las que cuenta Unity, en nuestros Script podemos crear vectores para utilizar en nuestros algoritmos.

El nombre del objeto con el que nos referimos a estos vectores en un Script C# para Unity es Vector2 y Vector3. Podemos observar algunos ejemplos en la figura 7.

Fig. 7: Ejemplos de utilización de vectores matemáticos en Scripts C#, en Unity.

Un vídeo sobre introducción a los VECTORES en lenguaje C#


Conclusión

Los vectores en matemática son una herramienta útil para representar trayectorias, fuerzas

Un vector tiene un módulo que determina su tamaño, una dirección que determina su orientación en el espacio y un sentido dentro de esa dirección.

Dado un vector podemos encontrar una única recta que lo contiene.


ACTUALIZACIÓN IMPORTANTE

He creado una solución más completa y simple de utilizar, se trata de un paquete de Unity para descargar e importar, con un prefabricado que al arrastrarlo al Canvas automáticamente produce el efecto de fundido que le configuremos en los parámetros del inspector.

Puedes descargar el paquete con esta solución en el siguiente link:

Descargar Asset: Efecto de fundido para Unity

En el vídeo de abajo explico cómo usar esta solución, pero no cómo funciona, si te interesa saber sobre el funcionamiento a nivel de programación continúa leyendo este artículo.  


CONTINÚA EL ARTÍCULO ANTIGUO

Introducción

En este artículo vamos a ver una manera simple de oscurecer la pantalla en Unity utilizando una componente imagen. Esto nos puede ser muy útil por ejemplo para usar como transición en un cambio de escena.

En el siguiente video se ve el resultado que obtendremos al finalizar.

https://gamedevtraum.com/wp-content/uploads/2019/07/prueba-black-screen-unity.mp4
Video 1: Prueba de pantalla negra para función Blackout y función Pausa.

Video explicativo y descargas

En el video se explica cómo funciona la solución, pero no se hace paso a paso.

🟢 VÍDEO: Cómo usar EFECTO FADE IN/FADE OUT

Descargar scripts GameController y UIManager.

Resolución paso a paso

Elementos de la jerarquía

Vamos a crear todos los elementos necesarios para resolver el problema. En primer lugar un empty GameObject que llamaremos «GameController» y un GameObject tipo Canvas que es el que contendrá todos los elementos de la interfaz gráfica.

Fig. 1: Comenzamos creando un Empty GameObject con el nombre «GameController»

Fig. 2: Creamos Canvas para mostrar elementos de la interfaz gráfica.

En la figura 3 vemos estos elementos creados y colocados al principio de la jerarquía.

Fig. 3: Al Canvas le doy el nombre «UI» y coloco el objeto control y el canvas en la primera posición.

Imagen para oscurecer la pantalla

Lo siguiente que hacemos es crear una Imagen como hijo del GameObject UI. Esta imagen usaremos para bloquear la visión de la cámara.

Fig. 4: Haciendo clic derecho sobre el objeto UI creamos una imagen.

En el inspector en la componente imagen hacemos que el color sea negro.

Fig. 5: Hacemos que el color de la imagen sea negro.

Configurar Canvas Scaler

Vamos al GameObject UI y en el inspector, en la componente CanvasScaler seleccionamos el modo «Scale with screen size» en el campo «UI Scale Mode» (figura 6). Con esto logramos que los elementos de la interfaz gráfica modifiquen su tamaño en función del tamaño de la pantalla.

Fig. 6: Configuramos el objeto CanvasScaler para que se adapte al ancho de la pantalla.

Para la resolución de referencia vamos a usar 1920 x 1080 pixels, que se conoce como resolución FullHD.

La configuración del CanvasScaler conviene hacerla al principio, antes de colocar los elementos de la interfaz gráfica. Sino luego tendremos que volver a modificar todos los elementos para que se ajusten a la nueva configuración.

Fig. 7: Introducimos la resolución FullHD, 1920 x 1080.

Volvemos al GameObject imagen y ajustamos su tamaño a 1920 x 1080 para que cubra toda la pantalla. Si todo funciona bien la pantalla se debería ver como en la figura 9.

Fig. 8: Ajustamos el tamaño de la imagen para que cubra toda la pantalla.

Fig. 9: Deberíamos ver el fondo de la escena obstruido por la imagen.

En el color negro de la imagen vamos a hacer que la componente Alfa sea 0, de esta forma será transparente.

Fig. 10: En el color de la imagen seteamos el valor Alfa en 0.

Ahora vamos a crear un par de botones para ejecutar las funciones que posteriormente definiremos en los Scripts.

Botones

Fig. 11: Creamos un botón para activar la función de pantalla negra.

Al primero vamos a llamarlo BlackoutButton que es el que se va a encargar de oscurecer completamente la pantalla.

Fig. 12: El botón se va a llamar «Blackout»al igual que la función.

Ajustamos los tamaños y colores a gusto.

Fig. 13: Ajustamos los parámetros del botón a gusto.

Debemos asegurarnos de que el botón esté por debajo de la imagen en la jerarquía, de esa forma primero se dibujará la pantalla negra y luego el botón, como vemos en la figura 14.

Fig. 14: El botón debe ser visible aún con la pantalla negra.

Una vez que configuramos el botón Blackout vamos a duplicarlo y darle el nombre «PauseButton», lo acomodamos donde nos guste en la pantalla. En la figura 16 está mi resultado.

Fig. 15: Duplicamos el botón de blackout, lo movemos hacia abajo y le damos el nombre «Pause».

Fig. 16: Con esto la escena está terminada.

Scripts C#

Ahora vamos a crear dos Scripts C#, el primero lo llamaremos «GameController» y el segundo «UIManager».

Fig. 17: Vamos a crear dos Scripts para controlar los elementos.

Fig. 18: Un Script se llamará «GameController» y el otro «UIManager»

El Script GameController lo asignamos al GameObject con el mismo nombre. Podemos seleccionarlo y arrastrarlo al inspector o usar el botón «Add Component».

Fig. 19: Seleccionamos el GameObject GameController y con AddComponent le asignamos el Script GameController.

Fig. 20: Ahora el Script GameController es una componente del GameObject.

El Script UIManager lo asignamos al GameObject «UI».

Fig. 21: Seleccionamos el GameObject UI y con AddComponent le asignamos el Script UIManager.

Fig. 22: Ahora el Script UIManager es una componente del GameObject.

Script GameController

El Script GameController será el que se encargue de indicar cuándo se debe oscurecer la pantalla y en qué valor de opacidad se debe establecer. No se encarga de controlar el objeto imagen directamente, esa será la responsabilidad del UIManager.

GameController tendrá dos funciones, una será la de Blackout que le dirá al UIManager que oscurezca completamente la pantalla y la otra será la función Pausa que hará que la pantalla se oscurezca al 50%.

Campos e Inicialización

Vamos a definir un objeto tipo UIManager para tener la referencia del Script UIManager asignado al GameObject UI y poder así acceder a sus métodos públicos.

También vamos a necesitar dos variables booleanas para indicar si la función de blackout y pausa están habilitadas.

En el método Start encontramos la referencia del Script UIManager asignado al GameObject UI con la instrucción 13 que se observa en la figura 23. Luego ponemos en false las variables booleanas.

Fig. 23: Campos e inicialización en el Script GameController.

Métodos

Para que el Script cumpla las dos funciones que dijimos, vamos a definir dos métodos públicos, uno se llamará «BlackoutFunction» y el otro «Pause». Estos métodos serán ejecutados al pulsar los botones de la interfaz gráfica.

Si querés saber un poco más sobre métodos te invito a leer este artículo que escribí anteriormente, habla sobre qué es un método de manera intuitiva, también hay un video en el canal.

En el método BlackoutFunction lo primero que hacemos es invertir el estado de la variable booleana blackoutActivated, osea que si estaba en false ahora va a ser true y visceversa.

Luego comprobamos el estado de esa variable y vamos a ejecutar un método del Script UIManager con el parámetro apropiado. En este punto aún no hemos escrito el método «SetBlackoutOpacity» de UIManager, así que si escriben las líneas 23 y 27 de la figura 24 van a tener un error.

En el método Pause vamos a hacer lo mismo pero en lugar de oscurecer completamente la pantalla vamos a hacer que se oscurezca a la mitad de opacidad.

Las líneas 23, 27, 37 y 41 podemos escribirlas luego de haber trabajado en el Script UIManager.

Fig. 24: Función Blackout y función Pause del Script GameController.

Script UIManager

Este Script se va a encargar de controlar los parámetros de la imagen para oscurecer la pantalla.

Para eso va a ejecutar constantemente la función Blackout que se encargará de ir modificando gradualmente la opacidad de la imagen. Además tendrá un método público llamado «SetBlackoutOpacity» que permitirá decirle el nivel de opacidad deseado, 0 significa pantalla negra invisible, 1 es pantalla negra completamente visible.

Campos e Inicialización

Para resolver el comportamiento vamos a necesitar un objeto tipo Image que llamamos «blackoutImage» y que marcaremos con «SerializeField» para que aparezca en el inspector.

Definimos un float llamado «blackoutOpacityChangeStep» para indicar la magnitud de cambio de la opacidad en el tiempo, también lo serializamos. Por último definimos un float llamado «blackoutTargetOpacity» que será el valor al que la opacidad tenderá a acercarse en cada paso de tiempo.

En el método Start hacemos que la opacidad objetivo sea 0. Todo esto podemos verlo en la figura 25.

Fig. 25: Campos e inicialización en el Script UIManager.

Métodos

Vamos a definir dos métodos, Blackout privado y SetBlackoutOpacity público. Dentro del método FixedUpdate haremos la llamada al método Blackout, como se observa en la figura 26.

En el método Blackout primero leemos el estado actual de opacidad del objeto imagen (instrucción 29 de la figura 26).

Si la opacidad de ese objeto es menor que la opacidad objetivo haremos que aumente, si la opacidad del objeto es mayor haremos que disminuya. Esto lo conseguimos con el «if» y «else if» de las líneas 31 y 39 respectivamente.

Para que la opacidad aumente o disminuya gradualmente crearemos un nuevo color al que le asignamos las mismas componentes RGB pero al parámetro alfa le sumamos o restamos el float «blackoutOpacityChangeStep». En la figura 26 podemos ver cómo resolver esto.

El método SetBlackoutOpacity será público y tomará como parámetro un valor float. Simplemente asignaremos este valor a la variable «blackoutTargetOpacity», como se observa abajo en la figura 26.

Fig. 26: Métodos del UIManager para oscurecer la pantalla en tiempo de ejecución.

El cambio del valor de opacidad lo hacemos en el método FixedUpdate debido a que no queremos que ocurra instantáneamente sino de manera gradual para tener un efecto Fade In/Out.

Te invito a ver este video en el que hago tres experimentos para entender mejor el flujo del programa y el orden de ejecución de los métodos Update y FixedUpdate.

Últimos ajustes

Para poder ejecutar los métodos de GameController usando los botones, tenemos que hacerlo en la función OnClick() de la componente botón.

Arrastramos el GameObject GameController al campo debajo de «Runtime Only» y usando el menú desplegable seleccionamos el método «BlackoutFunction».

Si no aparece en esta lista seguramente no lo hemos definido como público.

Con el botón de Pause hacemos lo mismo solo que eligiendo el método Pause de GameController.

Fig. 27: En el botón Blackout ejecutamos el método Blackout de GameController.

Fig. 28: En el botón Pause ejecutamos el método Pause de GameController.

Por último seleccionamos el GameObject UI y arrastramos el GameObject BlackoutImage (con la componente imagen, ver figura 12) al campo «Blackout Image». Para el float «BlackoutOpacityChangeStep» ingresamos el valor 0.1.

El resultado se muestra en la figura 29.

Fig. 29: Para la componente UIManager ingresamos un valor para el parámetro changeStep.

Conclusión

En este artículo vimos una forma de oscurecer la pantalla en Unity. Para lograrlo utilizamos una imagen negra y modificamos el valor de Alfa, que determina la opacidad del color. Si el alfa es igual a 1 el color es totalmente opaco, si es igual a 0, el color es totalmente transparente.

Configuramos dos botones para ejecutar métodos públicos del Script GameController, el cual le indica al objeto UIManager cómo debe ser la opacidad de la pantalla negra. UIManager finalmente hace los cambios en el valor de alfa de la pantalla negra.

Información actualizada sobre esta entrada

Este artículo pertenece a una serie que consiste en hacer un juego simple en primera persona acerca de encontrar objetos dentro de un laberinto. Es una de mis primeras series de cuando empecé el canal, ahora he mejorado mucho este proyecto y puedes descargar el código fuente para importarlo en tu propio proyecto de Unity. Algún día vamos a hacer un remake de esta serie, suscríbete a mi canal para estar al tanto del nuevo contenido sobre Blender, Unity y programación.

Sígueme en itch.io y descarga el código fuente de este proyecto



PUEDES TESTEAR ESTE JUEGO AQUÍ. TAL VEZ TARDE UN POCO EN CARGAR
🔻

MOVEMENT: WASD CAMERA LOOK: MOUSE



Introducción al artículo original

En este artículo vamos a ver una estrategia para colocar un objeto en una posición aleatoria del laberinto usando el método Random.Range y el prefab del pedestal con la espada que se creó en el segundo artículo del proyecto, clic aquí para descargar los archivos y ver cómo configurar los prefabs.

No podemos colocar el objeto en cualquier posición elegida al azar, debido a las paredes del laberinto, que quizás en el futuro nos interese generarlo de manera procedural. Así que para lograr el objetivo debemos analizar la geometría de las piezas y proponer una solución acorde.

Página principal del proyecto

Vídeo relacionado a este artículo




También te dejo este video en el que hablo sobre Generación de Datos Aleatorios en Unity, está dividido en dos partes, en la primera vemos cómo generar valores enteros y reales dentro de uno o más intervalos y en la segunda parte vemos cómo generar otras estructuras de datos como Vector2, Vector3, Quaternion y Color.

Descripción del problema

Necesitamos que el pedestal con la espada incrustada que se observa en la figura 1, pueda aparecer en cualquier parte del laberinto.

En el video 4 de la serie resolvimos un problema similar para colocar al personaje en una de las puertas al iniciar el juego. Aquí dejo el artículo por si quieres echar un vistazo.

Fig. 1: El pedestal con la espada aparece en una posición aleatoria del escenario.
Fig. 2: En el video y artículo 4 colocamos al personaje aleatoriamente en una de las puertas.

En el video 4 utilizamos empty GameObjects para designar posiciones puntuales en las cual colocar el prefab del personaje.

En este caso estamos buscando una solución un poco más compleja, primero vamos a elegir una de las piezas que componenen el laberinto. La pieza que elijamos será un duplicado de alguna de las piezas que se observan en la figura 3.

Fig. 3: Estas son las piezas del laberinto en las que se puede caminar.

No basta solo con elegir la pieza, necesitamos obtener una posición dentro de ella y esta posición debe ser dentro de una región en la que se pueda caminar. Estas regiones las vemos en la figura 4.

Fig. 4: En estas regiones podremos colocar el pedestal.

Al analizar el problema vemos que tendremos que tomar varias decisiones aleatorias y sería deseable que la solución que propongamos sea independiente de la cantidad de piezas que tenga el laberinto. Es decir si agregamos más piezas de la figura 4, estas se agreguen automáticamente al sistema de selección.

Estrategia propuesta – Definir segmentos

Dada una de las piezas de la figura 4, somos capaces de trazar una o dos líneas internas por donde el jugador puede circular dentro de la pieza.

Consideremos los extremos de estas dos líneas. En la figura 6 están identificados cuatro puntos que serían los extremos de estos dos segmentos.

Estos puntos podríamos representarlos con Empty GameObjects como hijos de la pieza.

Fig. 5: Pieza encrucijada del laberinto.

Fig. 6: En los extremos de la pieza colocamos 4 puntos.

En la figura 7 vemos dibujados estos dos segmentos.

Entonces podríamos colocar el pedestal con la espada en cualquier punto de uno de los segmentos.

Fig. 7: Estos puntos forman dos segmentos, horizontal y vertical.

En primer lugar elegiremos uno de los dos segmentos, supongamos el segmento B (figura 8) formado por los empty GameObjects B1 y B2.

Luego tomaremos un punto aleatorio entre los dos empty GameObjects, figura 9.

Fig. 8: Supongamos que elegimos el segmento B.

Fig. 9: Elegimos un punto aleatorio del segmento B.

Finalmente en ese punto elegido colocaremos el pedestal.

Fig. 10: En el punto elegido colocaremos el pedestal con la espada.

En el caso de las piezas del pasillo y callejón sin salida que solo tienen una dirección, haremos coincidir los puntos A y B, de esa forma tendremos dos segmentos coincidentes, entonces podremos utilizar la misma solución que para las demás piezas.

Fig. 11: En el caso del pasillo haremos coincidir los segmentos A y B.

Implementación de la estrategia

Hemos elaborado un plan para colocar el pedestal en algún punto interior de alguna de las piezas del laberinto. Ahora en base a esto vamos a resolver el problema.

Primero en la jerarquía voy a separar las piezas obstrucción de las demás, porque estas piezas no las vamos a considerar en nuestra solución.

En la figura 1 vemos seleccionadas las piezas que vamos a utilizar.

Fig. 12: Seleccionamos el laberinto y separamos las piezas que obstruyen

Necesitamos encontrar las referencias de estas piezas en nuestro código para poder elegir una de ellas. La forma más simple de hacerlo es utilizar un Tag.

Fig. 13: Creamos el tag SpawnPiece para las piezas que podrán contener al laberinto.

Voy a crear un Tag llamado «SpawnPiece» y se lo voy a asignar a todas las piezas seleccionadas en la figura 12.

El video 2 de la Serie Fundamental de Unity nos muestra varias formas en las que podemos encontrar las referencias de los GameObjects de la jerarquía desde un Script, aquí está el artículo correspondiente a ese video.

Fig. 14: Seleccionamos todas las piezas que podrán contener al pedestal.

Fig. 15: A las piezas seleccionadas se les asigna el tag SpawnPiece.

A continuación creamos el Script «LabyrinthPiece» (pieza de laberinto) que estará asignado a todas las piezas seleccionadas en la figura 12.

Fig. 16: Creamos un nuevo Script llamado LabyrinthPiece, que asignaremos a las piezas que puedan contener al pedestal.

En el Script primero vamos a definir cuatro GameObjects que serán los puntos A1, A2, B1 y B2. Los declaramos como campos serializables para que aparezcan en el inspector y podamos asignarlos manualmente.

Fig. 17: Definimos cuatro campos tipo GameObject para contener a los puntos de cada pieza.

Seleccionamos cualquier pieza tipo Encrucijada y le asignamos el Script LabyrinthPiece. En la figura 19 vemos que en el inspector aparecen los campos para los GameObjects.

Fig. 18: En la jerarquía seleccionamos la encrucijada.

Fig. 19: Asignamos el Script LabyrinthPiece a la encrucijada.

A continuación vamos a crear los cuatro empty GameObjects que llamaremos A1, A2, B1 y B2. En la figura 20 vemos creado el primer punto. Observen que está definido como hijo de un Empty GameObject llamado Spawn, que a su vez es hijo de la pieza encrucijada.

Fig. 20: Creamos cuatro empty GameObjects como hijos de esta pieza.

Vamos a posicionar estos cuatro objetos de acuerdo a la figura 6, en los extremos de los segmentos imaginarios que representan el area caminable dentro de la pieza.

Fig. 21: Utilizando la perspectiva ortográfica, posicionamos los empty GameObjects, uno en cada extremo.

Fig. 22: Utilizando la perspectiva ortográfica, posicionamos los empty GameObjects, uno en cada extremo.

Asignamos estos objetos a sus respectivos campos en el inspector, dentro del componente LabyrinthPiece.

Finalmente aplicamos los cambios. Esto es muy importante porque los cambios los estamos aplicando sobre el Prefab de la encrucijada, es decir que todas las encrucijadas del laberinto ahora van a tener sus propios objetos A1, A2, B1 y B2 y tendrán asignado el componente LabyrinthPiece, con sus propios puntos cargados en los campos.

Fig. 23: Asignamos los empty GameObjects a los espacios en el inspector.

Fig. 24: Aplicamos los cambios para que todas las encrucijadas del escenario tengan esta misma configuración.

Podemos comprobar eso chequeando cada encrucijada en la jerarquía y comprobando que tiene estos puntos y el Script asignado.

Fig. 25: Al aplicar los cambios, todas las encrucijadas del laberinto pasan a tener los objetos vacíos y el Script LabyrinthPiece.

Lo que sigue es repetir el proceso para las demás piezas. En la figura 26 vemos la pieza en forma de T, este caso es similar a la bifurcación solo que uno de los segmentos imaginarios tendrá su extremo en el centro de la pieza.

Fig. 26: Empty objects para la pieza bifurcación.

En la pieza del pasillo creamos solo los puntos A1 y A2. En la figura 28 vemos que estos puntos también los asignamos en los campos B1 y B2 respectivamente.

Fig. 27: Empty objects para la pieza tipo pasillo.

Fig. 28: Para la pieza del pasillo asignamos los puntos A1 y A2 también a los campos B1 y B2.

En la pieza de la esquina, figura 29, los segmentos imaginarios tendrán dos puntos coincidentes, podríamos crear solo tres Empty GameObjects y uno de ellos asignarlo por ejemplo a A2 y a B1, pero optamos por crear los cuatro puntos.

Fig. 29: Empty objects para la pieza tipo esquina.

El caso de la pieza callejón sin salida es igual al del pasillo solo que con menor distancia.

En la figura 31 vemos que en los puntos B1 y B2 repetimos los puntos A.

Fig. 30: Empty objects para la pieza callejón sin salida

Fig. 31: Para la pieza callejón sin salida asignamos los puntos A1 y A2 también a los campos B1 y B2.

Método para elegir posición aleatoria de la pieza – Random.Range

En el Script LabyrinthPiece vamos a crear un método público que devolverá un Vector3 que indicará una posición aleatoria de la pieza.

Video sobre métodosArtículo sobre métodos

La primera instrucción será declarar un Vector3 llamado posición que será el que retornemos al final de la ejecución.

Recordemos que son dos segmentos imaginarios formados uno por los puntos A1 y A2, otro por los puntos B1 y B2. Así que luego vamos a hacer un if para elegir un segmento o el otro.

En el argumento del if usamos Random.Value para generar un número aleatorio entre 0-1 y comprobamos si este valor es menor a 0.5f. Esto quiere decir que tendremos un 50% de probabilidades de elegir el segmento A y otro 50% de elegir el B.

Para elegir un punto aleatorio del segmento imaginario formado por los puntos utilizamos el método Vector3.Lerp, el cual hará una interpolación lineal entre dos Vector3 que indiquemos.

El método recibe tres argumentos, los dos primeros son los Vector3 entre los cuales se interpolará y el tercer parámetro es el punto de interpolación que nos interesa.

Para ejemplificar la función de interpolación consideremos lo siguiente: si el tercer valor del método Lerp vale 0 tendremos un Vector3 igual al primer parámetro indicado. Si vale 1 tendremos un Vector3 igual al segundo parámetro indicado. Y si vale 0.5f tendremos un Vector3 que estará situado exactemente en el punto central entre los dos Vector3 que indicamos como parámetros.

De esta forma usamos Random.Range para generar un Vector3 que se encontrará en algúna posición entre los puntos indicados en los dos primeros parámetros del método Lerp y ese vector lo asignamos al Vector3 posicion que habíamos definido al principio.

En una región del if utilizamos la posición de los puntos A1 y A2. En la otra región del if hacemos exactemente lo mismo pero con los puntos B1 y B2.

Finalmente devolvemos el Vector3 position.

Todo esto que se explicó está resumido en las 7 líneas del método GetRandomPosition en la figura 32.

Fig. 32: El método GetRandomPosition entregará una posición aleatoria dentro de la pieza.

Ahora bien, esto que hicimos era para el Script LabyrinthPiece que está asignado a cada pieza del laberinto.

En el Script GameControl vamos a crear un método que se encargará de colocar al pedestal en una posición aleatoria del laberinto y hará uso del método público de LabyrinthPiece.

Comenzamos definiendo los campos que se observan en la figura 33 debajo del comentario «//Video 9». Estos son los campo y variables que usaremos para resolver el problema.

Fig. 33: En el Script GameControl definimos un array de GameObjects para las piezas del laberinto.

En la componente GameControl en el inspector (asignada al GameObject Control), rellenaremos los campos. En SpawnPieceTag escribiremos «SpawnPiece».

Fig. 34: Escribimos el nombre del tag que asignamos en las piezas del laberinto, en este caso «SpawnPiece»

En ObjectToFind asignaremos el Prefab del pedestal con la espada, el cual es el objeto a encontrar.

Fig. 35: Buscamos el prefab del pedestal con la espada que hicimos en el video 2 de la serie.

En la distancia mínima por el momento escribimos el valor 75. Luego veremos para qué se utiliza esta variable.

Fig. 36: Asignamos el Prefab del pedestal al campo ObjectToFind.

No es necesario que el array LabyrinthPieces aparezca en el inspector así que voy a remover el [SerializeField] seleccionado en la figura 37.

Fig. 37: No es necesario que el array de GameObject de las piezas del laberinto sea visible en el inspector.
Fig. 38: No es necesario que el array de GameObject de las piezas del laberinto sea visible en el inspector.
Fig. 39: Al remover la línea SerializeField, el campo no aparece en el inspector ya que es privado.

En el método StartGame vamos a encontrar todos los GameObjects de la jerarquía que tengan el Tag que indicamos en el inspector. Última instrucción del método startGame en la figura 40.

Fig. 40: En el método Start de GameControl encontramos todos los GameObjects con el Tag indicado.

Luego declaramos el método PlaceObjectToFind (figura 41) y llamamos este método desde el método StartGame (figura 42).

Fig. 41: Definimos un método privado que se encargará de colocar el pedestal en alguna pieza del laberinto.

Fig. 42: Hacemos la llamada desde el método StartGame, es decir, cuando el juego empieza el pedestal es colocado en el escenario.

Método PlaceObjectToFind

Lo que haremos con este método será elegir una pieza aleatoria del laberinto, asegurarnos que esa pieza está lo suficientemente lejos del jugador utilizando la variable «minDistance» que le asignamos 70 en el inspector. Si la pieza seleccionada no cumple este requisito volveremos a elegir otra pieza. De esto se encarga el bucle While que se observa en la figura 43.

Una vez que damos con una pieza que cumple los requisitos, colocaremos el pedestal en un punto aleatorio de su interior. Para ello usamos una versión del método Instantiate, que recibe tres parámetros: El primero es el objeto a encontrar almacenado en el campo «objectToFind», el segundo es la posición que la recibiremos automáticamente de la pieza del laberinto ejecutando el método GetRandomPosition de la componente LabyrinthPiece que tiene asignada. El tercer parámetro es la rotación, aquí indicaremos: Quaternion.identity (una rotación identidad).

Fig. 43: Instrucciones del método PlaceObjectToFind.

Es importante que guardemos la referencia de este nuevo objeto que hemos creado, lo haremos en «objectToFindInstance» (última instrucción en la figura 43). De esta forma cuando la partida se acabe podremos destruir este objeto manualmente.

En el método EndGame hacemos la destrucción de la instancia del objeto a encontrar, figura 44.

Fig. 44: En el método EndGame se destruye la instancia del pedestal.

Al entrar en el modo juego y apretar el botón Start, todo parece funcionar correctamente. El pedestal con la espada aparece en una posición aleatoria del laberinto, dentro de una de las piezas.

Fig. 45: Al entrar en el modo juego el pedestal es colocado en una de las piezas del escenario.

Fig. 46: Podemos ver desde la ventana editor de qué pieza se trata.

Colocar objeto fuera de las piezas

Hay regiones del laberinto que se encuentran fuera de las piezas, por ejemplo la que se encuentra resaltada en la figura 47. Podríamos estar interesados en colocar el objeto en una posición perteneciente a esta área.

¿Cómo podríamos reutilizar lo que hemos hecho?

Fig. 47: Esta región puede ser un lugar donde quisiéramos que aparezca el pedestal.

Para empezar creamos un Empty GameObject y lo llamamos SpawnArea y lo ubicamos entre las piezas del laberinto.

Fig. 48: Creamos un Empty GameObject y lo llamamos SpawnArea, esto nos permitirá colocar el pedestal en otras partes del laberinto.

Fig. 49: Los cuatro puntos de la región son hijos del GameObject SpawnArea.

Luego creamos cuatro empty GameObjects para representar los puntos A1, A2, B1 y B2. Colocamos estos objetos en los extremos de los dos segmentos imaginarios del area resaltada en verde en la figura 49.

Fig. 50: Vamos a crear 4 empty GameObjects para utilizar en el script LabyrinthPiece.

Luego creamos un Prefab junto a las demás piezas del laberinto (figura 51), porque puede que reutilicemos este objeto cambiando de lugar los puntos internos.

Fig. 51: Tomamos el GameObject SpawnArea y creamos un Prefab para reutilizar.

Luego le asignamos la componente LabyrinthPiece y colocamos los puntos internos en los campos respectivos.

Fig. 52: Asignamos el Script LabyrinthPiece al GameObject SpawnArea.

No debemos olvidar asignar el Tag SpawnPiece en el inspector, de otro forma estas áreas no serán consideradas a la hora de elegir una posición para el pedestal. En mi caso, como se observa en la figura 53, no lo había asignado y estuve varios minutos probando para que el pedestal apareciera en estas áreas.

Fig. 53: Debemos recordar asignar el tag SpawnPiece al GameObject SpawnArea. Aplicar cambios.

Detalles

Al probar el juego noté que la puerta tenía un tamaño bastante grande en relación al personaje, así que la hice un poco más pequeña y apliqué los cambios.

Fig. 54: Las puertas estaban demasiado grandes en relación al personaje.

Otro problema que detecté es que había piezas con el Tag SpawnPiece cuya región interior resultaba inaccesible para el jugador. En la figura 55 observa una de estas piezas, si el pedestal aparece aquí, el jugador no podrá encontrarlo.

La solución a esto es seleccionar esta pieza y quitarle el Tag SpawnPiece, de esta forma la pieza no será considerada.

Fig. 55: Esta pieza se encuentra fuera del alcance del personaje, el pedestal no tiene que aparecer aquí.

Fig. 56: Seleccionamos esa pieza y en el campo tag seleccionamos: «Untagged». No aplicamos los cambios.

Conclusión

En este artículo logramos colocar el pedestal en una posición aleatoria dentro del laberinto.

Para hacerlo tuvimos que analizar el tipo de piezas que forman el laberinto, establecer ciertas reglas y plantear una estrategia que resuelva nuestro problema.

Hicimos uso del pensamiento de programación orientada a objetos para crear una solución flexible que se adapte a todos los tipos de piezas.

Como usualmente ocurre, no hay un solo camino para resolver un problema. Otra forma de abordar esta situación es hacer un Bake de un Navmesh y tomar un punto dentro de estas regiones.

Información actualizada sobre esta entrada

Este artículo pertenece a una serie que consiste en hacer un juego simple en primera persona acerca de encontrar objetos dentro de un laberinto. Es una de mis primeras series de cuando empecé el canal, ahora he mejorado mucho este proyecto y puedes descargar el código fuente para importarlo en tu propio proyecto de Unity. Algún día vamos a hacer un remake de esta serie, suscríbete a mi canal para estar al tanto del nuevo contenido sobre Blender, Unity y programación.

Sígueme en itch.io y descarga el código fuente de este proyecto



PUEDES TESTEAR ESTE JUEGO AQUÍ. TAL VEZ TARDE UN POCO EN CARGAR
🔻

MOVEMENT: WASD CAMERA LOOK: MOUSE

Introducción al artículo original

Vamos a ver cómo crear un terreno en Unity utilizando sus propias herramientas y vamos a crear un laberinto usando los prefabricados que se configuraron en el segundo artículo del proyecto, clic aquí para descargar los archivos y ver cómo configurar los prefabs.

Página principal del proyecto

Vídeo relacionado a este artículo


Descripción del objetivo

Crear el laberinto

Para crear un modelo del laberinto vamos a utilizar los prefabricados que creamos anteriormente, los colocaremos en la jerarquía y comenzaremos a duplicarlos y acomodarlos en el espacio, como se observa en la siguiente figura.

Fig. 1: Armado del escenario utilizando los prefabricados que se hicieron en el video 2.

Crear terreno en Unity

Para crear un terreno en Unity usaremos el GameObject «Terrain», el cual nos permitirá modificar su relieve y texturas de manera simple utilizando distintos pinceles y parámetros.

Fig. 2: Utilizando las herramientas de Unity podremos esculpir relieve en el terreno.

Archivos de descarga

Primero descargamos el siguiente archivo Zip, extraemos las carpetas y las llevamos a Unity.

754 Descargas
Fig. 3: Extraemos las texturas del archivo de descarga para añadirlas al proyecto.

Fig. 4: Cada textura viene con sus mapas Albedo, Metallic y Normal.

Lo siguiente que hacemos es eliminar todos los elementos pertenecientes al escenario.

Fig. 5: Comenzamos limpiando completamente el escenario.

Componente Terrain – Unity

Como se dijo anteriormente usaremos el componente Terrain para crear un relieve montañoso para el terreno.

Este componente utiliza «Height Maps» o mapas de altura para crear relieves, estos mapas son texturas en escala de grises en los cuales las areas completamente negras se mapean con altura mínima y las zonas blancas con altura máxima (ambas alturas configuradas en los parámetros), el resto de las zonas en gris tendrán una altura proporcial a su tono de color.

Podemos observar esto en la siguiente imagen.

Fig. 6: El terreno funciona con Height maps o mapas de altura.

En la jerarquía hacemos clic derecho > 3D Object > Terrain.

Fig. 7: Desde la jerarquía creamos un GameObject tipo terreno.

En el escenario aparece un plano en blanco y en el inspector podemos ver que tiene dos componentes que lo definen, la componente Terrain y un Collider tipo Terrain.

Fig. 8: El terreno aparece en el escenario y podemos ver sus propiedades en el inspector.

Configuración del componente Terrain

Es importante que primero configuremos los parámetros básicos del terreno, como la dimensión y la diferencia que existirá entre la altura máxima y mínima. Luego podemos modificar estos parámetros pero probablemente perderemos el diseño que teníamos.

Como se observa en la figura 9, hacemos clic en el ícono del engranaje para ir a la configuración. Luego ingresamos los parámetros.

Lo único que voy a modificar es la resolución del terreno, que será de 1000 x 1000.

Fig. 9: Primero configuramos los parámetros básicos del inspector.

Fig. 10: Ajustamos la resolución que tendrá el terreno.

Configuración de las texturas

Podremos pintar el terreno utilizando distintos conjuntos de texturas que consistirán en mapas Albedo y de Normales. Primero vamos a seleccionar todos los mapas de normales que tengamos, en el inspector elegimos el tipo de textura como «Normal map» y aplicamos los cambios. Esto lo hacemos para todos los mapas de normales que tengamos.

Fig. 11: Seleccionamos las texturas tipo Normal Map o mapa de normales.

Fig. 12: En el inspector podremos seleccionar el tipo de textura. Seleccionamos Normal Map y aplicamos.

A continuación seleccionamos nuevamente el terreno y hacemos clic en el ícono del pincel, luego en «Edit Textures…», «Add Textures» (figuras 13 y 14).

Fig. 13: Seleccionamos la herramienta pincel en el componente terreno.

Fig. 14: Debemos configurar al menos una pintura para el terreno.

En la ventana emergente podremos seleccionar una textura que representa el Albedo en los canales RGB y su canal Alpha se mapea en la suavidad. Otra textura para las Normales de la superficie. Además podemos configurar el tamaño del mosáico o TileSize utilizando los campos «Size» y «Offset» que se observan en la figura 15.

Fig. 15: En la ventana emergente seleccionamos una textura Albedo y otra de normales.

Hacemos clic en Select y elegimos primero el mapa Albedo y luego el de normales.

Fig. 16: Debemos asegurarnos de seleccionar los mapas apropiados.

Haremos esto para todas las texturas que usaremos para pintar el terreno.

En la figura 17 se ven algunos valores que funcionan para los TileSizes.

Fig. 17: Valores del tamaño del mosaico que funcionan.

Una vez que configuramos todas las texturas, deberíamos tenerlas todas disponibles para usarlas en la ventana Texturas en el inspector, como se observa en la figura 18.

Fig. 18: Todas las texturas de la descarga han sido configuradas.

Creación del laberinto

Primero para mantener las cosas ordenadas voy a crear un Empty GameObject que llamo «Maze», dentro del cual estarán todos los prefabricados que utilice para hacer el laberinto.

Fig. 19: Creamos un empty object que contendrá todas las piezas del laberinto.

Luego arrastro todos los prefabricados al escenario y comienzo a duplicarlos y moverlos para formar los pasillos.

Fig. 20: Comenzamos colocando todos los prefabricados en el escenario.

Para facilitar el posicionamiento de las piezas podemos usar la vista ortográfica. En la figura 20 arriba a la derecha tenemos lo que se llama un «Gizmo» que muestra los ejes X en rojo y Z en azul, haciendo cic en esas flechas podremos cambiar la vista de la escena y si hacemos clic en el cubo del centro podemos alternar la vista de Perspectiva a Ortográfica.

En las figuras 20 y 21 podemos ver cómo es esta vista ortográfica. El efecto que produce es como si todos los elementos se comprimieran en un plano, de modo que no observamos la profundidad.

Fig. 21: Duplicamos las piezas y las movemos en el escenario para construir el laberinto.

Fig. 22: Utilizando la vista ortográfica y acercando la cámara nos aseguramos que las piezas encajen.

También colocamos algunas puertas en el escenario. En el video 4 de la serie creamos el prefabricado de la puerta, aquí está el artículo.

Fig. 23: Colocamos varias puertas en el laberinto.

Entramos en el modo juego y recorremos el laberinto para detectar problemas. En la figura 5 se observa una de las puertas mal colocadas.

Fig. 24: Probamos la escena para detectar problemas.

Fig. 25: Recorriendo el laberinto encontramos una puerta que estaba mal posicionada.

Fig. 26: Paramos el modo juego para corregir el problema.

Modelado del relieve

A continuación vamos a empezar a modelar las montañas. Seleccionamos el GameObject Terrain de la jerarquía y en el inspector hacemos clic en el primer ícono del componente (figura 27).

Luego seleccionamos algún patrón en la sección «Brushes» que se observa en la figura 28.

Fig. 27: Seleccionamos la herramienta de elevación en el componente terreno.

Fig. 28: Elegimos un pincel para aplicar la modificación en el terreno.

Lo siguiente que hacemos es pintar en el editor y el terreno irá ganando altura.

Fig. 29: Comenzamos a pintar regiones del terreno para producir elevaciones y crear relieve.

Otra herramienta útil es la de nivelar (seleccionada en la figura 30).

Fig. 30: Seleccionamos la herramienta de nivelación de terreno.

Fig. 31: Elegimos el valor de altura al que queremos nivelar el terreno.

Esto nos permitirá establecer una determinada altura y al pasarla sobre el terreno en el editor, las zonas más bajas comenzarán a elevarse y las más altas a descender, esto se ilustra en las figuras 32 y 33.

Fig. 32: Las regiones que se encuentran por debajo del nivel se elevan y las que se encuentran por encima descienden.

Fig. 33: Podemos establecer zonas que tendrán una determinada altura en toda su extensión.

Fig. 34: Entramos en modo juego para observar el relieve montañoso desde la perspectiva del laberinto.

Fig. 35: La parte más elevada del relieve se encuentra en la dirección opuesta al sol de la escena.

Luego de jugar un buen rato con las herramientas terminé con algo similar a la figura 36.

El propósito más importante del relieve es cubrir el horizonte y de algún modo proveer de orientación al jugador.

Fig. 36: Utilizamos las herramientas mencionadas hasta que estemos satisfechos con el diseño.

Aplicar texturas al terreno

En la fase final de este artículo sobre cómo crear un terreno en Unity aplicaremos las texturas configuradas anteriormente.

Seleccionamos la herramienta pincel en el inspector, un patrón cualquiera y una textura.

Fig. 37: Seleccionamos la herramienta pincel, elegimos patrón y textura para aplicar.

Luego en el editor vamos recorriendo el terreno y pintando.

Podemos alternar las texturas y con esto evitar que el terreno se vea monótono con una única textura.

Fig. 38: A la textura base le aplicamos otra textura en distintos lugares para romper el patrón de repetición.

Las montañas tendrán una base de tierra y luego iremos aplicando en distintas partes la textura de roca.

Fig. 39: A las montañas le damos una base con la textura de tierra.
Fig. 41: Aplicamos la textura de roca en distintas partes del relieve.

Fig. 40: Vista cercana de la textura de tierra.
Fig. 42: Textura de roca en relieve montañoso.
Fig. 43: Textura de roca en relieve montañoso.

De vez en cuando entramos en el modo juego para ver cómo se observa el relieve desde el interior del laberinto.

Fig. 44: Entramos en el modo juego para observar los resultados.

Fig. 45: Diseño final del laberinto.

Fig. 46: Diseño final del relieve montañoso visto desde el laberinto.

Conclusión

Hemos visto como fácilmente podemos crear un terreno en Unity utilizando las mismas herramientas que el motor nos provee. Además aplicamos distintas texturas para darle diversidad.

El ejercicio propuesto en este artículo y video es muy simple, pero con suficiente tiempo y práctica podemos construir un sin número de relieves.

Este tipo de terrenos tiene la limitación de que no se pueden construir cavernas o cuevas. Para eso tendremos que superponer otro modelo 3D.

Además hemos creado el laberinto utilizando los prefabricados configurados en entradas anteriores. Simplemente haciendo duplicados y moviendo en el espacio.

Podemos hacer laberintos tanto simples como complejos y armar un sistema de selección de nivel si así lo deseamos. Las posibilidades son ilimitadas.

Tengamos en cuenta que este proyecto es sólo un prototipo, para conseguir una mejor calidad gráfica hay que construir mejores modelos 3D y utilizar mayor diversidad de texturas y de mayor calidad. No estaría mal añadir elementos decorativos como escombros o árboles.

Introducción

En este artículo vamos a ver qué es exactamente una función lineal, cuál es su expresión matemática, sus características, cómo hacer su gráfica en el plano cartesiano. Al final vamos a ver algunos ejemplos de aplicación en el desarrollo con Unity.



Expresión matemática de una función lineal

Una función lineal es una función polinómica cuya expresión es:

f (X) = a . X + b

Se lee comunmente «f de x», siendo X la variable independiente, a y b números reales constantes.

Analizando la expresión vemos que dado cualquier valor de X, primero lo multiplicamos por a y luego sumamos b. El resultado de toda esa operación será el valor de f (X).

Características de una función lineal

Para estudiar en profundidad las características de la función lineal vamos a analizar su dominio, gráfica en el plano cartesiano, valores característicos y distintos tipos de rectas.

Dominio

El dominio es el intervalo de valores que puede tomar la variable independiente, normalmente denominada X.

En el caso de la función lineal el dominio es el conjunto de los números reales, es decir que la variable X puede tomar valores desde menos infinito a más infinito.

Entonces, dado un valor de X perteneciente al conjunto de los números reales, encontraremos su valor f (X) correspondiente multiplicando a X por la pendiente y sumando la ordenada al origen.

Artículo: Conjuntos numéricos



Gráfica en el plano cartesiano de una función lineal

La gráfica de f (X) en el plano cartesiano es una línea recta. Podemos trazarla fácilmente encontrando dos puntos de la función y luego, utilizando una regla, trazar la línea que une ambos puntos. También puedes armar una TABLA de VALORES para graficarla.

Uno de estos puntos lo podemos encontrar fácilmente considerando X = 0, en ese punto la función vale lo que su ordenada al origen (el coeficiente b en la expresión genérica).

El segundo punto lo podemos encontrar eligiendo un valor distinto para X y realizando los cálculos, por ejemplo para la función de la figura 1, si consideramos X = 2, al reemplazar ese valor en la función obtenemos el resultado f (X) = 2.

Fig. 1: Ejemplo de la gráfica de una función lineal en el plano cartesiano.

Artículo: Sistema Cartesiano



Ordenada al origen

Este punto característico de la función lineal es el valor de la función cuando X = 0. De manera gráfica, es el punto donde la función lineal corta el eje vertical (conocido como eje de ordenadas). El punto (0,b) se lo conoce como ordenada al origen.

En la gráfica de la figura 1 vemos que la ordenada al origen es el punto (0,1).

Abscisa al origen

Análogamente al caso anterior, la abscisa al origen es el punto en el cual la función corta el eje horizontal o eje de abscisas. En este punto Y = 0.

Una función lineal podría no tener abscisa al origen si se trata de una recta paralela al eje x y con desplazamiento vertical.

La abscisa al origen puede encontrarse haciendo 0 = f (X) y luego reemplazando f (X) por la expresión lineal, por ejemplo en el caso de la figura 1 tenemos:

0 = (1/2) . X + 1.

Despejando X de la ecuación anterior obtenemos el valor de X en el cual f (X) es igual a 0. En la función lineal de la figura 1 la abscisa al origen es el punto (-2,0).

Pendiente de una función lineal

El coeficiente que multiplica a X en la expresión genérica de la función lineal se lo conoce como «pendiente» y es el que establece si la función es creciente o decreciente y en qué magnitud.

Si la pendiente es positiva la función es creciente y si la pendiente es negativa la función es decreciente. Si la pendiente vale 0, el término que contiene X se anula y sólo nos queda f (X) = b, la función lineal vale lo que su ordenada al origen en todo el dominio, en este caso tenemos una recta horizontal (paralela al eje X).

Si sólo disponemos de la gráfica de una función lineal, podemos calcular la pendiente como la tangente del ángulo que forma la recta con el eje horizontal. También podemos encontrar la pendiente utilizando el Teorema de Pitagoras.



Rectas paralelas

Dos rectas son paralelas si sus pendientes son iguales.

Ejemplo de dos rectas paralelas:

f (X) = 2 . X – 1

g (X) = 2 . X + 3

Rectas perpendiculares

Dos rectas son perpendiculares si la pendiente de una de ellas es igual a la pendiente invertida y opuesta de la otra. En el siguiente ejemplo vemos dos rectas que son perpendiculares.

f (X) = 3 . X + 2

g (X) = – ( 1/3 ) . X + 5

La pendiente de g es menos un tercio, el cual es el inverso y opuesto de 3.

Algunos ejemplos de aplicación de la función lineal en programación

La función lineal es una de las funciones matemáticas más útiles y su campo de aplicación es muy variado. Vamos a dar algunos ejemplos de posibles aplicaciones.

Representar trayectorias

Una de las cosas más básicas que se puede hacer con una función lineal es hacer que los objetos se muevan en una trayectoria recta, la función lineal nos relaciona dos magnitudes que podríamos elegir como queramos, por ejemplo la variable independiente podría ser una coordenada X en el espacio y la variable independiente puede ser la coordenada Y en el espacio, luego con cierto ritmo de ejecución podemos hacer que un objeto describa dicha trayectoria.

Por ejemplo en la figura 2 vemos una escena en Unity en la que las esferas van siguiendo una trayectoria que se describe como su posición en x es igual a su posición en y.

Fig. 2: Las esferas van avanzando en la dirección positiva del eje X, su altura Y se determina por el cálculo de la función y = x.

El mismo concepto y con distintas combinaciones de planos se puede aplicar para otros casos como movimientos de vehículos, trayectoria de proyectiles, etc.



Relacionar parámetros de manera lineal

Supongamos que tenemos dos parámetros cualesquiera, por ejemplo la posición del jugador en el eje X y la rotación de algún mecanismo respecto de alguno de sus ejes.

Digamos también que nos gustaría que estos parámetros estén conectados de alguna forma, es decir que la rotación que tenga el mecanismo va a depender de la posición del jugador.

Podemos establecer entonces una relación lineal entre estos dos parámetros, por ejemplo podríamos decir que la rotación del mecanismo respecto de su eje z sea igual a la mitad del valor de la posición del jugador en el eje x más cinco unidades.

Con esto logramos relacionar esos dos parámetros a través de una función lineal.

Implementar magnitudes lineales en nuestra aplicación

En la física existen muchas magnitudes lineales que podríamos estar interesados en implementar en un programa. Un ejemplo de esto puede ser el movimiento rectilineo uniforme, en el cual tenemos una dirección del movimiento establecida y la posición estará definida por una función lineal en la que la pendiente de la recta es la velocidad del objeto y la ordenada al origen es la posición inicial.

En el caso de la Ley de Ohm por ejemplo, si tenemos un circuito con una fuente conectada a una resistencia, la corriente que circula es igual al voltaje de la fuente dividido la resistencia, esto no es otra cosa que una función lineal con la forma i = v / R.

La energía potencial gravitatoria de un objeto es una función lineal de su altura respecto al suelo.

Hay varios fenómenos físicos que se caracterizan por tener un comportamiento lineal y que podríamos estar interesados en implementar.

[xyz-ips snippet=»responsiveAdDesktopAmp»

Conclusión

La función lineal es una de las funciones más básicas pero también de las más útiles por su simpleza de cálculos y su versatilidad. Su gráfica en el plano cartesiano se corresponde con una recta en la que podemos identificar una pendiente y una ordenada al origen, punto en el que la recta corta al eje vertical o eje de ordenadas.

Podemos utilizarla para establecer relaciones lineales entre distintos parámetros de nuestro juego y representar fenómenos físicos de caracter lineal.

Información actualizada sobre esta entrada

Este artículo pertenece a una serie que consiste en hacer un juego simple en primera persona acerca de encontrar objetos dentro de un laberinto. Es una de mis primeras series de cuando empecé el canal, ahora he mejorado mucho este proyecto y puedes descargar el código fuente para importarlo en tu propio proyecto de Unity. Algún día vamos a hacer un remake de esta serie, suscríbete a mi canal para estar al tanto del nuevo contenido sobre Blender, Unity y programación.

Sígueme en itch.io y descarga el código fuente de este proyecto

PUEDES TESTEAR ESTE JUEGO AQUÍ. TAL VEZ TARDE UN POCO EN CARGAR
🔻

MOVEMENT: WASD CAMERA LOOK: MOUSE

Introducción al artículo original

En este artículo vamos a ver cómo hacer un timer en Unity, el funcionamiento será contar hacia atrás desde los valores iniciales de minutos y segundos que indiquemos y además el Timer se encargará de actualizar la información en la interfaz de usuario creada en el video 5 de la serie del laberinto.

En el artículo en el que creamos la interfaz de usuario, lo que hicimos fue crear algunos elementos para el Canvas, que permitirán al usuario comenzar a jugar y ver información sobre el juego.

Ir a la página principal del proyecto

Vídeo relacionado a este artículo – Cómo hacer un timer para cuenta regresiva en Unity


Descargar Scripts de esta parte del proyecto

Descripción del objetivo

El prototipo es un juego en primera persona, en el cual el escenario es un laberinto. Dentro del laberinto habrá un objeto escondido que se debe encontrar antes de que se acabe el tiempo.

Ahora vamos a resolver la parte en la que el tiempo se acaba. Crearemos un Script que se encargará de llevar la cuenta del tiempo que transcurre.

El Timer comenzará la cuenta atrás desde un valor de minutos y segundos que indicaremos en el inspector, esto quiere decir que cada segundo de reloj debemos actualizar la información.

El Script Timer además se encargará de modificar el valor del elemento texto del Canvas (parte superior en figura 1).

Cuando la cuenta del tiempo llegue a cero, el Script Timer ejecutará el método EndGame del Script GameControl, de esta forma el juego terminará y regresaremos al menú principal.

Fig. 1: Se busca controlar el texto de la parte superior para que muestre el tiempo restante.

Resolución

Comenzamos creando el Script Timer y lo abrimos en el editor. Vamos a asegurarnos de incluir el namespace UnityEngine.UI en la parte superior del Script (figura 3). De esta forma podremos acceder a los objetos y métodos para implementar una interfaz de usuario.

Fig. 2: Se crea un nuevo Script C# de nombre Timer.

Fig. 3: Nos aseguramos de importar UnityEngine.UI para acceder a los métodos de la interfaz de usuario.

Campos y variables

Para resolver la funcionalidad del Timer en c# necesitamos contar con una serie de variables.

En la figura 4 vemos que se utiliza [SerializeField], esto simplemente hará que podamos ver y editar estos campos en el inspector.

Los dos enteros serializados (minutes y seconds) son los valores iniciales en los que empezará la cuenta regresiva.

Mientras que los dos enteros privados (m y s) los usaremos para llevar la cuenta del tiempo.

Fig. 4: Definimos algunos campos para que el timer pueda cumplir su función.

Además necesitamos un objeto tipo Text para guardar la referencia de la componente Text del Canvas que tenemos que modificar.

Seleccionamos el objeto Control de la jerarquía y le asignamos el Script Timer. Podemos observar los campos serializados del Script Timer, minutes, seconds y timerText.

Fig. 5: Asignamos el Script Timer al GameObject Control de la jerarquía.

Fig. 6: Tomamos el GameObject Timer y lo llevamos al campo TimerText de la figura anterior.

Vamos a escribir un minuto y cinco segundos para el inicio de la cuenta regresiva. Luego tomamos el GameObject Timer de la jerarquía (que contiene una componente Text, figura 6) y lo arrastramos al campo timerText.

Fig. 7: Escribimos valores para inicializar las variables.

En la figura 7 observamos que en el campo timerText se ha reconocido una componente tipo Text.

Métodos a implementar

Teniendo en mente cómo funciona un Timer, surgen algunas ideas de posibles métodos. Primero para que el Timer comience a funcionar ejecutaremos un método llamado StartTimer. Luego otro método que se ejecute en intervalos de a un segundo que es el que se va a encargar de los cálculos, lo llamaremos UpdateTimer. Un tercer método para detener el Timer, que se ejecutará cuando este termine de contar, este será StopTimer.

Fig. 8: Eliminamos el método Update y creamos nuevos métodos para resolver el problema.

Por último un método que se encargue de modificar lo que se está mostrando en la interfaz de usuario, este método se llamará WriteTimer y tomará como parámetros dos enteros para los minutos y segundos que debe escribir en la interfaz gráfica.

Si no estás muy familiarizado con qué es un método en programación en mi canal hay un video que podría ser útil, también escribí un artículo para la sección de aprender a programar.

Fig. 9: Agregamos también un método para escribir el Timer.

Noten que los parámetros que requiere este método tienen exactamente el mismo nombre que los dos enteros definidos previamente, que se encargan de llevar la cuenta del Timer.

Esto está hecho así a propósito para hablar un poco sobre contextos, probablemente para más adelante en otro artículo.

Instrucciones para los métodos

Método UpdateTimer

Hemos propuesto algunos métodos de manera intuitiva considerando a grandes rasgos el funcionamiento del Timer, ahora debemos rellenar esos métodos con instrucciones C# que son las que harán el trabajo.

El método UpdateTimer se encargará de restar un segundo, chequear si el tiempo no se agotó, y modificar los minutos apropiadamente. Finalmente utilizará el método WriteTimer pasándole los valores de minutos y segundos. En la figura 10 vemos un ejemplo de cómo implementar esto.

Fig. 10: Instrucciones correspondientes al método UpdateTimer.

En la primera instrucción del método disminuimos en una unidad la variable segundos, luego chequeamos si esta variable es negativa, es decir si previamente valía 0 y ahora vale -1.

Si esto pasa nos fijamos si la variable de los minutos vale 0, si es verdadero esto significa que el tiempo del Timer se agotó (dejo el comentario //endGame en esa región). Si los minutos no son iguales a cero, disminuimos en una unidad la variable minutos y hacemos que la variable segundos valga 59.

Finalmente ejecutamos el método WriteTimer pasando las variables m y s como parámetros.

Método WriteTimer

Este método se va a encargar de modificar el objecto Text que habíamos definido. Dependiendo de si la variable tiene un solo dígito (es decir es menor a 10) o tiene dos dígitos (mayor o igual a 10), vamos a ejecutar las instrucciones que se ven en la figura 11.

Lo que se hace es concatenar una serie de strings o cadenas de texto, en el caso de que s sea menor a 10, concatenamos un 0 delante de los segundos para que conserve el formato de dos números.

Hay otras formas de hacer esto como establecer un formato para las cadenas de texto, pero de esta forma aprovechamos para ver cómo fácilmente podemos concatenar texto usando el operador suma.

Fig. 11: Instrucciones correspondientes al método WriteTimer.

Método StartTimer

Este método realizará la inicialización del Timer, es decir tomamos las variables de minutos y segundo que aparecían en el inspector con los valores iniciales y las asignamos a las variables m y s que llevan la cuenta.

Se ejecuta el método para escribir los valores en la interfaz gráfica.

Finalmente utilizamos el método Invoke, dando como parámetros el string «updateTimer», el cual es el nombre del método y el segundo parámetro es 1f que representa un segundo. Esto hará que el método updateTimer se ejecute luego de un segundo.

Fig. 12: Instrucciones correspondientes al método StartTimer. Invocación de updateTimer.

Vamos a agregar esta misma instrucción al final del método updateTimer, de esta forma el método se va a auto invocar cada un segundo. Esto también se puede hacer utilizando el método InvokeRepeating en lugar de Invoke.

Fig. 13: El método updateTimer se invoca a sí mismo cada un segundo.

Para ver más sobre el método Invoke te invito a leer el artículo de la serie fundamental de Unity en el que hacemos que una máquina expendedora nos entregue una lata luego de un tiempo, también puedes ver el video aquí.

Interacciones entre Scripts

El Timer es como un aparato que cumple su función, podemos configurarlo, ponerlo a correr y detenerlo. Teniendo esto en mente vamos a hacer que el Script GameControl sea el que controle el Timer, pudiendo iniciarlo y detenerlo utilizando sus métodos públicos.

Primero vamos a definir un objeto tipo Timer para usar en el Script GameControl.

Luego en el método Start encontramos la referencia de Timer que está asignado al mismo GameObject Control de la jerarquía (jerarquía en figura 6).

Fig. 14: Agregamos un objeto tipo Timer a los campos del Script GameControl. Encontramos referencia en método Start.

En la figura 14 vemos subrayadas, la declaración de este objeto Timer y cómo encontramos la referencia en el método Start.

Ahora el Script GameControl tiene la referencia de la instancia del Script Timer, esto significa que podrá acceder a sus métodos y campos públicos usando el operador punto.

En el método StartGame de GameControl vamos a hacer que el Timer comience a trabajar.

Fig. 15: En el método startGame ejecutamos el método startTimer de Timer.

Paralelamente, también queremos que el Timer tenga alguna manera de avisarle al Script GameControl que se acabó el tiempo, por lo tanto también necesitamos la referencia del objeto GameControl dentro del Script Timer.

En la figura 16 vemos la declaración de este objeto y cómo se encuentra la referencia dentro del método Start.

Fig. 16: Agregamos un objeto tipo GameControl en Timer y encontramos la referencia en Start.

Por ahora vamos a hacer que cuando se acabe el tiempo el juego termine y regresemos al menú principal, es decir lo mismo que ocurriría si apretamos la tecla Escape. Esto podemos lograrlo ejecutando el método EndGame de GameControl.

En videos anteriores lo habíamos declarado con visibilidad privada, lo que implica este método no puede ser ejecutado desde un contexto externo, por ejemplo el Script Timer.

Para que podamos ejecutarlo desde el Script Timer cambiamos a visibilidad pública. Ver figuras 17 y 18.

Fig. 17: Observamos que la visibilidad del método EndGame es privada, por eso no podemos ejecutarlo desde Timer.

Fig. 18: Cambiamos la visibilidad a público para que sea accesible desde otro contexto.

Ahora si, en la región de UpdateTimer en la que detectamos que la cuenta llega a cero (habíamos dejado el comentario //endGame), ejecutamos el método EndGame del Script GameControl y luego la sentencia return para terminar la ejecución del método UpdateTimer.

Fig. 19: Identificamos la región del código en la que se ha agotado el tiempo y ejecutamos el método EndGame.

En el método EndGame del Script GameControl vamos a detener el Timer, como se observa en la figura 20.

¿Por qué no hicimos que el método UpdateTimer produzca la detención del Timer cuando la cuenta llega a cero?

Se pudo haber hecho de muchas formas distintas, en su momento opté por hacerlo así teniendo en mente que el Timer solo cumple la función de llevar la cuenta del tiempo y avisar cuando el tiempo se acaba, luego el Script GameControl que utiliza el Timer decide qué hacer con él.

Fig. 20: En el método EndGame ejecutamos la acción para detener el Timer.

Método StopTimer

En este método sólo vamos a cancelar todas las invocaciones que puedan estar pendientes, también podríamos resetear los valores del temporizador a los valores iniciales.

Como observación, en el código de la figura 19 si el Timer llega a cero, se ejecuta el método EndGame y posteriormente return, quiere decir que no hay ninguna invocación de updateTimer pendiente y en ese caso la instrucción CancelInvoke() no sería necesaria.

Pero podría ser que ejecutemos el método EndGame debido a otras razones, por ejemplo apretar la tecla Escape o el jugador gana la partida, en ese caso si es necesario cancelar las invocaciones de UpdateTimer.

Fig. 21: En el método StopTimer cancelamos las invocaciones que puedan estar pendientes.

Pruebas finales

Voy a asegurarme de que funcione como se espera, primero voy a colocar como valores iniciales 0 minutos 5 segundos y entro en el modo juego.

Fig. 22: Configuro los valores iniciales para observar rápidamente los resultados.

Al entrar en el modo juego el Timer comienza con 5 segundos, hace la cuenta regresiva y al llegar a 0 vemos en la figura 24 que vuelve al menú principal.

Fig. 23: Entramos en el modo juego para probar el funcionamiento de la cuenta regresiva.

Fig. 24: Al agotarse el tiempo, el resultado es el mismo que apretar la tecla Escape, se ejecuta el método EndGame y volvemos al menú principal.

Ahora verificamos que se respete el formato de dos números para los segundos y que se decrementen correctamente los minutos cuando los segundos pasan de cero a 59. Iniciamos el Timer con 10 minutos 5 segundos y entramos en el modo juego.

Observamos que se respeta el formato de dos dígitos para los segundos y además los minutos se decrementan correctamente. El ejercicio está terminado.

Fig. 25: Probamos nuevamente con el timer en 10 minutos 5 segundos.
Fig. 26: Se respeta el formato de la cuenta regresiva.

Conclusión

Hemos visto cómo crear un temporizador simple en Unity, que cumple la función de llevar la cuenta del tiempo, mostrarla en la interfaz de usuario y cuando el tiempo se agota informar al Script GameControl para que decida qué hacer.

Este temporizador se pudo haber implementado de varias formas distintas, en este caso usamos el método Invoke porque me pareció una alternativa bastante simple.

Vimos como podemos modificar fácilmente una componente texto del Canvas siempre que contemos con su referencia, por lo tanto podemos utilizar esto para hacer otras cosas como mostrar el puntaje, mensajes de información, etc.

En este ejercicio están implícitos los conceptos de clase y objeto de programación, el Script Timer es una clase que contiene campos y métodos para resolver un problema en particular, cuenta con la referencia a otros objetos como GameControl para poder enviarle mensajes cuando lo necesite.

Introducción

En este artículo vemos un prototipo de inteligencia artificial para enemigos en Unity.

Dichos enemigos estarán en distintos estados de acuerdo a lo que ocurra durante la partida. Los estados son «Guardia», «Buscando», «Atacando», «Cubriéndose».

La idea de este prototipo surgió porque una persona me escribió en los comentarios del canal preguntando cómo se podría hacer que los enemigos te detecten, se pongan en alerta, avisen a los demás y ataquen.

Para responder a su pregunta cree un proyecto en Unity proponiendo una aproximación al problema e hice un video explicando cómo funcionaba este prototipo de inteligencia artificial para enemigos. Lo puedes ver a continuación.


Descargar archivos del proyecto

Puedes descargar los archivos del proyecto para importar y ver en detalle los Scripts.

En la descarga tenemos un paquete de Unity que podemos importar haciendo clic derecho en la carpeta Assets y con la opción Import Package > Custom Package.

Al importarlo tendremos todos los archivos del prototipo, en la carpeta Assets tenemos los Scripts, prefabricados y escenas que componen la solución. El enemigo inteligente es el Third Person Controller versión inteligencia artificial de Standard Assets al cual se le aplica el Script enemigo que modela su comportamiento, esto hace que sea capaz de detectar al jugador, perseguirlo, dispararle y buscarlo en caso de que lo haya perdido de vista.

¿Qué vamos a hacer con esto?

Por el momento se trata simplemente de un prototipo que hice en un fin de semana y que cumple con el problema que me plantearon. Sin embargo todo puede mejorarse, en base a este prototipo podemos crear un sistema más complejo, mejorar el código, modularizar el código de manera mas coherente, crear un mejor modelo para el arma, separar el rango de visión del Script Enemigo, mejorar en general los estados individuales.

¿Qué es la Inteligencia Artificial?

Antes de empezar a explicar cómo funciona el prototipo me gustaría hablar un poco sobre el concepto de inteligencia artificial, vamos a acotar el alcance ya que hay varias definiciones.

En informática la inteligencia artificial hace referencia a un autómata (o máquina) que es capaz de percibir su entorno y tomar decisiones apropiadas de acuerdo al comportamiento que se le haya programado.

En nuestro caso queremos crear enemigos inteligentes en Unity, así que para hacerlo debemos pensar cuáles son las características fundamentales de un enemigo, su comportamiento y su manera de percibir el entorno.

Por supuesto debemos establecer límites para el alcance de la inteligencia artificial para el enemigo, por ejemplo nuestro enemigo no será capaz de aprender de su entorno.

Salir de la versión móvil
Secured By miniOrange