1 puntos por GN⁺ 2023-09-18 | 1 comentarios | Compartir por WhatsApp
  • ‘Caves of Qud’ inició una prueba técnica para eliminar su larga dependencia de Unity y compilar y arrancar el núcleo del juego en Godot; el objetivo inicial es ejecutarlo en modo ASCII+tiles, sin VFX ni UI modernos
  • El trabajo se dividió en importar assets de tiles, portar los ensamblados principales de C# y configurar el rig de renderizado y entrada; los antiguos archivos de tiles BMP se convirtieron a PNG y se cargaron en Godot
  • Los 5,641 errores de la compilación inicial se fueron reduciendo al trasladar GeneratedCode, ConsoleLib, Genkit, Language, HistoryKit, Newtonsoft JSON, CodeDom y otros componentes, mientras la superficie de dependencia de UnityEngine se fue acotando con stubs
  • Los puntos donde se usaban Color, GameObject, AudioSource, Debug.Log y Screen de Unity se resolvieron con UnityEngineReplacer y clases sustitutas; las capas de chargen y presentation, cercanas a PlayFab y Unity UI, se eliminaron temporalmente o quedaron como candidatas para separarse en módulos glue
  • Como resultado, un núcleo de juego en C# de unas 500 mil líneas logró arrancar en Godot, entregar frames y quedar esperando entrada, aunque todavía quedan por hacer el renderer, el arnés de entrada y el rigging de VFX, sonido y UI

Alcance del trabajo para pasar de Unity a Godot

  • El port se dividió en tres líneas
    • Importación de assets: llevar los assets de tiles al proyecto de Godot
    • Port del ensamblado principal: mover XRL Application, el motor principal del juego que no es Unity, y el GameManager del lado del motor
    • Configuración del rig de renderizado: conectar la salida en pantalla y la entrada una vez que el núcleo arranque
  • Se creó un proyecto “mobile” de Godot y se copió el contenido de texturas de Qud a la carpeta raíz del proyecto
  • Como Godot no podía procesar los antiguos archivos .bmp, se convirtieron en lote a PNG los archivos, incluidos los tiles ASCII
  • Tras la conversión, los assets se cargaron, pero tardó unos 30 segundos en aparecer el indicador de progreso de importación

Reducir errores de compilación mientras se porta el ensamblado del núcleo

  • Después de copiar XRL Application, se generaron un proyecto y una solución de C# en Godot, y en el estado inicial aparecieron 5,641 errores
  • Las bibliotecas de uso general como ConsoleLib y Genkit se trasladaron completas; Kobold, una solución antigua de sprites y atlasing, se decidió importar archivo por archivo revisando si hacía falta
  • KoboldJSON, al ser una biblioteca simple de serialización JSON, se trasladó tal cual
  • Al hacer un reemplazo global en 85 usos de Color, los errores bajaron a 4,700
  • Al agregar la carpeta GeneratedCode que faltaba, se incluyeron los resultados de generación de código de clases por evento y los errores se redujeron hasta 1,557
    • Caves of Qud tiene una estructura que genera código para clases por evento con mucho boilerplate, con lo que obtiene rendimiento y una superficie de eventos fuertemente tipada

Ordenar dependencias de Unity y la capa glue

  • Embark Builder y el chargen de creación de personaje tienen mucho código de Unity UI, así que se eliminaron para la prueba técnica inicial en ASCII
    • Todavía no existe una versión de consola, pero la validación inicial puede probarse cargando partidas guardadas o con un inicio aleatorio
  • La carpeta Game es en su mayor parte glue entre Unity y el juego, pero también contiene carpetas que no son glue, como CodeGeneration, así que se eligió moverlas a una raíz separada o a una carpeta Platform
  • Al trasladar las bibliotecas Language y HistoryKit, los errores bajaron hasta 454, y ayudó que, aunque no fuera perfecto, existiera una separación entre game y glue
  • Las grandes categorías de errores restantes eran CodeDom/Roslyn, PlayFab, Harmony, algunos problemas relacionados con el compilador y la superficie UnityEngine que se había filtrado a la capa del juego
  • El código relacionado con PlayFab se comentó temporalmente y quedó como candidato para subirlo a un módulo glue

Stubs de reemplazo para la API de Unity y diferencias de Godot C#

  • Como Color32 es un tipo de color basado en bytes, se escribió rápidamente una implementación sustituta
  • Se creó la carpeta UnityEngineReplacer y, sin consultar documentación de Unity ni referencias de código, se implementó solo la superficie necesaria observando los errores de compilación
  • Al crear un stub de GameObject, los errores de “no se conoce GameObject” se convirtieron en errores sobre campos y métodos realmente necesarios, lo que permitió identificar la superficie de interfaz que usa el juego
  • Con AudioSource también se fueron agregando uno por uno los miembros accedidos mirando la lista de errores, hasta completar toda la interfaz del puerto de AudioSource que el juego usa realmente
  • Otras superficies tratadas fueron las siguientes
    • shim sustituto para Debug.Log
    • implementación sustituta de Screen
    • port de la biblioteca de métodos de extensión IsNullOrEmpty
    • tratamiento de referencias a Math y de la diferencia entre PI/Pi
    • tratamiento de la diferencia en Godot de los nombres de coordenadas en mayúscula X, Y
    • limpieza de imports incorrectos de System.Drawing.Color y System.Numerics.Vector3 hechos por el IDE
  • Newtonsoft JSON se resolvió agregando el paquete NuGet desde Visual Studio, y CodeDom también se resolvió con el paquete NuGet CodeDom

Hasta el arranque completo en Godot

  • Cuando los errores bajaron a 11, el problema restante era código relacionado con la compilación dinámica de C# en la gestión de mods; no era imprescindible, pero era complejo y quedó hasta el final
  • Luego se resolvieron la fase de enlace y los errores de campos en stubs, y se logró compilar unas 500 mil líneas de C#
  • El siguiente paso fue crear un pequeño renderer y un arnés de entrada para arrancar el ensamblado del núcleo, con el objetivo de hacer que el juego pudiera ejecutarse en modo ASCII+tiles
  • Se creó una scene vacía en Godot y se adjuntó GameManager.cs al node básico; GameManager heredaba de Node y requería código partial
  • _Ready de Godot se usó como equivalente de Awake de Unity, y _Process como equivalente de Update de Unity
  • Durante la inicialización se movieron el load path y la gestión de mods, y el problema de que el type resolver no encontrara tipos por nombre se rastreó con un enfoque tipo printf
  • La causa era el manejo de dynamic assemblies
    • Como los mod assemblies eran dynamic, se estaban excluyendo los dynamic assemblies al inspeccionar el main assembly
    • En el editor de Godot, el main game assembly también es dynamic, por lo que quedaba fuera de los destinos de búsqueda de tipos
  • Tras la corrección, se logró el arranque completo, y el núcleo de juego de 500 kloc quedó entregando frames y esperando entrada
  • El trabajo restante se describió como “just work”, pero en la práctica todavía queda una cantidad considerable de trabajo en VFX, sonido y rigging de UI

1 comentarios

 
GN⁺ 2023-09-18
Opiniones en Hacker News
  • Este juego parece usar casi por completo un motor propio, y Unity solo como capa de abstracción de hardware y armazón para ports, así que en términos de dificultad para portarlo parece estar cerca del mejor escenario posible.
    Hay sorprendentemente muchos juegos así, pero claro, no representan a la mayoría de los títulos de Unity.

    • Sí. En alguna entrevista se dijo que Qud corría dentro de Unity como una app de consola.
      No sé si después de la renovación de la UI sigue siendo así, pero si lo es, siempre me pareció raro que no lo portaran a MonoGame para ahorrar costos.
    • Creo que el resultado es que, a largo plazo, casi todos los componentes básicos de Unity terminan siendo inútiles.
      Con Android pasa algo parecido: el sistema operativo solo se usa para cargar bibliotecas que reemplazan cosas como detección de cámara o cifrado, hasta que un día uno se da cuenta de que el sistema operativo en realidad no es más que una delgada capa de metal y un bootloader.
  • https://nitter.net/unormal/status/1703163364229161236

  • Wow, qué genial. También está bueno poder ver este proceso de port paso a paso, y me sorprende que el tiempo que tomó sea bastante razonable.

    • Godot tiene muchas menos funciones, pero creo que es realmente fácil de aprender.
    • Me gustaría ver cómo implementaron en Godot la atmósfera visual tan particular de Caves of Qud.
  • Si tiene tanto código custom y usa menos las funciones del editor, parece que podrían usar una biblioteca de renderizado en lugar de un motor.

    • Si quieres sacar un juego en varias plataformas a la vez, tener que manejar directamente el código de renderizado, sonido e input de cada plataforma se vuelve tedioso muy rápido.
      Con un motor como base obtienes todo eso gratis. Aunque con Unity quizá venga con un costo.
    • Ya están usando una biblioteca de renderizado. Se llama “Unity”.
  • Si quieres saber qué piensa Brian, conviene ver este video: https://www.youtube.com/watch?v=U03XXzcThGU
    Aprendí mucho de él hace tiempo.

  • Me recuerda a la reciente polémica de DHH sobre TypeScript.
    Si uno imagina hacer un port así sin tipos estáticos, habría que compilar y ejecutar con cada cambio y buscar los crashes. En momentos así, se agradece mucho haber construido todo con un lenguaje fácil de refactorizar y una buena arquitectura.

  • No tengo cuenta de Twitter, ¿qué pasó?