Curso de Jogos em Basic
Tratamento de eventos


  Uma vez criado o cenário e os personagens, temos que dar vida ao jogo! Para isso, precisamos movimentar os personagens pela tela, assim como tratar os eventos do jogo.

  Os principais eventos a serem considerados são:   Iremos começar nesse capítulo pelo tratamento de eventos, sem considerar ainda a animação dos personagens.
4.1. Delimitação do espaço de navegação   Dado que os cenários são sempre constituídos de desenhos feitos a partir de formas geométricas, desenho à mão ou combinação de tiles, não há um mecanismo pronto no MSX de detecção de colisão com as partes do cenário, como existe para a colisão entre sprites.
  Então, para detectar a colisão entre objetos e o cenário, os autores geralmente utilizam o seguintes recursos:   Vamos começar pelos tiles.

  O mecanismo de tiles não permite a detecção através do comando POINT, visto este comando se baseia na tabela de padrão/cores e não na tabela de nomes. Assim, a detecção é baseada na identificação do tile contido no espaço futuro. Por exemplo, se o código do tile for diferente do tile utilizado para o caminho livre, bloqueia a passagem. Caso contrário, permite o movimento.
  O exemplo a seguir cria um labirinto com base nos objetos do jogo Alcatraz e permite que o presidiário se movimente somente nas áreas pretas.
10 COLOR 15,0,0:SCREEN 2:X=2:Y=1
20 GOSUB 500: GOSUB 600
30 C=STICK(0)
40 VPOKE 6144+X+(Y*32),2:XX=X:YY=Y
50 IF C=1 THEN YY=Y-1:GOSUB3000:IF CF=0 THEN Y=Y-1
60 IF C=3 THEN XX=X+1:GOSUB3000:IF CF=0 THEN X=X+1
70 IF C=5 THEN YY=Y+1:GOSUB3000:IF CF=0 THEN Y=Y+1
80 IF C=7 THEN XX=X-1:GOSUB3000:IF CF=0 THEN X=X-1
90 GOTO 30
500 ' Cria tiles
510 FOR I=1 TO 2 : FOR C=0 TO 7
520 READ A$ : VPOKE I*8+C,VAL("&H"+A$)
530 NEXT C,I
540 FOR I=1 TO 2 : FOR C=0 TO 7
550 READ A$ : VPOKE 8192+I*8+C,VAL("&H"+A$)
560 NEXT C,I
570 RETURN
600 ' Desenha a tela
610 E=6144
620 FOR L=1 TO 8
630 READ LN$
640 FOR C=1 TO 32
650 VPOKE E,VAL(MID$(LN$,C,1))
660 E=E+1:NEXT C,L
670 RETURN
1000 ' Tiles - Padrão
1010 DATA 01,01,01,FF,10,10,10,FF : ' Tijolo=1
1020 DATA 18,18,3C,5A,99,18,24,24 : ' Presidiário=2
1100 ' Tiles - Cores
1110 DATA E6,E6,E6,E6,E6,E6,E6,E6 : ' Tijolo=1
1120 DATA F0,60,10,F0,10,F0,10,F0 : ' Presidiário=2
2000 ' Mapa
2010 DATA 01111110000000000000000000000000
2020 DATA 01000010000000000000000000000000
2030 DATA 01011110000000000000000000000000
2040 DATA 01010110000000000000000000000000
2050 DATA 01010110000000000000000000000000
2060 DATA 01000010000000000000000000000000
2070 DATA 01111110000000000000000000000000
2080 DATA 00000000000000000000000000000000
3000 ' Verifica posição futura e apaga posição corrente
3010 CF=VPEEK(6144+XX+(YY*32))
3020 IF CF=0 THEN VPOKE 6144+X+(Y*32),0
3030 RETURN

  A linhas 30-90 contêm um loop que fica a espera do usuário pressionar uma tecla do cursor.
30 C=STICK(0)
40 VPOKE 6144+X+(Y*32),2:XX=X:YY=Y
50 IF C=1 THEN YY=Y-1:GOSUB3000:IF CF=0 THEN Y=Y-1
60 IF C=3 THEN XX=X+1:GOSUB3000:IF CF=0 THEN X=X+1
70 IF C=5 THEN YY=Y+1:GOSUB3000:IF CF=0 THEN Y=Y+1
80 IF C=7 THEN XX=X-1:GOSUB3000:IF CF=0 THEN X=X-1
90 GOTO 30
  Ao pressionar um tecla, uma rotina de verificação da posição futura é chamada em 3000. Caso o caractere da posição futura possua código ASCII igual a 0, essa rotina apaga o tile do presidiário na posição atual e o movimento é efetivado.

  Agora, os outros tipos de desenhos como os feitos com o DRAW, LINE, CIRCLE etc.

  A detecção dos limites da tela é feita com base na cor do pixel da posição futura. Entretanto, devemos considerar se o objeto se move caractere a caractere (8 pixels) ou suavemente (1 pixel). Nesse caso, há duas estratégias:
  O teste feito para movimentação 8x8 pode ser realizado a partir de um ponto apenas. Porém, preste atenção para que o ponto a ser testado nunca tenha a mesma cor da área de trânsito do personagem, ou poderemos ter o efeito de "fantasma" atravessando paredes.
  O exemplo a seguir desenha um sprite na tela e o cerca. Você pode mover a bolinha, mas não consegue sair do quadrado.
