Programador líder  ·  Signum Ludens  ·  no estúdio desde abril de 2024

Sky Paws Awakening

Um action-platformer em duas eras: a mesma visão de game design no hardware do Mega Drive e no Unity, com gráficos modernos.

Sega Mega Drive · SGDK · C Unity 6 · URP · C#

Sobre o NDA. Este projeto está sob acordo de confidencialidade. Esta página usa apenas informações já públicas (a página da Steam) e descrições técnicas escritas por mim, sem código-fonte, nomes internos ou conteúdo inédito do jogo.

O projeto

Sky Paws Awakening é um action-adventure de plataforma em que o jogador alterna em tempo real entre dois cadetes com estilos de combate diferentes, o Twin Tag System.

Publicado pela Signum Ludens, com lançamento ainda a anunciar, para Windows, Linux/SteamOS e Steam Deck, em inglês e português do Brasil. A versão Unity é fiel à visão de game design do original, mas apresenta gráficos modernos, sem se prender ao visual retrô. O original, para Sega Mega Drive, também anunciado publicamente, é onde o design nasceu.

  • 10+ fases
  • 20+ tipos de inimigos
  • Vários chefes
  • 6 cutscenes

Dados da página pública da Steam.

Sky Paws Awakening
Trailer da versão Mega Drive ↗

Meu papel

Sou o programador líder e respondo pelo código de gameplay e de sistemas das duas versões. Trabalho ao lado de um game designer, dois ilustradores e um artista de VFX, que implementa os efeitos visuais.

  • Arquitetura e sistemas de gameplay: movimento, combate, troca de personagem, IA de inimigos e chefes, diálogo e cutscenes, HUD, áudio, save e progressão.
  • Porte Mega Drive → Unity com paridade de comportamento entre as versões.
  • Depuração e otimização em hardware real, com foco em memória de vídeo, temporização e estabilidade.
  • Ferramentas de desenvolvimento sob medida: monitor de VRAM, menu de debug e etapas de build automatizadas.
  • Publicação e integração com a Steam.
  • Documentação técnica: planos, guias de configuração e relatórios de causa raiz, mantidos junto ao código.
2a 6mna Signum Ludens; este é o meu 3º projeto lá
2bases de código: C (68000) e C# (Unity 6)
70k+ / 35k+linhas de C e de C# de gameplay
1.700+commits meus nos dois repositórios
120+documentos técnicos escritos

Números aproximados; servem como indicador de escala, não como medida de qualidade.

Como eu planejo: SOLID

O Mega Drive impõe lógica procedural e acoplada. No Unity, a mesma lógica vira componentes pequenos, intercambiáveis e configuráveis. Antes de escrever código, decomponho o sistema em responsabilidades, e sigo este roteiro:

  1. Entender o comportamento antes do código: o original e os documentos de design são a especificação.
  2. Listar as responsabilidades do sistema e dar a cada uma um dono.
  3. Definir interfaces pequenas nas fronteiras entre os donos.
  4. Separar dados de lógica: tudo o que o game design pode querer ajustar sai do código e vai para ScriptableObjects, exposto no inspector.
  5. Regra de parada: se uma tradução vira um arquivo grande com muitas responsabilidades, paro e redesenho antes de implementar.

Responsabilidade única

Um orquestrador de personagem só coordena; movimento, combate e animação vivem em componentes próprios. Nenhuma classe “faz-tudo”.

Aberto/fechado

Inimigos, ataques e personagens novos entram como dados (ScriptableObjects), prefabs e, quando preciso, um estado novo, sem editar o que já funciona.

Substituição de Liskov

Estados de jogador, de inimigo e de jogo seguem o mesmo contrato e são intercambiáveis: a máquina de estados não sabe qual está rodando.

Segregação de interfaces

Contratos pequenos e específicos (receber knockback, ser agarrável, ter vida, tocar música, tocar efeito sonoro) em vez de uma interface gorda.

Inversão de dependência

Os sistemas dependem de abstrações resolvidas por um registro de serviços; dependências concretas chegam pelo inspector ou por dados, sem buscas globais em runtime.

O ganho prático: valores de tuning ficam no inspector, pensados para o game design ajustar sem mexer em código, e conteúdo novo não coloca em risco o que já funciona.

Estudos de caso

04 casos
01 Crashes que só existiam no hardware real

Problema

O Mega Drive (Motorola 68000) é muito menos tolerante que os emuladores. O jogo rodava perfeito no emulador, mas o console desligava de forma aleatória, geralmente na segunda metade das fases.

Investigação

