1 pontos por GN⁺ 2023-09-18 | 1 comentários | Compartilhar no WhatsApp
  • ‘Caves of Qud’ realizou uma prova de conceito técnica para compilar e dar boot no núcleo do jogo no Godot, removendo uma longa dependência de Unity, e o objetivo inicial é fazê-lo rodar em modo ASCII+tiles, sem VFX ou UI modernos
  • O trabalho foi dividido em importação de assets de tiles, porte dos assemblies centrais em C# e montagem do rig de renderização e entrada; os antigos arquivos de tile em BMP foram convertidos para PNG e carregados no Godot
  • Os 5.641 erros do build inicial foram reduzidos ao mover GeneratedCode, ConsoleLib, Genkit, Language, HistoryKit, Newtonsoft JSON, CodeDom etc., enquanto a superfície de dependência de UnityEngine foi sendo reduzida com stubs
  • Os pontos de uso de Color, GameObject, AudioSource, Debug.Log e Screen do Unity foram tratados com UnityEngineReplacer e classes substitutas, enquanto as camadas de chargen e presentation, mais próximas de PlayFab e Unity UI, foram removidas temporariamente ou mantidas como candidatas a separação em módulos de glue
  • Como resultado, cerca de 500 mil linhas de código C# do núcleo do jogo chegaram ao ponto de dar boot no Godot, fornecer frames e esperar entrada, mas ainda restam trabalhos no renderer, no harness de entrada e no rigging de VFX, som e UI

Escopo da migração de Unity para Godot

  • O trabalho de port foi dividido em três frentes
    • Importação de assets: colocar os assets de tiles no projeto Godot
    • Porte dos assemblies centrais: migrar o XRL Application, que é o motor principal do jogo, e o GameManager do lado do engine, em vez de Unity
    • Montagem do rig de renderização: conectar saída de tela e entrada depois que o núcleo der boot
  • Foi criado um projeto Godot “mobile”, e o conteúdo de texturas de Qud foi copiado para a pasta raiz do projeto
  • Como o Godot não conseguia lidar com arquivos .bmp antigos, os arquivos, incluindo os tiles ASCII, foram convertidos em lote para PNG
  • Depois da conversão, os assets foram carregados, mas levou cerca de 30 segundos até aparecer a barra de progresso da importação

Reduzindo erros de build enquanto os assemblies centrais são migrados

  • Após copiar o XRL Application, foram gerados o projeto e a solução C# no Godot, e no estado inicial surgiram 5.641 erros
  • Bibliotecas genéricas como ConsoleLib e Genkit foram movidas por inteiro, enquanto Kobold, uma solução antiga de sprites e atlasing, seria importada arquivo por arquivo conforme a necessidade
  • KoboldJSON foi migrado como estava, por ser uma biblioteca simples de serialização JSON
  • Ao substituir globalmente 85 pontos de uso de Color, os erros caíram para 4.700
  • Quando a pasta ausente GeneratedCode foi adicionada, os resultados da geração de código de classes por evento passaram a ser incluídos, e os erros caíram para 1.557
    • Caves of Qud usa uma estrutura que gera código para classes por evento com muito boilerplate, ganhando desempenho e uma superfície de eventos fortemente tipada

Organizando dependências de Unity e a camada de glue

  • Embark Builder e a criação de personagem, o chargen, têm muito código de Unity UI, então foram removidos nesta prova de conceito inicial em ASCII
    • Ainda não existe uma versão para console, mas a validação inicial pode ser testada com carregamento de save ou início aleatório
  • A pasta Game é em sua maior parte glue entre Unity e o jogo, mas também contém pastas que não são glue, como CodeGeneration, então a direção adotada foi movê-las para uma raiz separada ou para uma pasta Platform
  • Ao migrar as bibliotecas Language e HistoryKit, os erros caíram para 454, e ajudou o fato de já existir alguma separação entre jogo e glue, mesmo que imperfeita
  • As principais categorias de erros restantes eram CodeDom/Roslyn, PlayFab, Harmony, alguns problemas ligados ao compilador e a superfície de UnityEngine que havia vazado para a camada do jogo
  • O código relacionado a PlayFab foi temporariamente comentado e ficou como candidato para subir ao módulo de glue

