sábado, 12 de abril de 2008

Alterando a Fonte

Eu sempre achei a fonte padrão da PAlib limitada por não conter acentuação. Hoje eu resolvi mudar essa situação. Passei a tarde toda e parte da noite preparando uma fonte baseada no padrão ISO-8859-1, que é a fonte latina padrão do Windows em português (BR). Procurei colocar todos os caracteres dessa fonte, mas no final eu deixei a última linha em branco, pois eram sinais tipo um C com acento, um A cou um u em cima, sinais que realmente eu nunca usei.

Como usar a fonte nova:
- Ter a fonte em gif, com a cor que quiser e o fundo em magenta. Eu fiz a letra branca pois fica mais fácil de usar os comandos de cores.
- Converter o gif para tiled background usando o PAgfx.
- Usar o comando PA_InitCustomText para definir a fonte customizada como a default do texto.
- Escrever normalmente.

O único comando para alterar a cor do texto que funciona é o PA_SetTextCol. Os outros comandos deixam as letras pretas.

Arquivo gif com a fonte:


Fonte do programa:
// Includes
#include <pa9.h> // Include for PA_Lib

#include "gfx/all_gfx.h"
#include "gfx/all_gfx.c"
// Function: main()
int main(int argc, char ** argv)
{
  PA_Init(); // Initializes PA_Lib
  PA_InitVBL(); // Initializes a standard VBL

  PA_InitText(0, 0);
  PA_InitCustomText(1,0,fonte3);
  PA_OutputSimpleText(1,2,2,"texto com acentuação");
  PA_OutputSimpleText(1,0,3,"áéíóúàãõâêôüç ÁÉÍÓÚÀÃÕÂÊÔÜÇ");

// Infinite loop to keep the program running
  while (1)
  {
    if (Pad.Newpress.Up) { PA_SetTextCol(1, 0, 0, 31); }
    if (Pad.Newpress.Down) { PA_SetTextCol(1, 0, 31, 0); }
    if (Pad.Newpress.Left) { PA_SetTextCol(1, 31, 0, 0); }
    if (Pad.Newpress.Right) { PA_SetTextCol(1, 31, 31, 31); }
    PA_WaitForVBL();
  }

  return 0;
} // End of main()


Alguns avisos:
- Eu realmente desisti do jogo de tanque. Eu não estava contente com o jogo e estava apenas me estressando. Eu gosto de programar jogos de raciocínio. Jogos de ação não são minha praia.
- Eu estou começando a projetar um jogo que vai misturar adventure point&click (tipo os da Lucas Arts) com aqueles adventures puro texto (tipo os da Infocom). Por isso que precisava de uma fonte acentuada.
- Quem tentar instalar a PAlib 080203 (beta) no DevKitPro vai ter problemas na compilação, é necessário imcluir uma variável de ambiente chamada papath apontando para onde está a PAlib (usando / ao invés de \). Como fazer isso:
1) Clique com o botão direito em "Meu Computador" e clique em propriedades.
2) Vá na aba "Avançado" e clique no botão "Variáveis de Ambiente".
3) Em "Variáveis do Sistema", clique no botão "Nova".
4) Em "Nome da Variável", escreva papath
5) Em "Valor da Variável", escreva c:/devkitpro/palib
6) Vá clicando em "OK" até fechar todas as janelas.
No passo 5, é importante colocar o caminho para onde está a palib. Se for diferente do que eu postei, tem que ser o caminho do seu HD até chegar na palib.

quarta-feira, 13 de fevereiro de 2008

Novidades na PAlib

Não testei nada, só li o que estava no site e estou passando aqui:
- Antigamente, os arquivos de som só podiam ser RAW (um Wave sem cabeçalho) e MOD (do Amiga). Agora, além desses formatos, tem bibliotecas para MP3, s3m, it e outros formatos similares ao MOD.
- Funciona no Windows Vista
- Biblioteca para comunicação direta entre dois DSs.
- EFSlib - para tornar os programas que acessam a FAT do flashcard compatíveis com a DLDI. A PAFS só funcionava em flashcards de slot2.
- A partir de agora, é necessário alterar o Makefile para compilar com a biblioteca ARM7 apropriada. Agora tem diversas bibliotecas conflitantes, como a do Wifi e a de comunicação direta entre dois DSs (só pode usar uma delas num programa). Mod e MP3, também.
- Novos exemplos, abrangendo as novas bibliotecas.
- E mais correções de bugs.

