ALAN JAHNEL / Engenharia de software

Artigos

GBA em Assembly — 8. Seu primeiro minijogo

Leve um quadrado até o alvo e crie uma condição de vitória. Entenda colisão entre áreas, estado da partida e reinício pelo botão Start.

Um objeto que se move já é uma experiência interativa. Para transformá-lo em um jogo pequeno, vamos acrescentar um objetivo: leve o quadrado branco até o quadrado verde. Ao encostar no alvo, a barra inferior fica verde e o movimento para. Start começa outra tentativa.

Esse minijogo reúne os conhecimentos anteriores sem introduzir inimigos, dinheiro ou torres. Use minijogo.s, com os arquivos de apoio do pacote da série. O movimento vem do capítulo anterior.

Roteiro deste capítulo

Antes de começar: o quadrado do capítulo 7 deve andar, parar nas bordas e não deixar rastro. Guarde essa versão funcionando.

  1. Salve minijogo.s completo ao lado dos arquivos de apoio. Execute os quatro comandos de construção abaixo e abra minijogo.gba.
  2. Mapeie o direcional e Start no mGBA. Leve o quadrado branco até o alvo verde próximo ao canto inferior direito.
  3. Confira três resultados juntos: a barra inferior fica verde, o movimento para e Start inicia outra tentativa.
  4. Leia as explicações de estado e colisão e repita a partida. Preveja qual valor game_state guarda durante o movimento e depois da vitória.

Ponto de conferência: você consegue vencer, reiniciar e voltar a andar. Se, depois de uma edição, o alvo parecer correto mas a vitória ocorrer em outro lugar, compare as coordenadas de desenho com os limites da colisão. Acrescentar um contador de tempo ou derrota depois é opcional; o jogo fornecido com dois estados completa este capítulo.

O hardware não possui uma condição de vitória

Vamos criar uma variável game_state na RAM, com dois valores possíveis:

ValorSignificado escolhido pelo programaComportamento
0JogandoO direcional atualiza a posição
1VenceuA posição fica parada até reiniciar

Esses números não configuram uma peça do GBA. São uma convenção das nossas regras. A CPU executa comparações e desvios com eles, e nossa rotina visual escolhe a cor da barra.

Essa é a diferença entre um registrador de hardware, como DISPCNT, e um estado de jogo, como game_state. Ambos podem ser acessados por instruções de memória, mas apenas o primeiro tem um significado definido pelo circuito.

Inicializar e reiniciar são ações do programa

Consulte PUSH e POP para preservar lr e BL e BX para entender chamadas e retornos aninhados.

A rotina new_game apaga o quadrado na posição antiga, coloca o jogador em (16,16) e volta o estado a zero. Ela é chamada durante a inicialização e quando detectamos uma nova pressão de Start. A limpeza da tela inteira acontece somente no boot; para reiniciar, basta restaurar a pequena área ocupada pelo jogador.

Usamos pressed, não keys, para o reinício. Assim, segurar Start não chama a rotina novamente em todos os quadros. As variáveis dos controles são preservadas no reinício; zerar a leitura anterior enquanto o botão continua pressionado criaria uma nova transição artificial.

O arquivo inclui estas duas rotinas de regra:

<!-- source: minijogo.s#state -->
Assembly ARM
new_game:
    push {r4, lr}
    mov r4, #0
    bl paint_hero
    ldr r0, =hero_x
    mov r1, #16
    str r1, [r0]
    ldr r0, =hero_y
    str r1, [r0]
    ldr r0, =game_state
    mov r1, #0
    str r1, [r0]
    pop {r4, lr}
    bx lr

check_goal:
    ldr r0, =hero_x
    ldr r0, [r0]
    cmp r0, #208
    bxge lr
    add r0, r0, #8
    cmp r0, #200
    bxle lr
    ldr r0, =hero_y
    ldr r0, [r0]
    cmp r0, #128
    bxge lr
    add r0, r0, #8
    cmp r0, #120
    bxle lr
    ldr r0, =game_state
    mov r1, #1
    str r1, [r0]
    bx lr

new_game chama outra rotina com bl, então precisa preservar seu próprio endereço de retorno. push {r4, lr} guarda esse endereço e a cor anterior; o pop restaura ambos. Sem esse cuidado, o retorno poderia voltar para o ponto errado.

Detectar contato entre duas áreas

Cada retorno antecipado depende de CMP e TST e da tabela de condições GE e LE.

O jogador e o alvo têm oito por oito pixels. O alvo começa em (200,120). Portanto, ocupa X de 200 a 207 e Y de 120 a 127.

Para existir sobreposição, quatro condições precisam ser verdadeiras:

Texto simples
X do jogador < 208
X do jogador + 8 > 200
Y do jogador < 128
Y do jogador + 8 > 120

check_goal retorna cedo se qualquer condição falhar. Se todas passarem, grava 1 em game_state.

Encostar apenas as bordas, sem compartilhar nenhum pixel, ainda não conta neste exemplo. Se o jogador estiver em X igual a 192, seu último pixel estará em 199; ele ainda não ocupa nenhum pixel do alvo. Em X igual a 193, já existe sobreposição horizontal.

Escolhemos comparar retângulos porque é simples e verificável. Não estamos perguntando ao vídeo se duas imagens encostaram: usamos coordenadas numéricas da RAM. No Mode 3, esses quadrados nem são sprites de hardware.

Fazer as regras controlarem a atualização

Para ler blne, bne e ldreq, combine as condições com a operação base correspondente.

O laço do minijogo é:

<!-- source: minijogo.s#game-loop -->
Assembly ARM
main_loop:
    bl wait_vblank
    bl tick
    bl read_keys
    ldr r0, =pressed
    ldr r1, [r0]
    tst r1, #8
    blne new_game
    mov r4, #0
    bl paint_hero
    ldr r0, =game_state
    ldr r1, [r0]
    cmp r1, #0
    bne render
    bl update_position
    bl check_goal
render:
    mov r0, #200
    mov r1, #120
    mov r2, #8
    mov r3, #8
    ldr r4, =0x03e0
    bl fill_rect
    ldr r4, =0x7fff
    bl paint_hero
    mov r0, #8
    mov r1, #156
    mov r2, #224
    mov r3, #4
    ldr r4, =0x318c
    ldr r5, =game_state
    ldr r5, [r5]
    cmp r5, #1
    ldreq r4, =0x03e0
    bl fill_rect
    b main_loop

A entrada continua sendo lida mesmo depois da vitória, pois precisamos aceitar Start. O movimento e a verificação do alvo só são executados se game_state for zero. O desenho acontece em ambos os estados.

A sequência tem consequências. Apagamos o jogador na posição antiga, atualizamos sua posição e verificamos o objetivo. Depois redesenhamos o alvo e o jogador, e escolhemos a cor da barra conforme o estado. O alvo é restaurado a cada quadro para que um apagamento não destrua parte dele.

Podemos explicar o programa sem mencionar um endereço de hardware: “se ainda está jogando, mova; se tocou no alvo, vença”. Essas regras usam rotinas que já sabemos conectar aos botões e ao vídeo. Essa separação será valiosa ao ler o Tower Defense.

Executar uma partida inteira

Shell
arm-none-eabi-as -mcpu=arm7tdmi -g -o minijogo.o minijogo.s
arm-none-eabi-ld -T gba.ld -o minijogo.elf minijogo.o
arm-none-eabi-objcopy -O binary minijogo.elf minijogo.gba
python3 rom.py minijogo.gba

Abra minijogo.gba. Mova o quadrado para a região inferior direita até sobrepor o alvo verde. A barra inferior deve mudar de cinza para verde. Depois da vitória, o direcional não deve deslocar o quadrado. Aperte Start: ele deve voltar ao início, com a barra cinza.

Vitória no minijogo executado no mGBA: quadrado branco sobrepõe o alvo e a barra inferior fica verde

Experimente segurar Start e uma direção ao mesmo tempo depois do primeiro reinício. A posição deve continuar mudando; não deve voltar a (16,16) a cada quadro. Esse teste revela por que preservamos a diferença entre um botão segurado e um novo aperto.

Uma mudança de regra por vez

O primeiro exercício é mover o alvo para (120,80). Atualize tanto os números usados no desenho quanto os quatro limites usados em check_goal. Se alterar apenas um lado, a vitória acontecerá em um lugar diferente da imagem.

Essa duplicação sugere uma melhoria: definir constantes para X, Y e tamanho do alvo e calcular os limites a partir delas. É um pequeno passo de organização que já tem uma razão concreta: evitar que desenho e regra discordem.

Depois, acrescente um estado 2 para derrota e um contador regressivo em RAM. Diminua o contador uma vez por atualização somente durante a partida. Ao chegar a zero, mude o estado e mostre a barra vermelha. Teste separadamente ganhar antes do prazo, perder pelo tempo e reiniciar após cada resultado.

Não é necessário resolver esses exercícios para seguir. A versão fornecida tem apenas os estados jogando e venceu. No próximo capítulo, vamos revisar como investigar falhas e ligar esses fundamentos ao artigo principal.

Anterior: mover um quadrado. Próximo: depuração e a passagem para o Tower Defense. Índice da série.

Voltar ao início