Herramientas de usuario

Herramientas del sitio


es:manual:items:interactivos:unity

Interacción con Unity

Unity es un motor de videojuego multiplataforma que puede funcionar dentro de una página web. Unity se basa en un entorno propio para desarrollo de videojuegos, lo que proporciona mucha flexibilidad a la hora de configurar ciertas tareas. Unity se basa en el lenguaje de programación C#, pero puede interactuar con código javascript para comunicarcíon entre la aplicación y la página web.

A través de esta interactividad Unity-Javascript mediante WebGL se pueden integrar llamadas desde la pagina que contiene un ítem en Siette, y el juego, lo que permite construir una pregunta interactiva.

A continuación se muestran ejemplos que como se ha resuelto en algunos casos concretos:

Ejemplo 1

Se trata de la implementación del conocido juego Simón, en el que se emiten secuencias de sonidos, cada vez mas largas, asociadas a unos botones de colores, que el alumno debe reproducir en el mismo orden. Esta pensado para medir la capacidad de memoria visual y sonora.

El juego es configurable, se puede modificar la imagen del fondo, el número máximo de secuencias que se presentan, la velocidad de exposición, etc. y presenta unas instrucciones breves al comenzar mediante un texto leído de forma automática.

Como resultado de la interacción el juego devuelve a Siette varios valores numéricos, correspondientes al número de secuencias correctas, incorrectas, tiempo de ejecució, etc.

Proceso de carga del juego

El juego esta implementado en Unity, aunque para Siette se trata de un ítem interactivo integrado mediante de javascript por lo que usará las mismas funciones. En este caso, el enunciado de la pregunta contiene el siguiente script:

<link rel="shortcut icon" href="/siette.htdocs/techcat/unity/Simon/TemplateData/favicon.ico">
<link rel="stylesheet" href="/siette.htdocs/techcat/unity/Simon/TemplateData/style.css">
<script src="/siette.htdocs/techcat/unity/Simon/TemplateData/UnityProgress.js"></script>
<script src="/siette.htdocs/techcat/unity/Simon/Build/UnityLoader.js"></script>
 
  <div id="unityContainer" style="width: 480px; height: 300px"></div>
 
<script>
   var unityInstance = UnityLoader.instantiate("unityContainer", 
          "/siette.htdocs/techcat/unity/Simon/Build/Simon.json", 
          {onProgress: UnityProgress});
 
   var play = true;
   function setParameters(fondo, iteraciones, velocidad) {
      if (play) {
	unityInstance.SendMessage("GameManager", "SelectFondo", fondo);
        unityInstance.SendMessage("GameManager", "NumIteraciones", iteraciones);
	unityInstance.SendMessage("GameManager", "Velocidad", velocidad);
        unityInstance.SendMessage("GameManager", "Sonido", 1);
      }
   }
   function evaluacion() {
        unityInstance .SendMessage('Log', 'Stop');
   }
   function resolver(respuesta, correccion) {
       play = false;
       for(conta=2500; conta<60000; conta+=2500) {
           setTimeout(function() {
               unityInstance.SendMessage("Log", "Solve", respuesta);  
           }, conta );
       }
    }
    function load() {
       for(conta=5000; conta<60000; conta+=2500) {
           setTimeout(function()  { setParameters(2,3,6); }, conta );
       }
    }
    load();
</script>

La cabeceera de este código carga los ficheros auxiliares de Unity, y a continuación establece un div de dimensiones 480×300 sobre el que se cargará el juego. Unity resscala todas las imagenes para adaptarse al tamaño del div en el que se ejecuta. El juego propiamente dicho se carga mediante la sentencia:

   var unityInstance = UnityLoader.instantiate("unityContainer", 
          "/siette.htdocs/techcat/unity/Simon/Build/Simon.json", 
          {onProgress: UnityProgress});

lo que provoca que aparezca el logo de Unity y comience la carga:

El juego está programado para esperar a recibir los parámetros antes de comenzar propiamente su ejecución, presentando una vez concluida la carga, la siguiente pantalla

La carga del juego y de la pagina web que lo contiene se hace de forma asíncrona. Los parámetros se envian al juego tan pronto como se ha cargado la página y se invoca la ejecución de la función load() que pasa los parámetros de inicialización a la aplicación en Unity, que comienza su ejecución:

Un detalle importante a tener en cuenta es que el juego puede no haber terminado de cargarse al ejecutar la función load(), por lo que esta función lo que hace es programar (mediante setTimeout) diversas llamadas de forma iterativa para garantizar que la aplicación Unity recibe los mensajes.:

    function load() {
       for(conta=5000; conta<60000; conta+=2500) {
           setTimeout(function()  { setParameters(2,3,6); }, conta );
       }
    }

Final del juego

