Puntería dinámica

En esta ocasión vamos a estar viendo la teoría y practica detrás de la puntería dinámica. Esta mecánica es usada en prácticamente todos los juegos de acción, se trata de como la precisión de un arma de fuego decrece a medida que se dispara en modo automático o ráfaga. También veremos como la distancia afecta la puntería y cómo manejar la distancia como un factor.

acc

Teoría

Para entender cómo funciona la aplicación que vamos a estar usando es necesario entender aún mas sobre matemática vectorial y proyección de rayos. Básicamente el comportamiento al que queremos llegar es que, al disparar nuestra arma, el impacto no sea siempre certero según donde indica nuestra mira. Desde un punto de vista geométrico, convertimos un punto en una esfera de imprecisión.

2018-04-23 22_09_21-Vector math Acc1 - Google Drawings

Una vez que tenemos definida la distancia de puntería, tomamos un punto al azar dentro de la esfera. Con ese punto y teniendo en cuenta que nuestro punto de vista es el objeto de escena conocido como Cámara. Podemos trazar un rayo desde el arma hasta el punto.

2018-04-23 22_09_37-Vector math Acc2 - Google Drawings
Las líneas azules punteadas representan proyecciones de rayos.

El detalle con esto es que nuestra perspectiva como jugador y el rifle no están centrados en el mismo punto. Acá comienza el problema de la perspectiva en tercera persona. Lo que si podemos hacer es encontrar el punto al que la cámara está centrada y hacer que el rifle dispare hacia ese punto. De esta manera estamos trazando un rayo desde la cámara hasta el punto generado y desde el rifle hasta el punto donde la cámara encontró un obstáculo.

2018-04-23 22_09_55-Vector math Acc3 - Google Drawings

Con esta solución además cubrimos el caso donde la cámara se centra en un objeto que para el rifle esta ocluido por otro objeto, pero para la cámara no.

2018-04-23 22_10_09-Vector math Acc4 - Google Drawings

Practica

Esta práctica no tiene muchas clases pero si son extensas. Podemos comenzar con nuestra clase de rifle, sabemos que va a tener varias propiedades para definir su comportamiento. Estas incluyen, puntería, distancia de puntería, velocidad de disparo, referencias a prefabs, etc.

2018-04-23 23_41_30-UnityLab - Microsoft Visual Studio

Una de nuestras propiedades expuestas al editor es puntería, pero en realidad para facilitarnos los cálculos es mejor trabajar internamente con el concepto de imprecision inaccuracy.

2018-04-23 23_41_45-UnityLab - Microsoft Visual Studio
Para calcular la imprecisión tenemos que invertir el valor de la puntería y expresarla en un numero comprendido entre 0 y 1. El numero 100 es la regla de escala que vamos a usar en nuestro juego, es decir, la puntería va a estar expresada en un numero porcentual, lo cual tiene bastante sentido para el jugador. Por último, como la imprecisión va a ser usada como factor, necesitamos lograr que cuanto mayor puntería, menor imprecisión, y que tener 100 de puntería, involucra que no hay imprecisión, por ende, el cálculo total debería poder dar cero, para eso agregamos el valor de corrección de 1.

Cuando se trata del comportamiento principal, el de disparar es cuando entramos en una sección un poco más compleja, pero por suerte podemos separar diferentes secciones en diferentes métodos para mejorar la legibilidad. Primero podemos conseguir el punto aleatorio dentro de la esfera de imprecisión. Acto seguido, proyectamos el rayo desde el rifle hasta el punto de foco de la cámara. Según el resultado de esa proyección, decidimos si mostramos el impacto o solo la línea de disparo.

2018-04-23 23_42_18-UnityLab - Microsoft Visual Studio
Como se puede ver, cada disparo que se produce reduce la puntería según el factor dado. Como la imprecisión siempre se calcula en base a currentAccuracy y no Accuracy, nos aseguramos que el cálculo de disparo sea siempre el correcto.

Physics.Raycast(Ray ray, out RaycastHit hit, float range) proyecta un rayo (línea recta) cubriendo una distancia dada y devuelve un bool. El bool es true si el rayo intersecta cualquier collider en su trayecto, y la información de la intersección se guarda en el 2do valor retornado, el RaycastHit. Esta clase contiene informacion muy útil como el punto exacto de colisión y una referencia explícita al collider detectado.

Un detalle no menor es el rayo proyectado desde el arma. aimPoint es un punto en el espacio que denota una posición hacia donde queremos mirar. El detalle es que Ray(Vector3 from, Vector3 direction) usa el 2do vector como dirección y no como punto espacial. Todo Vector3 usado como dirección es trasladado hacia el punto de referencia antes de ser usado como posición espacial. Por eso la resta de vectores (aimPoint – firePosition) basicamente lo que estamos diciendo es que se trace un rayo desde firePosition en dirección hacia el punto espacial de aimPoint.

2018-04-23 22_08_49-Acc5 - Google Drawings

El beneficio de adoptar la práctica de hacer métodos sin implementación previa al uso es que podemos separar en pasos nuestro flojo de código, previamente ya sabíamos que necesitaríamos un Vector3 que era nuestro punto al azar, y es mejor trabajar primero en el flujo general, todo lo que sea un requisito de ese flujo general lo podemos delegar a funciones sin implementación. Esto nos ayuda a tener una mejor visión del caso de uso sin tener que interrumpir la cadena de pensamiento para implementar algún algoritmo.

Ahora implementemos la generación del punto aleatorio. Podemos usar una función muy cómoda del Random de Unity que nos devuelve un Vector3 aleatorio que se ubica dentro de una esfera de radio 1. A este vector lo podemos multiplicar por la imprecisión, y aquí es la razón por la cual nos conviene a nivel de cálculo usar imprecisión y no puntería. ¡La imprecisión es un factor y no un divisor!

Una vez obtenido ese punto lo que tenemos que hacer es trasladarlo a la distancia de puntería, esto nos termina dando un punto aleatorio en una esfera de imprecisión a la distancia adecuada. Una vez tenemos este punto, trazamos un rayo desde la cámara, la idea de este rayo es detectar si hay algún objeto entre el punto donde terminaría nuestro disparo y la cámara. Si existe un objeto, vamos a devolver el punto de intersección, sino, el calculado al azar. La razón detrás de esto esta explicada en la teoría, no podemos simplemente hacer que el arma dispare hacia el punto calculado porque si hay un objeto de por medio, nos va a mostrar un impacto lejos del punto de mira. Geometricamente no es que este mal, pero para el jugador se va a sentir muy extraño que el personaje este disparando al costado de donde nuestra perspectiva está apuntando.

2018-04-24 14_35_32-UnityLab - Microsoft Visual Studio
El concepto de tomar un punto aleatorio se compone por componer un vector de distancia y sumarlo al aleatorio radial multiplicado por un factor.

Por ultimo tenemos la cortina que nos permite repetir la acción de disparo solo si se cumple el tiempo de velocidad de disparo. Y la estructura básica de la clase Rifle.

2018-04-23 23_43_10-UnityLab - Microsoft Visual Studio

A nivel de estructura básica lo único que nos tenemos que asegurar es que la puntería actual tienda a regresar al valor por defecto y actualizar la mira dinámica.

2018-04-23 23_41_58-UnityLab - Microsoft Visual Studio

El efecto que se busca con esta práctica es que la imprecisión dependa de la distancia y del arma, por ende, la agrupación de impactos va a ser menor en obstáculos más cercanos.

Mira Dinámica

Nuestro objeto de mira dinámica es bastante sencillo de implementar, pero no vamos a estar manejando escalas ni posición especifica de ninguno de los 4 rectángulos que conforman la mira. Vamos a usar un acercamiento mas ingenioso, usando las anclas de los componentes de UI de Unity para forzar que los rectángulos siempre estén en la posición relativa adecuada y luego lo que hacemos es ajustar las dimensiones del rectángulo padre.

2018-04-25 13_41_53-
¡El inspector de cada componente de la mira, lo importante es especificar las anclas de Unity!

Los 4 componentes de la mira van a estar anidados bajo un padre que solo tendrá el comportamiento de la mira avanzada. La idea de este script es modificar el sizeDelta del rectángulo (que se traduce al ancho y alto en la transform) según la imprecisión del arma.

2018-04-23 23_43_10-UnityLab - Microsoft Visual Studio
El comportamiento de mira solo modifica las dimensiones del rectángulo, esta función es llamada desde el arma o algún otro componente cuando es necesario.
2018-04-25 13_41_32-Unity 2017.4.1f1 Personal (64bit) - Accuracy.unity - UnityLab - PC, Mac & Linux
Anidación de la mira, cada hijo es uno de los rectángulos verdes.

Aquí abajo vemos el efecto deseado, agregue una imagen blanca que cubre toda la dimensión del padre mira para demostrar el punto de las Anclas.

crosshair

Movimiento, Cámara y control

Por último, sin entrar mucho en detalles, este es el código para controlar el personaje y la cámara. Es bastante estándar y no es relevante al concepto de puntería en sí.

El pase de diapositivas requiere JavaScript.

 

 

Deja un comentario