martes, 23 de diciembre de 2014

Aprendiendo videojuegos con la historia de las consolas: Atari (Continuación)

En el post anterior nos quedamos con nuestro tirador de penaltis apuntando mediante una flecha. Sin embargo, esta no tenía limitaciones a la hora de girar por lo que tendremos que editar el script PlayerControl para añadirle los límites 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 función que impide que la flecha continúe 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 <= 320)
{
 BlockRotation(1);
}
Como vemos tenemos que si el ángulo de rotación en el eje Z del Pivot Point en este caso, esta entre 40 y 50 grados, vamos a llamar a la función que limita dicha rotación pasándole un -1 para indicar que estamos girando a la izquierda y si por el contrario el ya citado ángulo esta entre 310 y 320 grados llamaremos a la función indicando un 1 positivo para decirle que la rotación está siendo a la derecha. De esta forma tendremos la función BlockRotation como sigue:

/*-----------------------------------------------------------------------
*  - BlockRotation() -
*
*  Impide a la flecha de apuntar superar el limite de rotacion a la derecha
* ----------------------------------------------------------------------*/

   
void BlockRotation(int rotationDir)
{
    if(rotationDir == -1)
        thisTransform.eulerAngles = new Vector3(0,0,40);
    else
        thisTransform.eulerAngles = new Vector3(0,0,320);
}


Recibe un entero que indica la dirección en la que está rotando la flecha para apuntar (-1 a la izquierda o 1 a la derecha). Dependiendo de hacia donde sea el giro, va a bloquear en un punto, no dejando pasar de 40 grados a la izquierda o 320 a la derecha. Ángulos que han sido elegidos para permitir tirar al palo o fuera de portería si hacemos un mal cálculo, pero para impedir que la flecha se coloque sobre el gráfico del jugador.

¿Qué hace la sintaxis eulerAngles? Como podemos ver es una función dentro de la clase Transform encargada de manejar la rotación a través de las componentes x,y,z de este en grados. Si queremos cambiar sus valores tendremos que pasarle un vector3 (de tres componentes) como vemos en la función BlockRotation.

Llega el momento por fin de manejar el tiro a puerta, para lo que vamos a necesitar añadir un par de componentes a nuestro balón. El primero será un Box Collider 2D al que aplicaremos un Physic Material para que pueda chocar con otros objetos en la escena y rebotar. El segundo será un Rigidbody 2D que nos permitirá aplicar fuerzas para lanzar la pelota a puerta.

Para añadir tanto el Box Collider 2D como el RigidBody 2D, seleccionaremos nuestro objeto Ball (el balón) e iremos al menú Component/Physics2D/Box Collider 2D primero y luego a Component/Physics2D/RigidBody 2D (bueno, el orden da igual). Una vez aplicados al objeto podremos ver sus propiedades en el inspector mientras lo tengamos seleccionado.


El Box Collider 2D rodea el gráfico con un cuadrado que ayuda a detectar las colisiones a Unity y es el más sencillo de configurar de los dos nuevos componentes. Para empezar tenemos que saber que el tamaño de nuestro balón es demasiado pequeño y daría problemas de cálculo al motor, por lo que vamos a usar un Size o tamaño de 0.05 en X y 0.05 en Y que es el mínimo recomendado para evitar problemas y con él que no vamos a notar diferencia. Además hay que destacar que, aunque para un balón puede ser normalmente más apto un Circle Collider 2D (Con forma de círculo) como estamos manejando pixel art, el Box Collider 2D cumplirá muy bien su función y consume algunos recursos menos al tratar las colisiones.


Con lo hecho hasta ahora aun no hemos terminado con nuestro Box Collider 2D, y es que queremos que la pelota tenga un poco de rebote y no simplemente colisione y frene en seco. Para conseguir esto vamos a crear un Physic 2D Material haciendo clic con el botón derecho en la ventana Project y seleccionando Create/Physics 2D material, al cual vamos a llamar Bounce Material y a guardarlo en una carpeta llamada Physics Materials para tenerlo todo ordenado.

El material que acabamos de crear va a tener una Fricción (Friction) de 1 y un Bounciness (o rebote) de 0.1. Con esto hacemos que haya un cierto rebote pero que la fricción pare relativamente pronto la pelota y así no salga disparada fuera de la pantalla o haga alguna cosa rara. Vamos que queremos imitar un poco de realismo pero tampoco un comportamiento físico perfecto.

Como ya tenemos nuestro Bounce Material lo vamos a arrastrar a la ranura Material del Box Collider 2D de nuestro balón, con lo que este comportamiento se aplicará a la reacción que tendrá nuestro objeto ante una colisión. (¡El balón rebotará contra la portería y el portero!)



¿Cómo configuramos ahora el Rigidbody 2D de nuestro objeto Ball? Para empezar usaremos una masa (Mass) muy pequeña, para que el golpe no sea capaz de desplazar a nuestro portero y meterlo dentro de la portería. Aplicaremos un Linear Drag de 1.2 para ayudar al frenado de la pelota y un Angular Drag de 0, porque no queremos que haya rotación en nuestro gráfico durante su movimiento (básicamente por que es pixel art y la rotación no le va a sentar bien gráficamente hablando). Marcaremos la casilla Fixed Angle para remarcar el que no queremos que haya rotación y elegiremos en Interpolate el valor Extrapolate, en Sleeping Mode Start Awake y en Collision Detection el tipo Continuous, que aunque tiene mayor coste de recursos para realizar cálculos nos va a dar mejor resultado (y nuestro juego es pequeñito y podemos permitírnoslo).