El final del juego se puede producir por dos circunstancias:

   function evaluacion() {
        unityInstance .SendMessage('Log', 'Stop');
   }

Normalmente, esta función debería devolver un valor, pero en esta ocasión no lo hace, sino que simplemente emite un mensaje para la aplicación Unity, indicandole que pare el juego. Internamente al juego este mensaje desencadena una llamada a la función RespuestaActiva(), y el proceso continúa como en el caso anterior.

Las llamadas a a la función RespuestaActiva(respuesta), tiene como argumento una cadena de texto que contiene una secuencia de valores con el siguiente formato:

'{' respuesta0 , respuesta1 , respuesta2 , …. '}'

En este caso estos valores son indicadores del resultado obtenido con la interaccion del juego en concreto:

  • Número de secuencias correctas ,
  • Número de secuencias incorrectas ,
  • Máximo número de secuencias consecutivas correctas ,
  • Máximo número de secuencias consecutivas incorrectas ,
  • Tiempo de juego

Siette almacena evaluará esta respuesta, por ejemplo mendiante patrones de funciones de evaluación, y onbtendrá así una evaluación de la actividad realizada en el juego.

Presentación de resultados

Cuando Siette necesite presentar los resultados de esta evaluación, volverá a cargar el ítem que contiene el juego javascript y llamará a la función resolver(respuesta,correcto), pasandole como argumentos la respuesta del alumno (es decir, la cadena de caracteres con los indicadores que recibió como respuesta); y la corrección de cada una de los patrones de funciones de evaluación. La función resolver() lo que hace es simplemente pasarle al juego esta respuesta que la muestra como resultado.

   function resolver(respuesta, correccion) {
       play = false;
       for(conta=2500; conta<60000; conta+=2500) {
           setTimeout(function() {
               unityInstance.SendMessage("Log", "Solve", respuesta);  
           }, conta );
       }
    }

En la imagen puede verse que Siette tambien muestra la respuesta del alumno, (la cadena de indicadores recibida). Esta duplicidad de información puede evitarse añadiendo return true al final de la función resolver(), que inidica a Siette que la corrección de la pregunta ya se ha mostrado. Nótese que al igual que en la carga del juego para su ejecución, hay que esperar a que el juego se cargue para poder enviarle los mensajes. No hemos encontrado manera de obtener esta información desde la página javascript, por lo que una manera de sortear el problema es programar una secuencia de mensajes para estar seguros que el juego los recibe.

En el siguiente ejemplo se describe en detalle como hay que programar en Unity la recepción y envío de todos estos mensajes.

Ejemplo 2

Se trata de un minijuego denominado EndlessRoad con el que se quiere medir el tiempo y la constancia que un niño tiene en la realización de una tarea concreta. El objetvo final es medir la función ejecutiva de inhibición. Para ello se propone este juego en el que aparece un coche, una carretera y un regalo al final de la carretera.

El regalo nunca llega a alcanzarse, pero el niño debe intentarlo procurando no salirse de la carretera. Si lo hace aparece un tono de aviso.

Finalmente el niño se aburre y pulsa el botón Siguiente, o bien trascurren 10 minutos y el propio sistema fuerza la terminación, obteniendo la respuesta.

Como resultado de la interacción el juego devuelve a Siette varios valores numéricos, correspondientes al tiempo que ha estado el coche dentro de la carretera, el tiempo que ha estado fuera, el número de veces que se ha salido, etc. Con estos datos Siette realiza la evaluación utilizando como patrones funciones de evaluación a las que asigna diversas puntuaciones dependiendo de la combinación de valores.

Ejecución del juego

El juego esta implementado en Unity, aunque para Siette se trata de un ítem interactivo integrado mediante de javascript por lo que usará las mismas funciones. En este caso, el enunciado de la pregunta contiene el siguiente script:

<iframe allowfullscreen 
   id="unity" 
   src="/siette.htdocs/techcat/unity/EndlessRoad/index.html" 
   width="960" height="600"
>
</iframe>
 
   <script>
       function evaluacion() {
          var iframe = document.getElementById("unity");
          iframe.contentWindow.evaluacion();
      }
 
       for(conta=2500; conta<60000; conta+=2500) {
          setTimeout(
             function()  { 
               document.getElementById("unity").contentWindow.setParameters(1,0);  
             }
             , conta );
      }
    </script>

Esta pagina carga el juego en unity en un iframe que contiene el código de inicialización de Unity 1), en concreto para este ejemplo este codigo es el siguiente:

<!DOCTYPE html>
<html lang="en-us">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Unity WebGL Player | Endless Road</title>
    <link rel="shortcut icon" href="TemplateData/favicon.ico">
    <link rel="stylesheet" href="TemplateData/style.css">
    <script src="TemplateData/UnityProgress.js"></script>
    <script src="Build/UnityLoader.js"></script>
    <script>
      var gameInstance = UnityLoader.instantiate(
                                         "gameContainer", 
                                         "Build/EndlessRoad.json", 
                                         {onProgress: UnityProgress}
                                      );
     function RespuestaActiva(respuesta) {
          window.parent.RespuestaActiva(respuesta);
     }
     function evaluacion() {
           gameInstance.SendMessage('Log', 'OnApplicationQuit');
     }
     function setParameters(curvas, textura) {
           gameInstance.SendMessage('MaterialSelector', 'SelectMaterial',textura); 
           gameInstance.SendMessage('MaterialSelector', 'SelectScene',curvas);
       } 
    </script>
  </head>
  <body>
    <div class="webgl-content">
      <div id="gameContainer" style="width: 960px; height: 600px"></div>
    </div>
  </body>
</html>

Paso de parámetros de configuración

Dentro de esta página se ha definido una funcion javascript setParameters(curvas, textura) a la que se llama desde el código que se ha escrito en el enunciado y que pasa dos parámetros que controlan algunos aspectos del juego, en este caso, si la carretera tiene curvas o es recta y la textura de la carretera. Modificando la llamada a esos parametros se obtiene una cambio en el funcionamiento del juego. Por ejemplo con la llamada setParameters(2,1) se obtiene este otro juego:

Como puede observarse en el código del enunciado, la llamada a esta función se repite varias veces a lo largo de la carga del juego, esto es debido a que hasta que el juego no termina de cargar las llamadas no tienenefecto, por lo que se repiten varias veces para conseguir este efecto.

En el código del iframe, las llamadas a la funcion setParameters(curvas, textura) se traducen en dos llamadas a la funcion SendMesssage que es la que envia el mensaje a la instancia de Unity. (Véase la documentación de Unity sobre la integración con javascript). En concreto en este caso dentro de Unity se ha implementado la clase MaterialSelector.cs con (entre otras) la funcion SelectScene que es la que recibe el mensaje de configuración.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.InteropServices;
using UnityEngine.SceneManagement;
 
public class MaterialSelector : MonoBehaviour {
    ....
    string SceneName;
    public void SelectScene(int Scene) {
        if (Scene == 1) {
            SceneName = "Straight";
            SceneManager.LoadScene(SceneName);
        } else if (Scene == 2) {
            SceneName = "Curve";
            SceneManager.LoadScene(SceneName);
        }
    }
}

Obtención de resultados

Para obtener los resultados de la evaluación se usa la función evaluación(), que en este caso redirige a otra función del mismo nombre dentro del iframe. Esta función lo único que hace es enviar un mensaje al juego indicandole que debe finalizar, es decir envia el mensaje OnApplicationQuit a la clase Log. Ésta lo recibe y genera una llamada a la función RespuestaActiva pasandole como parámetro la respuesta virtual del alumno, es decir los valores correspondientes al tiempo transcurrido dentro de la carretera, fuera, número de veces que se ha salido, etc. Esta respuesta virtual es la que usará Siette para evaluar aplicando las funciones de evaluación.

En concreto dentro de Unity se ha implementado la clase Log.cs con el siguiente contenido:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System.Runtime.InteropServices;
 
public class Log : MonoBehaviour
{
    [DllImport("__Internal")]
    private static extern void ReportState(string str);
 
    float t;
    float clickcount=0;
    float clickout;
    float clickin;
    float timeout;
    float timein;
 
    .....
 
    void OnApplicationQuit()
    {
        timeout = clickout / clickcount * t;
        timein = clickin / clickcount * t;
        ReportState(
          "{" 
              + t.ToString("f2") + "," 
              + timein.ToString("f2") + "," 
              + timeout.ToString("f2") + 
          "}");
    }
}

La función ReportState se implementa en el fichero script.jslib que se incluye dentro del subdirectorio Plugins del directorio Assets de Unity, que es la que finalmente llama a la función RespuestaActiva() pasándole la cadena de caracetres que contiene la respuesta virtual.

 mergeInto(LibraryManager.library, {
     ReportState: function (str) {
         RespuestaActiva(Pointer_stringify(str));
     }
 });

Cuando ha transcurrido el tiempo límite (en otros casos pueden darse otras circunstancias), la aplicación en Unity puede determinar que ya ha llegado el momento de finalizar. Para ello simplemente tendrá que hacer una llamada proactiva a la función RespuestaActiva() sin que sea necesaria una llamada previa por parte de la función evaluacion()

1)
en este caso se usa un iframe para poder visualizar el juego de forma independiente y poder ejecutarlo en pantalla completa
es/manual/items/interactivos/unity.txt · Última modificación: 2023/05/08 12:26 por root

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki