Fala, comunidade! 👋
Recentemente, me deparei com um problema clássico no mundo dos games: um jogo mobile (Heartopia) ganhou um port para PC, mas trouxe consigo todos os controles de toque. O resultado? Para girar a câmera, o jogador precisava segurar o clique esquerdo e arrastar o mouse. Uma experiência de UX terrível para quem joga no teclado e mouse.
Como desenvolvedora, eu não ia simplesmente aceitar isso. Decidi criar um mod para transformar essa câmera em um "Free Look 360º" contínuo, nativo de jogos de PC. O que parecia simples virou um desafio de engenharia reversa e manipulação de SO.
Bem-vindos ao Heartopia PC Camera Fix, o primeiro projeto open-source do meu estúdio indie, Lovel Studio.
🚧 O Desafio: Unity IL2CPP e o Input Manager
O jogo foi compilado em IL2CPP, o que já dificulta bastante a engenharia reversa se comparado ao Mono tradicional. A arquitetura original utilizava o New Unity Input System altamente acoplado para mobile (ScriptsRefactory.BaseService.Input.InputManager).
Tentar interceptar o UnityEngine.Input diretamente via Harmony Hooks estava quebrando o jogo. A solução? Fazer um bypass na engine e lidar diretamente com o Windows.
🛠️ A Solução: C# + user32.dll
Criei um plugin em BepInEx 6 e adotei uma abordagem de "Esteira Virtual" usando a API nativa do Windows.
A arquitetura final do mod (v2.5.0) faz o seguinte:
1. O Paradoxo da Esteira (Virtual Treadmill)
Quando o jogador aperta ALT, eu confino o cursor e simulo um MOUSEEVENTF_RIGHTDOWN. Para permitir o giro infinito (360º), eu leio as coordenadas do monitor via ClientToScreen e teletransporto o cursor (SetCursorPos) de volta para o centro sempre que ele atinge uma margem de 100 pixels. É um loop infinito e invisível de arrasto.
2. Segurança de Interface (WndProc Hook)
O maior bug inicial era: enquanto o jogador girava a câmera livremente, o jogo continuava registrando "cliques" invisíveis na interface, fazendo a personagem atacar do nada.
Para resolver isso, criei um Hook direto no WndProc da janela do jogo:
private IntPtr WndProcHook(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
// Bloqueia cliques esquerdos apenas quando a câmera Lovel está ativa
if (isLocked && (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONUP || msg == WM_LBUTTONDBLCLK))
return IntPtr.Zero;
return (oldWndProc != IntPtr.Zero) ? CallWindowProc(oldWndProc, hWnd, msg, wParam, lParam) : IntPtr.Zero;
}
Isso literalmente cega o jogo para cliques físicos esquerdos enquanto o modo de câmera está ativo. Um bloqueio limpo no nível do sistema operacional.
🐛 Aonde preciso da ajuda de vocês (Open Source)
Apesar da estabilidade excelente, há um Known Issue: a câmera sofre um leve stuttering ocasional.
Isso acontece porque a Main Thread da Unity às vezes dá gargalos pesados (ex: carregando chunks ou texturas) e o jogo "perde" o frame exato em que a esteira virtual reseta o mouse para o centro, quebrando o movimento por uma fração de segundo.
O projeto está sob Licença MIT no GitHub. Se você manda bem em C#, engenharia reversa ou otimização de Unity, adoraria receber seus Pull Requests! O objetivo é encontrar os offsets corretos para um Hook nativo na classe controladora de câmera via Harmony, eliminando a dependência da user32.dll.
🔗 Acesse o repositório do Heartopia Camera Fix aqui
Se você joga o game, a aba de Releases já tem o .zip pronto no modelo Drag & Drop pra facilitar a vida.
Adoraria saber a opinião de vocês sobre outras abordagens para injetar inputs em jogos IL2CPP. Como vocês resolveriam esse stuttering da Main Thread?
Desenvolvido com muita quebra de cabeça por Stefhany (Lovel Studio) 💜
Top comments (0)