Como não dava para reproduzir no emulador, auditei o código por classes de erro típicas de hardware: índices fora dos limites, retornos de alocação sem checagem de nulo, buffers de texto de debug estourando a pilha (no 68000, sobrescrever o endereço de retorno não gera erro; o jogo simplesmente trava) e escrita no chip de vídeo durante a interrupção de vídeo.

Solução

Validação de limites nos pontos de entrada, checagem de retorno nulo em toda alocação de sprite, regras de tamanho para buffers de formatação e interrupções desabilitadas nas janelas críticas de escrita de vídeo (em transições, com a tela já preta, então sem custo visível).

Aprendizado

“Passa no emulador” não prova nada. Cada causa virou regra de código e item de checklist de revisão, para o problema não voltar.

02 Memória de vídeo sobrando, e mesmo assim sem espaço

Sintoma

Sprites do jogador, inimigos e efeitos corrompiam ao mesmo tempo, de forma intermitente, mesmo com muita VRAM livre no visualizador de depuração.

Hipótese descartada

Orçamento. Medi o pico de uso do pool de sprites em combate denso e ele ficava perto de 85%: nunca estourava o total.

Causa raiz

Fragmentação. O motor precisa de um bloco contíguo. Sprites nascendo e morrendo deixam o espaço livre espalhado em buracos pequenos; quando o pedido é maior que o maior buraco, a alocação falha e o fallback do motor sobrepõe endereços, corrompendo tudo de uma vez.

Ilustração: 18 células livres no total, maior bloco contíguo de 4. Um pedido de 5 falha, mesmo com espaço “de sobra”.

Diagnóstico

Instrumentei o alocador para registrar, a cada falha, o total livre versus o maior bloco livre. A assinatura ficou óbvia: total alto, maior bloco pequeno.

Solução

Alocação tolerante a fragmentação (desfragmenta e tenta de novo) nos pontos críticos. O custo, cerca de um frame, só aparece como fallback raro. Achado relacionado: sprites pequenos e frequentes não podem usar compressão, pois a fila de DMA lia um buffer já liberado.

Status

Correção confirmada em emulador.

03 De C procedural a componentes SOLID

Contexto

O código original do Mega Drive é procedural: estruturas globais, funções grandes e uma ordem de atualização fixa, com cerca de vinte etapas por frame. Traduzir isso linha a linha para C# levaria o acoplamento junto.

Decisão

Traduzir a lógica, não as limitações do hardware. O que existia só por restrição da plataforma não foi portado:

Mega DriveUnity
Pools estáticos, sem alocação dinâmicaPooling normal, sem limite artificial
Ponto fixo 16.16Float / Vector2
Colisão empacotada em tiles de 16×16Tilemap + Collider2D
Fases divididas em sessões por limite de VRAMFase inteira em uma cena

Execução

Cada sistema foi redesenhado com um orquestrador enxuto (só coordena), estados intercambiáveis, interfaces pequenas, serviços resolvidos por abstração e valores de tuning em ScriptableObjects.

Resultado

Novos tipos de inimigo reaproveitam a mesma IA com dados e estados próprios, sem editar o núcleo. A arquitetura foi desenhada para que chefes e ataques novos entrem do mesmo jeito.

04 Um jogo, dois stacks: mantendo a paridade

Desafio

Manter o mesmo design em C/SGDK e em C#/Unity sem que virasse dois jogos diferentes.

Abordagem

O código original serve como especificação executável: valores de física, padrões de inimigos, fases de chefe e cenas de diálogo já estão definidos e testados nele. Cada feature portada é validada contra esse comportamento. A fidelidade é à visão de game design, não ao visual: a versão Unity usa gráficos modernos e não se compromete com a estética retrô. As diferenças intencionais são listadas em vez de deixadas ao acaso.

Aprendizado

Paridade se define pelo comportamento observável, não pela semelhança do código. Ter as duas versões também expõe bugs de design que uma só esconderia.

Stack deste projeto

// mega drive

  • SGDK (C, GCC / 68000)
  • Ferramentas de build em Python
  • Monitor de VRAM e menu de debug próprios

// unity

  • Unity 6 (URP, 2D Tilemap)
  • C# · Cinemachine · Input System
  • Addressables · Localization
  • Odin Inspector · Steamworks.NET

// padrões

  • Máquinas de estado
  • Mediator, service registry
  • Object pooling
  • Design orientado a dados (ScriptableObjects)

// método

  • Plano, implementação e guia de configuração, por sistema
  • Relatório de causa raiz para cada bug crítico
  • Uso assistentes de IA como ferramenta; as decisões de arquitetura e a revisão do código são minhas

Contato