Como as empresas de jogos "criavam à mão" seus jogos antes dos motores maduros?
A resposta para esta pergunta é muito mais crua do que a maioria imagina.
Os desenvolvedores de jogos de hoje abrem Unity ou Unreal, e o editor está lá, o motor de física está lá, o pipeline de renderização está lá, o sistema de iluminação está lá. O trabalho deles é preencher conteúdo dentro de um framework já construído. Isso simplesmente não existia nos anos 1980. Não havia framework, não havia engine, não havia infraestrutura reutilizável. Se você quisesse fazer um jogo, primeiro tinha que construir uma máquina que pudesse executá-lo, e então, naquela máquina, usando linguagem assembly, dizer a ela de que cor cada pixel deveria ser.
Tudo começou com pixels.
Os gráficos dos primeiros jogos eram escritos diretamente na memória. Os programadores precisavam saber o endereço de memória correspondente a cada posição na tela. Para desenhar um ponto em determinado local, você escrevia um valor naquele endereço — o valor representava a cor. Para desenhar uma linha, escrevia um loop, dizendo à CPU para escrever a cada N bytes deste endereço até aquele. Para desenhar um personagem, convertia cada linha de pixels em números hexadecimais, escrevia-os linha por linha, encontrava o deslocamento correto na tela e colocava aqueles números lá.
Esse processo hoje é chamado de “renderização por software”. Na época não tinha nome, porque não havia alternativa — era simplesmente chamado de “como a imagem saía”.
Quando Carmack estava trabalhando no primeiro Wolfenstein, ele enfrentou um problema específico: a arquitetura gráfica do IBM PC não suportava rolagem horizontal suave. A tela atualizava refrescando o quadro inteiro de uma vez, causando tearing quando a imagem se movia — parecia que estava tremendo. Ele passou uma quantidade enorme de tempo estudando os registradores de baixo nível da placa gráfica e encontrou uma maneira de enganar o hardware para fazer algo que ele não suportava nativamente, simulando por software para que a imagem pudesse rolar suavemente.
Não havia documentação para isso, nem experiência prévia. Ele simplesmente se sentou, estudou exaustivamente os manuais de hardware e descobriu tudo passo a passo por tentativa e erro.
Essa era a condição básica de trabalho dos programadores daquela época. Você não estava enfrentando um sistema com um manual — você estava enfrentando o próprio hardware. Você precisava conhecer o hardware melhor que o hardware se conhecia, e encontrar espaço nas suas fissuras.
O som era um problema de uma dimensão completamente diferente.
O sistema de som dos primeiros PCs era um buzzer — o PC Speaker. Ele só conseguia produzir um som: um bipe. Para tocar música com ele, você precisava controlar a frequência de vibração desse buzzer, alternando entre diferentes frequências em velocidade extremamente alta, usando a persistência auditiva humana para fazer parecer que uma melodia estava tocando. Essa técnica se chama PWM, modulação por largura de pulso. O princípio é física acústica, a implementação é controle de temporização preciso ao microssegundo. Se você errasse nem que fosse um pouco, o resultado era apenas ruído.
Havia programadores na época que专门研究 como extrair mais camadas de som do PC Speaker. O método deles era alternar entre diferentes frequências em janelas de tempo extremamente curtas, criando um efeito pseudo-estéreo. Essa técnica era tratada como magia negra na cena demo da época. Você pegava um buzzer que só fazia “bipe” e produzia algo que soava próximo à música. Outros programadores que ouviam não faziam ideia de como você tinha feito — porque o princípio não era intuitivo. Era preciso uma compreensão extremamente precisa do controle de temporização para conseguir.
Quando Adlib e Sound Blaster surgiram, trouxeram o som de síntese FM. Este era outro sistema que precisava ser aprendido do zero. O princípio da síntese FM é usar a forma de onda de uma frequência para modular a forma de onda de outra, produzindo timbres complexos. Diferentes parâmetros de modulação produziam diferentes sons de instrumentos. Os programadores tinham que ajustar os parâmetros eles mesmos, testar quais combinações soavam como piano, quais como violino, quais como explosão. Não havia resposta padrão — tudo dependia do ouvido e da experiência. Na época, cada programador de som de cada empresa tinha suas próprias tabelas de parâmetros acumuladas. Eram segredos comerciais no sentido mais verdadeiro.
O senso de design no nível da lógica do jogo era outra questão.
Sem engine, não havia detecção de colisão pronta, não havia simulação física pronta, não havia framework de IA pronto. Se você quisesse que um personagem parasse ao chegar em uma parede em vez de atravessá-la, precisava escrever a detecção de colisão você mesmo, definir o que significa “colisão” você mesmo, e decidir como a velocidade e a posição do personagem mudariam após a colisão você mesmo.
A detecção de colisão dos primeiros jogos era simples e brutal: tratar tanto o personagem quanto os obstáculos como retângulos, verificar se os dois retângulos se sobrepunham e, se sim, parar. Esse esquema era rápido, mas o problema é que os personagens reais de jogos não são retângulos — são sprites com formas. Sempre havia uma discrepância entre a caixa de colisão retangular e o gráfico real. Por isso, nos jogos antigos, era comum que balas que claramente erravam ainda fossem registradas como acertos, ou que personagens parassem antes de tocar visivelmente a parede.
Os jogadores da época não chamavam isso de bug. Chamavam de característica do jogo. Porque não sabiam que podia ser diferente.
Outra coisa que vale a pena mencionar: os primeiros PCs tinham memória extremamente limitada. 640KB foi o teto de hardware por uma década inteira. Esse número hoje não consegue nem armazenar uma única captura de tela. Os programadores tinham que enfiar todo o conteúdo do jogo — gráficos, efeitos sonoros, mapas, código lógico — dentro desses 640KB, e ainda deixar espaço para os dados em tempo de execução.
Como? Compressão, carregamento por blocos e reutilização.
A compressão de dados gráficos era uma disciplina própria. Os programadores tinham que inventar seus próprios algoritmos de compressão, tornando os dados gráficos o menor possível no armazenamento e descomprimindo-os instantaneamente quando necessário. Se o mapa era grande demais para caber, você o dividia em blocos, carregava o bloco onde o jogador estava e o descarregava quando ele saía. Hoje essa técnica se chama streaming. Na época, se chamava “não tem outro jeito”.
Cada byte na memória era disputado. Ao escrever código, os programadores tinham que pensar constantemente: posso usar um tipo de dado menor para esta variável? Posso usar menos variáveis temporárias para esta lógica? Posso reduzir o número de acessos à memória neste loop? Essa forma de pensar é amplamente desnecessária para os programadores de hoje. O hardware moderno tem margem de memória suficiente e os compiladores são inteligentes o bastante em otimização — você não precisa contar cada byte.
Mas os programadores daquela época carregavam todo o funcionamento interno do computador na cabeça. Dos ciclos de instrução da CPU ao layout da memória e aos registradores da GPU, eles precisavam entender cada camada, porque cada camada era uma fissura que poderiam precisar explorar.
Carmack escreveu uma vez um código para calcular a raiz quadrada inversa, com um comentário que dizia “what the fuck”. Esse código usava um truque de manipulação de bits extremamente contraintuitivo para aproximar rapidamente a raiz quadrada inversa, muito mais rápido que a biblioteca matemática padrão — tudo para economizar tempo de computação na renderização 3D. Esse fragmento de código se tornou mais tarde uma lenda na cultura dos programadores, citado repetidamente, porque representava uma forma de pensar que desapareceu: o método de extrair desempenho da matemática e da criatividade dentro das limitações do hardware.
Os programadores de jogos daquela época estavam mais próximos de um híbrido de engenheiros e cientistas. O problema que resolviam não era apenas “como implementar esta funcionalidade” — era “se esta funcionalidade pode ser implementada dentro dos limites físicos desta máquina”.
São duas perguntas muito diferentes.
A primeira pergunta tem uma resposta padrão: consultar a documentação, chamar a API, seguir os passos. A segunda pergunta não tem resposta padrão. Você tem que inventar a resposta você mesmo. Às vezes a resposta existe, às vezes não. Você precisava primeiro descobrir em qual caso se encontrava e então decidir o que fazer.
A indústria de jogos atual é um sistema colaborativo massivo. Empresas de engines, empresas de middleware, empresas de ferramentas — cada camada tem alguém trabalhando nela. Os desenvolvedores estão no topo desse sistema, focados no conteúdo em si. Isso é progresso, é algo bom — permite que mais pessoas façam jogos, e que a escala e complexidade dos jogos superem em muito o que era possível no passado.
Mas aquela era de construir jogos a partir dos pixels tinha algo que já não existe hoje.
Você abre o jogo e sabe de onde veio cada ponto na tela, como cada som foi feito, como cada colisão foi julgada. Porque tudo isso foi escrito por você, linha por linha. Não havia nada que outra pessoa tivesse feito por você.
Esse sentimento tem um nome. Chama-se controle total.
Então, os grandes que desenvolviam jogos naquela época — eram verdadeiramente grandes. Verdadeiramente notáveis.