Brian Bucklew está portando ‘Caves of Qud’ de Unity para Godot
(twitter.com/unormal)- ‘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 oGameManagerdo 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
.bmpantigos, 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
ConsoleLibeGenkitforam movidas por inteiro, enquanto Kobold, uma solução antiga de sprites e atlasing, seria importada arquivo por arquivo conforme a necessidade KoboldJSONfoi 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
GeneratedCodefoi 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 Buildere 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, comoCodeGeneration, então a direção adotada foi movê-las para uma raiz separada ou para uma pastaPlatform - Ao migrar as bibliotecas
LanguageeHistoryKit, 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
PlayFabfoi 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
Mathe da diferença entrePI/Pi - tratamento da diferença de nomes de coordenadas em maiúsculas
X,Yno Godot - correção do problema em que
System.Drawing.ColoreSystem.Numerics.Vector3eram importados incorretamente pela IDE
- shim substituto para
- 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.csfoi anexado ao node principal;GameManagerherdava deNodee exigia código partial _Readydo Godot foi usado de forma correspondente aoAwakedo Unity, e_Processem correspondência aoUpdate- 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
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
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
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
Droga, Elon. Agora está realmente irritante lidar com o Twitter
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
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
Com um motor por baixo, você ganha essas coisas de graça. No caso do Unity, talvez com algum custo
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?