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.
- Salve
minijogo.scompleto ao lado dos arquivos de apoio. Execute os quatro comandos de construção abaixo e abraminijogo.gba. - Mapeie o direcional e Start no mGBA. Leve o quadrado branco até o alvo verde próximo ao canto inferior direito.
- Confira três resultados juntos: a barra inferior fica verde, o movimento para e Start inicia outra tentativa.
- Leia as explicações de estado e colisão e repita a partida. Preveja qual valor
game_stateguarda 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:
| Valor | Significado escolhido pelo programa | Comportamento |
|---|---|---|
| 0 | Jogando | O direcional atualiza a posição |
| 1 | Venceu | A 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 -->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:
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 -->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
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.

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.