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 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. Cómo colocar Prefab en una posición aleatoria
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.
Creamos un empty GameObject que vamos a usar para marcar la posición donde el jugador aparecerá al comenzar el juego.
A este objeto lo vamos a llamar «SpawnPoint».
En el inspector podemos elegir un ícono para poder visualizar mejor el GameObject en la escena.
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.
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.
Volvemos a seleccionar el GameObject «SpawnPoint» de la jerarquía y en el menú Tag seleccionamos el Tag «SpawnPoint» que acabamos de crear.
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.
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í
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.
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.
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
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.
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».
Con el nuevo GameObject seleccionado, arrastramos el Script GameControl al inspector para agregarlo como componente.
En la figura 18 se observa que el Script ha sigo agregado como componente al Empty GameObject Control.
Otra forma de lograr esto es utilizando el botón Add Component y buscando el Script GameControl.
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.
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).
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.
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.
Tomamos el Prefab FPSController y lo arrastramos al nuevo campo en el inspector, como se observa en la figura 24.
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.
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.
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.
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.
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.
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».
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.
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).
Vamos a crear algunas copias de este GameObject con CTRL-D y entrar nuevamente en el modo juego.
Ahora podemos ver que el array es de tamaño 4 y contiene todas las referencias de los GameObjects.
Colocamos un SpawnPoint 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.
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.
En las figuras 40 y 41 vemos dos pruebas del juego en las cuales se ha elegido un punto de aparición distinto.
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.
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.
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.
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.
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.
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.
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á.
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.
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.
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.