martes, 24 de junio de 2025

Quiero empezar por el final

 


   Agradeciendo a todos aquellas personas o entidades, que de una forma u otra contribuyen y en muchos casos de manera altruista, a orientar y formar. Estamos en una sociedad circular, en la que lo que pasa allí, se acaba sintiendo aquí. Que quiero decir con esto, pues que gracias a que es así, algunos como es mi caso, crecemos y creemos.

   Esta formación tiene su origen en Extremadura, mas concretamente en Almendralejo, localidad cercana a Badajoz, que a través de IRIA (Incubadora de Realidades Inmersivas de Almendralejo), ofrece un programa de apoyo a las iniciativas de emprendedores, autónomos y Pymes con proyectos de innovación y desarrollo en el ámbito de la industria 4.0, Realidad Virtual, Aumentada, Realidad Mixta y Extendida y es aquí donde se propone un curso de formación, gratuito, Introducción a Unreal Engine 5.

   Se ha impartido, durante 30 intensas horas y no son todas las que se echan, porque hablamos de un motor, no solo de videojuegos, con inmensas posibilidades, de la mano experta de Javier Jurado Gámez (Unreal Gold Instructor partner for Epic Games y Director en Cursos Unreal Engine) y esto no es cualquier cosa. 30 horas puede parecer poco, pero con Javier esto adquiere otra dimensión y si, aunque no se lo crean, es básico.

   El programa ha sido extenso e intenso, muy ambicioso y ha desbordado las 30 horas por todos lados. Realizado entre el 08 de mayo y el 12 de junio del 2025 y a buen seguro, este no será el último. Abarca desde Interfaz de Usuario, Navegación y fundamentos 3D en Unreal, Creación de Niveles y Escenarios, Materiales y Texturas, Iluminación, Programación con Blueprints, Interfaz de Usuario (UI), Sonidos y Partículas y finalmente, Animación y Cinemática.

   Bienvenida sean estas iniciativas, otros lo tomen como ejemplo y que se multipliquen en el futuro.

Respecto del curso, vamos con esas "cosillas" que pueden ser de interés y que aparecen en tu día a día.

1.- Rutas importantes (No os perdáis).

Ruta por defecto de la carpeta VAULTCACHE,  donde se instalan los assets desde FAB.

C:/ProgramData/Epic/EpicGamesLauncher/VaultCache/

Ruta por defecto donde se instala el Launcher.

C:\Program Files (x86)\Epic Games

Ruta por defecto donde se instalan las vesiones del motor.

C:\Program Files\Epic Games

 

 2.- Atajos, algunos de los vistos, son interesantes.


Generalidades

ctrl + B -> Asset Actions -> Migrate (en proyecto, buscar carpeta Content), en mi anterior formación este me lo perdí y me hubiera venido de perlas.

alt + F4 -> Matar la aplicación.

ctrl + L -> Control dirección del sol (si, pero la tecla ctrl de la derecha del teclado).

Materiales

C -> en el grafo, cajita de comentarios.

L + botón izq. ratón -> Muestra la luz incidiendo en la muestra de material.

Blueprints

alt + botón izq. ratón -> Elimina línea.

ctrl + botón izq. ratón -> Mueve punto a otro nodo.

Nº atajo + botón izq. ratón -> Pone el nodo correspondiente (a la derecha podemos desplegar y ver los atajos).

Seleccionamos una línea de nodos, pulsamos Q y se alinean los cables (para los ordenados, se recomienda).

ctrl + C y ctrl + V -> Por medio de un archivo .txt y así lo pasamos entre proyectos, los BluePrint, (y esto porqué no me lo enseñasteis antes?, increíble!!!).

IA

P -> Vemos la zona en la que puede desplazarse el enemigo (esto parece evidente, pero a mi me costó encontrarlo).

Viewport

F8 -> Puedo seleccionar en el editor, en play mode cualquier actor del mundo y poder localizarlo en el Outliner (muy interesante).


3.- De todas las carpetas que crea Unreal, unas son importantes y otras, no tanto.

Carpetas Importantes: Config, Content y *.uproject

Carpetas que se pueden eliminar (estas las vuelve a crear, es importante en algunos cuelgues, realizar esta operación): Intermediate, Saved, DerivedDataCache.


4.- El Gizmo, esas flechas de colores.

      En Unreal Engine, el eje X positivo se representa con el color rojo y es el que se considera la dirección Forward por defecto. Por si te interesa el esquema completo del gizmo:

X (rojo): adelante (Forward)

Y (verde): a la derecha (Right)

Z (azul): hacia arriba (Up)


   Este sistema de coordenadas es típico en motores de juegos como Unreal, a diferencia de otros como Unity, donde Forward suele estar en el eje Z.

        Cambiar el punto de pivote de un mesh, podemos hacerlo desde el Modeling Mode -> XForm -> Edit Pivot -> Elegimos la posición nueva que nos interese.


5.- Cuando queremos incluir un video y que se renderice en el juego, pues...

   los videos deben ponerse en la carpeta .../Content/Movies, sino no compila el video (esto lo cuenta Javier, pero otros se lo callan).


6.- Respecto de los materiales.

   Esto no es solo en Unreal, también en Unity y supongo que en la mayoría de los motores. Los materiales se crean basados en unos shaders, estos se escriben en un lenguaje propio llamado HLSL (High-Level Shader Language). En la Programación de shaders: se usa para escribir vertex shaders, pixel shaders y más.


7.- Colocar un objeto en tu personaje.

Abrimos el Skeletal Mesh del personaje. Tienes que ver el Skeleton Tree.

Buscamos el hueso donde queremos poner nuestro objeto (mochila, arma,...), por ejemplo la mano derecha y si no existe el socket (según versiones), lo creamos con botón derecho ratón, Add Socket, sobre el hueso en cuestión.

Sobre el Socket creado, click botón derecho y Add Preview Asset, buscamos y seleccionamos. Resituamos el objeto con las herramientas de transformación y listo.

Ahora, vamos a tu personaje, seleccionamos el Viewport, añadimos un componente de Static Mesh para el objeto y lo arrastramos bajo nuestro Mesh.

En Details de nuestro objeto, seleccionamos el Static Mesh del objeto y buscamos nuestro Parents Socket, anteriormente creado.

Si es necesario, reinicia los valores del Transform, si tu objeto no queda bien situado.

Ahora nuestro personaje porta el objeto, donde le hemos dicho.


8.- Muchas son las cosas que se pueden hacer desde la línea de comando, como botón de muestra.

   En línea consola de comandos, en Unreal, poner HighResShot 2 y toma una imagen de pantalla. Interesante para documentar tu trabajo.


9.- Cuando empaqueto el proyecto para Windows, y quiero personalizar el icono del ejecutable. Después de varios intentos, en diferentes resoluciones, encuentro la solución, la Community es grande. Os lo pongo mas abajo, junto con la aplicación web que he usado para crear el favicon.ico, pero lo mas importante, no os lo vais a creer y es como debeis proceder, borrar la caché de temporales y reiniciar el PC, o no se actualizaran los cambios.


10.- Mixamo.

Descargar de Mixamo un personaje, formato FBX Binary(.fbx) y Pose T-pose y guardamos el personaje.fbx.

Descargamos una animación, formato FBX Binary(.fbx), Skin, Without Skin, Frames por segundo, 30 fps y KeyFrame Reduction, none. En las animaciones Walk, marcar In Place.

Vamos al Content de tu proyecto, creamos una carpeta llamada Characters y 4 subcarpetas: Mesh, Materials, Textures, Animations.

Damos a Import desde la carpeta Mesh y lo primero que añado es el character. Muevo texturas, Materiales y animaciones.

Después, vamos a importar la animación y en la ventana de dialogo decimos a que skeleton pertenece y listo.

Como podemos hacer el retargeting, es decir reorientar los huesos. 

Click derecho en la animación, Retarget Animations, queremos pasar la animación a Manny, autoretargeting y exportamos la animación adaptada, con prefijo MM_, para que coincida con las del personaje receptor, en este caso Manny.


11.- Links de interés.


Recursos

Mixamo, para descargar personajes y animaciones.

Audacity , para editar sonidos, util para pasar a mono y reducir tamaño del audio.

Sonidos libre , para uso particular.

Convierte una imagen en un favicon.ico (es el formato de archivo usado por Unreal para cambiar el icono del ejecutable).


Materiales


How To Create And Use LODs In Unreal Engine 4/5 To Make Your Games Run Smoother.

How To Create Atlas Master Materials in Unreal Engine 5.


Iluminación


Creating Natural Lighting in Unreal Engine: A Step-by-Step Guide.


Landscape


Creating Non-Repeating Landscape Materials in Unreal Engine | Texture Blending & Macro Variations.


Sonidos


Cómo añadir SONIDOS de PISADAS en Tercera Persona en Unreal Engine 5.


Niagara


Cómo crear NIAGARA en forma de static o skeletal MESH en Unreal Engine 5.


IA en Unreal Engine


Inteligencia Artificial en Unreal Engine 5 🤖/ Enemigo que te persigue 🏃🏼 y sigue puntos aleatorios !


UI


Make An Easy WIN and LOSE System For Your Games In Unreal Engine 5 Using Blueprints.

Unreal Engine 5 Health and Damage System Tutorial [2023].


Cinematics


How to Create an Opening Cinematic in Unreal Engine 5

Unreal Engine 5 Beginner Tutorial Part 16 : Cameras, Rig Rail, and Crane

Full Beginner Filmmaking Masterclass for Unreal Engine 5 - Make your First Cinematic.

Cómo Hacer CINEMÁTICAS en UNREAL ENGINE 5 | Principiante (Sequencer UE5).


Sockets


Cómo Añadir y Cambiar Armas en tu Personaje de Unreal Engine 5 (Paso a paso)


Empaquetado


Ojo, si quieres cambiar el icono en el ejecutable, tienes que tener en cuenta esto que se dice aquí.


Documentación, un tema que ha quedado pendiente y fundamental para el control de versiones, sobre todo si trabajas en equipo.


Fundamentos de Git


Sobre la plataforma Android, no llegamos a ver nada, pero me los apunto para verlo en un futuro proximo

Android


Unreal Engine 5.5: Android Target Platform Setup & Build .APK | Step-by-Step Guide


AR Android


AR from scratch in 10 minutes using Unreal Engine 5.3!


_

Especial mención.


Los promotores y medios

Incubadora de Realidades Inmersivas de Almendralejo

Plataforma de Capacitación Tecnológica de Extremadura


El Profesor

Javier Jurado Gámez


Los Alumnos

Que viven este camino, con admirable pasión y sacrificio.


sábado, 1 de febrero de 2025

Entre entusiastas y detractores




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)
En este evento se recibirán los datos x e y del Joystick para el desplazamiento del personaje.
void HandleButtonInput(string jsonData)
En este evento se recibirán los datos state y action, acerca de las teclas pulsadas para el desplazamiento del personaje y visión de cámara.
void HandleInputMethod(string jsonData)
En este evento se recibirá el dato state, sobre el método de input elegido, a off, desde teclado, a on, desde dispositivo móvil.

      
      
      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.



[Dispositivo Móvil] --> (WebSocket) --> [Servidor Flask] --> (WebSocket) --> [Unity WebGL]
     ^                                                                            |
     |                                    Feedback                                |
     +--------------------------------------------------------------------------- +
 			


Partes del código que cabe resaltar



game.html, este es el verdadero contenedor de Unity. Tomamos todo lo que nos interesa del código index.html, de la build de Unity y lo reescribimos en este.


websocket.js, recibe los eventos del servidor y los comunica a Unity vía SendMessage


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 HandleJoystickInputHandleButtonInputHandleInputMethod, 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🔧

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.