lunes, 27 de abril de 2015

Adaptando la cámara al cambio de gravedad

Después de tener la cámara funcionando andando por el suelo, faltaba ponerla apunto para que funcione al cambiar la gravedad, cuando andemos por las paredes o el techo.



Cambio de gravedad


Ahora en lugar de tener un trazado la cámara, tiene 4, uno por cada posible cambio de gravedad, y según la gravedad en ese momento la cámara irá por un raíl u otro. Además como tenemos el suavizado de cámara, a la hora de pasar de uno a otro se hará automáticamente de forma progresiva.




Necesito suavizar 3 valores ahora, la posición de la cámara, hacia donde mira, y el vector up. Este último es nuevo, ya que hasta ahora dábamos por supuesto que arriba era arriba, pero al cambiar la gravedad el concepto de arriba ya no está tan fijo. A la hora de implementar esto tuve bastantes problemas. Utilizaba la posición, vector forward y vector up del transform de la cámara como referencia actual e iba modificando su posición haciendo Lerps tomando como meta el valor de cámara deseado. Pero no conseguía que el vector up cambiase, siempre quedaba como el vector up normal (y+). Después de bastantes pruebas comprobé que tenía que ver con el vector forward, al dar valor a este se sobrescribía el up. Algo que no tiene mucho sentido, pues yo puedo estar mirando hacia delante y estar haciendo el pino o estar de pie normal, un vector no debería afectar al otro.



Probé a ir girando la cámara al igual que hago con el personaje, aprovechar la misma función que va girando uno y girar el otro. Pero no funcionó bien, no gira o gira, pero al llegar al giro máximo vuelve a la posición inicial. Finalmente conseguí que funcionase como se espera usando la función LookAt. Esta función toma dos parámetros, el punto hacía el que mirar y el up. Cambia un poco el concepto, no toma el vector forward sino un punto hacia el que mirar. Son cosas complementarias, pero yo lo tenia hecho para sacar el forward, así que me tocó cambiar un poco esa parte. Por otra parte no hay un punto “LookAt” en el transform, así que no puedo ir haciendo una transición hacia ese punto directamente, por lo que guardo un valor actual al margen para poder seguir usando Lerp sobre el valor. Con esto funciona bien en giros de 90º y hace un cambio algo brusco en los de 180º, pero podría aceptarlo. Bueno, cuando funciona bien, el botón responde y el personaje no se queda girando de forma infinita sin gravedad como si hubiera salido al espacio.





Y es que me ha dado varios problemas no vi en las pruebas anteriores o que vi pero achaqué a que la cámara no estaba todavía bien. El primer problema es que había veces que daba a uno de los botones de cambio de gravedad y ni caso. Tuve que buscar el origen del problema, a ver donde se perdía esa pulsación de botón que no se transformaba en la acción esperada. Y descubrí que por mi código iba pasando de un lado a otro de forma correcta, y que el problema estaba en la máquina de estados de mecanim. Es decir que yo le ponía que tocaba giro de gravedad y mecanim a su bola. Comprobé transiciones, al ser un estado que se alcanza desde cualquiera (usando el meta-estado any), es muy difícil comprobar que puede interrumpir cualquier otro estado o transición, algo que en teoría debería hacer, pero hay algo en la práctica que no va bien. Después de consultar por foros he llegado a la solución / apaño de obligar a hacer esa transición desde código en lugar de pasar el valor a mecanim para que lo haga él. Osea que ahora cuando llega el momento en que hay que hacer un cambio de gravedad hago algo en plan “gravedad.play()”, forzando el cambio de estado en lugar de dar los parámetros para que la lógica de la FSM lo hiciera sola. No me gusta como solución, ya que entonces tengo una máquina intervenida externamente cuya lógica no queda cerrada en si misma.



La cosa es que así ya funciona, ahora siempre que doy al botón obtengo la respuesta oportuna en la pantalla. El problema es que hay veces que el giro no para, como se ve al final del vídeo superior. Trasteando llegué a la misma conclusión que antes. Yo detectaba bien ese momento y ponía bien el valor a la variable para terminar el giro en mecanim, y la FSM de mecanim se lo pasaba por el arco de Trajano. Misma solución, obligar la transición de estado por código. Problema solucionado... salvo en un caso. Si mientras cambias la gravedad tienes pulsado otro botón como uno de dirección siempre se queda pillado girando y girando como Sandra Bullock.