10 COLOR15,0,0:SCREEN 2
20 GOSUB 500 : GOSUB 800
30 X=128:Y=96
40 C=STICK(0):XX=X:YY=Y
50 PUT SPRITE 0,(X,Y-1),15,0
60 IF C=1 THEN YY=Y-8:GOSUB 900:IF CF=0 THEN Y=YY
70 IF C=3 THEN XX=X+8:GOSUB 900:IF CF=0 THEN X=XX
80 IF C=5 THEN YY=Y+8:GOSUB 900:IF CF=0 THEN Y=YY
90 IF C=7 THEN XX=X-8:GOSUB 900:IF CF=0 THEN X=XX
100 GOTO 40
500 ' Desenha o sprite
510 FOR I=1 TO 8
520 READ A$
530 S$=S$+CHR$(VAL("&B"+A$))
540 NEXT I
550 SPRITE$(0)=S$
560 RETURN
600 DATA 00111100
610 DATA 01111110
620 DATA 11111111
630 DATA 11111111
640 DATA 11111111
650 DATA 11111111
660 DATA 01111110
670 DATA 00111100
800 ' Desenha cenário
810 LINE(88,56)-(167,135),12,BF
820 LINE(96,64)-(159,127),0,BF
830 RETURN
900 ' Verifica colisão
910 CF=POINT(XX,YY)
920 RETURN
  Obs: o sprite deve ser desenhado uma posição acima (Y-1) para corrigir um bug dos sprites do MSX e ser desenhado na posição correta da tela.

  Acrescente a linha a seguir, de modo a abrir um buraco na cerca da bolinha. Tente sair da cerca pelo buraco.
825 LINE(96,56)-(103,63),0,BF
  Nos tiles vistos anteriormente, se abrirmos um buraco no labirinto do presidiário, ele poderá escapar. Tente modificar o mapa e comprovar isso.

  Ao alterarmos as linhas 60-90, de forma a ajustar o passo para 1 em vez de 8, temos o efeito da bolinha atravessar a parede quando movemos para a direita ou para baixo.
60 IF C=1 THEN YY=Y-1:GOSUB 900:IF CF=0 THEN Y=YY
70 IF C=3 THEN XX=X+1:GOSUB 900:IF CF=0 THEN X=XX
80 IF C=5 THEN YY=Y+1:GOSUB 900:IF CF=0 THEN Y=YY
90 IF C=7 THEN XX=X-1:GOSUB 900:IF CF=0 THEN X=XX
  Isso acontece porque o pixel de teste está localizado no canto superior esquerdo da bolinha. Assim, somente quando esse ponto atinge a área verde, a bola pára.

  Observe a ilustração a seguir. Ela representa nosso sprite ao encontrar com a parede (área verde). O ponto vermelho representa o ponto de teste da cor de fundo.
  Ao realizarmos a movimentação 8x8 em direção a esta parede, o ponto vermelho cai sobre a área verde e a detecção de colisão funciona.
  Porém, quando realizarmos a movimentação 1x1, ao adentramos pela parede, o pixel de teste ainda está na área preta.

  A solução para esse problema é criar um "bounding box" ou retângulo envoltório em torno do sprite. Na ilustração a seguir, o bounding box é representado pelo retângulo azul, delimitado pelos pixels vermelhos.
  Assim, a partir de dois pontos que delimitam um retângulo em torno do objeto, podemos realizar a movimentação 1x1 e detectar corretamente os limites do cenário.

  Altere a linha 910 por essa.
910 CF=POINT(XX,YY) OR POINT(XX+7,YY+7)
  Pronto! Agora a bolinha não atravessa a parede.

  Apesar desses dois pontos garantirem a movimentação correta em labirintos, o mesmo não se pode dizer para áreas livres, como no jogo Guie o Balão. No jogo original publicado na revista MSX Micro #4, o autor colocou apenas esses dois pontos de verificação. Entretanto, áreas do balão ficam descobertas (diagonais esquerda-baixa e direita-superior), fazendo com que essas partes do balão quando toquem o cenário, nada aconteça.

  Observe as ilustrações abaixo.
A detecção funciona A detecção falha

  A primeira bolinha quando se move para a direita, o ponto vermelho detecta a área verde. Porém, a segunda bolinha quando movimentada para a esquerda, a detecção falha.

  A solução para o jogo Guie o Balão foi adicionar mais dois pontos de teste nas outras diagonais do retângulo envoltório.

  Quando o cenário for ao "ar livre", devemos criar as zonas de atuação do personagem. Para isso, criamos um retângulo imaginário na tela que irá limitar os movimentos dos personagens.
  A maneira de criar essa limitação é verificando se o movimento ultrapassou os limites desse retângulo. Veja o exemplo a seguir.
10 SCREEN 2:X=128:Y=95	
20 FOR I=1 TO 8
30 READ A$
40 S$=S$+CHR$(VAL("&H"+A$))
50 NEXT I
60 SPRITE$(0)=S$
70 C=STICK(0)
80 PUT SPRITE 0,(X,Y),15,0
90 IF C=1 THEN Y=Y-1 : IF Y<45 THEN Y=45
100 IF C=3 THEN X=X+1 : IF X>180 THEN X=180
110 IF C=5 THEN Y=Y+1 : IF Y>135 THEN Y=135
120 IF C=7 THEN X=X-1 : IF X<80 THEN X=80
130 GOTO 70
200 DATA 3C,7E,FF,FF,FF,FF,7E,3C
  Mova a bola com os cursores. Ela não passa do retângulo definido em (80,45)-(180,135).
4.2. Colisão entre personagens e objetos   A colisão entre personagens e outros personagens/objetos pode ser feita de duas maneiras. A primeira é quando ambos são sprites, utilizamos o recurso ON SPRITE GOSUB do Basic, que detecta automaticamente a colisão entre dois sprites. A outra é quando um deles é um tile. Nesse caso, utilizaremos a detecção manual, inclusive utilizar mesmas técnicas da seção anterior.
4.2.1. Colisão entre sprites   Vamos ver o caso de dois sprites colidindo. O exemplo a seguir desenha duas bolinhas na tela. Você controla a bola branca e deve movimentá-la até alcançar a bolinha rosa.
10 SCREEN 2
20 OPEN"GRP:" AS #1
30 X=40:Y=95
40 FOR I=1 TO 8
50 READ A$
60 S$=S$+CHR$(VAL("&H"+A$))
70 NEXT I
80 SPRITE$(0)=S$
90 PUT SPRITE 1,(128,95),13,0
100 ON SPRITE GOSUB 500:SPRITE ON
110 C=STICK(0)
120 PUT SPRITE 0,(X,Y),15,0
130 IF C=1 THEN Y=Y-1
140 IF C=3 THEN X=X+1
150 IF C=5 THEN Y=Y+1
160 IF C=7 THEN X=X-1
170 GOTO 110
200 DATA 3C,7E,FF,FF,FF,FF,7E,3C
500 ' Trata evento de colisão de sprite
510 PRESET(10,170),0:PRINT#1,"Voce me pegou!"
520 GOTO 520
  A declaração ON SPRITE GOSUB 500 mais a ativação SPRITE ON, ambos na linha 100, garantem a utilização do recurso de colisão de sprites. Assim, quando uma bolinha toca na outra, a execução do programa desvia para a linha 500 e executa um sub-rotina de tratamento de colisão, escrevendo a mensagem na tela "Você me pegou!".

  Apesar de ser uma "mão na roda" para detectar colisões, a função ON SPRITE GOSUB não indica quais são os sprites que colidiram. Assim, se tivermos mais de dois sprites na tela, não sabemos qual par de sprites se tocou ou até mesmo se houve mais de uma colisão ao mesmo tempo. Entretanto, um pequeno teste pode ser feito na rotina de tratamento de colisões, de forma a verificar isso. Veremos no exemplo a seguir.
10 SCREEN 2
20 OPEN"GRP:" AS #1
30 X=40:Y=95:IA=0:IB=250:AD=1:BD=-1
40 FOR I=1 TO 8
50 READ A$
60 S$=S$+CHR$(VAL("&H"+A$))
70 NEXT I
80 SPRITE$(0)=S$
90 ON SPRITE GOSUB 500:SPRITE ON
100 PUT SPRITE 1,(IA,50),12,0
110 PUT SPRITE 2,(IB,50),4,0
120 C=STICK(0)
130 PUT SPRITE 0,(X,Y),15,0
140 IF C=1 THEN Y=Y-1
150 IF C=3 THEN X=X+1
160 IF C=5 THEN Y=Y+1
170 IF C=7 THEN X=X-1
180 IA=IA+AD:IB=IB+BD
190 IF IA<1 OR IA>250 THEN AD=-AD
200 IF IB<1 OR IB>250 THEN BD=-BD
210 GOTO 100
300 DATA 3C,7E,FF,FF,FF,FF,7E,3C
500 ' Trata evento de colisão de sprite
510 IF ABS(IA-IB)<10 THEN RETURN
520 PRESET(10,170),0:PRINT#1,"Voce me pegou!"
530 GOTO 530
  Há duas bolinhas passeando pela mesma linha, uma verde e outra roxa. Quando a colisão entre elas é detectada, o programa desvia para a linha 500. Então, um teste é feito para calcular o módulo da distância entre elas na linha 510. Se elas estiverem próximas, nada acontece. Entretanto, caso a bolinha branca, movida pelos cursores, tocar uma delas, o evento da linha 520 é disparado.

  Outro ponto importante é que na colisão da bolinha verde com a roxa, se desejarmos fazer com que elas colidam e voltem, devemos tirar uma bolinha de cima da outra. Isso porque, se apenas alterarmos o sentido de movimento, ao sair do tratamento e voltar ao loop, elas ainda estarão sobrepostas e dispararão novamente o tratamento da colisão.
  Uma das formas de se resolver isso é calcular a distância entre elas (como já é feito) e afastá-las a uma distância superior a 8 pixels (tamanho do sprite).

  Modifique as linhas do programa anterior para estas.
90 ON SPRITE GOSUB 500
...
115 SPRITE ON
...
510 SPRITE OFF
520 D=ABS(IA-IB)
530 IF D<10 THEN AD=-AD:BD=-BD:IA=IA+SGN(AD)*D/2:IB=IB+SGN(BD)*D/2:RETURN
540 PRESET(10,170),0:PRINT#1,"Voce me pegou!"
550 GOTO 550
  Na linha 510, desabilitamos a detecção de colisão de sprites temporariamente. Assim, alteramos as coordenadas das bolinhas e devolvemos o programa para o loop. Outra modificação foi colocar a habilitação de detecção depois de desenhar os sprites, na linha 115.

  Observamos nos exemplos anteriores que o teste extra para as bolinhas atrasa a animação. Para evitar esse tipo de teste, podemos delimitar a área de atuação dos inimigos, ou até mesmo testar a colisão entre eles sem a utilização do recurso ON SPRITE GOSUB.
4.2.2. Colisão entre objetos quaisquer   A maneira mais simples de se testar a colisão entre objetos de qualquer natureza no MSX é calcular a superposição com base nas coordenadas, principalmente para movimentos do tipo 8x8 (de oito em oito pixels).

  Suponha as variáveis X e Y para designar as coordenadas do personagem, e IX e IY para as coordenadas do inimigo. A movimentação é do tipo 8x8. A detecção de colisão pode ser feita através do seguinte teste:
IF X=IX AND Y=IY THEN PRINT"Me pegou!"
  Esse tipo de teste é feito no jogo Pucky. Na linha 650 do jogo, um teste é feito ao final para detectar a colisão entre o Pucky (X,Y) e o inimigo (BX,BY). Caso haja a colisão, desvia para a linha 940, que é a rotina de fim de jogo.
650 (...) IF X=BX AND Y=BY THEN 940

  No caso de tiles, podemos realizar os mesmos testes da seção 4.1, ou seja, verificar se o local onde passa o personagem possui um determinado código ASCII.
4.2.3. O perigo do "salto longo"   Todos os mecanismos até então utilizados por nós aqui presumem que, em caso de rota de colisão, os objetos irão em um determinado momento se sobrepor. Porém, há o risco do movimento combinado entre os objetos fazer com que eles saltem a uma distância suficiente para evitar a colisão.

  No primeiro programa da seção 4.2.1, troque nas linhas 130-160 o passo de movimento de 1 para 16.
130 IF C=1 THEN Y=Y-16
140 IF C=3 THEN X=X+16
150 IF C=5 THEN Y=Y+16
160 IF C=7 THEN X=X-16
  A detecção de colisão está ligada. Tente pegar a bolinha rosa.
  O que houve? O salto da bolinha branca é tão grande, que não há sobreposição entre elas.

  Uma situação que um salto combinado entre dois objetos pode ser de 16 pixels em que falha a detecção de colisão, é quando o computador e o jogador estão lado a lado em movimentos de 8x8. Ao se moverem simultaneamente, ambos passam a trocar de posições e a detecção de colisão falha.
Instante 1 Instante 2

  Entretanto, a probabilidade de ambos se moverem juntos e cair nesse caso é muito pequena. Ao testar o jogo Lock-in Man, que não toma cuidado com esse problema, constatamos isso.
4.3. Efeitos sonoros   Vista toda a parte de detecção de colisão, esse evento torna-se bastante simples de implementar. Para isso, quando um determinado evento é detectado, podemos desviar o programa para uma rotina que gere som.

  Na primeira listagem da movimentação 8x8 do sprite da seção 4.1, adicione a seguinte linha:
915 IF CF<>0 THEN PLAY"L8O2C" 
  Toda vez que a bolinha tocar na parede, emite um efeito sonoro.

  Agora, vamos juntar tudo que aprendemos e fazer o labirinto da seção 4.1 com uma chave e uma porta.
10 COLOR 15,0,0:SCREEN 2:X=2:Y=1:CH=0
20 GOSUB 500: GOSUB 600
30 C=STICK(0)
40 VPOKE 6144+X+(Y*32),2:XX=X:YY=Y
50 IF C=1 THEN YY=Y-1:GOSUB3000:IF MV=1 THEN Y=Y-1
60 IF C=3 THEN XX=X+1:GOSUB3000:IF MV=1 THEN X=X+1
70 IF C=5 THEN YY=Y+1:GOSUB3000:IF MV=1 THEN Y=Y+1
80 IF C=7 THEN XX=X-1:GOSUB3000:IF MV=1 THEN X=X-1
90 GOTO 30
500 ' Cria tiles
510 FOR I=1 TO 4 : FOR C=0 TO 7
520 READ A$ : VPOKE I*8+C,VAL("&H"+A$)
530 NEXT C,I
540 FOR I=1 TO 4 : FOR C=0 TO 7
550 READ A$ : VPOKE 8192+I*8+C,VAL("&H"+A$)
560 NEXT C,I
570 RETURN
600 ' Desenha a tela
610 E=6144
620 FOR L=1 TO 8
630 READ LN$
640 FOR C=1 TO 32
650 VPOKE E,VAL(MID$(LN$,C,1))
660 E=E+1:NEXT C,L
670 RETURN
1000 ' Tiles - Padrão
1010 DATA 01,01,01,FF,10,10,10,FF : ' Tijolo=1
1020 DATA 18,18,3C,5A,99,18,24,24 : ' Presidiário=2
1030 DATA 18,24,3C,18,10,18,10,18 : ' Chave=3
1040 DATA 18,18,18,18,18,18,18,18 : ' Porta=4
1100 ' Tiles - Cores
1110 DATA E6,E6,E6,E6,E6,E6,E6,E6 : ' Tijolo=1
1120 DATA F0,60,10,F0,10,F0,10,F0 : ' Presidiário=2
1130 DATA A0,A0,A0,A0,A0,A0,A0,A0 : ' Chave=3
1140 DATA 10,40,10,40,10,40,10,40 : ' Porta=4
2000 ' Mapa
2010 DATA 01111110000000000000000000000000
2020 DATA 01000040000000000000000000000000
2030 DATA 01011110000000000000000000000000
2040 DATA 01013110000000000000000000000000
2050 DATA 01010110000000000000000000000000
2060 DATA 01000010000000000000000000000000
2070 DATA 01111110000000000000000000000000
2080 DATA 00000000000000000000000000000000
3000 ' Verifica posição futura, apaga posição corrente e verifica ações
3010 CF=VPEEK(6144+XX+(YY*32)) : MV=0
3020 IF CF=4 AND CH=1 THEN CF=0
3030 IF CF=3 THEN CH=1 : CF=0 : PLAY"L8O6A"
3040 IF CF=0 THEN VPOKE 6144+X+(Y*32),0 : MV=1
3050 RETURN
  Saída:   O presidiário consegue abrir a porta e sair somente quando pega a chave. Quando a chave é pega, um som é emitido.

  Explicação dos tratamentos de eventos:

  A variável CH controla se o presidiário está com a chave (CH=1) ou não (CH=0).
  Na linha 3020, ao tentar passar pela porta, a rotina verifica se o presidiário tem a chave em mãos. Caso tenha, libera a passagem, habilitando a instrução da linha 3040, que apaga a posição atual e habilita o movimento.
  Quando o presidiário vai de encontro com a chave, a linha 3030 entra em execução e modifica CH para 1 e libera a passagem do local, fazendo sumir a chave.




<< Anterior BasGame Próxima >>


/MARMSX/CURSOS/BasGame