Aprendiendo Videojuegos Con La Historia de Las Consolas

71
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 añadirlos a nuestro muñeco de madera. (Si no recordáis como se hace volved al capítulo 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 portería 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 verificación is Kinematic, de nuestro RigidBody 2D, ayudando así a Unity a realizar mejor los cálculos físicos necesarios y consiguiendo que solo exista colisión con otros objetos en desplazamiento, como la pelota, y que no se pueda chocar con el poste de la portería que tiene un Collider estático (Sin RigidBody). Si probamos a pulsar play y durante el juego, lanzar la pelota contra nuestro muñequito de madera veremos cómo sale rebotada, por lo que con esto ya podemos avanzar al siguiente paso. Queremos hacer que nuestro portero de prácticas se mueva constantemente para tratar de dificultarnos un poco el meter el balón dentro de la portería. Para ello crearemos un nuevo Script en C# al que vamos a llamar GoalKeeperControl, lo arrastraremos sobre nuestro muñeco de madera, y después lo editaremos escribiendo el siguiente código: using UnityEngine;

description

Aprendiendo Videojuegos Con La Historia de Las Consolas

Transcript of Aprendiendo Videojuegos Con La Historia de Las Consolas

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