Problemas na instalação da PAlib


Eu andei testando a instalação da PAlib com o novo DevKitPro e não funcionou. Na hora de compilar o programa só dava erro

A solução eu descobri no fórum da PAlib. Não pode mais usar o instalador da PAlib. Tem que baixar a PAlib em ZIP e mandar extrair na pasta onde está o DevKitPro.

Detalhe importante: Não pode extrair a pasta libnds, senão sobrescreve a biblioteca do DevKitPro e dá o erro.

Eu estava olhando (agora) o site da PAlib e vi que saiu uma nova versão em 03/02/2008. Vou olhar o que tem de novidades e depois posto aqui. É até capaz de já ter corrigido o problema acima.

terça-feira, 29 de maio de 2007

Vetores

Vetores são como variáveis, mas podem conter diversos valores.

Digamos, por exemplo, que eu queira trabalhar com moedas. Cada tipo de moeda tem um valor (em centavos): moeda1=1, moeda2=5, moeda3=10, moeda4=25, moeda5=50 e moeda6=100. Usando as variáveis dessa forma, se eu quisesse usar um for para mostrar o valor de todas as moedas, eu não conseguiria.

O vetor vem ao meu socorro. Por exemplo:
s16 moeda[6]={1,5,10,25,50,100};
for (i=0; i<6; i++) {
  PA_OutputText(0,0,i,"moeda[%d]=%d",i,moeda[i]);
}

O código acima apresentará uma saída asim:
moeda[0]=1
moeda[1]=5
moeda[2]=10
moeda[3]=25
moeda[4]=50
moeda[5]=100

Nós fizemos um vetor de 6 posições (de 0 a 5) contendo os valores das moedas do Brasil.

Vamos aplicar isso ao nosso jogo. O cenário tem áreas de areia, de terra, de grama e de água. O tanque não deve andar sobre a água. Mas o tipo de solo também influencia na velocidade do tanque. Então faremos o tanque andar na metade da velocidade na areia (1 pixel a cada 2 frames), em velocidade normal na grama (1 pixel por frame) e em velocidade dobrada na terra (2 pixels por frame). Usando a lógica de bit shifting que já estudamos, teríamos uma tabela contendo velocidade=0 se o terreno for água, 128 se for areia, 256 se for grama e 512 se for terra.

Eu dividi o background em quadrados de 16x16 pixels e coloquei num vetor. Como a imagem tem 256x192 pixels, isso divindo por 16 cada valor me gerou um vetor de 192 posições (16x12=192). O vetor é representado de forma linear, mas a tela tem linhas e colunas. Então é necessária uma fórmula para acessarmos todas as posições. Essa fórmula é: linha*largura+coluna. Neste caso é linha*16+coluna. A posição de linha e coluna é calculada relativamente ao centro do tanque para facilitar a programação.

Como existe a possibilidade de ter áreas onde o tanque não se move, ele jamais deve entrar com o centro nessas áreas. Quando ele entrar em uma área de velocidade 0, a posição do tanque deve ser restaurada para posição antiga, anterior à entrada nessa área. Se o tanque entrasse numa área de velocidade 0, ele jamais sairia dela, pois a sua velocidade seria 0.

Outra coisa que corrigi é que quando o tanque atingia uma borda da tela e o controle estivesse sendo pressionado na diagonal, o tanque continuava se movendo em apenas um dos eixos ao invés de parar. Agora ele pára.

Vamos ao código-fonte. Analisem o fonte e encontrem o que foi falado acima. Se houver alguma dúvida, façam a pergunta nos comentários.


