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.
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.
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.
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