[XNA] El arte de saber tocar: arrastrar y soltar modelos 3D

Waw! Una de las cosas a la que tenía ganas de echarle mano (literalmente) es a la codificación del touch en el nuevo Windows Phone 7. Encima he tenido tiempo de leer el artículo al respecto de Nick Gravelyn (del XNA Team). Digamos que en XNA 4.0 para Windows Phone 7 tenemos acceso al touch programáticamente de dos formas posibles:

  • Lectura directa del touch
  • Lectura «interpretada» de gestos (interpretada por el propio framework)

Toda esta maravilla se encuentra en: Microsoft.Xna.Framework.Input.Touch.

En ocasiones nos puede interesar interpretar directamente el touch, por ejemplo, sólo queremos saber si el jugador está tocando la pantalla, nos da igual el gesto que haga. Como en este jueguecillo en el que estoy trabajando:

[View:http://www.youtube.com/watch?v=nbO_pV23_NE:550:0]

Aquí lo que estoy haciendo es leer del TouchCollection el primer «toque» que el usuario hace sobre la pantalla (touches[0], recordemos que WP7 puede leer hasta 5 touches a la vez). Después trazo un rayo para ver si la posición del dedo, y teniendo en cuenta la dirección del frustum de la cámara, intersecciona con la bola. Si esto sucede muevo la bola en el eje de las X. Cuando el estado del touch pasa a Release (el usuario deja de tocar el teléfono), lanzo la bola hacia el eje de las Z.

Esto se hace fácilmente de la siguiente forma en el método Update del juego:

   1:              TouchCollection touches = TouchPanel.GetState();
   2:              Vector3 posicionReal = Vector3.Zero;
   3:   
   4:              // Manejamos el "coger" la bola
   5:              if (touches.Count > 0)
   6:              {
   7:                  // Si estamos moviendo el dedo...
   8:                  if (touches[0].State == TouchLocationState.Pressed 
   9:                   || touches[0].State == TouchLocationState.Moved)
  10:                  { 
  11:                      posicionReal = ColisionDedoBola(touches[0].Position);
  12:   
  13:                      posicionTouch = touches[0].Position;
  14:                      posicionTouch3D = posicionReal;
  15:   
  16:                      if (posicionReal != Vector3.Zero)
  17:                      {
  18:                          // Bloqueo la Y
  19:                          posicionReal.Y = entidadFisicaBola.worldTransform.Translation.Y;
  20:                          // Bloqueo la Z
  21:                          posicionReal.Z = entidadFisicaBola.worldTransform.Translation.Z;
  22:   
  23:                          // Muevo la bola
  24:                          entidadFisicaBola.worldTransform = Matrix.CreateTranslation(posicionReal);
  25:                      }
  26:                  }
  27:                  // Si soltamos la bola...
  28:                  else if (touches[0].State == TouchLocationState.Released 
                           && estadoPrevioTouch != TouchLocationState.Invalid
  29:                      && ColisionDedoBola(touches[0].Position)!= Vector3.Zero)
  30:                  {
  31:                      Vector3 velocidadLienal = -Vector3.UnitZ * 50.0f;
  32:   
  33:                      Random rand = new Random(gameTime.TotalGameTime.Milliseconds);
  34:   
  35:                      double aleatoriedad = rand.NextDouble();
  36:                      int signo = rand.Next(0, 1);
  37:                      
  38:                      if(signo==0)
  39:                          velocidadLienal += new Vector3((float)aleatoriedad, 0, 0);
  40:                      else
  41:                          velocidadLienal += new Vector3(-(float)aleatoriedad, 0, 0);
  42:   
  43:                      entidadFisicaBola.linearVelocity = velocidadLienal;
  44:                      entidadFisicaBola.becomeDynamic(20.0f);
  45:                  }
  46:                  
  47:                  estadoPrevioTouch = touches[0].State;
  48:              }

Si quisieramos hacer una interpretación más avanzada de los movimientos del usuario, habría podido utilizar la API de los gestos. Lo primero sería inicializar los gestos:

TouchPanel.EnabledGestures =
    GestureType.Tap |
    GestureType.DoubleTap |
    GestureType.FreeDrag;

Aquí estamos diciendo qué gestos queremos tener habilitados (la lista completa podéis consultarla en el blog de Nick). Una vez habilitados los gestos, en el método Update del juego nos limitaremos a actuar cuando se produzca cualquiera de estos:

while (TouchPanel.IsGestureAvailable)
{
    GestureSample gesture = TouchPanel.ReadGesture();

    switch (gesture.GestureType)
    {
        // TODO: handle the gestures
    }
}

Vale, el touch no tiene secretos, ¿verdad? A mi lo que me gusta más de este código es el método ColisionDedoBola. Este lo que hace es devolver un vector 3D en el “mundo” del juego, que sería el punto en el que el rayo trazado a partir de la posición del dedo colisiona con la bola. Este es el código para dicho método:

   1:          private Vector3 ColisionDedoBola(Vector2 touch)
   2:          {
   3:              Vector3 origenRayo;
   4:              Vector3 destinoRayo, direccionRayo;
   5:   
   6:              origenRayo = GraphicsDevice.Viewport.Unproject(
                        new Vector3(touch.X, touch.Y, 0), camara.projection, camara.view, Matrix.Identity);
   7:              destinoRayo = GraphicsDevice.Viewport.Unproject(new Vector3(touch.X, touch.Y, 1), camara.projection, camara.view, Matrix.Identity);
   8:              direccionRayo = Vector3.Normalize(destinoRayo - origenRayo);
   9:   
  10:              float toi;
  11:              Entity entidadHit;
  12:              Vector3 puntoHit, normalHit;
  13:   
  14:              if (this.space.rayCast(origenRayo, direccionRayo, 1000, 
                       false, out entidadHit, out puntoHit, out normalHit, out toi))
  15:              {
  16:                  if (entidadHit!=null && entidadHit.GetType() == typeof(Sphere))
  17:                  {
  18:                      Vector3 posicionFinal = origenRayo + direccionRayo * toi;
  19:   
  20:                      return posicionFinal;
  21:                  }
  22:              }
  23:   
  24:              return Vector3.Zero;
  25:          }

La gracia del asunto está en el método Unproject del Viewport del objeto GraphicsDevice. Por otro lado el ray cast lo hago usando un método del engine de físicas que estoy utilizando, pero sería igualmente sencillo con la clase Ray propia del Framework de XNA. Dado que en un artículo anterior escribí sobre el trazado de rayos, no voy a repetirme.

Para quien quiera trastear más con el touch: ya que sólo unos pocos afortunados disponen del dispositivo físico, y la mayoría de mortales nos tenemos que conformar con el emulador, el touch en este software lo que hace es emular el toque a través de las pulsaciones con el mouse. Lo primero que pensarán muchos es: ¿cómo emulo entonces el multitouch? Pues con dos ratones 😛 Existe un proyecto en codeplex de una herramienta –Multi Touch Vista– que precisamente nos permite trabajar con dos ratones al mismo tiempo (puede parecer raro pero es un experimento interesante).

6 comentarios sobre “[XNA] El arte de saber tocar: arrastrar y soltar modelos 3D”

  1. hola! primero me gustaria felitarte por el blog porque me esta sirviendo para aprender c# y como se estructuran los juegos que es un gustazo!

    ayer empeze con el tutorial de las fisicas de newton y bueno, se lo he pasado a unos amigos para que lo vean pero no les funciona. les paso el proyecto entero pero cuando ejecutan el exe les dice que el programa dejo de funcionar. (tambien les he pasado tu version del codigo)

    hay que hacer alguna cosa para que otra gente pueda probar las pelotas de newton?

    muchas gracias y animo con el blog!

  2. hola JM! Muchas gracias 🙂

    En teoría si haces un deploy del juego debería generarse un instalable, (botón derecho en el proyecto->deploy o «desplegar»). Ese instalable, si lo ejecutan, generará un acceso directo en el menú de inicio al juego, que podrás ejecutar sin problemas en cualquier máquina.

    Un saludo

  3. muchas gracias por contestar tan rapido!

    el deploy seria como hacer un publish? lo digo porque como comentas el tema de la instalacion. antes lo he hecho (he estado provando distintas maneras esta tarde) y al instalarlo les hace actualitzar el xna framework pero luego nada, sigue sin funcionar.

    puede ser que no les vaya porque lo estoy haciendo con la beta del xna 4.0? yo instalaria el 3.1 pero me dice que necesito el VS2008 express (tengo el VS2010 ultimate) original.

    muchas gracias y buenas noches.

  4. hola de nuevo,

    no he probado todavía de hacer un deploy con el XNA 4.0. Qué ejemplo es el que estás intentando desplegar?

    VS2008 puede estar instalado paralelamente sin problemas con VS2010. En cambio, la beta de windows phone no se si es compatible con VS2010 Ultimate (se que el CTP no lo era, quizá la bea la han mejorado en ese sentido).

    Si no te sale plantea la pregunta en el foro de la comunidad de XNA y lo hablamos allí:

    http://www.dotnetclubs.com/forums/44.aspx

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *