jueves, 3 de diciembre de 2015

Tiro con arco con Kinect

Autores:

  • Antonio José Navarro Céspedes
  • Miguel Ángel Valenzuela Hidalgo


Problema a resolver


El problema a resolver se basa en la simulación de un tiro con arco mediante la interacción del usuario con su cuerpo, permitiendo que el usuario realice el mismo gesto que haría con un arco real. De esta forma, se el programa muestra el objetivo del usuario así como todos los pasos que debe seguir para realizar el tiro correctamente. Para hacer más atractiva la aplicación, todo ello se engloba en un entorno con tiempo limitado y puntuación acumulable.


Desarrollo de la Práctica

 

La aplicación pasa por una serie de etapas que se organizan mediante variables globales. Por ejemplo, cuando el usuario se tiene que posicionar (que es la primera etapa), se modifica una variable cuando el usuario ya se ha posicionado para saber que no hay que volver a esa etapa.
De esta forma pasamos por las siguientes etapas:
  • Posicionamiento del usuario
  • Selección diestro/zurdo
  • Simulación de tiro con arco
  • Pantalla final con opción a reinicio

 

Posicionamiento del usuario


Primero debemos situar al usuario en un lugar óptimo para reconocer su cuerpo. Para ello dibujamos una elipse en la pantalla en la cual se ve dibujado el usuario, que tendrá que dirigirse hacia la elipse. El usuario se dibuja con drawBody(), la elipse con drawEllipse() y la posición donde el usuario debe posicionarse la declaramos como variables globales (FloorCenter X, Y, Z).






Cuando comprobamos que las coordenadas de los dos pies del usuario coinciden con zona cercana al centro de la ellipse, pasamos a la siguiente etapa.

Selección diestro/zurdo


Tras posicionarse, el usuario debe seleccionar si quiere tensar el arco con la mano derecha o izquierda, indicando si es diestro o zurdo. Esto lo hacemos dibujando dos imágenes (con drawImage()) en la pantalla, así como el esqueleto del usuario. Cuando el usuario pasa la mano derecha por la imagen de “diestro”, esta cambia de color (drawImage() lee otra imagen que tiene un color distinto), igual con la mano izquierda y la imagen de “zurdo”.







Cuando el usuario cierra la mano sobre una imagen, se selecciona esa opción y se pasa a la siguiente etapa. Se comprueba mirando el estado de las manos del usuario (handState), que pudiendo tener valores “open” y “closed”.


Simulación de tiro con arco


Lo primero que hacemos es dibujar una diana sobre la pantalla. Para ello lo que hacemos es dibujar 5 elipses con radios declarados globalmente siendo cada uno 0.5 metros menor que el anterior, siendo el más grande de 2.5 metros.

Además, en la esquina superior derecha aparece una imagen de una flecha tachada, indicando que el usuario no tiene una flecha para disparar. Para conseguirla, el usuario debe llevarse su mano principal (seleccionada anteriormente) detrás y encima de la nuca, de tal forma que comprobamos que la altura y la profundidad de la mano son mayores que los de la nuca. Cuando esto ocurre, la imagen cambia a una imagen con una flecha, indicando que el usuario puede disparar.


 


Como vemos en la imagen, además mostramos tiempo y puntos los cuales mostramos en pantalla usando la clase FormattedText. El tiempo parte de 30 segundos, durante los cuales el usuario puede realizar disparos y los puntos parten de 0 y se incrementan dependiendo de la precisión del disparo, siendo 10 puntos si da en el centro, 5 si da en el segundo círculo más pequeño, 3 en el siguiente, 2 en el siguiente y 1 en el último, el más exterior. Si el disparo cae fuera de la diana se restan 5 puntos. Todo esto se calcula con la función computeShotPoints(). Para el tiempo usamos la clase DateTime y guardamos los milisegundos actuales una vez llegamos a la pantalla de tiro y le sumamos 30.000 milisegundos (30 segundos). Por cada fotograma, le restamos los milisegundos actuales y de esta forma obtenemos una cuenta atrás. Ambas variables son variables globales.

Para simular el disparo, el usuario apunta con su mano no principal y “tensa la cuerda” con su mano principal. Para apuntar, calculamos el vector que va desde el punto del hombro hasta la mano, sabiendo así los ángulos alpha (vertical) y beta (horizontal) en el cual saldrá disparado el proyectil. Además, saldrá con una velocidad inicial que dependerá de cuánto haya tensado el usuario la cuerda del arco. Esto lo calculamos guardando el eje Z de la mano principal cuando se cierra por primera vez y comparándolo con el eje Z de la posición actual del usuario, de tal forma que si la cierra y la echa hacia atrás, obtendrá mayor velocidad. Para que tenga suficiente potencia, esta distancia va multiplicada por un factor (47). En caso de echarse hacia delante, el valor permanece a 0.


Así, cuando el usuario abre la mano se simula el disparo. Esta simulación se produce suponiendo que la diana virtual se encuentra a 20 metros del lanzamiento, calculándose con las ecuaciones del tiro parabólico dónde corta en el plano XY de la diana a esa distancia.


Mientras que no abra la mano, también se simula el disparo calculando dónde impactaría si se abriera la mano en ese momento. De esta forma, dibujamos en esas coordenadas una mirilla (haciéndose más pequeña cuanta más potencia de disparo, lo que da sensación de mayor precisión al usuario). Para dibujar la mirilla cargamos una imagen png la cual modificamos su tamaño con un factor en función de la potencia de tiro con la variable sizeEyeHole. De igual forma, se dibuja una barra de potencia (con la clase Pen()) en la esquina superior izquierda, a partir de un punto fijo (el inferior) y de un punto variable (el superior) cuyas coordenadas y color dependen de la potencia del disparo. Para cambiar ese color, se modifica dinámicamente el valor del SolidColorBrush() en función de esa potencia.



 


Cuando el usuario abre la mano, se produce realmente el disparo y se suman tanto los puntos obtenidos como el tiempo en función de la cercanía al centro de la diana. Después se vuelve al estado en el que el usuario no tiene flecha, por lo que deberá repetir el proceso desde el principio.
Cuando el tiempo llega a 0, se pasa a la siguiente etapa.


Pantalla final con opción a reinicio


En esta fase mostramos la puntuación total que ha obtenido el usuario así como que el juego ha terminado, junto con el esqueleto del usuario y un botón realizado de la misma forma que los botones de diestro y zurdo. Este botón es el de volver a jugar, el cual el usuario puede presionar con cualquier mano. En caso de hacerlo, las variables que se han ido fijando al ir pasando etapas se reinician a excepción de la de posición inicial (porque el usuario sigue colocado en el mismo sitio). De esta forma, el siguiente frame sobre el que se actuará volverá a ejecutar el código desde la parte de selección de diestro o zurdo.



 


Para cualquier duda, se puede consultar el código comentado en
https://github.com/ajncespedes/Practica-2-NPI

No hay comentarios:

Publicar un comentario