#9 Pedestal en posición aleatoria. Random.Range

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.

Salir de la versión móvil
Secured By miniOrange