// Includes
#include <PA9.h> // Include for PA_Lib
#include "gfx/all_gfx.c"
#include "gfx/all_gfx.h"
// Variaveis Globais
s16 angulo_body=0;
s16 angulo_turret=0;
s32 x=111<<8;
s32 y=(79+192+48)<<8;
s16 angulo_tiro=0;
s32 x_tiro=-8<<8;
s32 y_tiro=-8<<8;
s16 vel_tiro=4;
s8 atirando=0;
s16 vel_terra[192]={0,128,128,256,256,256,256,256,256,256,256,256,256,128,128,0,
  0,128,128,256,256,512,512,512,512,512,512,256,256,128,128,0,
  0,128,128,256,256,512,512,512,512,512,512,256,256,128,128,0,
  0,128,128,256,256,512,512,512,512,512,512,256,256,128,128,0,
  0,128,128,256,256,512,512,512,512,512,512,256,256,128,128,0,
  0,128,128,256,256,256,256,256,256,256,256,256,256,128,128,0,
  0,128,128,256,256,256,256,256,256,256,256,256,256,128,128,0,
  0,128,128,256,256,256,256,256,256,256,256,256,256,128,128,0,
  0,128,128,256,256,256,256,256,256,256,256,256,256,128,128,0,
  0,128,128,128,128,128,128,128,128,128,128,128,128,128,128,0,
  0,128,128,128,128,128,128,128,128,128,128,128,128,128,128,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

void giraturret() {
  if (Stylus.Held) {
    angulo_turret=PA_GetAngle((x>>8)+16,(y>>8)+16-240,Stylus.X,Stylus.Y);
    PA_DualSetRotsetNoZoom(0,angulo_turret);
  }
}

void movetanque() {
  s16 centro_x=((x>>8)+15)>>4;
  s16 centro_y=((y>>8)-240+15)>>4;
  s16 tipoterra=0;
  s16 velocidade=0;
  s32 xvelho=x;
  s32 yvelho=y;
  if (centro_x<0||centro_x>15||centro_y<0||centro_y>11) {
    tipoterra=0;
    velocidade=0;
  } else {
    tipoterra=centro_y*16+centro_x;
    velocidade=vel_terra[tipoterra];
  }
  if ((Pad.Held.Left && Pad.Held.A) || (Pad.Held.Right && Pad.Held.Y) || (Pad.Held.Up && Pad.Held.B) || (Pad.Held.Down && Pad.Held.X) || (Pad.Held.X && Pad.Held.B) || (Pad.Held.Y && Pad.Held.A)) {
   //o usuário está sacaneando, não executa nada
  } else if (!(Pad.Held.Up || Pad.Held.Down || Pad.Held.Left || Pad.Held.Right || Pad.Held.A || Pad.Held.B || Pad.Held.X || Pad.Held.Y)) {
   //nenhuma direção foi pressionada
  } else {
    if ((Pad.Held.Left || Pad.Held.Y || Pad.Held.Right || Pad.Held.A) && !(Pad.Held.Up || Pad.Held.X || Pad.Held.Down || Pad.Held.B)) {
      angulo_body=256*(Pad.Held.Left || Pad.Held.Y);
      x+=velocidade*(Pad.Held.Right || Pad.Held.A)-velocidade*(Pad.Held.Left || Pad.Held.Y);
    } else if (!(Pad.Held.Left || Pad.Held.Y || Pad.Held.Right || Pad.Held.A) && (Pad.Held.Up || Pad.Held.X || Pad.Held.Down || Pad.Held.B)) {
      angulo_body=128*(Pad.Held.Up || Pad.Held.X)+384*(Pad.Held.Down || Pad.Held.B);
      y+=velocidade*(Pad.Held.Down || Pad.Held.B)-velocidade*(Pad.Held.Up || Pad.Held.X);
    } else {
      angulo_body=256*(Pad.Held.Left || Pad.Held.Y);
      if (angulo_body) {
        angulo_body=192*(Pad.Held.Up || Pad.Held.X)+320*(Pad.Held.Down || Pad.Held.B);
      } else {
        angulo_body=64*(Pad.Held.Up || Pad.Held.X)+448*(Pad.Held.Down || Pad.Held.B);
      }
      x+=velocidade*(Pad.Held.Right || Pad.Held.A)-velocidade*(Pad.Held.Left || Pad.Held.Y);
      y+=velocidade*(Pad.Held.Down || Pad.Held.B)-velocidade*(Pad.Held.Up || Pad.Held.X);
    }
    centro_x=((x>>8)+15)>>4;
    centro_y=((y>>8)-240+15)>>4;
    if (centro_x<0||centro_x>15||centro_y<0||centro_y>11) {
      tipoterra=0;
      velocidade=0;
    } else {
      tipoterra=centro_y*16+centro_x;
      velocidade=vel_terra[tipoterra];
    }
    if (velocidade==0 || x<0 || x>57088 || y<61440 || y>102144) {
      x=xvelho;
      y=yvelho;
    }
//    if (x<0) { x=0; }
//    if (x>57088) { x=57088; }
//    if (y<61440) { y=61440; }
//    if (y>102144) { y=102144; }
    PA_DualSetRotsetNoZoom(1,angulo_body);
    PA_DualSetSpriteXY(0,x>>8,y>>8);
    PA_DualSetSpriteXY(1,x>>8,y>>8);
  }
}

void atira() {
  if (atirando) {
    x_tiro+=PA_Cos(angulo_tiro)*vel_tiro;
    y_tiro-=PA_Sin(angulo_tiro)*vel_tiro;
    PA_DualSetSpriteXY(2,x_tiro>>8,y_tiro>>8);
    if (x_tiro<-8<<8 || x_tiro>260<<8 || y_tiro<-8<<8 || y_tiro>440<<8) {
      atirando=0;
    }
  } else {
    if (Pad.Newpress.L || Pad.Newpress.R) {
      atirando=1;
      angulo_tiro=angulo_turret;
      x_tiro=((x>>8)+12)<<8;
      y_tiro=((y>>8)+12)<<8;
      x_tiro+=PA_Cos(angulo_tiro)*24;
      y_tiro-=PA_Sin(angulo_tiro)*24;
      PA_DualSetRotsetNoZoom(2,angulo_tiro);
      PA_DualSetSpriteXY(2,x_tiro>>8,y_tiro>>8);
    }
  }
}

// Function: main()
int main(int argc, char ** argv)
{
  
  PA_Init(); // Initializes PA_Lib
  PA_InitVBL(); // Initializes a standard VBL
  PA_DualLoadSpritePal( 0, (void*)tank_turret_Pal);
  PA_DualCreateSprite( 0, (void*)tank_turret_Sprite, OBJ_SIZE_32X32, 1, 0, x>>8, y>>8);
  PA_DualLoadSpritePal( 1, (void*)tank_body_Pal);
  PA_DualCreateSprite( 1, (void*)tank_body_Sprite, OBJ_SIZE_32X32, 1, 0, x>>8, y>>8);
  PA_DualLoadSpritePal( 2, (void*)tiro_Pal);
  PA_DualCreateSprite( 2, (void*)tiro_Sprite, OBJ_SIZE_8X8, 1, 0, -8, -8);
  PA_DualSetSpriteRotEnable( 0, 0);
  PA_DualSetSpriteRotEnable( 1, 1);
  PA_DualSetSpriteRotEnable( 2, 2);
  PA_EasyBgLoad(0,3,set_bg_1);
  PA_EasyBgLoad(1,3,set_bg_2);
  PA_InitText(1,0);
  PA_SetTextCol(1,31,31,31);
  // Infinite loop to keep the program running
  while (1)
  {
    giraturret();
    movetanque();
    atira();
    PA_WaitForVBL();
  }
  return 0;
} // End of main()

segunda-feira, 28 de maio de 2007

À espera de Guitar Hero DS

Enquanto Guitar Hero não chega, eu estou jogando AmplituDS.

AmplituDS é um homebrew de ação musical. Sander Stolks, o autor desse homebrew, disse que ele é uma versão do jogo Amplitude do PS2, que é dos mesmos autores do Guitar Hero.

No jogo, a gente controla uma nave que deve disparar em pontos dispostos sobre faixas coloridas, no ritmo da música que está tocando. Quando a gente acerta toda uma seqüência, deve ir rapidamente para a outra faixa antes do fim do compasso, para dar tempo de começar a atirar nos pontos.

Contoles do jogo:
- Direcional - Controla a nave, sobre qual faixa ela vai andar
- L - dispara no botão à esquerda
- X - dispara no botão central
- R - dispara no botão à direita
- Y - coleta os bônus

As músicas são .MOD, a música da primeira fase é o tema do Mario.

[página do autor]


O jogo é compatível com a DLDI, podendo rodar em muitos modelos de flashcards. Na página do autor tem as instruções do jogo e de como instalar. Também tem dois vídeos demonstrativos. Tem também o link de download do jogo bem como do código-fonte.

domingo, 20 de maio de 2007

domingo, 6 de maio de 2007

Ocupando as Duas Telas

Tudo até agora foi programado usando apenas a tela de baixo, a tela 0. Mas este jogo deve ocupar as duas telas do DS. O nosso tanque tem que ficar só na tela de baixo, mas os tiros devem alcançar a tela de cima. Uma maneira de resolver isso seria bastante complexa, colocando os mesmos desenhos dos sprites de baixo nos sprites de cima e ficar controlando para ocultar um sprite em baixo quando ele passa a aparecer em cima.
Outra opção seria usar a própria PAlib para fazer esse controle. É o que nós faremos. Todos os comandos de sprites possuem um irmão "Dual", que não precisa especificar qual a tela ele será colocado, o valor de y é que definirá isso. A altura padrão de tela passa a ser 432 pixels (192 pixels da tela de baixo, mais 192 pixels da tela de cima, mais 48 pixels que não mostram imagem porque são o espaço entre as duas telas). O espaço entre as duas tela pode ser alterado pela função PA_SetScreenSpace mas nós continuaremos a usar os 48 pixels pois isso dá uma continuidade bem legal ao tiro.
Ah, sim. Faltou falar que os comandos para trabalhar com as duas telas como se fossem uma, são os mesmos comandos de gerenciamento de sprites, somente com a palavra "Dual" na frente (logo após o "PA_") e sem o parâmetro do número da tela.
Também iremos colocar os fundos das telas. O fundo set_bg_1.gif vai na tela de baixo e o set_bg_2.gif vai na tela de cima.
No programa, as alterações não são muito radicais:
  • trocar os comandos de sprites para os dual
  • tratar o tanque na tela de baixo, ou seja, y+240
  • permitir que o tiro ande por todos os 432 pixels
  • não desenhar mais o fundo de 16 bits amarelo sujo
  • colocar os dois tiled backgrounds

Fonte do programa:

// Includes
#include <PA9.h> // Include for PA_Lib
#include "gfx/all_gfx.c"
#include "gfx/all_gfx.h"

// Variaveis Globais
s16 angulo_body=0;
s16 angulo_turret=0;
s16 velocidade=1;
s16 x=111;
s16 y=79+192+48;
s16 angulo_tiro=0;
s32 x_tiro=-8<<8;
s32 y_tiro=-8<<8;
s16 vel_tiro=4;
s8 atirando=0;

void giraturret() {
  if (Stylus.Held) {
    angulo_turret=PA_GetAngle(x+16,y+16-240,Stylus.X,Stylus.Y);
    PA_DualSetRotsetNoZoom(0,angulo_turret);
  }
}

void movetanque() {
  if ((Pad.Held.Left && Pad.Held.A) || (Pad.Held.Right && Pad.Held.Y) || (Pad.Held.Up && Pad.Held.B) || (Pad.Held.Down && Pad.Held.X) || (Pad.Held.X && Pad.Held.B) || (Pad.Held.Y && Pad.Held.A)) {
   //o usuário está sacaneando, não executa nada
  } else if (!(Pad.Held.Up || Pad.Held.Down || Pad.Held.Left || Pad.Held.Right || Pad.Held.A || Pad.Held.B || Pad.Held.X || Pad.Held.Y)) {
   //nenhuma direção foi pressionada
  } else {
    if ((Pad.Held.Left || Pad.Held.Y || Pad.Held.Right || Pad.Held.A) && !(Pad.Held.Up || Pad.Held.X || Pad.Held.Down || Pad.Held.B)) {
      angulo_body=256*(Pad.Held.Left || Pad.Held.Y);
      x+=velocidade*(Pad.Held.Right || Pad.Held.A)-velocidade*(Pad.Held.Left || Pad.Held.Y);
    } else if (!(Pad.Held.Left || Pad.Held.Y || Pad.Held.Right || Pad.Held.A) && (Pad.Held.Up || Pad.Held.X || Pad.Held.Down || Pad.Held.B)) {
      angulo_body=128*(Pad.Held.Up || Pad.Held.X)+384*(Pad.Held.Down || Pad.Held.B);
      y+=velocidade*(Pad.Held.Down || Pad.Held.B)-velocidade*(Pad.Held.Up || Pad.Held.X);
    } else {
      angulo_body=256*(Pad.Held.Left || Pad.Held.Y);
      x+=velocidade*(Pad.Held.Right || Pad.Held.A)-velocidade*(Pad.Held.Left || Pad.Held.Y);
      if (angulo_body) {
        angulo_body=192*(Pad.Held.Up || Pad.Held.X)+320*(Pad.Held.Down || Pad.Held.B);
      } else {
        angulo_body=64*(Pad.Held.Up || Pad.Held.X)+448*(Pad.Held.Down || Pad.Held.B);
      }
      y+=velocidade*(Pad.Held.Down || Pad.Held.B)-velocidade*(Pad.Held.Up || Pad.Held.X);
    }
    if (x<0) { x=0; }
    if (x>223) { x=223; }
    if (y<240) { y=240; }
    if (y>399) { y=399; }
    PA_DualSetRotsetNoZoom(1,angulo_body);
    PA_DualSetSpriteXY(0,x,y);
    PA_DualSetSpriteXY(1,x,y);
  }
}

void atira() {
  if (atirando) {
    x_tiro+=PA_Cos(angulo_tiro)*vel_tiro;
    y_tiro-=PA_Sin(angulo_tiro)*vel_tiro;
    PA_DualSetSpriteXY(2,x_tiro>>8,y_tiro>>8);
    if (x_tiro<-8<<8 || x_tiro>260<<8 || y_tiro<-8<<8 || y_tiro>440<<8) {
      atirando=0;
    }
  } else {
    if (Pad.Newpress.L || Pad.Newpress.R) {
      atirando=1;
      angulo_tiro=angulo_turret;
      x_tiro=(x+12)<<8;
      y_tiro=(y+12)<<8;
      x_tiro+=PA_Cos(angulo_tiro)*24;
      y_tiro-=PA_Sin(angulo_tiro)*24;
      PA_DualSetRotsetNoZoom(2,angulo_tiro);
      PA_DualSetSpriteXY(2,x_tiro>>8,y_tiro>>8);
    }
  }
}

// Function: main()
int main(int argc, char ** argv)
{
  PA_Init(); // Initializes PA_Lib
  PA_InitVBL(); // Initializes a standard VBL
  PA_DualLoadSpritePal(0, (void*)tank_turret_Pal);
  PA_DualCreateSprite(0, (void*)tank_turret_Sprite, OBJ_SIZE_32X32, 1, 0, x, y);
  PA_DualLoadSpritePal(1, (void*)tank_body_Pal);
  PA_DualCreateSprite(1, (void*)tank_body_Sprite, OBJ_SIZE_32X32, 1, 0, x, y);
  PA_DualLoadSpritePal(2, (void*)tiro_Pal);
  PA_DualCreateSprite(2, (void*)tiro_Sprite, OBJ_SIZE_8X8, 1, 0, -8, -8);
  PA_DualSetSpriteRotEnable(0, 0);
  PA_DualSetSpriteRotEnable(1, 1);
  PA_DualSetSpriteRotEnable(2, 2);
  PA_EasyBgLoad(0,3,set_bg_1);
  PA_EasyBgLoad(1,3,set_bg_2);
  // Infinite loop to keep the program running
  while (1)
  {
    giraturret();
    movetanque();
    atira();
    PA_WaitForVBL();
  }
  return 0;
} // End of main()

Imagens para fazer os fundos: