#4 Colocar Prefab en una posición aleatoria

Por GameDevTraum

Introducción

En este artículo creamos el primer Script de programación que se va a encargar de colocar al jugador en una posición aleatoria del escenario. El prefabricado que vamos a colocar es el controlador de primera persona FPSController de Standard Assets.

En el escenario habrá varias puertas de entrada y salida, de modo que elegiremos una de ellas de manera aleatoria y luego colocaremos al personaje frente a la puerta elegida.

Página principal del proyecto

En el siguiente video puedes ver cómo se resuelve este problema.

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.

Punto de aparición del personaje

Comenzamos armando con los prefabricados una escena como la que se observa en la figura 1.

escena de unity 3d en la que se observa un suelo de cesped con margaritas y cuatro portales.
Fig. 1: Disposición inicial de objetos en la escena.

Creamos un empty GameObject que vamos a usar para marcar la posición donde el jugador aparecerá al comenzar el juego.

crear empty game object en unity 3d. modelo 3d de un portal
Fig. 2: Se crea un nuevo Empty GameObject para asignar el Script.

A este objeto lo vamos a llamar “SpawnPoint”.

unity 3d se observa un atardecer y un suelo de cesped con margaritas
Fig. 3: El nuevo GameObject tendrá el nombre “SpawnPoint”.

En el inspector podemos elegir un ícono para poder visualizar mejor el GameObject en la escena.

selección de ícono para gameobject en unity 3d
Fig. 4: Podemos asignar un ícono al GameObject para visualizarlo mejor en la escena.

empty gameobject en unity 3d con ícono asignado.
Fig. 5: El GameObject ahora se muestra con la etiqueta seleccionada.

Vamos a crear un nuevo Tag para asignarle a este GameObject. En la parte superior del inspector tenemos un menú desplegable llamado Tag, al hacer clic nos permite elegir un Tag para asignar o crear uno nuevo con “Add Tag”. Hacemos clic en esta opción.

menu para añadir tag en en unity 3d.
Fig. 6: Menú para asignar un Tag existente o crear uno nuevo.

En la ventana que aparece hacemos clic en el signo más y escribimos el nombre del Tag. Es importante tener en cuenta el uso de mayúsculas.

crear nuevo tag en unity 3d.
Fig. 7: Creamos un nuevo Tag llamado SpawnPoint.

Volvemos a seleccionar el GameObject “SpawnPoint” de la jerarquía y en el menú Tag seleccionamos el Tag “SpawnPoint” que acabamos de crear.

seleccionar tag en unity 3d.
Fig. 8: Asignamos el Tag creado al GameObject “SpawnPoint”.

Creación del primer Script

Finalmente vamos a empezar a programar en C#. Para empezar necesitamos crear un Script C#, para esto hacemos clic derecho en alguna carpeta del proyecto (sugiero la carpeta Script que creamos para organizarnos), luego vamos a Create > C# Script, como se observa en la figura 9.

crear script c# en unity 3d.
Fig. 9: Menú de creación, seleccionamos C# Script.

Vamos a nombrar el archivo como “GameControl” debido a que se encargará de controlar los aspectos fundamentales de nuestro juego. El archivo que se crea se puede observar en la figura 10.

Para saber un poco más sobre Scripts haz clic aquí

script c# en unity
Fig. 10: El nuevo Script lo llamamos GameControl sin espacios.

En programación se suele utiliza una forma de escribir como la que se ve en la figura 10, se la conoce como Camel Case y consiste en eliminar los espacios y distinguir las palabras utilizando mayúsculas.

Usamos esta notación para nombres de Scripts, variables y métodos.

Antes de comenzar a programar, vamos a seleccionar un editor de Scripts, hacemos clic en Edit > Preferences.

ventana de preferencias de unity 3d
Fig. 11: Ventana de preferencias de Unity, seleccionamos el editor MonoDevelop.

Para la serie Mi Primer Juego en Unity usamos el editor de scripts MonoDevelop, este editor fue descontinuado, es por eso que en la Serie Fundamental de Unity se usa Visual Studio.

selección de editor de scripts en unity 3d.
Fig. 12: Se puede elegir el editor que más nos guste, actualmente el editor por defecto es Visual Studio.

Al abrir el Script para editar nos encontramos con un poco de código ya escrito.

En la figura 10 las primeras 3 líneas que comienzan con la palabra “using” sirven para importar librerías que nos permitirán acceder a determinada funcionalidad.

Luego viene la definición de una clase que lleva el nombre que le dimos al Script, en este caso GameControl. Además se indica la super clase, es decir la clase de la cuál hereda su comportamiento, en este caso MonoBehaviour. Osea que todos los atributos y métodos de la clase MonoBehaviour estarán disponibles para que utilicemos.

El párrafo anterior tiene varios conceptos de programación orientada a objetos que no tiene sentido profundizar en este artículo, debido a que es un tema bastante extenso. Sin embargo considero que la mención de palabras como “clase”, “atributos” o “super clase” puede despertar curiosidad y animar al lector a investigar más sobre el tema. En este momento no cuento con artículos sobre programación orientada a objetos, aunque es mi deseo escribirlos pronto.

Luego hay dos métodos definidos, el método Start y el método Update. El método Start se ejecutará automáticamente una única vez al iniciar el juego y el método Update se ejecutará automáticamente una vez en cada Frame del juego.

Clic aquí para saber más sobre métodos

script c# genérico en unity 3d.
Fig. 13: Vista de un Script recién creado.

En la región que se encuentra entre la definición de la clase y la definición dle método Start vamos a colocar todos los atributos de la clase, es decir todos las variables y objetos que utilizaremos para que el Script cumpla su funcionalidad. Hace un tiempo escribí un artículo sobre los tipos básicos de variables que vamos a utilizar en los tutoriales.

Clic para leer el artículo sobre variables

Vamos a escribir dos líneas de código, la primera es “SerializeField” entre corchetes, esto sirve para indicarle a Unity que lo que viene a continuación es un campo que se debe serializar. Sin entrar en detalles esta línea hará que los campos (variables u objetos) privados aparezcan en el inspector.

La siguiente instrucción es “private string tag”, con esto estamos declarando una variable de tipo string de nombre “tag” con visibilidad privada.

Visibilidad también es un tema de programación orientada a objetos, se utiliza para ocultar atributos o métodos y que estos no sean accesibles desde un contexto externo a la clase. Los atributos o métodos con visibilidad pública son accesibles desde otras clases.

script c# genérico en unity 3d. definir variables
Fig. 14: Se define una variable de tipo String llamada “tag”.

Asignar Script a un GameObject

Para que el código se ejecute no basta con crear un Script, debemos asignarlo al menos a un GameObject de la jerarquía.

GameControl se va a encargar de controlar los aspectos básicos del juego. En principio podría estar asignado a cualquier GameObject que esté presente durante todo el juego, pero para una mejor organización vamos a crear un Empty GameObject que llamaremos “Control”.

crear empty game object en unity 3d
Fig. 15: Creamos un nuevo empty GameObject.

Con el nuevo GameObject seleccionado, arrastramos el Script GameControl al inspector para agregarlo como componente.

renombrar game object en la jerarquía de unity 3d
Fig. 16: El nuevo GameObject se llamará Control.
asignar script c# en el inspector en unity 3d
Fig. 17: Podemos arrastrar el Script GameControl a la jerarquía del objeto Control para asignárselo.

En la figura 18 se observa que el Script ha sigo agregado como componente al Empty GameObject Control.

inspector en unity 3d
Fig. 18: El Script ahora es una componente del objeto Control.

Otra forma de lograr esto es utilizando el botón Add Component y buscando el Script GameControl.

add component en unity 3d
Fig. 19: Otra forma de asignar el Script es a través del botón Add Component.

La razón por la que en la figura 18 no vemos el String “tag” que definimos es porque no guardamos los cambios en el Script. En la figura 20 se ve que en la pestaña del Script aparece un círculo en lugar de una cruz, esto significa que hay cambios sin guardar, se puede guardar fácilmente presionando CTRL-S.

Es recomendable guardar con bastante frecuencia para que ante eventuales fallos no perdamos nuestro trabajo.

definicion de variables que se encargarán de colocar el prefab aletoriamente unity 3d.
Fig. 20: El círculo en la pestaña indica que hay cambios sin guardar.

Luego de guardar los cambios vemos en el inspector el string “Tag”. En este campo vamos a escribir “SpawnPoint” que es el nombre que le dimos al tag que asignamos al GameObject SpawnPoint (figuras 5 a 8).

Fig. 21: El string Tag serializado aparece en el inspector.
Fig. 22: Podemos escribir un valor para el String.

Colocar prefab del personaje aleatoriamente

El personaje en definitiva va a ser un GameObject, en particular el Prefab FPSController de Standard Assets. Tenemos que indicarle a Unity de algun modo cuál es el prefab que queremos poner en el escenario.

Para esto vamos a definir un GameObject privado que llamaremos “playerPrefab” y lo indicaremos como serializable para que aparezca en el inspector.

En la figura 23 se observan las dos instrucciones que permiten hacer esto.

definicion de variables que se encargarán de colocar el prefab aletoriamente unity 3d.
Fig. 23: Definimos un GameObject para guardar el Prefab del personaje a instanciar.

En videos anteriores habíamos creado un Prefab del FPSController y lo habíamos colocado en Assets > Prefabs > Personaje. Este es el Prefab que vamos a colocar en el escenario.

prefab a colocar aleatoriamente en unity 3d
Fig. 25: El prefab FPSController de Standard Assets será nuestro personaje.

Tomamos el Prefab FPSController y lo arrastramos al nuevo campo en el inspector, como se observa en la figura 24.

Fig. 24: Asignamos el prefab en el nuevo espacio en el inspector.

Es muy importante que conservemos la referencia del GameObject del jugador, por ejemplo para destruirlo cuando la partida se termine. Esto lo vamos a hacer definiendo un nuevo GameObject privado que llamaremos “player”.

En este caso no marcaremos este campo como serializable, debido a que es para uso interno del Script GameControl.

definicion de variables que se encargarán de colocar el prefab aletoriamente unity 3d.
Fig. 26: Definimos un GameObject privado para guardar la referencia del personaje que colocamos en la escena.

Luego tenemos que saber dónde es que vamos a colocar al personaje en el escenario. Para esto creamos el Empty GameObject que llamamos SpawnPoint (figuras 2 a 5). vamos a hacer que sea hijo de una puerta y crearemos un Prefab de la puerta con el objeto SpawnPoint.

De modo que cada puerta tendrá su propio SpawnPoint. Si tenemos sólo una puerta en el escenario, el personaje aparecerá en ese lugar, pero si tenemos cuatro puertas (como será en nuestro caso) el personaje podrá aparecer en cualquiera de ellas aleatoriamente.

Para empezar necesitamos la referencia de los GameObject SpawnPoint, ¿pero de cuántos?. Como no queremos limitar la funcionalidad de este Script a una cantidad fija de puertas, simplemente haremos que detecte automáticamente cuántos puntos de aparición hay y elija uno de ellos aleatoriamente con la misma probabilidad.

Vamos a definir un vector o array de GameObjects, es decir una estructura de datos que contiene varios GameObjects ordenados, los cuales podremos acceder con un valor entero que indicará su posición en el array.

Para definir este array usamos la palabra GameObject seguida de corchetes, como se ilustra en la figura 27. Como este array contendrá todos los puntos de aparición del escenario lo voy a llamar “spawnPoints” y además lo definiré como privado y un campo Serializable.

definicion de variables que se encargarán de colocar el prefab aletoriamente unity 3d.
Fig. 27: Se define un Array de GameObjects para guardar la referencia de todos los puntos de aparición en la escena.

En el inspector se observa cómo es esta estructura de datos. Aparece como un menú desplegable que tiene un valor entero llamado “Size”, el cual indicará el tamaño del array.

Como se observa en la figura 29, si escribimos un número en esta variable, vemos que aparece esa cantidad de campos de GameObjects.

En este punto podríamos hacer que la variable Size sea igual a 4 y asignar manualmente todos los puntos de aparición, pero no queremos estar limitados a eso, queremos lograr que cuando agreguemos o quitemos una puerta, la selección del punto de aparición se haga automáticamente.

Fig. 28: Visualización del Array en el inspector, podemos indicar el tamaño del Array.

Fig. 29: Al definir el tamaño del Array nos aparecen los campos para rellenar. Podríamos colocar manualmente los puntos de aparición.

Vamos a definir un nuevo GameObject que tendrá la referencia del punto de aparición elegido, lo llamaremos “selectedSpawnPoint” y lo haremos privado y serializable para poder observarlo en el inspector.

definicion de variables que se encargarán de colocar el prefab aletoriamente unity 3d.
Fig. 30: Definimos un GameObject para guardar la referencia del punto de aparición seleccionado al azar.

Programación del comportamiento

Hasta el momento estuvimos definiendo los objetos que necesitaremos, ahora comenzaremos a escribir instrucciones que son las que harán que las cosas ocurran en nuestro juego.

En este momento no cuento con artículos sobre programación básica de C#, eso queda para más adelante.

La primera instrucción la colocaremos en el método Start y se encargará de encontrar todos los puntos de aparición que haya en el escenario y guardarlos en el array “spawnPoints”.

En la figura 31 se observa que el editor nos ayuda sugiriéndonos código a medida que vamos escribiendo. Si esto no ocurre podría ser por dos motivos, la opción “code-autocomplete” está desactivada en la configuración.

El otro motivo puede ser un fallo en el editor, o que Unity no está conectado con el editor. Si esto último pasa hay tratar de solucionarlo porque no solo no tendremos la sugerencia de código sino que no tendremos análisis de sintaxis.

instrucciónes c# que se encarga de colocar el prefab aletoriamente unity 3d. findgameobjectwithtag
Fig. 31: A medida que escribimos código el editor nos va sugiriendo variables o métodos que empiecen con lo que escribimos.

Para encontrar las referencias de los puntos de aparición vamos a utilizar un método de la clase GameObject que nos permitirá encontrar objetos utilizando su tag.

En las figuras 31 y 32 vemos que hay dos variantes de este método, una nos devuelve un único GameObject (figura 31) y la otra nos devuelve un array que contiene todos los GameObjects que tienen el tag que indicamos (figura 32).

Observen cómo estos dos métodos sólo se diferencian por una letra “s”.

instrucciónes c# que se encarga de colocar el prefab aletoriamente unity 3d. findgameobjectwithtag
Fig. 32: Vamos a utilizar el método FindGameObjectsWithTag de la clase GameObject.

La instrucción entonces sería la siguiente:

spawnPoints=GameObject.FindGameObjectsWithTag(tag);

Los parámetros que utiliza un método se ingresan entre paréntesis separados por coma, esto lo explico en el artículo que escribí sobre qué es un método en programación.

instrucciónes c# que se encarga de colocar el prefab aletoriamente unity 3d
Fig. 33: La instrucción en el método Start se encarga de encontrar todos los puntos de aparición del escenario y guardarlos en el Array spawnPoints.

En el segundo video de la serie fundamental de Unity analizo esta forma de encontrar las referencias de los GameObjects y otras cuatro formas más. También escribí un artículo sobre ese video profundizando la información.

En la figura 34 se observa que al entrar en el modo juego, nuestro array es de tamaño 1 y contiene la referencia del GameObject SpawnPoint de la jerarquía (resaltado en amarillo por el propio Unity).

Fig. 34: Al entrar en el modo juego, vemos que en el inspector está la referencia del punto de aparición.

Vamos a crear algunas copias de este GameObject con CTRL-D y entrar nuevamente en el modo juego.

Fig. 35: Se crean copias de los puntos de aparición.

Ahora podemos ver que el array es de tamaño 4 y contiene todas las referencias de los GameObjects.

Fig. 36: Al entrar nuevamente en el modo juego, ahora el Array tiene cuatro elementos y están todas las referencias.

Colocamos un SpawnPoint en cada puerta.

colocar prefab aleatoriamente. posicionar empty objects
Fig. 37: Colocamos un punto de aparición en cada puerta.

Seleccionar un elemento aleatorio del array

Para elegir un punto de aparición aleatorio necesitamos elegir aleatoriamente un elemento del array. Esto lo haremos definiendo una variable entera de nombre “rand” que será igual a un número aleatorio entre 0 y la cantidad de elementos del array menos 1.

Esto último es así porque en C# el primer elemento del array tiene la posición 0 y el último la posición “Length-1” (tamaño del array menos 1).

Para generar un número entero aleatorio utilizamos el método Range de la clase Random e indicamos dos parámetros, el inicio del intervalo (valor que se incluye) y el final del intervalo (valor que se excluye). Como se indica en la figura 38.

instrucciónes c# que se encarga de colocar el prefab aletoriamente unity 3d
Fig. 38: La segunda instrucción genera un número entero aleatorio entre 0 y la cantidad de puntos de aparición menos 1.

Noten como el rago de valores de este número se ajustará automáticamente a la cantidad de puntos de aparición que haya en la jerarquía.

El siguiente paso es obtener la referencia del punto de aparición que se encuentra en la posición que define ese entero aleatorio. Para acceder a la componente de un array utilizamos el nombre del array y entre corchetes el número entero de la posición.

El elemento del array lo asignamos al GameObject selectedSpawnPoint previamente definido. Esto y lo anterior lo podemos ver en la figura 39.

instrucciónes c# que se encarga de colocar el prefab aletoriamente unity 3d
Fig. 39: En la tercer instrucción se selecciona el punto de aparición del array.

En las figuras 40 y 41 vemos dos pruebas del juego en las cuales se ha elegido un punto de aparición distinto.

Fig. 40: En esta prueba el punto de aparición es el D.
Fig. 41: En esta prueba el punto de aparición es el B.

El siguiente paso es colocar el prefab del personaje en la posición del punto de aparición seleccionado.

Para esto vamos a utilizar el método Instantiate de la clase MonoBehaviour (super clase de GameControl).

El cuarto video de la serie fundamental de Unity se trata sobre el método Instantiate, aunque ese video es algo extraño, en el artículo se explica en detalle cómo funciona el método.

El método Instantiate tiene varias versiones, vamos a utilizar una que requiere el Prefab a instanciar, la posición en donde se va a colocar y la rotación que tendrá.

Para el prefab habíamos definido el GameObject “playerPrefab”. Mientras que la posición y la rotación la leeremos del GameObject “selectedSpawnPoint”.

La ejecución del método Instantiate devolverá un GameObject el cual será la referencia del GameObject creado, es decir la referencia del jugador. Vamos a guardar esta referencia en el GameObject “player”, porque probablemente la usemos mas adelante.

Lo que se explicó en los últimos tres párrafos se ilustran en la última instrucción de la figura 42.

instrucciónes c# que se encarga de colocar el prefab aletoriamente unity 3d
Fig. 42: La cuarta y última instrucción coloca el prefab del personaje en el punto de aparición elegido.

Como observación se puede mencionar el uso del operador punto para acceder a los atributos position y rotation, los cuales se encuentran definidos en la componente Transform.

En principio no es tan simple de entender, pero a medida que adquiramos práctica y estudiemos un poco de programación va a pasar a ser algo trivial.

En las figuras 43 y 44 se muestran dos pruebas del juego en las cuales el jugador aparece en distintas posiciones.

Fig. 43: El personaje aparece en la puerta de la esquina superior izquierda.

Fig. 44: El personaje aparece en la puerta de la esquina inferior derecha.

Modularización

Estas cuatro instrucciones que hemos creado colocan al personjate en una de las puertas aleatoriamente.

Seria genial si tuviésemos una instrucción que se llame “colocarAlPersonajeEnUnaPuertaAleatoria” y que haga todo el trabajo. Entonces podríamos poner una sola instrucción dentro de start en lugar de cuatro.

Afortunadamente podemos hacer exactamente esto definiendo un nuevo método.