¿Qué significa cada cosa? Interpolate es el metodo de cálculo para la interpolación, es decir, algo así como una previsión de donde estará nuestra pelota en el siguiente fotograma(Extrapolate) para que al motor no se le escape que vaya a haber contacto con otro GameObject. Por otro lado Collision Detection indica el método usado para calcular la colisión (Discrete o Continuous) que indica el tiempo que Unity va a estar "atento" para calcular choques con otros objetos (Explicaciones un poco "por encima". Para más info o más precisa, documentación de Unity).

¡Y hemos terminado de configurar nuestro balón! Sin embargo para que funcione tendremos que añadir la posibilidad de ejecutar un disparo desde el script PlayerControl. Es, por tanto, momento para volver a editarlo. (Ya estabais impacientes por picar más código, ¿eh?)

Tras abrir el Script lo primero que haremos será añadir una referencia pública al transform del balón (Ball), con esto seremos capaces de acceder a dicho transform desde el Script PlayerControl. Añadimos por tanto, la siguiente línea debajo de la variable rotationSpeed que es la única pública que teníamos hasta ahora:

public Transform ballTransform;                // Referencia al transform del balon

Al hacer esto, volver a Unity y seleccionar nuestro Pivot Point (Que es el que contiene el script PlayerControl y el encargado de rotar nuestra flecha para apuntar) vemos que se ha añadido una nueva casilla que espera un Transform. Arrastraremos hasta allí el Game Object Ball (El balón de futbol) indicando así al motor que es a de ese objeto del que necesitamos acceder a su componente transform.

Podemos por fin añadir el siguiente código dentro de una función especial llamada FixedUpdate()

void FixedUpdate()
{
    // Si pulsamos la tecla espacio
    if(Input.GetKeyDown(KeyCode.Space))
    {
        Shoot();
    }
}


Aquí de forma similar a como ya hicimos anteriormente llamaremos a una función Shoot() que crearemos después, cuando se reciba el evento de pulsar la tecla Espacio. Esta vez estamos usando Input.GetKeyDown, que detecta una pulsación de tecla (presionar hacia abajo) pero no que la tecla se quede pulsada (Que sería GeKey) o se suelte (GetKeyUp). Respecto al KeyCode, este toma las teclas a partir de un nombre clave que en este caso es Space para el espacio. Pero vamos a ver también como sería la función Shoot()

/*-----------------------------------------------------------------------
*  - Shoot() -
*
*  Funcion encargada de las acciones para tirar a porteria
* ----------------------------------------------------------------------*/

   
void Shoot()
{
    // añadimos fuerza al balon
    ballTransform.rigidbody2D.AddForce( thisTransform.up * Time.deltaTime * 800 );
}


Esta función añadirá al rigidbody 2D del transform de nuestro balón (mediante AddForce) una fuerza hacia "arriba" haciendo que salga disparado hacia portería en la dirección indicada por la flecha. Para saber qué dirección debe seguir, tomamos la del Pivot Point (thisTransform) y lo multiplicamos por una fuerza de 800 que es aceptable y por Time.deltaTime, para ayudar a que su desplazamiento sea similar aunque varíe el rendimiento del dispositivo. Tenemos por fin una explicación de por qué llamamos a la función Shoot anteriormente desde FixedUpdate y no desde Update. El motivo es que Unity tiene FixedUpdate como recomendación para llamadas a procesos que involucren físicas, como es el caso de añadir fuerza al RigidBody2D en la función Shoot(). Su utilidad es la misma que Update pero se ejecuta a intervalos constantes y no depende del tiempo que se tarde en cargar el fotograma como pasaba con Update. Esto lo hace más apto para cálculos que tengan que ser más precisos. (Como siempre en la documentación de Unity encontrareis una mejor y más acertada explicación de la mano de sus creadores).


Si ejecutamos ahora el juego y probamos a pulsar la tecla espacio nos encontraremos tres problemas. El primero es que el balón sale disparado y se pierde por el fondo de la pantalla atravesando todos los objetos. El segundo es que si pulsamos repetidamente la tecla espacio añadimos más fuerza y mandamos la pelota muy lejos (Es posible que no os deis cuenta de esto porque la pelota ya se habrá salido de la pantalla). Por último, está el tercer problema que es que la pelota no vuelve a su sitio para que podamos volver a tirar a puerta, por lo que hay que parar el juego y volver a ejecutarlo si queremos tirar otra vez. Vamos a trabajar por solucionarlos todos, pero como no nos dará tiempo a algunos en este post, empezaremos por el más simple. Que la pelota choque con la portería.

Para añadir una colisión con la portería vamos a añadir un componente Collider a la misma, pero como esta vez no nos vale una caja, porque la pelota tiene que meterse dentro y eso lo impediría, usaremos un Poligon Collider 2D. Este en un principio puede parecer la misma caja que el Box Collider, pero si en la ventana Scene vamos a esta caja que rodea el gráfico de nuestra portería y pulsamos Mayusculas mientras hacemos clic y luego arrastramos sobre una linea, añadiremos un punto que podremos colocar. Así, daremos forma a los palos de nuestra meta para permitir colisión con ellos (Para borrar usamos control y hacemos clic en alguno de los puntos). Una vez lista la forma si ejecutamos el juego, podemos ver como la pelota se detiene al tocar alguno de los palos.



Y aquí nos vamos a quedar por ahora. A ver si en próximos post podemos hacer que nuestro muñequito de madera se haga alguna paradita y podamos volver a intentar meter gol después de tirar sin tener que detener el juego. Hasta la próxima.

No hay comentarios:

Publicar un comentario