Criando um jogo para o Game Boy

imagem

Algumas semanas atrás, eu decidi trabalhar em um jogo para o Game Boy, cuja criação me deu um grande prazer. Seu nome de trabalho é Aqua and Ashes. O jogo tem código aberto e é postado no GitHub .

Como essa ideia me veio à mente


Recentemente, consegui um emprego de estágio criando um back-end em PHP e Python para o site da minha universidade. Este é um trabalho bom e interessante, pelo qual sou muito grato. Mas ... ao mesmo tempo, todo esse código de desenvolvimento da Web de alto nível me infectou com um desejo insaciável. E era o desejo de um trabalho de baixo nível com bits.

Eu recebia um resumo semanal do itch.io sobre compotas de jogos, no qual o lançamento do Mini Jam 4 era anunciado. Foi um engarrafamento de 48 horas (bem, na verdade, um pouco maior), no qual a limitação era criar gráficos no estilo de Game Boy. Minha primeira reação lógica foi criar um jogo homebrew para Game Boy. O tema da geléia era "estações" e "chama".

Depois de pensar um pouco sobre o enredo e a mecânica que podem ser implementados em 48 horas e que se encaixam nas limitações do tópico, criei um clone de uma nova interpretação do nível do jogo SNES de 1993 Tiny Toon Adventures: Buster Busts Loose !, em que o jogador no papel de Baster joga futebol americano .


Eu sempre gostei de como os criadores deste nível adotaram um esporte incrivelmente complexo, se livraram de todos os truques, posições e elementos estratégicos, resultando em um jogo extremamente interessante e fácil. Obviamente, uma visão tão simplificada do futebol americano não substituirá Madden, assim como o NBA Jam (uma idéia semelhante: apenas 4 jogadores em um campo muito menor com uma jogabilidade mais direta do que em um jogo normal) não substituirá a série 2K. Mas essa idéia tem um certo charme, e os números de vendas da NBA Jam confirmam isso.

Como tudo isso se relaciona com a minha ideia? Decidi pegar esse nível de futebol e refazê-lo para que ele permaneça semelhante ao original e, ao mesmo tempo, atual. Primeiro, reduzi o jogo para apenas quatro jogadores - um defensor e um atacante. Isso foi feito principalmente devido às limitações do hardware, mas, ao mesmo tempo, permitirá que eu experimente um pouco mais com uma IA mais inteligente, não se limitando ao princípio de "correr para a esquerda e às vezes pular" de jogar no SNES.

Por uma questão de conformidade com o tópico, substituirei os portões por colunas em chamas, ou por fogueiras, ou algo assim (ainda não decidi) e a bola de futebol por tochas e baldes de água. O vencedor será o time que controla as duas fogueiras, e em torno deste conceito simples você pode facilmente criar uma trama. As estações também são levadas em consideração: decidi que as estações mudariam a cada turno, para que a equipe de bombeiros obtenha vantagem no verão e a equipe de bombeiros no inverno. Essa vantagem parece obstáculos no campo que interferem apenas na equipe adversária.

Obviamente, ao criar duas equipes, eram necessários dois animais que amam e não gostam de fogo. No começo, pensei em formigas de fogo e algum tipo de inseto aquático, louva-a-deus e coisas do gênero, mas, tendo estudado a questão, não encontrei nenhum inseto ativo no inverno, então substituí-os por raposas polares e lagartixas. Raposas polares gostam de neve, lagartixas gostam de se deitar ao sol, então tudo parece lógico. No final, é apenas um jogo para o Game Boy.

Além disso, caso isso ainda não esteja claro, no final do congestionamento, o jogo não estava quase completo. Enfim, foi divertido de qualquer maneira.

Treinamento de Game Boy