Encontré que el problema del giro infinito venía de la forma de detectar el final del giro. Hice una función que comparaba cuaternios y te daba la diferencia de sus giros. El problema es que si estás pulsando alguna dirección mientras das a uno de los botones de giro de gravedad, aunque los botones de dirección no se tengan en cuenta durante el giro le pueden haber dado algo de impulso al personaje y que gire un poco todavía, sobre si mismo, entonces ese giro que no afecta al giro de gravedad (da un poco igual si giras mirando para Cuenca o Alaska), pero en la diferencia de cuaternios si afecta y nunca llegamos a estar por debajo del umbral como para detectar que hemos acabado de girar. La solución fue sencilla, tener en cuenta solo el eje de giro en la diferencia de cuaternios, es decir solo el giro que nos interesa, y pasar de los demás.



Entonces descubrí nuevos problemas en las pruebas. Si hacías muchos cambios de gravedad rápido podía pasar que la gravedad no coincidiese con la cámara. O bien veíamos al personaje pegado a una pared, en cuclillas y contra la pared como spiderman; o el personaje iba bien, pero la cámara iba por el techo, en lugar de por el suelo, por lo que apenas vemos al personaje.



No solo esto, en las distintas pruebas, correcciones, intentos de hacer las cosas distintas, etc he sufrido: nuevos giros infinitos, veces en que el giro del personaje, de la gravedad y de la cámara no coincidían al acabar, que la cámara no gire 90º sino menos y quede la cámara torcida como en una peli de miedo, que el giro sea inmediato y no gradual, que gire 180º cuando intentas girar 90º, o que al personaje de repente le cueste más moverse, por alguna razón.



Y lo malo es que cada vez que tiene un problema hay que rastrear el código, ver qué cosas se ejecutan con qué valores, y si algo no se ejecuta una vez, encontrar el porqué. Porque además la mayor parte de errores se daban después de estar uno o dos minutos jugando con la gravedad, al principio funciona bien. Y cada vez que parcheaba un problema surgía otro, un horror que no se si seré capaz de narrar entero. Algunos de los cambios que he hecho son los siguientes.



Volver a la detección original de final de giro con un evento de rescate de emergencia. La nueva forma de detectar los giros estaba bien, salvo porque hay varias formas posibles de representar una rotación y a veces coincidían representaciones que daban un valor de 0º en el eje que yo miraba. Por no enrollarme mucho es parecido a lo que pasa en el Gimbal Lock. Y como había veces que si había algún tipo de giro de última hora antes del cambio de gravedad la resta de cuaternios no coincidía, entonces he puesto un evento de emergencia. El giro normalmente dura 1 segundo, si no se ha detectado el final del giro y ha pasado 1,1 segundos, entonces termino el giro de emergencia y pongo los valores deseados de final de giro. Es una forma un poco chapucera para mi gusto, pero efectiva, no he vuelto a tener giros infinitos.



También he vuelto a girar la cámara poco a poco con el giro del personaje, y cuando acabo este giro me aseguro de dejar la cámara con los valores que quiero, sobre todo el up actual, que cambio al up que quiero tener de forma inmediata, para evitar el efecto repetición de la jugada, ya que se hacía una vez el giro a mano y otra al interpolar el valor del up, del anterior al giro al que quiero.



Intentando combatir el problema del personaje que pierde fuerza de movimiento, he hecho una función que limpia los vectores canónicos de la cámara tras hacer el giro de gravedad, para evitar pequeños errores de redondeo que se iban acumulando. Pero es único problema que no estoy seguro de haber acabado del todo con él.



Hace también muchos cambios que no me pasa que el protagonista acabe con un giro distinto que la cámara o la gravedad del juego. Pero decidí dejar esta eventualidad para otro momento, ya que lo voy a solucionar matando dos pájaros de un tiro. Como al caer fuera del escenario el personaje volverá a caer en el escenario y tendrá que levantarse, haré un sistema que detecte cómo has caido para ver si te tienes que levantar del suelo, y todo arreglado.



En uno de esos ratos de prueba de gravedad, con muchos cambios y ver que todo funciona bien me he grabado, y me ha recordado una escena de una película, he hecho un pequeño montaje, a ver si pilláis la referencia en el vídeo de abajo.



No hay comentarios :

Publicar un comentario