quarta-feira, dezembro 06, 2006

Dicas para o tratamento numérico

O DS é extremamente lento em operações de ponto flutuante. O autor do PAlib recomenda que, quando precisarmos de valores entre 0 e 1, se utilize um inteiro (s32, por exemplo) com bit shift. Ele utiliza 8 bits, dessa forma, 256>>8 é 1. Dessa forma eu poderia ter feito o seguinte no controle dos 3 fundos (sem usar o parallax):
while (1) {
  x0+=512;
  x1+=256;
  x2+=128;
  PA_EasyBgScrollXY(1, 0, x0>>8, 0);
  PA_EasyBgScrollXY(1, 1, x1>>8, 0);
  PA_EasyBgScrollXY(1, 2, x2>>8, 0);
}
Seria algo como dizer: x0+2; x1+=1; x2+=0.5; sem usar ponto flutuante.

Outra coisa: como eu havia notado, na rotação dos sprites, o giro completo é 512. A relação é a seguinte: 0 = 0°; 128 = 90°; 256 = 180°; 384 = 270°; 512 = 360°. A PAlib tem rotina para calcular seno e cosseno desse angulo do DS (PA_Sin e PA_Cos), só que os limites são -256 a 256, ao invés de -1 a 1 (justamente pelo bit shift). Isso é útil em cálculo de tragetória (esquerda e direita rotaciona o sprite, cima move para a frente, baixo move para trás). Daí a solução pode ser assim:
if (Pad.Held.Up){ // Move para a frente
  x += PA_Cos(angulo);
  y -= PA_Sin(angulo);
}
if (Pad.Held.Down){ // Move para trás
  x += -PA_Cos(angulo);
  y -= -PA_Sin(angulo);
}

Números aleatórios são gerados pela função PA_Rand, só que essa função é meio inútil pois a gente não tem controle sobre os limites dos números gerados. Para gerar um n° aleatorio entre 0 e x, usar PA_RandMax(x); para gerar um n° aleatorio entre x e y, usar PA_RandMinMax(x,y); PA_SRand(semente) serve para definir uma semente específica para os nros aleatórios; PA_InitRand() inicializa a semente de números aleatórios baseando no relógio do DS (fica muito mais aleatório).

O resto, em C é %. Assim 3%2 retorna 1. Quando o segundo valor for potência de dois, pode-se usar o resto binário (&), ganhando performance (só o segundo valor tem que ser diminuido 1). por exemplo x%4 retorna o mesmo valor que x&3.

True vale 1 e false vale 0. Com isso pode-se evitar o comando if, que é bastante lento por consumir muitos ciclos de clock para ser executado.
if (soma==true) {
  x++
}
pode ser substituido por
x+=(soma==true);

Com essa lógica matemática fica fácil verificar colisões. Considerando que os desenhos nos sprites tendem a ter uma forma redonda, ou uma área de contato redonda, verificar a colisão significa ver se a distância dos centros dos dois sprites é menor ou igual à soma do raio das duas áreas redondas. Para ver a distância de dois pontos, tem a função PA_Distance(x1,y1,x2,y2).
Se os sprites tenderem à forma quadrada, basta verificar o contato das bordas.

4 comentários:

Anônimo disse...

puxa, muito mão na roda a biblioteca. E as observações sobre o consumo de clock, não imaginei usar x+=(soma==true) ;

com essa das colisões agora... Como será que fica uma boa prática para checar várias colisões sem ficar pesado no ds?

Guilherme - Homebrew Maker disse...

A mais leve é justamente a da distância dos centros dos círculos (faz uma verificação). Para verificar um quadrado, tem que verificar 4 lados.

Leonardo Zimbres disse...

o que me parece absurdamente pesado, pelo menos no termo que conheço, é a checagem de teste de colisão de balas de uma metralhadora e múltiplos inimigos. A forma que encontrei até então é cruzar os hittestes de todos os objetos da tela em um for. Se existir maneira mais leve de fazer isso, puxa, como vai ser bom.

Guilherme - Homebrew Maker disse...

aí realmente complica, pois balas de metralhadora são muitos objetos por cena. Mas se não me engano, mais adiante no tutorial que estou seguindo, eles apresentam mapas de colisão. Quando chegar lá aí vou saber o que é, como é feito e se ajuda o problema das balas de metralhadora.