Elysium: um vampiro entra no Unreal
Pega um mapa de Vampire: The Masquerade – Bloodlines, importa no Unreal, coloca uma iluminação bonita e tira uma screenshot. Parece que funcionou. Aí você tenta abrir a primeira porta e descobre que ainda falta o jogo inteiro por trás dela.
O Elysium é meu projeto de reconstruir o jogo de 2004 na Unreal Engine 5.8. O Unreal renderiza, anima, cuida da física e oferece um monte de ferramentas boas. Só que Bloodlines também é conversa, missão, NPC reagindo a barulho, script disparando na hora certa e umas regras meio esquisitas que o jogo usa o tempo todo. Trazer o cenário é só o começo.
O processo funciona mais ou menos assim.
Primeiro: qual arquivo o jogo usa?
Eu começo pela instalação de Bloodlines no meu computador. O conteúdo está espalhado por mapas da era Source, arquivos empacotados, modelos, materiais, texturas, diálogos, scripts Python e configurações. Ainda tem o Unofficial Patch: um arquivo solto dele pode substituir um arquivo original. Se o exportador escolher a versão errada, o Unreal vai montar o mapa direitinho. O mapa errado.
O trecho que escolhe o mapa é pequeno. Procura primeiro no patch, depois no jogo base; o primeiro arquivo encontrado vence:
for root in LOOSE_ROOTS + [GAME]:
p = os.path.join(root, "maps", stem + ".bsp")
if os.path.exists(p):
return p
É um trecho abreviado de pipeline/src/elysium_pipeline/formats/install.py. O índice da instalação usa a mesma ordem para resolver os outros arquivos soltos e empacotados.
Depois, um pipeline em Python lê os arquivos escolhidos e gera dados intermediários documentados, sem depender do Unreal para entendê-los. Dá para abrir a geometria e outros recursos visuais como glTF/GLB; os dados de jogabilidade mantêm os nomes e as relações que o runtime vai precisar. O repositório do Elysium guarda o código e a pesquisa, não os assets extraídos do jogo. Para gerar esses dados, você usa a sua própria instalação.
Separar essas etapas salva um tempo absurdo quando algo dá errado. Sumiu uma parede? Primeiro vejo se o Python encontrou a parede. Depois, se ela está no arquivo exportado. Só então vou olhar a geração do mapa na Unreal. Dá para descobrir onde o problema entrou, em vez de ficar chutando no projeto inteiro.
É Source, mas não aquele Source
Quando a Troika começou Bloodlines, a Valve ainda estava fazendo Half-Life 2. Eles partiram de uma versão mais antiga do Source e colocaram as próprias regras de RPG em cima. O SDK público da Valve ajuda muito, só que não é um manual dos arquivos que Bloodlines gravou. Passei um tempão descobrindo onde uma coisa deixava de bater com a outra.
Pega os .mdl dos personagens. No SDK posterior da Valve, a animação de cada osso vem em registros encadeados, com flags dizendo qual tipo de compressão foi usado. Em Bloodlines, cada osso tem um registro fixo de 32 bytes e sete referências para trilhas comprimidas: três para posição e quatro para a rotação. As trilhas usam RLE, uma forma de guardar valores repetidos sem escrever cada frame inteiro. Se eu usar o leitor mais novo, vou interpretar os bytes errados. O modelo pode até aparecer no Unreal, mas a pose pode virar um macarrão quando a animação roda. Tive que recuperar o formato antigo, decodificar canal por canal e conferir as poses nos modelos do jogo.
E a Troika ainda colocou eventos próprios nas animações. Um frame de mordida, por exemplo, pode avisar quando o agarrão começa ou termina. Se eu importar só esqueleto e movimento, o personagem pode se mexer certo enquanto a regra do jogo desaparece. Screenshot parada nenhuma vai me contar isso.
As notas de animação do Elysium e o levantamento dos eventos mostram o que veio no jogo; o studio.h posterior da Valve mostra o formato que muita ferramenta de Source espera encontrar.
O Unreal faz a parte dele
Um commandlet do editor pega o mapa exportado e gera geometria, materiais, texturas, objetos, decals, luzes e uma fase que o Unreal consegue carregar. Aí ele faz a parte em que é muito bom: renderizar tudo isso com ferramentas modernas. Dá para melhorar a imagem sem lavar a sujeira da Los Angeles gótico-punk de Bloodlines.
Python e editor trabalham antes de você apertar Play. Eles exportam e geram os assets; não ficam rodando por trás de cada frame. Durante o jogo, o C++ abre a fase pronta e usa os dados exportados para fazer as coisas acontecerem.
No dia a dia, o caminho básico cabe em três comandos:
uv run elysium export map sp_tutorial_1
uv run elysium bake map --maps sp_tutorial_1
uv run elysium run play sp_tutorial_1
A screenshot mostra que o mapa carregou. Ela não conta se alguém ali está seguindo as regras do jogo.
O mapa abriu. E o resto do jogo?
Pega um NPC do tutorial. O mapa diz onde ele aparece e a quais eventos está ligado. Uma ficha fornece atributos. O código do jogo decide o que ele percebe e qual rotina segue. Os scripts colocam as consequências na história. A animação ainda precisa mostrar a ação certa na hora certa. Parece um personagem só, mas tem várias peças conversando.
Por isso também faço salas de teste sem enfeite, com os pontos de cobertura e as direções desenhados na tela. Fica bem mais fácil entender uma decisão da IA ali do que num beco cheio de efeito, com o NPC sumindo atrás da esquina.
Se um NPC vira para o lado errado, eu posso girar o ator no Unreal e deixar a screenshot bonita. Só que aí talvez eu tenha escondido o bug. Quem escolheu aquela direção? Foi o mapa, um script ou a primeira atualização da IA? Portas, diálogos, combate e saves dão o mesmo tipo de trabalho. A camada de jogabilidade em C++ precisa reconstruir essas regras a partir dos dados exportados.
Pega uma porta. Parece simples, mas olha o que acontece com a entrada Open em Source/ElysiumUE/Private/Substrate/ElysiumMover.cpp:
if (IsUseRefused(Activator))
{
return;
}
if (ToggleState != EToggleState::AtTop)
{
static const FName OnOpen(TEXT("OnOpen"));
FireOutput(OnOpen, Activator);
DoorGoUp(Activator);
}
Olha a pegadinha: trancada, a porta ignora esse Open em silêncio. Se pode abrir, dispara OnOpen e chama DoorGoUp, que dispara OnOpen outra vez antes de começar a mover. Parece um evento duplicado que dá vontade de “consertar”. Só que o jogo original faz isso, e os scripts podem enxergar a ordem. A porta não é só a animação da porta.
OnOpen aparece duas vezes. Esse detalhe também faz parte do comportamento do jogo.Quando algo não bate, volto ao original. Olho os arquivos, rastreio o código, anoto o que ele lê e altera, e comparo uma execução controlada com o Elysium. Às vezes falta um dado na exportação. Às vezes o evento saiu um instante cedo demais. E às vezes aquela coisa que parece um bug já está embutida no jeito que uma missão funciona. Melhor descobrir antes de sair corrigindo por instinto.
Onde entram os agentes de IA
No Elysium, o código é escrito por agentes de IA. Eu defino o que investigar, a arquitetura, os critérios de aceitação e reviso o resultado; os agentes implementam e pesquisam. Isso acelera muita coisa. Também torna bem fácil aceitar uma resposta que soa ótima e está errada. Então o patch tem que passar por build, teste e comparação com o jogo original. Confiança no texto não conta como evidência.
O projeto ainda está em desenvolvimento. Já dá para abrir e examinar mapas e cenas roteirizadas, mas reconstruir o resto da jogabilidade continua sendo trabalho em andamento. Também não tem Bloodlines para baixar no repositório: para gerar o conteúdo local, você precisa da sua própria instalação.
O momento legal é quando alguma coisa funciona pelo motivo certo. Não porque eu arrastei o ator até ficar bonito na imagem, mas porque os arquivos, os eventos e o código chegaram juntos à mesma resposta. Aí começa a parecer Bloodlines de verdade.
Tem mais na página do projeto Elysium e no código e nas notas de pesquisa no GitHub.