Stubs para substituir a API de Unity e diferenças do C# no Godot

  • Color32 é um tipo de cor baseado em bytes, então foi rapidamente escrita uma implementação substituta
  • Foi criada a pasta UnityEngineReplacer, e, sem consultar documentação ou código do Unity, só a superfície necessária foi implementada com base nos erros de compilação
  • Quando foi criado um stub de GameObject, os erros de “não conhece GameObject” viraram erros de campos e métodos realmente necessários, permitindo identificar a superfície de interface que o jogo usa
  • O mesmo foi feito com AudioSource, adicionando um a um os membros acessados conforme a lista de erros, até completar a interface de porte de AudioSource realmente usada pelo jogo
  • Outras superfícies tratadas foram as seguintes
    • shim substituto para Debug.Log
    • implementação substituta para Screen
    • porte da biblioteca de métodos de extensão IsNullOrEmpty
    • tratamento de referências a Math e da diferença entre PI/Pi
    • tratamento da diferença de nomes de coordenadas em maiúsculas X, Y no Godot
    • correção do problema em que System.Drawing.Color e System.Numerics.Vector3 eram importados incorretamente pela IDE
  • Newtonsoft JSON foi resolvido adicionando o pacote NuGet no Visual Studio, e CodeDom também foi resolvido com o pacote NuGet CodeDom

Até o boot completo no Godot

  • Quando os erros caíram para 11, o problema restante era o código ligado à compilação dinâmica de C# no gerenciamento de mods; não era obrigatório, mas era complexo e ficou até o fim
  • Depois disso, foram resolvidos problemas da etapa de linkedição e erros de campos nos stubs, e cerca de 500 mil linhas de C# passaram a compilar
  • O próximo passo foi criar um pequeno renderer e um harness de entrada para dar boot nos assemblies centrais, com o objetivo de tornar o jogo executável em modo ASCII+tiles
  • No Godot, foi criada uma scene vazia e GameManager.cs foi anexado ao node principal; GameManager herdava de Node e exigia código partial
  • _Ready do Godot foi usado de forma correspondente ao Awake do Unity, e _Process em correspondência ao Update
  • Durante a inicialização, o load path e o gerenciamento de mods foram migrados, e o problema de o resolvedor de tipos não conseguir encontrar tipos por nome foi rastreado com depuração no estilo printf
  • A causa era o tratamento de dynamic assembly
    • como o assembly de mod era dynamic, a inspeção do assembly principal estava excluindo assemblies dynamic
    • no editor do Godot, o assembly principal do jogo também era dynamic, então ficava fora do alvo da busca de tipos
  • Após a correção, o boot completo foi bem-sucedido, e o núcleo do jogo com 500 kloc passou a fornecer frames e esperar entrada
  • O trabalho restante foi descrito como “just work”, mas, na prática, ainda há bastante volume de trabalho em VFX, som e rigging de UI

1 comentários

 
GN⁺ 2023-09-18
Comentários do Hacker News
  • Este jogo parece usar quase inteiramente um motor próprio, com o Unity servindo apenas como camada de abstração de hardware e estrutura para ports, então parece estar perto do melhor caso possível em termos de dificuldade de portabilidade
    Há um número surpreendente de jogos assim, mas é claro que isso não representa a maioria dos títulos em Unity

    • Isso mesmo. Em uma entrevista, já disseram que Qud rodava dentro do Unity como um app de console na época
      Não sei se isso ainda é verdade depois da reformulação da UI, mas, se for, sempre achei estranho não terem economizado fazendo o port para MonoGame
    • Vejo isso como o resultado de que os componentes básicos do Unity acabam se tornando quase todos inúteis no longo prazo
      O Android é parecido: o sistema operacional acaba sendo usado só para carregar bibliotecas que substituem coisas como detecção de câmera ou criptografia, até que um dia você percebe que o sistema operacional é, na prática, apenas uma fina camada de metal e um bootloader
  • https://nitter.net/unormal/status/1703163364229161236

  • Uau, muito legal. Também é bom poder ver esse processo de portabilidade passo a passo, e me surpreende que o tempo gasto tenha sido bem razoável

    • O Godot tem bem menos recursos, mas acho que é realmente fácil de aprender
    • Queria ver como implementaram no Godot a atmosfera visual tão característica de Caves of Qud
  • Se há tanto código customizado e a estrutura usa menos recursos do editor, parece que daria para usar uma biblioteca de renderização em vez de um motor

    • Se você quer lançar um jogo em várias plataformas ao mesmo tempo, lidar diretamente com código de renderização, som e entrada específico de cada plataforma fica cansativo bem rápido
      Com um motor por baixo, você ganha essas coisas de graça. No caso do Unity, talvez com algum custo
    • Já estão usando uma biblioteca de renderização. O nome dela é “Unity”
  • Se quiser saber o que o Brian pensa, vale assistir a este vídeo: https://www.youtube.com/watch?v=U03XXzcThGU
    Aprendi muito com ele no passado

  • Isso me lembra a recente polêmica do TypeScript do DHH
    Imagine fazer um port desses sem tipagem estática: a cada mudança, seria preciso compilar, executar e procurar crashes. Nessas horas, você fica realmente grato por ter construído tudo com uma linguagem fácil de refatorar e uma boa arquitetura

  • Não tenho conta no Twitter; o que aconteceu?