El uso de la llamada Inteligencia Artificial se está generalizando en el
mundo de las marcas y aplicaciones. El tiempo de las versiones Beta a dado
paso, masivamente, a las versiones oficiales y no existe hoy en día, una
marca que se precie, que no haya incorporado esta tecnología a su buque
insignia.
Yo como aficionado a las tecnologías no paro de sorprenderme de la rapidez
en su avance y la curiosidad me llama.
Entre entusiastas y detractores no es un artículo que
pretende ahondar en la brecha abierta por el uso de estas tecnologías,
sino más bien en su buen uso, para el desarrollo de nuevas ideas,
provocando el cambio en los modelos de aprendizaje, derribando fronteras y
procurando un cambio progresivo y beneficioso al conjunto de la sociedad.
Uno de los grandes miedos hacia esta, podría ser, la dependencia que se
crea con su uso generalizado y para ello, nada mejor que el debate abierto
y permanente.
Una mente lucida, nunca deja en manos de otro, lo que considera
importante, al menos sin una correcta supervisión. Y esta, ¿la dejaremos también en manos de la que podiamos llamar
"IA vigilante"?.
Para el ejemplo que os traigo he usado Replit, un IDE en la nube que incorpora dos poderosas herramientas basadas en
Inteligencia Artificial, Agent y Assistant. No se trata de contar las
bonanzas de la aplicación, que sin duda las tiene. Tampoco tengo claro, si
lo que hay detrás es unicamente la IA, o un equipo mixto (Humano/Máquina
> 1 o Humano/Máquina < 1), así que me gustaría pensar que el merito se
debe a su gran equipo humano de desarrollo.
La idea, que no es original pero para mí novedosa y bastante técnica, trata
de crear un "input remote" o mando a distancia para un videojuego
desarrollado con Unity, utilizando para ello un dispositivo móvil. Como
siempre, esto es solo un prototipo de desarrollo, sin animo de lucro, con el
foco puesto en el aprendizaje. Todas las herramientas usadas, son solo para
uso particular y poder dar una salida a mi inquietud y curiosidad. A continuación os muestro un enlace de YouTube, donde se puede ver en que consiste facilmente Vídeo InputRemote
Las explicaciones y el código que he usado en este desarrollo lo podeis
consultar libremente en https://github.com/jmcaneda/MobileJoystickControllerDef
Para llevarla a cabo, he tomado uno de "mis pequejuegos", el que mejor
podría adaptarse, desarrollado en Unity.
Vamos a comenzar por a describir el apartado del videojuego con el motor
Unity y el código que tiene mas relación con esta idea.
Unity
En la Build Settings
- Utilizo como plataforma WebGL, que como sabéis significa Web Graphics Library, es una API (interfaz de programación de aplicaciones) utilizada para renderizar gráficos 3D y 2D en navegadores web sin necesidad de plugins y permite a los desarrolladores crear gráficos interactivos y animaciones utilizando JavaScript y el estándar de gráficos OpenGL ES. La mayoria de los navegadores web ya incorporan este framework por defecto.
En Player Settings. Estos parámetros han sido consultados en el manual de
Unity https://docs.unity3d.com/2022.3/Documentation/Manual/webgl-deploying.html
- Compression Format: a Gzip.
- Data Caching: a check.
- Decompression Fallback: a check.
¿Qué nos proporciona la Build?. Las carpetas Build, TemplateData y el
archivo index.html. Si comprimimos el conjunto en un archivo .zip y lo
subimos a la Plataforma de videojuegos Indie itchio, indicándole que se
pueden ejecutar en un navegador web, ya podriamos disfrutar del mismo,
utilizando como mando del videojuego, el propio teclado.
Pero..., ¿dónde colocamos todo este código para que podamos ejecutar nuestra
aplicación correctamente?. Pues colocamos el resultado de la Build en Unity
en la carpeta static del servidor, tal cual, sin modificar nada. ¿Quiere
decir esto que si en vez del juego utilizado MaribelProject, lo cambio por
otra Build, respetando su ubicación, funcionaría el resto?. La respuesta es
si. Podemos reutilizar todo el código y solo, crear nuestro juego y podremos
manejar los inputs, desde cualquier dispositivo móvil. Esto es lo
fantástico!!!.
Archivos de la Build en Unity
static/Build/ ├── Builds.data ├── Builds.framework.js ├── Builds.loader.js └── Builds.wasm |
static/TemplateData/ ├── style.css └── [assets visuales] |
index.html |
Del código C#, el que mayor interés tiene para la cuestión que estamos
tratando es PlayerController.cs, de este extraeremos la parte que nos
interesa resaltar. Son la declaración de clases de los diferentes tipos de
datos que manejaremos en los diferentes eventos.
void HandleJoystickInput(string jsonData)
void HandleButtonInput(string jsonData)
void HandleInputMethod(string jsonData)
void HandleJoystickInput(string jsonData)
{
if (GameManager.instancia.inputRemote)
{
JoystickData data = JsonUtility.FromJson(jsonData);
movement = new Vector3(data.x, 0, data.y) * moveSpeed;
animator.SetFloat("Horizontal", data.x);
animator.SetFloat("Vertical", data.y);
}
}
void HandleButtonInput(string jsonData)
{
if (GameManager.instancia.inputRemote)
{
ButtonData data = JsonUtility.FromJson(jsonData);
if (data.state == "pressed")
{
switch (data.action)
{
case "up":
{
movement = Vector3.forward * moveSpeed;
animator.SetFloat("Horizontal", movement.x);
animator.SetFloat("Vertical", movement.y);
break;
}
case "down":
{
movement = Vector3.back * moveSpeed;
animator.SetFloat("Horizontal", movement.x);
animator.SetFloat("Vertical", movement.y);
break;
}
case "left":
{
movement = Vector3.left * moveSpeed;
animator.SetFloat("Horizontal", movement.x);
animator.SetFloat("Vertical", movement.y);
break;
}
case "right":
{
movement = Vector3.right * moveSpeed;
animator.SetFloat("Horizontal", movement.x);
animator.SetFloat("Vertical", movement.y);
break;
}
case "A":
{
break;
}
case "B":
{
// Iniciar el desplazamiento a la siguiente esquina
GameManager.instancia.indiceEsquinaActual = (GameManager.instancia.indiceEsquinaActual + 1) % GameManager.instancia.esquinas.Length;
GameManager.instancia.offset = GameManager.instancia.esquinas[GameManager.instancia.indiceEsquinaActual];
break;
}
}
}
else if (data.state == "released")
{
if (data.action != "A" && data.action != "B")
{
movement = Vector3.zero;
}
}
}
}
void HandleInputMethod(string jsonData)
{
InputMethodData data = JsonUtility.FromJson(jsonData);
switch (data.state)
{
case "on": GameManager.instancia.inputRemote = true; break;
case "off": GameManager.instancia.inputRemote = false; break;
}
}
[System.Serializable]
public class JoystickData
{
public float x;
public float y;
}
[System.Serializable]
public class ButtonData
{
public string action;
public string state;
}
[System.Serializable]
public class InputMethodData
{
public string state;
}
Objeto importante en Unity
En Unity, debe existir el objeto MaribelController y tener como
componente, entre otros el script PlayerController.cs
Python y JavaScript
Esta es la parte en la que la asistencia de la IA de Replit, no
con pocos intentos en la fase de depuración, facilitó las claves para
encaminar este desarrollo, a buen término.
Este proyecto implementa una comunicación bidireccional en tiempo
real, entre un controlador móvil y un juego
Unity WebGL, utilizando WebSocket como protocolo de
comunicación.
Componentes Principales
Servidor WebSocket (app.py)
Implementado con Flask-SocketIO
Gestiona conexiones y desconexiones
Retransmite eventos entre el controlador y Unity
Cliente Controlador (controller.js)
Implementa la interfaz de usuario del controlador
Captura eventos táctiles y de botones
Emite eventos WebSocket al servidor
Cliente Unity (websocket.js)
Recibe eventos del servidor
Comunica con el juego Unity via SendMessage
Gestiona la conexión WebSocket del lado del juego
Vamos a verlo sobre un diagrama de bloques.
|
Partes del código que cabe resaltar
handleGameControl(data) {
if (!this.gameInstance) {
console.warn('Game instance not ready, retrying connection...');
setTimeout(() => this.socket.connect(), 1000);
return;
}
// Send the control data to Unity
switch(data.type) {
case 'joystick':
this.gameInstance.SendMessage('MaribelController', 'HandleJoystickInput',
JSON.stringify(data.data));
break;
case 'button':
this.gameInstance.SendMessage('MaribelController', 'HandleButtonInput',
JSON.stringify(data.data));
break;
case 'inputMethod':
this.gameInstance.SendMessage('MaribelController', 'HandleInputMethod',
JSON.stringify(data.data));
break;
}
}
Importante, se puede observar que SendMessage busca un objeto
llamado MaribelController y los
métodos HandleJoystickInput, HandleButtonInput
y HandleInputMethod, incluidos en el componente
PlayerController.cs
Error en la versión de producción (Deployments)
Cuando ya disponía de todo el código probado en desarrollo, al pasarlo a
producción en Replit (Deployments), se producía un error, que
pese a estar documentado en parte en los manuales de Unity, no lo estaba
para su uso con servidores Flask. Paso a describirlo, junto con
la solución adoptada.
"WebAssembly streaming compilation failed! This can happen for example
if "Content-Encoding" HTTP header is incorrectly enabled on the server
for file /static/Build/Builds.wasm, but the file is not pre-compressed
on disk (or vice versa)".
En app.py podrás ver como se ha resuelto este tema y la clave, en
el siguiente código.
@app.route('/static/Build/')
def serve_build(filename):
try:
response = send_from_directory('static/Build', filename)
if filename.endswith('.wasm'):
response.headers['Content-Type'] = 'application/wasm'
response.headers['Cross-Origin-Embedder-Policy'] = 'require-corp'
response.headers['Cross-Origin-Opener-Policy'] = 'same-origin'
response.headers['Cross-Origin-Resource-Policy'] = 'cross-origin'
response.headers['Cache-Control'] = 'no-cache'
response.headers['Accept-Ranges'] = 'bytes'
# Asegurarse de que no haya compresión
response.direct_passthrough = True
if 'Content-Encoding' in response.headers:
del response.headers['Content-Encoding']
if 'Content-Length' in response.headers:
del response.headers['Content-Length']
return response
except Exception as e:
app.logger.error(f'Error serving {filename}: {str(e)}')
return str(e), 500
Técnica empleada y herramientas adicionales🔧
- Replit
- Unity versión 2022.3
- Copilot como asistente en programación C#
- Información adicional en https://github.com/jmcaneda/MobileJoystickControllerDef
Asset Store 🏬
He utilizado los siguientes packages de Asset Store, casi siempre en su versión free o con mínima inversión.
Links de interés 🔗
He utilizado los sonidos de nuestro querido "Chiquito de la calzada".
El juego original, sin input remote, en WebGL y teclado, podéis probarlo sin
problemas. Este es un videojuego regalo y me sirve como base para el
desarrollo de este artículo. Gracias Maribel.
Elementos necesarios 🔑
- La app requiere un dispositivo móvil, con capacidad para el escaneo de código QR y navegador web, compatible o habilitado para JavaScript (la mayoría ya lo tienen configurado por defecto).
- La aplicación comenzará con el linkado desde el navegador web, compatible con WebGL (la mayoría ya lo son por defecto). Recomiendo que se realice desde equipo de sobremesa o portátil.
- Desde el dispositivo móvil escanearemos el código QR y dispondremos el mismo en apaisado. Es importante que veamos el "connected" en verde, esto es indicativo que podemos hacer uso del dispositivo.
- El foco debe estar puesto en el navegador, esto lo podemos conseguir posicionando el ratón en el mismo. Cambiaremos el input mode, en el dispositivo móvil, de Off a On y ya está, podremos manejar a nuestro personaje "Maribel", en su recorrido por las calles, escuchando los mensajes y procurando no ser convertida por los "UrbanZombies", de dos maneras: como Joystick o por medio de los botones de desplazamiento. El botón "B", esta programado para el cambio de posición de la cámara.
- Requiere de una conexión a internet.
_
✨ C.A.C. (Soho Málaga), supuso para nosotros (los de entonces), un lugar de encuentro.
0 comments:
Publicar un comentario