Primeiro você precisa determinar os requisitos. Decidi escrever para o DMG (nome interno do modelo Game Boy, abreviação de Dot Matrix Game). Principalmente para atender aos requisitos de um congestionamento de jogo, mas também porque eu realmente queria. Pessoalmente, nunca tive jogos para DMG (embora existam vários jogos para Game Boy Color), mas acho que a estética de 2 bits é uma limitação muito agradável e interessante para experimentos. Talvez eu adicione cores adicionais para SGB e CGB, mas até agora não pensei nisso.

Também decidi usar um cartucho com 32K ROM + sem RAM, para o caso de querer criar uma cópia física do jogo. CatSkull, que publicou vários jogos de Game Boy, como Sheep it Up !, tem cartuchos de flash de 32 kilobytes muito baratos à venda, perfeitos para mim. Essa é outra limitação adicional, mas não acho que em um futuro próximo eu possa superar o volume de 32K com um jogo tão simples. O mais difícil será com os gráficos e, se tudo estiver completamente ruim, tentarei compactá-lo.

Quanto ao trabalho de Game Boy, tudo é bastante complicado. No entanto, para ser sincero, de todos os retro-consoles com os quais tive que trabalhar, o Game Boy foi o mais agradável. Comecei com um excelente tutorial (pelo menos pela primeira vez, porque nunca foi concluído) pelo autor do AssemblyDigest. Eu sabia que é melhor escrever em ASM, por mais doloroso que seja, às vezes, porque o hardware não é projetado para C e eu não tinha certeza de que a linguagem legal que o Wiz mencionada no tutorial seria adequada a longo prazo. Além disso, faço isso principalmente porque posso trabalhar com o ASM.

Verifique o commit 8c0a4ea

A primeira coisa a fazer foi iniciar o Game Boy. Se o logotipo da Nintendo não for encontrado com uma compensação de $104 e o restante do cabeçalho não estiver configurado corretamente, o equipamento Game Boy assumirá que o cartucho foi inserido incorretamente e se recusará a carregar. Resolver esse problema é muito simples, porque muitos tutoriais já foram escritos sobre isso. Aqui está como eu resolvi o problema do cabeçalho. Não há nada digno de atenção especial.

Será mais difícil executar ações significativas após o carregamento. É muito simples fazer com que o sistema entre em um ciclo ocupado infinito, no qual executa uma linha de código repetidamente. A execução do código começa com o rótulo main (onde o salto para o endereço $100 indica), então você precisa inserir um código simples lá. Por exemplo:

 main: .loop: halt jr .loop 

e não faz absolutamente nada além de aguardar o início da interrupção, após o que retorna ao rótulo .loop . (A seguir, omitirei a descrição detalhada do ASM. Se você ficar confuso, verifique a documentação do assembler que eu uso .) Você pode estar curioso por que simplesmente não volto ao rótulo main . Isso é porque eu quero que tudo antes do rótulo .loop seja a inicialização do programa e tudo depois que ele acontece em todos os quadros. Portanto, não preciso ignorar o ciclo de carregamento de dados do cartucho e limpar a memória em cada quadro.

Vamos dar mais um passo. O pacote do assembler RGBDS que eu uso contém um conversor de imagens. Como, nesta fase, ainda não tenho recursos para o jogo, decidi usar o botão monocromático da minha página Sobre como um bitmap de teste. Usando o RGBGFX, converti-o para o formato Game Boy e usei o comando .incbin assembler para inseri-lo após a função main .

imagem

Para exibi-lo na tela, preciso do seguinte:

  1. LCD desligado
  2. Definir paleta
  3. Definir posição de rolagem
  4. Limpar memória de vídeo (VRAM)
  5. Carregar gráficos de bloco na VRAM
  6. Faça o download do mapa de fundo da telha VRAM
  7. Ligue o LCD novamente

LCD desligado


Para iniciantes, esse se torna o obstáculo mais sério. No primeiro Game Boy, é impossível simplesmente gravar dados na VRAM a qualquer momento. É necessário aguardar o momento em que o sistema não desenha nada. Imitando o brilho do fósforo em televisões CRT antigas, o intervalo entre cada quadro quando o VRAM é aberto é chamado Vertical-Blank, ou VBlank (no CRT, é um impulso para extinguir o feixe do cinescópio durante uma varredura reversa). (Também há HBlank entre cada linha da tela, mas é muito curto.) No entanto, podemos solucionar esse problema desligando a tela LCD, ou seja, podemos gravar em VRAM, independentemente de onde a faixa de fósforo da tela CRT esteja localizada.

Se você está confuso, esta revisão deve explicar muito para você . Nele, a questão é considerada do ponto de vista do SNES; portanto, não esqueça que não há feixe de elétrons e os números são diferentes, mas em todo o resto é bastante aplicável. Essencialmente, precisamos definir a bandeira do FBlank.

No entanto, o truque do Game Boy é que você só pode desligar o LCD durante o VBlank. Ou seja, temos que esperar pelo VBlank. Para fazer isso, use interrupções. Interrupções são sinais de que o Game Boy envia hardware para a CPU. Se o manipulador de interrupções estiver definido, o processador interromperá o trabalho e chamará o manipulador. O Game Boy suporta cinco interrupções e uma delas inicia quando o VBlank é iniciado.

As interrupções podem ser tratadas de duas maneiras diferentes. A primeira e mais comum é a tarefa de um manipulador de interrupções que funciona como expliquei acima. No entanto, podemos ativar uma interrupção específica e desativar todos os manipuladores, definindo o sinalizador de habilitação para essa interrupção e usando o código di operação. Geralmente não faz nada, mas tem o efeito colateral de sair do código de operação HALT, que interrompe a CPU antes que ocorra uma interrupção. (Isso também acontece quando os manipuladores são ativados, o que nos permite sair do ciclo HALT em main .) Caso você esteja interessado, também criaremos um manipulador VBlank ao longo do tempo, mas muito disso dependerá de certos valores em determinados endereços. Como nada foi definido na RAM até agora, uma tentativa de chamar o manipulador do VBlank pode levar a uma falha no sistema.

Para definir os valores, precisamos enviar comandos para os registros de hardware do Game Boy. Existem endereços de memória especiais diretamente relacionados a várias partes do equipamento, no nosso caso, a CPU, que permite alterar a maneira como ele funciona. Estamos particularmente interessados ​​nos endereços $FFFF (campo de bit de ativação de interrupção), $FF0F (campo de bit de $FF0F ativado mas não $FF0F ) e $FF40 (controle de LCD). Uma lista desses registros pode ser encontrada nas páginas associadas à seção "Documentação" da lista Awesome Game Boy Development.

Para desligar o LCD, ligamos apenas a interrupção do VBlank, atribuindo $FFFF valor $01 , execute HALT até que a condição $FF0F == $01 satisfeita e, em seguida, atribua o bit 7 do endereço $FF40 a 0.

Definindo a paleta e a posição de rolagem


Isso é fácil de fazer. Agora que o LCD está desligado, não precisamos nos preocupar com o VBlank. Para definir a posição de rolagem, basta definir os registros X e Y como 0. Com a paleta, tudo é um pouco mais complicado. No Game Boy, você pode atribuir tons do primeiro ao quarto gráfico de qualquer um dos quatro tons de cinza (ou verde do pântano, se desejar), o que é útil para fazer transições e coisas do gênero. %11100100 um gradiente simples como uma paleta, definida como uma lista de bits %11100100 .

Limpando VRAM e carregando gráficos de bloco


Quando lançados, todos os dados gráficos e o mapa de fundo consistem apenas em um logotipo da Nintendo rolante, que é exibido quando o sistema é inicializado. Se eu ativar sprites (eles estão desativados por padrão), eles serão espalhados pelo lixo na tela. Você precisa limpar a memória de vídeo para começar do zero.

Para fazer isso, preciso de uma função como memset de C. (Também preciso de um memcpy analógico para copiar os dados gráficos.) A função memset define o fragmento especificado de memória para um determinado byte. Será fácil implementar isso sozinho, mas o tutorial do AssemblyDigest já possui essas funções, então eu as uso.

Nesse ponto, eu posso limpar o VRAM com o memset escrevendo $00 (embora o primeiro commit tenha usado o valor $FF , o que também era adequado) e depois carregar os gráficos do bloco no VRAM usando o memcpy . Mais especificamente, eu preciso copiá-lo para o endereço $9000 , porque esses são blocos usados ​​apenas para gráficos de plano de fundo. (Os endereços $8000-$87FF são usados ​​apenas para blocos de sprite e os endereços $8800-$8FFF são comuns para ambos os tipos.)

Configuração de mapa de lado a lado


O Game Boy tem uma camada de fundo, dividida em blocos de 8x8. A camada de segundo plano em si ocupa cerca de 32x32 blocos, ou seja, possui um tamanho total de 256x256. (Para comparação: a tela do console tem uma resolução de 160x144.) Eu precisava indicar manualmente os blocos que compõem minha imagem linha por linha. Felizmente, todos os blocos foram organizados em ordem, então eu apenas tive que preencher cada linha com valores de N*11 a N*11 + 10 , onde N é o número da linha e os 22 elementos restantes preenchendo $FF .

Ligar o LCD


Aqui não precisamos esperar pelo VBlank, porque a tela ainda não será ligada até o VBlank, então acabei de escrever no registro de controle do LCD novamente. Também incluí camadas de plano de fundo e sprite e também indiquei os endereços corretos do mapa e dos gráficos do bloco. Depois disso, obtive os seguintes resultados. Também liguei os manipuladores de interrupção novamente usando o ei opcode.

Nesse ponto, para torná-lo ainda mais interessante, escrevi um manipulador de interrupções muito simples para o VBlank. Adicionando o código de operação de transição a $40 , eu posso fazer com que o manipulador funcione de qualquer maneira. Nesse caso, escrevi uma função simples que rola a tela para cima e para baixo.

Aqui estão os resultados finais. [Além disso: acabei de perceber que o GIF não está em loop corretamente, ele deve transferir constantemente a imagem.]


Até agora, nada de surpreendente, mas ainda é legal que, teoricamente, eu possa pegar meu antigo Game Boy Color e ver como meu próprio código é executado nele.

Diversão com folhas quadriculadas


Para desenhar algo na tela, naturalmente preciso de algum tipo de sprite. Tendo estudado o PPU (Picture Processing Unit) do console Game Boy, decidi me concentrar nos sprites 8x8 ou 8x16. Provavelmente, precisarei da última opção, mas apenas para sentir o tamanho, rapidamente rabisquei em um papel quadriculado uma captura de tela do jogo em uma escala de 1: 8.


Eu queria deixar a parte superior da tela sob o HUD. Pareceu-me que pareceria mais natural do que a partir de baixo, porque quando estiver pronto, se os personagens precisarem bloquear temporariamente o HUD, como em Super Mario Bros, eles poderão fazê-lo. Este jogo não terá nenhuma plataforma complexa e, de fato, o design de níveis também, então não preciso mostrar uma visão muito geral do campo. A posição dos personagens na tela e, possivelmente, os obstáculos que aparecem de tempos em tempos serão suficientes. Portanto, eu posso pagar sprites muito grandes.

Portanto, se um quadrado tiver um bloco de 8x8, um sprite não será suficiente, independentemente do tamanho que eu escolher. Isso é especialmente verdade, já que quase não haverá movimento vertical no jogo, com exceção dos saltos. Então eu decidi criar sprites a partir de quatro sprites 8x16. A exceção foi a cauda da raposa, que ocupa dois sprites de 8x16. Após cálculos simples, ficou claro que duas raposas e duas lagartixas ocuparão 20 dos 40 sprites, ou seja, será possível adicionar muito mais sprites. (Sprites 8x8 esgotariam rapidamente o meu limite, o que eu não quero fazer nos estágios iniciais de desenvolvimento.)

Por enquanto, eu só preciso desenhar sprites. Abaixo estão esboços aproximados em papel quadriculado. Eu tenho um sprite de espera, um sprite de "pensamento" para escolher entre passar ou correr, como em um jogo SNES ... e é isso. Eu também planejava fazer sprites de personagens em execução, pulando e personagens que os oponentes pegam. Mas, para começar, desenhei apenas sprites de espera e pensamento, para não complicar. Eu ainda não fiz o resto, preciso fazer isso.


Sim, eu sei, não desenho muito bem. A perspectiva é uma coisa complicada. (Sim, e esta face da raposa polar é terrível.) Mas combina comigo perfeitamente. O design de personagens não possui nenhum recurso especial, mas é adequado para o game jam. É claro que usei lagartixas e raposas polares como referência. Isso é imperceptível?



Você não pode dizer. (Para constar: depois de ver essas fotos novamente, percebi que há uma enorme diferença entre lagartixas e lagartos. Não sei o que fazer com isso, exceto para me considerar estúpido ...) Acho que você pode imaginar que a fonte de inspiração para Blaze the Cat da série de jogos Sonic serviu como cabeça da raposa.

Inicialmente, eu queria que os zagueiros e atacantes de cada equipe fossem de sexos diferentes e fosse mais fácil distinguir entre eles. (Eu também deixaria os jogadores escolherem o gênero de seu personagem.) No entanto, isso exigiria muito mais desenho. Então decidi por lagartixas e raposas.

E, finalmente, desenhei uma tela inicial porque havia um lugar para ela em um pedaço de papel quadriculado.


Sim, poses de ação ainda estão longe do ideal. A raposa polar deve ficar mais chateada e correr, e a lagartixa deve parecer ameaçadora. Defender Fox em segundo plano - uma referência divertida à arte na caixa Doom.

Digitalização de sprites


Então comecei a transformar desenhos em papel em sprites. Para isso, usei o programa GraphicsGale, recentemente liberado. (Eu sei que você pode usar asesprite, mas eu prefiro GraphicsGale.) O trabalho em sprites acabou sendo muito mais complicado do que eu esperava. Cada um desses quadrados dos sprites mostrados acima ocupa até 4 pixels em uma grade 2x2. E esses quadrados geralmente tinham MUITO mais detalhes do que 4 pixels. Portanto, eu tive que me livrar de muitos detalhes dos esboços. Às vezes até era difícil aderir a uma forma simples, porque era necessário deixar um local aceitável para os olhos ou nariz. Mas parece-me que tudo parece bom, mesmo que o sprite tenha se tornado completamente diferente.


Os olhos da raposa perderam a forma amendoada e se transformaram em uma linha de dois pixels de altura. Os olhos da lagartixa mantiveram sua redondeza. A cabeça da lagartixa teve que ser aumentada, livrando-se dos ombros largos, e todas as curvas que a raposa podia ter foram significativamente suavizadas. Mas, honestamente, todas essas mudanças fáceis não são tão ruins. Às vezes, mal conseguia escolher qual das variações é melhor.


GraphicsGale também possui funções convenientes para camadas e animações. Isso significa que eu posso animar a cauda da raposa separadamente do corpo dela. Isso ajuda muito a economizar espaço precioso no VRAM, porque não preciso duplicar a cauda em todos os quadros. Além disso, isso significava que você poderia abanar o rabo com uma velocidade variável, diminuindo a velocidade quando o personagem estava em pé e acelerando enquanto corria. No entanto, isso torna a programação um pouco mais complicada. Mas ainda assim eu assumirei esta tarefa. Eu decidi por 4 quadros de animação, porque isso é suficiente.

Você pode notar que a raposa polar usa os três tons mais claros de cinza, enquanto a lagartixa usa os três tons mais escuros de cinza. No GameBoy, isso é aceitável, porque, embora possa haver apenas três cores em um sprite, o console permite especificar duas paletas. Fiz isso para que a paleta 0 seja usada para raposas e a paleta 1 para lagartixas. Esse conjunto de paletas disponíveis acabou, mas acho que não precisarei de outras.

Eu também precisava cuidar do fundo. Não me incomodei com os esboços dele, porque planejei que fosse uma cor sólida ou um simples padrão geométrico. Ainda não digitalizei o protetor de tela, porque não tinha tempo suficiente.

Carregando sprites no jogo


Verifique com a confirmação be99d97 .

Depois que cada quadro individual dos gráficos dos personagens foi salvo, foi possível começar a convertê-los para o formato GameBoy. Descobriu-se que no RGBDS existe um utilitário muito conveniente para isso chamado RGBGFX. Ele pode ser chamado com o comando rgbgfx -h -o output.bin input.png e criará um conjunto de blocos compatíveis com o GameBoy. (A opção -h especifica um modo de bloco compatível com 8x16, para que a conversão seja realizada de cima para baixo e não da esquerda para a direita.) No entanto, ele não fornece ligações e não pode rastrear blocos duplicados quando cada quadro é uma imagem separada. Mas deixaremos esse problema para mais tarde.

Após gerar os arquivos .bin de saída, basta adicioná-los no assembler usando o incbin "output.bin" . Para manter tudo junto, criei um arquivo comum “gfxinclude.z80”, que contém todos os gráficos a serem adicionados.

No entanto, era muito chato regenerar manualmente os gráficos sempre que algo mudava. Então editei o arquivo build.bat, adicionando a linha for %%f in (gfx/*.png) do rgbds\rgbgfx -h -o gfx/bin/%%f.bin gfx/%%f , que converte cada arquivo. png na pasta gfx / para o arquivo bin e salva-o em gfx / bin. Isso simplificou muito minha vida.

Para criar gráficos de fundo, usei uma maneira muito mais preguiçosa. O RGBASM possui uma diretiva dw ` . É seguido por uma linha de 8 valores de 0 a 4 igual a uma linha de dados de pixel. Como os sprites de fundo eram muito simples, tornou-se mais fácil copiar e colar um padrão geométrico simples para criar um padrão sólido, listrado ou quadriculado. Aqui, por exemplo, se parece com um terreno.

 bg_dirt: dw `00110011 dw `00000000 dw `01100110 dw `00000000 dw `11001100 dw `00000000 dw `10011001 dw `00000000 

Ele cria uma série de listras deslocadas com a ilusão de perspectiva. Essa é uma abordagem simples, mas inteligente. Com a grama era um pouco mais complicado. Inicialmente, era um grupo de linhas horizontais com 2 pixels de altura, mas adicionei manualmente alguns pixels que adicionam um pouco de ruído que faz a grama parecer melhor:

 bg_grass: dw `12121112 dw `12121212 dw `22112211 dw `11121212 dw `22112211 dw `21212121 dw `12121212 dw `12211222 

Renderização gráfica


No GameBoy, os sprites são armazenados em uma área chamada OAM, ou Object Attribute Memory. Ele contém apenas atributos (direção, paleta e prioridade), além do número do bloco.Foi o suficiente para eu preencher essa área de memória para exibir sprites na tela.

Embora haja pequenos recursos. Primeiro, você precisa carregar os gráficos da ROM na VRAM. O GameBoy pode renderizar apenas blocos armazenados em uma área especial da memória chamada VRAM. Felizmente, para copiar da ROM para o VRAM, basta executá-lo memcpyno estágio de inicialização do programa. Aconteceu que, com apenas 6 sprites de caracteres e 4 peças de cauda, ​​eu já ocupei um quarto da área de VRAM alocada para sprites. (VRAM geralmente é dividido em áreas de segundo plano e sprites, e 128 bytes são comuns a eles.)

Além disso, o acesso ao OAM só é possível durante o VBlank. Comecei dizendo que o VBlank estava aguardando cálculos de sprite, mas tive problemas porque os cálculos de sprite duravam todo o tempo alocado pelo VBlank e era impossível finalizá-los. A solução aqui é gravar em uma área de memória separada fora do VBlank e simplesmente copiá-los para o OAM durante o VBlank.

Como se viu, o GameBoy possui um procedimento especial de cópia de hardware, um tipo de DMA (acesso direto à memória, acesso direto à memória) que faz exatamente isso. Escrevendo para um registro específico e indo para o ciclo ocupado no HiRAM (porque a ROM não está disponível durante o DMA), você pode copiar dados da RAM para o OAM muito mais rapidamente do que usar a funçãomemcpy. Se estiver interessado, detalhes interessantes podem ser encontrados aqui .

Nesse estágio, eu só conseguia criar um procedimento que determina o que será gravado no DMA. Para fazer isso, eu precisava armazenar o estado dos objetos em outro lugar. No mínimo, era necessário o seguinte:

  1. Tipo (lagartixa, raposa polar ou item de transporte de uma das equipes)
  2. Direção
  3. Posição X
  4. Posição Y
  5. Quadro de animação
  6. Temporizador de animação

Na primeira decisão muito desajeitada, verifiquei o tipo do objeto e, dependendo dele, mudei para um procedimento que desenha spriticamente esse tipo de objeto. O procedimento raposa polar, por exemplo, assumiu uma posição em X, dependendo da direção, somada ou subtraída 16, adicionado dois sprites de cauda e depois movido para cima e para baixo no sprite principal.

Aqui está uma captura de tela de como o sprite ficou em VRAM ao renderizar na tela. A parte esquerda é composta por sprites individuais, números hexadecimais próximos a eles, de cima para baixo - posição vertical e horizontal, sinalizadores de blocos e atributos. À direita, você pode ver como tudo parecia depois da montagem.


Com a animação da cauda, ​​foi um pouco mais complicado. Na primeira solução, simplesmente incrementei o timer de animação em cada quadro e realizei um incremento lógico andcom um valor %11para obter o número do quadro. Em seguida, você pode simplesmente adicionar 4 * o número do quadro (cada quadro de animação consiste em 4 blocos) ao primeiro bloco final no VRAM para obter 4 quadros diferentes armazenados no VRAM. Funcionou (especialmente aquele com a busca do ladrilho da cauda), mas o rabo balançou incrivelmente rápido, e eu precisava encontrar uma maneira de desacelerá-lo.

Na segunda solução melhor, realizei um incremento do cronômetro global em cada quadro e quando o valor da operação andcom elee o grau de dois escolhidos por mim foi 0, o incremento do cronômetro do objeto foi realizado. Assim, cada objeto individual pode contar seu cronômetro de animação a qualquer velocidade necessária. Isso funcionou perfeitamente e me permitiu diminuir a cauda para um nível razoável.

Dificuldades


Mas se tudo fosse tão simples. Não esqueça que eu gerenciei tudo isso no código, usando meu próprio subprocedimento para cada objeto e, se você precisar continuar, precisará fazer isso em todos os quadros. Eu tive que especificar como proceder para o próximo sprite, bem como em qual bloco ele consiste, manipulando manualmente os registros.

Era um sistema completamente instável. Para desenhar um quadro, era necessário manipular um número suficientemente grande de registros e tempo de CPU. Era quase impossível adicionar suporte a outras pessoas e, mesmo que eu conseguisse, o suporte ao sistema seria muito doloroso. Acredite, foi um verdadeiro caos.Eu precisava de um sistema no qual o código para renderizar sprites fosse generalizado e direto, para que não fosse um entrelaçamento de condições, manipulação de registradores e operadores matemáticos.

Como eu consertei isso? Vou falar sobre isso na próxima parte do artigo.

imagem

Source: https://habr.com/ru/post/pt436330/


All Articles