script c# genérico en unity 3d, método que se encarga de colocar el prefab aletoriamente
Fig. 45: Se define un método privado llamado “placePlayerRandomly” que no requiere parámetros y no devuelve parámetros.

El método se va a llamar “placePlayerRandomly” (colocar personaje aleatoriamente), no va a requerir parámetros, no va a devolver datos y va a ser privado. La definición de un método con todas las características enunciadas se ilustra en la figura 45.

En los primeros dos videos de esta serie utilicé nombres en español, pero considero que es mejor acostumbrarnos a utilizar nombres en inglés, además de que me facilita el trabajo de traducción.

script c# genérico en unity 3d, método que se encarga de colocar el prefab aletoriamente
Fig. 46: Movemos las cuatro instrucciones al método placePlayerRandomly.

Vamos a seleccionar las cuatro instrucciones que pusimos en el método Start, las vamos a cortar y a pegar en este nuevo método, como se observa en la figura 46

Luego en el método Start hacemos la llamada al método placePlayerRandomly, como vemos en la figura 47.

script c# genérico en unity 3d, método que se encarga de colocar el prefab aletoriamente
Fig. 47: Hacemos la llamada a placePlayerRandomly en el método Start.

Reiniciar escena

Para probar fácilmente la función de colocar al jugador en una posición aleatoria, voy a hacer que la escena se reinicie
al pulsar la tecla R.

Para esto necesitamos agregar el Namespace UnityEngine.SceneManagement, un Namespace es simplemente una colección de clases agrupadas bajo un nombre, en este caso se trata de clases relacionadas al manejo de las escenas en Unity.

Agregamos el Namespace en la línea 4 que se observa en la figura 48.

script c# genérico en unity 3d. definir variables y métodos. scenemanagement
Fig. 48: Importamos la librería SceneManagement.

Para hacer la lectura del teclado usamos una sentencia if y la clase Input. El video 1 de la Serie Fundamental de Unity se trata sobre leer las entradas del teclado y el mouse, en el artículo del video se profundiza la información.

Dentro de la sentencia if ejecutamos el método estático LoadScene de la clase SceneManager y pasamos como parámetro el número cero, indicando que se cargue la escena 0. Esta escena sería la primer escena declarada en la ventana Build Settings. En nuestro caso solo tenemos una escena así que simplemente se reiniciará.

script c# genérico en unity 3d. definir variables y métodos. loadscene
Fig. 49: En el método Update hacemos la lectura de la tecla R y si es pulsada cargamos la escena 0.

Correcciones finales

Lo último que haremos será modificar la rotación de los Empty GameObjects SpawnPoint, de tal forma que la flecha azul que indica la dirección, apunte en dirección opuesta a la puerta.

Con la tecla E seleccionamos la herramienta de rotación y manteniendo pulsada la tecla SHIFT hacemos que las modificaciones del ángulo sean con paso fijo, si no me equivoco de 15 grados. De esa forma si la el ángulo del eje Y es por ejemplo 0 grados, podemos llevarlo exactamente a 90 grados gracias a ese paso fijo.

Fig. 50: Rotación de los puntos de aparición para que el personaje aparezca como si hubiese entrada por la puerta.

Fig. 51: En este caso el eje azul indica la dirección a la que aparece mirando el personaje. En este caso hay que girarlo 90 grados en sentido horario.

Para concluir hacemos una compilación. En las figuras 53 y 54 se observan dos inicios distintos del juego, en cada uno el jugador aparece en una puerta distinta.

colocar prefab aleatoriamente. el personaje aparece en una de las puertas
Fig. 53: Prueba de compilación 1.

colocar prefab aleatoriamente. el personaje aparece en una de las puertas
Fig. 54: Prueba de compilación 2.

Conclusión

En este artículo vimos cómo crear el primer Script C# que se encargará de colocar al personaje en una posición aleatoria del escenario.

Se analizaron todos los atributos para cumplir el objetivo y se explicó para qué se usaba cada uno.

La modularización es una herramienta útil para organizar nuestro código y hacerlo más intuitivo.