Aprendiendo videojuegos con la historia de las consolas: Atari
(Parte VI) En la anterior parte de este tutorial nos quedamos
pendientes de que nuestro portero de madera colisionase con la
pelota, as que ha llegado el momento de configurarlo para que esto
sea posible y, como ya aprendimos anteriormente, esto ser posible
gracias a un Box Collider 2D y un RigidBody 2D. Vamos a aadirlos a
nuestro mueco de madera. (Si no recordis como se hace volved al
captulo anterior) y a configurarlos con los valores que vemos en la
imagen. Con esto tendremos un portero fuerte que no ser desplazado
dentro de la portera cuando la pelota le golpee.
En este caso vamos a tener (cuando lo programemos) un portero
que estar en movimiento, por lo que marcaremos la casilla de
verificacin is Kinematic, de nuestro RigidBody 2D, ayudando as a
Unity a realizar mejor los clculos fsicos necesarios y consiguiendo
que solo exista colisin con otros objetos en desplazamiento, como
la pelota, y que no se pueda chocar con el poste de la portera que
tiene un Collider esttico (Sin RigidBody). Si probamos a pulsar
play y durante el juego, lanzar la pelota contra nuestro muequito
de madera veremos cmo sale rebotada, por lo que con esto ya podemos
avanzar al siguiente paso.
Queremos hacer que nuestro portero de prcticas se mueva
constantemente para tratar de dificultarnos un poco el meter el
baln dentro de la portera. Para ello crearemos un nuevo Script en
C# al que vamos a llamar GoalKeeperControl, lo arrastraremos sobre
nuestro mueco de madera, y despus lo editaremos escribiendo el
siguiente cdigo:
using UnityEngine;using System.Collections;
public class GoalKeeperControl : MonoBehaviour { public float
speed; // velocidad de movimiento public float limits; // Limites
del movimiento
private Transform thisTransform; // Referencia al transform del
portero private int direction; // Direccion de movimiento private
float timePassed; // Tiempo pasado desde el ultimo cambio de
direccion private float changeTime; // Tiempo para cambiar de
direccion
// Use this for initialization void Start () { thisTransform =
transform; ChangeDirection(); // damos una direccion inicial
aleatoria de movimiento } // Update is called once per frame void
Update () { if(Time.timeSinceLevelLoad < timePassed +
changeTime){ MoveWoodenKeeper(); // mueve el portero } else {
ChangeDirection(); // mover hacia un sitio aleatorio () } }
/*-----------------------------------------------------------------------
* - ChangeDirection() - * * Cambia la direccion de movimiento del
portero *
----------------------------------------------------------------------*/
void ChangeDirection() { timePassed = Time.timeSinceLevelLoad; //
tiempo de juego direction = Random.Range(1,3); // Numero entre 1 y
2 changeTime = Random.Range (0.3f,0.7f); // Rango de tiempo
aleatorio para el cambio MoveWoodenKeeper(); // controla el
movimiento }
/*-----------------------------------------------------------------------
* - MoveWoodenKeeper() - * * Mueve el portero en una direccion
despendiendo de la variable direction *
----------------------------------------------------------------------*/
void MoveWoodenKeeper() { switch (direction) { case 1: // moving
right if (thisTransform.position.x > limits) {
ChangeDirection(); // si se mueve a la derecha debe ir a la
izquierda } else { thisTransform.Translate(thisTransform.right *
speed * Time.deltaTime); } break; case 2: // moving left if
(thisTransform.position.x < -limits) { ChangeDirection(); // si
se mueve a la izquierda debe ir a la derecha } else {
thisTransform.Translate(thisTransform.right * -speed *
Time.deltaTime); } break; default: break; } }}
Como se puede leer en el cdigo tenemos dos variables pblicas de
tipo float que posteriormente ajustaremos desde el Inspector en
Unity y que se llaman speed y limits. Estas se encargarn de definir
la velocidad a la que se mover nuestro mueco de prcticas y que
limites de movimiento tiene, que no van a ser ms que una distancia
desde el centro donde se sita inicialmente hasta los palos de la
portera.
Despus, tenemos varias variables privadas. Por ejemplo
thisTransform lo hemos usado muchas otras veces y en esta ocasin
tiene el mismo objetivo, servir de referencia al transform del
objeto que contiene este script (el portero) para poder usarla y
acceder a dicho transform consumiendo menos recursos por tener que
buscarlo cada vez. Por otro lado, la variable entera direction, se
encargar de decidir si el mueco se mueve hacia la derecha o hacia
la izquierda de forma aleatoria. Nos encontraremos tambin
timePassed, que almacenar el tiempo que llevamos de juego en cada
momento que el movimiento del portero cambia de direccin y
changeTime, encargada esta ltima de recoger un numero de segundos
al azar para hacer el prximo cambio en el sentido del movimiento y
que as no cambie de forma constante y repetitiva.
En Update() vamos a mover nuestro mueco mediante
MoveWoodenKeeper() si el tiempo desde que el nivel se inici
(Time.timeSinceLevelLoad es menor que la suma de timePassed +
changeTime. Es decir, si no ha pasado bastante tiempo desde que el
portero hizo un cambio de direccin sumado al tiempo en el que tiene
que cambiar de nuevo. En caso contrario, cambiaremos de direccin
con ChangeDirection(), iniciando de nuevo el proceso.
La funcin ChangeDirection() se va a encargar de inicializar
timePassed igualndolo al tiempo desde que el nivel se inici
(Time.timeSinceLevelLoad) cada vez que se ejecute esta funcin.
Adems, recoger un valor aleatorio entre 1 y 2 (uno derecha y dos
izquierda) y lo almacenar en direction. Para hacer esto, usamos
Random.Range(), en este caso entre 1 y 3 por que al ser enteros
busca la aleatoriedad entre el nmero mnimo y el mximo-1 (es decir
no incluye el 3, solo elige entre 1 y 2). Con esta misma operacin
obtenemos tambin un nmero entre 0.3 y 0.7 (a los que aadimos una f
para indicar que son float, es decir, decimales) para decidir el
tiempo en segundos que tardar en volver a cambiar la direccin del
movimiento (volviendo a llamarse a esta funcin). Por ltimo llamamos
a MoveWoodeKeeper() para que el mueco ejecute el movimiento en la
nueva direccin elegida.
MoveWoodenKeeper() es una funcin encargada de mover el portero
dependiendo hacia donde le indique la variable direction, que como
ya dijimos cambiar cada changeTime segundos. Usamos pues, un
condicional de tipo switch para hacer que se mueva a la derecha si
direction vale 1 o a la izquierda si vale 2. Sin embargo, tambin
tenemos que tener en cuenta si la posicin x del portero de madera
ha alcanzado su lmite por la derecha (limits) o por la izquierda
(-limits) recurriendo a condicionales if que comprueben si la
posicin del transform es mayor que limits o menor que -limits. Por
otro lado el movimiento en s, lo conseguiremos con
thisTransform.Translate(), una funcin que vara la posicin de un
objeto cuando le indiquemos mediante sus parmetros. En este caso
para ir a la derecha usamos thisTransform.right * speed *
Time.deltaTime, indicando as que se mueva a la derecha a velocidad
speed y teniendo en cuenta Time.deltaTime para que la velocidad de
movimiento no dependa de la del dispositivo en el que se dibuje.
(Esto ya se explic un poco en anteriores captulos).
Por cierto, en el inspector de Unity he configurado speed a 1.5
y limits a 0.5
Si observamos que el portero se dibuja por detrs de la portera
podemos corregir su eje Z del transform.position desde el
inspector. Yo finalmente lo he tenido que poner con un valor de
-0.6.
Como vemos, el movimiento del mueco no supone un reto para el
jugador, que puede practicar el tiro a portera libremente, pero s
que molesta y le obliga a estar atento y hacer clculos, por lo que
creo que es el entrenamiento perfecto de cara al lanzamiento real
en el modo de juego normal. Podemos pasar entonces a lo siguiente:
Reinicio del nivel.
Resulta un poco molesto tener que parar y reiniciar nuestro
juego despus de cada tiro, as que vamos a crear una solucin
temporal para que podamos tirar penaltis infinitos hasta que
decidamos detener la partida. Para conseguir esto, recurriremos a
Application.LoadLevel(NombreDeEscena) para cargar la misma escena
en la que estamos actualmente. Para esto tendremos que pensar en un
evento para lanzar la accin de reiniciar el nivel, as que pudiendo
elegir factores como el fin del movimiento de la pelota, pulsar una
tecla u otros muchos, nos vamos a decidir por el factor tiempo, es
decir, esperaremos un tiempo desde la pulsacin de la tecla de
disparo, o si lo prefers, desde el lanzamiento a portera, dejaremos
que el usuario vea un rato como rebota la pelota o se pierde por el
fondo, y restauraremos la escena actual con el ya citado
Application.LoadLevel(). Descrita la explicacin, veamos el cdigo
que introduciremos en el Script PlayerControl.
Primero necesitamos incluir dos variables privadas y una pblica.
En este caso timeAfterShoot ser pblica para poder decidir desde el
Inspector de Unity el tiempo que queremos que pase para reiniciar
el nivel despus de tirar a puerta (yo he elegido 3 segundos). Las
privadas sern timeSinceShoot, para guardar el tiempo que pasa desde
que se tira a puerta, y un booleano llamado shooted, que nos avisar
de que ya se ha efectuado el lanzamiento. Adems timeSinceShoot ser
inicializado a 0 y shooted a false en la funcin Start(), ya que al
inicio de juego estamos seguros de que no se ha hecho ningn disparo
a puerta y necesitamos un punto de partida.
private float timeSinceShoot; // Tiempo desde que se tira a
puertaprivate bool shooted;public float timeAfterShoot; // Timepo
necesario para reiniciar
// Use this for initializationvoid Start () { timeSinceShoot =
0; shooted = false;...}
Para evitar que el nivel se reinicie constantemente usaremos en
Update() el booleano shooted, que comprobar el tiempo desde que se
tir a portera nicamente despus de que se haya efectuado el tiro y
lo har mediante la funcin CheckLevelRestart()
if(shooted) // El jugador tira a puerta, contemos el tiempo
hasta reiniciar CheckLevelRestart();
Pero antes, para saber cundo se tira a puerta e iniciar el
tiempo desde all hasta el reinicio del nivel, nos vamos a la funcin
Shoot() y aadimos las siguientes lneas al final:
timeSinceShoot = Time.timeSinceLevelLoad;shooted = true;
Indicamos con timeSinceShoot, que tome como referencia el tiempo
desde que empez el nivel para ir contando segundos desde all y con
shooted = true, que acabamos de efectuar un tiro a portera.
Finalmente la funcin CheckLevelRestart() comprobar si el tiempo
pasado desde el inicio del nivel es mayor que el tiempo pasado
desde el inicio del nivel al tiro a puerta (timeSinceShoot) ms
timeSinceShoot, que es el tiempo que debe pasar hasta el reinicio
del nivel despus del ya mencionado disparo a puerta. (Cargamos el
nivel con Application.LoadLevel(GameScene);)
/*-----------------------------------------------------------------------
* - CheckLevelRestart() - * * Funcion que comprueba si ha pasado
tiempo para reiniciar el nivel *
--------------------------------------------------------------------*/
void CheckLevelRestart(){ if(Time.timeSinceLevelLoad >
timeSinceShoot + timeAfterShoot) {
Application.LoadLevel("GameScene"); }}
Si ejecutramos ahora el juego cargara el nivel porque es el nico
que tenemos, pero para hacer bien las cosas necesitamos un poco de
trabajo fuera de cdigo con Unity. Iremos a file/Build Settings y
donde pone Scenes in Build, tendremos que cargar nuestra escena
actual GameScene arrastrndola desde la ventana Project.
Como vemos, el juego sigue un poco despus del tiro y no hay una
pausa, por lo que el portero sigue movindose y saca el baln de la
portera. Solucionaremos esto creando un nuevo script muy sencillo
llamado GameManager que no vamos a asociar a ningn objeto. En este
script aadiremos una variable de tipo Static para manejar estados
del juego que permanecer en la memoria durante toda la ejecucin y
conservar su valor. Y es este el mejor mtodo para hacer esto? No,
pero es el ms sencillo y rpido para nuestro pequeo proyecto, aunque
los expertos programadores pueden recurrir a otro modo de hacer las
cosas. Veamos el contenido de GameManager.
using UnityEngine;using System.Collections;
public class GameManager : MonoBehaviour {
public enum States { inGame, Waiting}; // En juego, En espera
public static States gameState; // Estado del juego
}
Hemos utilizado el tipo enum de C# para aclarar los estados de
juego. As decidimos que haya dos estados: en juego y esperando
(inGame y Waiting), usando estos para bloquear los controles y el
movimiento del portero cuando estemos en espera y para permitir
continuar la partida cuando estemos en juego.
Cundo vamos a poner el juego en espera? Pues en mi caso he
decidido hacerlo cuando la pelota est en una posicin Y determinada.
Con esto, cuando haya lanzado el jugador a puerta y el baln este
subiendo hacia meta, se bloquear en cierto momento el juego
impidiendo que el mueco de madera se siga moviendo y que el usuario
contine teniendo el control del personaje. Para hacer esto vamos a
necesitar otro Script que llamaremos BallScript y que enlazaremos
con la pelota (ya veremos que en un futuro prximo nos va a venir
muy bien). En este script escribiremos el siguiente cdigo.
using UnityEngine;using System.Collections;
public class BallScript : MonoBehaviour {
Transform thisTransform;
// Use this for initialization void Start () { thisTransform =
transform; } // Update is called once per frame void Update () {
if(thisTransform.position.y > 0.2f) { GameManager.gameState =
GameManager.States.Waiting; } }}
Por supuesto, para contrarrestar la activacin del juego en
espera, vamos a poner el estado de en juego nada ms comience el
juego, para ello dentro del script PlayerControl, dentro y al final
de la funcin Start(), aadiremos la siguiente lnea:
GameManager.gameState = GameManager.States.inGame;
Con esto cada vez que se reinicie el nivel y se invoque a esta
funcin, el estado de juego se colocara como inGame.
Como ltimo paso para bloquear el juego cuando el estado sea
Waiting tendremos que colocar condicionales en PlayerControl y
GoalKeeperControl antes de ejecutar las acciones encargadas del
movimiento y el control. As, solo se ejecutar esa parte del cdigo
cuando el estado de juego sea inGame, parndose durante el resto de
la ejecucin. (Pongo un ejemplo pero esto se pondra tanto en Update
y FixedUpdate de PlayerControl, como en Update de
GoalKeeperControl, dejando BallScript sin cdigo de este tipo por
que queremos ver a la bola rebotar)
if(GameManager.gameState == GameManager.States.inGame){ horAxis
= Input.GetAxis...}
Lo siguiente para acabar el captulo de hoy va a ser comprobar si
hemos marcado gol. Pero para conseguir esto tendremos que aadir un
Collider (en este caso a un objeto vaco porque queremos que sea
invisible) que ms que Collider tal como hemos visto hasta ahora,
har de Trigger o interruptor, para chivarnos cuando el baln ha
entrado en una zona concreta de juego. Este tipo de objetos no
visibles se usan en juegos para activar puertas, interruptores
lanzar eventos de animacin y mil cosas ms.
Creamos por tanto un objeto vacio y le aadimos un Box Collider
2D de una medida x = 1, y = 0.17. Con estas dimensiones podemos
centrar nuestro Trigger dentro de la portera, cuando ya se ha
cruzado la lnea blanca de gol y sin acercarnos mucho a los postes,
para evitar un falso tanto si los clculos fsicos de Unity son poco
precisos y sealan gol cuando la pelota ha rebotado en uno de los
postes. Por cierto, llamaremos a este objeto GoalZone y es
importante marcar la casilla isTrigger del Collider, para hacer que
Unity deje atravesarlo con la pelota y lo utilice como Trigger.
Una vez lo tenemos listo, en BallScript tendremos que introducir
el cdigo que detecte que la pelota est entrando en esta zona de gol
e indicar de alguna forma (temporal por ahora) al jugador que ha
marcado, pero para poder lograrlo tambin tenemos que aprender
algunos conocimientos nuevos sobre nuestro motor de juegos
favorito, las etiquetas.
Las Etiquetas nos permiten marcar varios objetos bajo un nombre
comn y realizar acciones con ellos, como comprobar colisiones.
Gracias a las etiquetas podemos elegir que un personaje realice una
accin o emita un sonido cuando choca con un tipo concreto de objeto
y otras acciones distintas si choca con otro objeto distinto. (Por
ejemplo que la lava nos quite energa y por la hierba podamos
caminar libremente).
Para aadir una etiqueta seleccionaremos nuestro objeto GoalZone
y desplegaremos en el Inspector, al lado de Tag, hasta seleccionar
Add Tag Despus, en la casilla Element 0, introduciremos el nombre
de nuestra etiqueta (en este caso tambin GoalZone) y pulsaremos
Enter. Ahora podemos ir de nuevo al inspector, seleccionando el
objeto GoalZone y elegir la etiqueta que acabamos de aadir
volviendo a desplegar al lado de Tag y escogiendo esa nueva
etiqueta que habr aparecido.
Como ya tenemos una etiqueta para saber cuando el baln entre en
una zona de gol, y as nos avise, vamos a editar el script
BallScript y a aadir el siguiente cdigo (una funcin aparte al final
pero dentro de la clase.
void OnTriggerEnter2D(Collider2D col){ if(col.tag == "GoalZone")
{ Debug.Log ("Goal!!"); }}
Bsicamente OnTriggerEnter2D() es una funcin predefinida de Unity
que responde cuando un objeto entra en la zona del Trigger que
hicimos hace un momento. Recibe como parmetro un Collider2D que
hemos llamado col y con el podemos comprobar si el objeto que ha
colisionado cuando la bola entra en la zona tiene la etiqueta
GoalZone. En caso de que la bola est dentro de GoalZone significar
que el jugador ha marcado, por lo que usamos Debug.Log para
escribir en la consola de Unity el texto correspondiente (Que se
pone entre comillas).
Con esto, veremos como cuando el baln entre en la portera
aparecer en la consola de Unity el mensaje Goal!! indicando que el
jugador ha anotado.
En el futuro ya tendremos tiempo de hacer esto de forma mucho ms
bonita, pero de momento lo vamos a dejar as. :) Publicado por
rathsodic en 9:00 2 comentarios Enviar por correo electrnicoEscribe
un blogCompartir con TwitterCompartir con FacebookCompartir en
Pinterest Etiquetas: Desarrollo, Programacin, Unity martes, 23 de
diciembre de 2014Aprendiendo videojuegos con la historia de las
consolas: Atari (Continuacin) En el post anterior nos quedamos con
nuestro tirador de penaltis apuntando mediante una flecha. Sin
embargo, esta no tena limitaciones a la hora de girar por lo que
tendremos que editar el script PlayerControl para aadirle los
lmites que deseamos.
Dentro de la funcion RotateArrow (Es decir entre las dos llaves
que indican su principio y fin) incluiremos unos condicionales con
llamadas a una funcin que impide que la flecha contine rotando. //
Limite de rotacion hacia la izquierda// la flecha se para al entrar
entre un rango de angulos dentro de la circunferencia
if(thisTransform.eulerAngles.z >= 40 &&
thisTransform.eulerAngles.z < 50){ BlockRotation(-1);} // Limite
de rotacion hacia la derecha if(thisTransform.eulerAngles.z >
310 && thisTransform.eulerAngles.z