Bacharelado em Ciência da Computação   Instituto de Matemática e Estatística da USP
Kanji de Nukenin
Projeto Nukenin
(ou Apenas outro jogo amador para o GBA)
"Game developers are the brightest and the best of computer programmers.
They are the most creative, smartest, most admired, and most marketable."
Diana Gruber - Game Developer
Supervisor: Siang Wun Song

Edgar Kenji Yamamoto NUSP:3506736
Roberto Seiti Yamashiro NUSP:2979909
{edgar, seiti}@linux.ime.usp.br
Sumário

Parte I - Aspectos técnicos

1. Introdução

Garça branca na chuva, Koson

Este documento descreve um projeto apresentado como trabalho de formatura na disciplina Trabalho de Formatura Supervisionado - MAC499, do curso de Bacharelado em Ciência da Computação do Instituto de Matemática e Estatística da USP.

O projeto consiste em produzir um jogo de plataforma para o Game Boy Advance®, utilizando a linguagem de programação C e o conjunto de compilador, bibliotecas e ferramentas devkitARM, baseado no GCC, criado especialmente para se desenvolver programas para este pequeno console portátil.

Jogos eletrônicos estão entre as peças de software que mais podem abraçar diferentes áreas do conhecimento, não se restringindo à área da Computação. Basta pensar, por exemplo, em jogos envolvendo personagens e momentos históricos ou simuladores que requerem uma física convincente. Mesmo na área específica da computação (e de exatas em geral, como a Física, Álgebra Linear etc.), diferentes campos podem ser inclusos no desenvolvimento de jogos: Computação Gráfica, Inteligência Artificial, Estrutura de Dados, Engenharia de Software, Organização de Computadores, Autômatos, Programação em Redes e Paralela etc.

Neste projeto, as áreas da computação mais pertinentes são Organização de Computadores e Programação Orientada a Objetos (utilizada nas ferramentas em Java), ambos regados a Estruturas de Dados.

Na seção 2 são descritas as características básicas do hardware do GameBoy Advance; na seção 3 notas sobre elaboração de jogos em 2D com algumas notas sobre as particularidades do GBA; na seção 4 o cronograma do projeto; na seção 5 é apresentada algumas características do ambiente de programação; na seção 6 os aspectos principais da programação no GBA e do jogo Nukenin, tais como algoritmos, implementações e ferramentas adicionais criadas e, por fim, na seção 7 a esperada conclusão do projeto seguida pelo material utilizado como referência.

[voltar]

2. Game Boy

"...on consoles it's just you, the CPU and memory.
Basically, it's the Real Programmer's dream."
TONC - GBA Programming
Aparelho GameBoy Color

O Game Boy Color

O Game Boy® original da Nintendo foi criado em 1989 por Gunpei Yokoi e é considerado o videogame de maior sucesso da história tanto em vendas quanto em longevidade no mercado. Sua arquitetura é baseada no processador de 8 bits Z80 e 64kbits de memória RAM, dispondo de um LCD com 160x144 pixels de resolução máxima e uma paleta de cores contendo quatro tons de cinza. Em 1996 foi lançado o Game Boy Pocket, cujas características eram as mesmas, a não ser o tamanho reduzido e uma tela LCD de melhor qualidade. Pouco depois foi lançado o Game Boy Color, cuja diferença principal é a presença de cores na tela, além da maior velocidade do processador Z80 e um pouco mais de memória RAM.

aparelho GameBoy Advance

O Game Boy Advance

Já o Game Boy Advance® (GBA) é um produto que, apesar de compartilhar o mesmo nome, possui características bem diferentes. Ele também se trata de um videogame portátil, porém sua arquitetura é baseada em um processador ARM de 32 bits: o ARM7TDMI. Um processador Z80 ainda pode ser encontrado em seu interior, sendo utilizado apenas para manter a compatibilidade com os jogos antigos. Os dois processadores, o ARM e o Z80, não podem ser utilizados simultaneamente. Segue uma especificação simplificada do hardware do aparelho:

Meu GameBoy Advance SP

O Game Boy Advance SP

Há também o Game Boy Advance SP®, que possui o mesmo hardware que o Game Boy Advance. As diferenças mais notáveis são a iluminação ativa do monitor TFT através de um frontlight (diferente de backlight; quem tem um GBA SP sabe do que estou falando) e seu formato diferente.

Para testarmos nosso código iremos utilizar um emulador que simule o funcionamento do hardware do GBA (o Visual Boy Advance) e também testaremos o código direto no próprio GBA, gravando nossos binários em cartuchos regraváveis especiais, compatíveis com o videogame, comumente chamados de "flash-carts".

Aspectos um pouco mais técnicos

Do ponto de vista do programador, aqui vão mais algumas características técnicas úteis para trabalharmos na produção de jogos. O Game Boy é um computador, com processador e memória de trabalho (RAM). No entando, diferente de um computador comum, muitas funções úteis para os jogos estão implementadas em hardware e, para serem acessadas, são mapeadas em posições específicas de sua memória.

Um documento muito útil para se aprofundar nas especificações técnicas do hardware do Game Boy Advance é a especificação do emulador CowBite, escrito por Tom Happ.

CPU

Processador ARM7TDMI de 16.78MHz de 32 bits baseado em arquitetura RISC. Apesar de ser um processador de 32 bits (no denominado modo ARM), pode-se operar em 16 bits utilizando o modo Thumb, que configura o processador a manusear um conjunto de operações de 16 bits. Os dois modos, ARM e Thumb, podem ser utilizados no mesmo programa. Neste projeto utilizaremos somente o modo ARM, exceto por algum caso específico que precise do modo Thumb (como o sistema de som).

Memória

Mapa esquemático da memória RAM

Mapa esquemático da memória do GBA

A memória utilizada pelo jogos pode ser separada em memória RAM e ROM. A memória ROM se trata dos dados imutáveis do jogo, que para serem utilizadas devem ser alocadas na memoria RAM. A memória RAM, por sua vez, pode ser dividida em IWRAM (Internal Working RAM), EWRAM (External Working RAM) e VRAM (Video RAM), além de áreas específicas para armazenamento de sprites (Object Attribute Memory, ou OAM) e paletas de cores, dependendo do modo de tela utilizado.

A diferença entre a IWRAM e a EWRAM é que a IWRAM está embarcada no próprio processador, podendo ser executada em uma velocidade muito maior que a EWRAM.

Cada pedaço da memória RAM tem um tamanho fixo, além da largura da palavra utilizada poder diferir de um pedaço para outro, como podemos ver especificado na tabela 1. Estas informações são extremamente importantes para termos controle da utilização e alocação de memória, efetuada diretamente pelos endereços, evitando o uso da função malloc, e portanto requerendo a definição da finalidade de uso de vários endereços.

Tab.1: Esquema da memória RAM do GBA
NomeInício Fim Tamanho Largura
System ROM 0x0000:0000 0x0000:3FFF 16kb 32 bit
External Work RAM 0x0200:0000 0x0203:FFFF 256kb 16 bit
Internal Work RAM 0x0300:0000 0x0300:7FFF 32kb 32 bit
IO Ram 0x0400:0000 0x0400:03FF (0x0401:0000) 1kb porta dupla 32 bit
Palette RAM 0x0500:0000 0x0500:03FF 1kb 16 bit
VRAM 0x0600:0000 0x0601:7FFF 96kb 16 bit
OAM 0x0700:0000 0x0700:03FF 1kb 32 bit
GAME PAK ROM 0x0800:0000 (não se aplica) o tamanho do cartucho (0 - 32 megabytes) 16 bit
CART RAM 0x0E00:0000
(parece também existir em 0x0F00:0000)
(não se aplica) 0 - 64 kb 8 bit

Isto não quer dizer que temos de saber cada endereço da memória. Fazemos um uso bem intenso de macros para isto.

Como exemplo do tipo de código utilizado no projeto, temos a seguinte alocação típica de memória, efetuada por programadores em geral:

...
typedef unsigned short u16;        
...
int n = 20; /* um valor arbitrário */
u16* bg_tiles = (u16*)malloc(n*sizeof(u16)); 
...
    

Que em nosso projeto foi efetuada desta maneira:

...
typedef unsigned short u16;        
#define BGTILEDATA (u16*) 0x60004000
...
u16* bg_tiles = BGTILEDATA
...
    

Note que não temos como definir, a priori, o tamanho do espaço alocado. Mas isto não faz muita diferença, pois teríamos também de tomar cuidado no exemplo mais acima ao utilizarmos o espaço alocado (embora o sistema operacional possa bloquear alguns tipos de acesso indevido).

Note também que o endereço de memória atribuído à bg_tiles, 0x6000:4000, faz parte da área de memória de vídeo (VRAM), que inicia-se em 0x6000:0000. Como pode-se observar pelo nome, bg_tiles armazena os tiles na memória de vídeo.

[voltar]

3. O jogo

Telas dos jogos Ninja Gaiden e Pitfall

Exemplos de jogos de plataforma

Um dos heróis do Suikoden

O nome Nukenin (抜忍, onde 抜 significa expulso e 忍 ninja) significa ninja renegado, expulso de seu clã, um termo semelhante a ronin (samurai sem senhor). Temos, então, como personagem principal um ninja, ainda sem nome.

O sistema do jogo trata-se de uma aventura em plataforma, onde o ambiente é visto de uma perspectiva lateral, e o personagem principal pode se mover para a esquerda e para a direita, além de poder pular, atirar, subir escadas, cair em fossos etc.

Alguns exemplos de jogos do estilo, e que serviram como base para várias das idéias implementadas e por implementar, são a série Castlevania (Konami), os jogos em 2D da série Metroid (Nintendo) e a trilogia Ninja Gaiden (Tecmo) para o Nintendo 8 Bits (Nintendo Entertainment System).

O objetivo principal de nosso pequeno ninja é a de correr pelo mapa, fatiando os inimigos que encontrar. Roteiro e outras coisas foram deixados de lado, pois nos concentramos nos aspectos tecnológicos e de implementação.

Mecanismo Gráfico

O mecanismo gráfico, ou graphic engine, utilizado é puramente 2D, ou seja, com gráficos em duas dimensões, visando simplificar o trabalho na programação no projeto, pois, embora a criação de um mecanismo 3D seja possível, o hardware do GBA possui algumas propriedades que facilitam muito a criação de um jogo exclusivamente em 2D.

Como boa parte dos jogos 2D, o sistema gráfico é totalmente baseado em tiles e sprites. E o que são estes tiles e sprites?

Tiles

Imagem repartida em tiles

Os tiles podem ser vistos como a unidade básica para se construir qualquer objeto gráfico. O tile não passa de um bloco de pixels, no nosso caso um quadrado de 8x8 pixels, que é utilizado para se criar qualquer figura.

Estes blocos são utilizados para se compor qualquer imagem no GBA. Desta maneira, caso seja necessário inserir uma figura no jogo, devemos tratá-la, repartindo-a em diversos tiles, e depois inserir estes tiles na memória do console. Este processo, embora possa parecer estranho no início, visa economizar memória, pois podemos criar um número pequeno de tiles e utilizá-los para criarmos um mapa gigantesco, apenas utlizando uma matrix (ou vetor, no nosso caso específico), em que cada elemento possui o número do tile correspondente. Uma maneira de se visualizar este processor é lembrar-se do jogo Sim City, onde o mapa era construído pelo próprio jogador, a partir de unidades pré-definidas.

Vamos entender como uma imagem é colocada na tela do aparelho. Nós carregamos (ou compilamos) código C correspondente à imagem na memória ROM - presente no cartucho. Esta imagem é composta por um vetor de cores, a paleta e um vetor contendo os pixels que formam uma imagem. Cada entrada neste vetor corresponde à cor do pixel, indicado pelo índice da cor na paleta:

const u16 castelo_pal[16] = {
   0x21,   0x8ab,  0xcc8,  0xc57,  0x152a, 0xd0e,  0xd12,  0x1573, 
   0x1db0, 0x25f6, 0x2634, 0x3235, 0x3698, 0x406,  0x4f3d, 0x6fbe
}; 

const u8 castelo_tile[] = {

/* Tile (0,0) */
0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 

/* Tile (0,1) */
0xd0, 0xdd, 0xdd, 0xdd, 
0x4d, 0x44, 0x44, 0x44, 
0x8d, 0x44, 0x44, 0x44, 
0x4d, 0x44, 0x44, 0x42, 
0xa2, 0x48, 0x44, 0x44, 
0xa4, 0x4a, 0x44, 0x42, 
0xa2, 0x4a, 0x44, 0x4, 
0xa4, 0x4c, 0x44, 0x44,

...
     

Note que utilizamos números em hexadecimal e que cada entrada no vetor de tiles consiste, na verdade, de dois índices da paleta, pois cada número de 8 bits (identificado pelo tipo u8) pode abarcar dois números de 4 bits, cada um representando um número de 1 a 16, que é o número máximo de cores para cada tile no modo de vídeo utilizado.

Assim, observando o código anterior, podemos montar a tabela 2, correspondente ao vetor castelo_tile:

Tab.2: Alguns pixels representados no vetor castelo_tile
índiceentrada1º pixelcor2º pixelcor
00x0000x0000x00
10x0000x0000x00
20x0000x0000x00
..................
350xddd0x406d0x406
360x4d40x152ad0x406
370x4440x152a40x152a
..................

A paleta, por sua vez, possui em cada entrada uma cor no formato BGR, em que cada cor é representada por um número de 0 a 31 (5 bit), formando um número de 15 bits. Como utilizamos números de 16 bit, o último bit mais a esquerda é ignorado. A tabela 3 mostra as cores já no usual formato RGB, com a diferença de contemplar 32x32x32 cores, ao invés de 256x256x256.

Tab.3: Paleta de cores em castelo_pal
índiceentradaRGBcor
40x152a01010 = 1001001 = 900101 = 5
130x40600110 = 600000 = 000001 = 1

Ao inserirmos as imagens recortadas em tiles em uma área de memória, podemos automaticamente utilizar cada tile através de um índice do vetor de tiles, "criado" automaticamente pelo GBA.

Em nosso projeto, utilizamos esta idéia para criarmos as figuras de plano de fundo, ou backgrounds. Criamos assim figuras que são retalhadas em pequenos tiles, e utilizando um editor desenvolvido por nós (Ninpo Map Editor), criamos o vetor, ou mapa, que representa o plano de fundo através dos índices do vetor de tiles.

Sprites

Exemplo de Sprite

Sprite, além de dar nome à uma bebida a base de limão e de pequenas criaturas mágicas, significa para nós uma pequena imagem manipulável em termos de posicionamento (e também de rotação e distorção, dependendo da necessidade).

Como o próprio sprite é uma imagem, ela também é constituída por tiles. Para movermos todos estes tiles em conjunto, basta a configuração de uma entrada na memória de atributos de objetos, ou OAM (Object Attribute Memory), que pode ser visto como a seguinte estrutura:

typedef struct tagOAMentry{	
    u16 attr0; //pos Y, rot/escal, sing/doub, transp, mosaico, prof.cores, tamanho 
    u16 attr1; //pos X, flip hor/vert, rot, tamanho
    u16 attr2; //tiles, prioridade, paleta */
    u16 attr3; //bits de controle de rot/escal.
}OAMentry;
    

Cada elemento, ou atributo de sprite, da estrutura é uma palavra de 16 bits, em que cada bit, ou grupo de bits, tem um significado próprio, necessário para a configuração do sprite. Devido a capacidade de memória atribuída para a OAM, podemos ter apenas 128 entradas de sprites no vetor OAMEntry. Como exemplo temos a seguinte caracterização do atributo 2 (attr2), retirado diretamente da especificação do CowBite:

Bytes 5 and 6 (Attribute 2)
    F E D C  B A 9 8  7 6 5 4  3 2 1 0
    L L L L  P P T T  T T T T  T T T T
   
    0-9 (T) = Tile number. This value indexes selects the bitmap of the tile to be
          displayed by indexing into the tile data area. Each index refernces
          32 bytes, so the memory address of a tile is roughly 0x6010000 + T*32.
          (see Sprite Tile Data for details) 
  
    A-B (P) = Priority. This controls the priority of the sprite. Note that the 
          sprites take precedence over backgrounds of the same priority.  See the 
	  description of priority under REG_BG0 - REG_BG3 for a more detailed 
	  explanation.

    C-F (L) = Palette number. If you use 16 color palettes, this tells you which 
          pallette number to use. 

Desta maneira basta "alocarmos" memória para cada entrada na OAM e configuramos corretamente os valores de cada atributo. Para movimentarmos o sprite pela tela do GBA também é simples, é só modificarmos os bits correspondentes à posição no eixo X, localizado no primeiro byte do atributo 1, e no eixo Y, localizado no primeiro byte do atributo 0.

Nukenin correndo

Sprite animado

Isto basta para movermos uma figura pela tela. Mas e para animarmos nosso personagem? Existem duas alternativas: (1) colocarmos os diferentes sprites que correspondam ao movimento (ou frames de animação) na memória e atualizarmos as entradas na OAM de acordo; ou (2) substituirmos todos os tiles correspondentes ao sprite alvo. Em ambas é preciso a produção de cada frame, feitas individualmente, requerendo tanto o trabalho artístico quanto o do processo de transformação em código C. Em nosso projeto decidimos pela segunda alternativa, que parece mais simples de se implementar e estender.

Backgrounds

Para definirmos uma ou mais imagens de backgrounds, primeiro carregamos as imagens na memória utilizando o esquema dos tiles. Com os tiles na memória, utilizamos um vetor que efetua o mapeamento de alguns tiles para construir um background, no nosso caso de 32x32 tiles.

Como é um tanto difícil enxergar no mapa o resultado final, utilizamos nosso editor de mapas para produzir o código abaixo:

const u16 castelo_map_0[] = {
98,47,47,47,47,47,47,47,47,97,97,97,97,97,97,97,
97,97,97,97,97,97,97,98,98,98,98,98,95,98,98,98,
98,95,98,98,98,98,47,95,97,91,97,97,97,97,97,97,
91,91,97,97,97,97,97,98,98,98,98,95,98,91,98,98,
98,98,95,95,95,98,98,98,97,97,97,97,90,90,90,97,
97,97,97,97,97,97,98,95,98,98,98,95,93,98,98,98,
98,98,95,98,98,98,98,98,98,92,79,79,79,93,93,79,
79,93,97,97,91,97,97,98,93,98,98,98,98,98,98,98,
98,98,98,93,93,63,98,98,91,97,98,97,36,37,38,39,
40,97,97,97,95,95,98,91,98, 9, 9, 6,98,98,98,98,
98,98,98,98,93,98,98,92,98,98,98,97,52,53,54,55,
56,97,97,91,97,97,95,98,98,98, 2, 6,98,98,98,98,
98,98,98,98,98,98,98,98,95,98,98,97,68,69,70,71,
72,98, 2, 2, 4,93,93,98,98,97,97, 1, 1, 1, 1,10,
98,98,93,93,93,93,93,93,98,98,98,97,84,85,86,87,
88,97,97,97,97,95,97,93,97,97,97,97, 1, 1, 1,10,
98,98,91,89,91,89,89,91, 8, 9, 5,97,41,42,43,44,
45,97,97,93,93,97,95,97,47,97,97,97,97,97, 1,10,
98,93,98,91,98,98,91,98,93,98,97,97,57,58,59,60,
61,97,97,93,97,97,97,97,97,97,93,97,97,97, 1,10,
91,95,91,91,91,63,98,63,98,97,97,97,73,74,75,76,
77,97,91,97,97,97,97,97,97,97,97,97,97,97,97,10,
...
...
    

Para finalmente colocarmos cada tile, especificado no mapa acima, na memória de vídeo, precisamos configurar algumas propriedades especificadas logo abaixo, retiradas da especificação do CowBite:

Text Background Map Format:
The tile map, which stores the layout of the tiles on screen, begins at the tile map address found for a particular background, determined by REG_BG0CNT - REG_BG3CNT. It has a selectable size up to 512x512. The tile map contains a 16-bit entry for each tile, with has the following format:

    F E D C  B A 9 8  7 6 5 4  3 2 1 0 
    L L L L  V H T T  T T T T  T T T T 
    
    0-9 (T) = The tile number 
    A   (H) = If this bit is set, the tile is flipped horizontally left to right. 
    B   (V) = If this bit is set, the tile is flipped vertically upside down. 
    C-F (L) = Palette number  
    

E utilizamos para isto nossa função:

 
void setBackgroundMap(pBg bg, const u16 map[], int start, int size){
	int i;

	for (i = start; i < size; i++){
		bg->mapData[i] = map[i] | (bg->palNumber << 12);
	}
}
    

Note que a função apenas insere os tiles na memória de vídeo, utilizando para isto nossa estrutura Bg (o campo mapData da estrutura bg contém o endereço de memória reservada para armazenar os tiles), concatenando o índice do tile com o número da paleta correspondente (podemos ter 16 paletas de 16 cores cada). Não configuramos os bits que efetuam o espelhamento (ou flip) dos tiles.

[voltar]

4. Planejamento

Mulher após o banho, Hashiguchi Goyô

Antes de iniciarmos o projeto, convém planejar como se dará o desenrolar das ações. E para isto é necessário estabelecermos metas, ou marcos, até sua finalização. O cronograma criado levou em conta algumas experiências passadas em desenvolvimento de programas, como o jogo feito na disciplina de Laboratório de Programação I.

Plano:

  1. Início da monografia e planejamento; [jan~fev] já executado
  2. Pesquisa e coleta de material; [jan~mar] já executado
  3. Estabelecimento de um ambiente de desenvolvimento; [mar~abr] já executado
  4. Abertura de conta no Sourceforge.net para utilizar os serviços de CVS; [abr] já executado
  5. Análise de códigos-exemplos e produção de um jogo-teste; [abr~jul] já executado
  6. Criação do mecanismo gráfico e de ferramentas de auxílio; [mai~jul] já executado
  7. Início da produção do jogo;[set] já executado
  8. Definição do roteiro, estilo, gráficos, e demais características do jogo; [set] já executado
  9. Ciclo: produção de código, testes, depuração, documentação;[set - novembro] (faltou documentar o código)
  10. Finalização do código e da monografia.[novembro] já executado

O cronograma de planejamento foi seguido sem contar com atrasos significativos. Mas é bom observar que faltou documentar melhor o código fonte - havíamos planejado utilizar o Doxygen - o que pode atrapalhar um pouco quem quiser estudá-lo.

[voltar]

5. Ambiente

Monge examinado Miyamoto Musashi

Nosso computador de desenvolvimento é baseado em processador i386, o mais comum em desktops, como o Pentium4, Athlon, entre outros. O sistema operacional utilizado é o GNU/Linux, devido a sua robustez e facilidade de uso para o desenvolvimento de programas, além de ser o sistema operacional em uso na rede de computadores de nossa faculdade (que, não à toa, se chama Rede Linux).

Para iniciarmos a produção do código relativo ao projeto, estabelecemos um ambiente de desenvolvimento, instalando, primeiramente, o GNUARM toolchain, mas depois substituindo-o pelo devkitARM, mais específico para nossos objetivos; o arquivo crt0.s em Assembly, contendo informações de inicialização de memória e registradores, executada antes do código do jogo propriamente dito; um arquivo contendo um script, chamado lnkscript que é utilizado pelo GCC, informando como organizar várias informações na memória do aparelho. Também tivemos de perder alguns fios de cabelo para elaborar um Makefile que funcionasse a contento.

Se no início pensávamos em programar no Emacs (boa parte do Asteróide foi criado nele), após a criação da ferramenta Ninpo Map Editor em Java, sob o IDE Eclipse, e verificando as facilidades que este IDE proporciona, decidimos configurar o Eclipse para editar código C, utlizando o plugin CDT, e criar um projeto ligado ao devkitARM, de forma a podermos criar arquivos binários compilados para a plataforma ARM.

Também tivemos de compilar, tendo como alvo o processador ARM, e instalar o GDB, a fim de depurarmos o programa. Para realizarmos os testes utilizamos o VisualBoyAdvance, um emulador de GBA disponível tanto para Linux quanto Windows.

Método de trabalho

Na elaboração do código-fonte, tanto do projeto quanto das ferramentas auxiliares, o método escolhido foi o Pair Programming, ou Programação Pareada. Neste método de trabalho, uma dupla de pessoas elabora código utilizando a mesma estação de trabalho. Isto facilita o trabalho, pois ocorrem menos erros, existem mais discussão sobre o projeto em si, aumentando a produtividade, no longo prazo. Esta técnica é especialmente útil caso os dois programadores estejam aprendendo ou se familiarizando com o projeto, as bibliotecas etc.

[voltar]

6. Execução

Demônio Hannya

O início do projeto se deu há muitos anos, quando surgiu a vontade de se produzir um jogo que pudesse rodar em um videogame comercial, e também pudesse ser experimentado por várias pessoas. Hoje isto é possível graças ao nosso curso aqui no BCC; à Nintendo, por produzir este fantástico aparelho portátil: o Game Boy Advance; e dos esforços de muitas pessoas pelo mundo, que também realizaram experiências semelhantes, documentando-as de forma a auxiliar outros incautos que se entreguem à produzir jogos eletrônicos.

Dando seguimento à nossa loucura, pesquisamos um bom material de consulta na Internet e encontramos vários textos e fontes valiosas de informações. Uma delas se trata de um trabalho de formatura deste mesmo curso, realizado por Fernando França, Gabriel Cesário e Wendel Scardua em 2004. Deste projeto aproveitamos principalmente os arquivos do Makefile e de Linkscript. As outras fontes e documentos estão em nossas referências.

Em nossas buscas constatamos que o desenvolvimento caseiro de jogos (ou homebrew games) para o GBA é bem disseminado, com várias pessoas criando jogos, aplicativos e bibliotecas completas de programação, utilizando as linguagens C, C++ e Assembly.

Escovando bits

Antes de entrarmos na produção de nosso já estimado jogo, Nukenin, decidimos que era melhor nos familiarizar com nosso ambiente. Não estamos acostumados a produzir este tipo de código, que envolve inevitáveis "escovações de bits", nem a efetuar o chamado cross-compiling, ou produzir código binário que será executado em uma máquina e processador diferente da utilizada para se compilar.

Asteróides

Desta maneira, o primeiro produto deste projeto foi um jogo, mas não nosso jogo principal, e sim um jogo simples, baseado no popular Asteroids (ou Asteróides), em que uma nave circula pelo espaço detonando qualquer asteróide que se atreva a passar em seu caminho.

Em nossa implementação do Asteróides, optamos por ter como objeto controlável o próprio asteróide, que deve, por sua vez, flutuar pelo espaço destruindo naves indefesas.

Imagem do jogo Asteróide

Asteróides

Em meio à programação do Asteróides verificamos que o emulador de GBA, o VisualBoyAdvance para Linux era um tanto deficiente em ferramentas úteis para desenvolvedores. Sua implementação para o sistema Windows é muito mais completa, possuindo diversas funções úteis, como um visualizador de tiles, outro de planos de fundo, de memória e de sprites. Felizmente existe o Wine na Rede Linux, um emulador de Windows para sistemas UNIXes. No fim das contas utilizamos um emulador de Windows para rodar um emulador de GBA para testarmos e depurarmos nosso código.

O jogo consiste apenas em um asteróide controlável pelo jogador, que deve destruir as naves que circulam pelo espaço. Este jogo serviu principalmente para testarmos algumas idéias, implementando alguns efeitos gráficos, como um background com rotação e alpha-blending, que dá a aparência transparente à imagem.

A ROM desta demonstração de jogo pode ser encontrada na seção 2 da segunda parte deste documento.

Nukenin

Após concluído (ou quase) o projeto Asteróides, finalmente iniciamos o jogo principal: Nukenin. O conhecimento sobre o hardware do GameBoy, adquirido produzindo-se o Asteróides, mostrou-se muito útil, bem como o editor de mapas. No entanto, um jogo de plataforma é muito mais difícil de se programar que um jogos do tipo do Asteróides, além de exigir maior cuidado com a arte e criação de gráficos.

Para se ter uma idéia, nos asteróides foram utilizados apenas dois sprites, o da nave e o do asteróide em si. A movimentação destas figuras foram obtidas através de um simples reposicionamento das mesmas na tela, e os efeitos de rotação foram obtidos através da utilização de matrizes de transformação linear. Já para se obter apenas o efeito do ninja correndo são necessários três sprites, e mais três para uma pequena animação dele parado. Estimamos que são necessários vinte sprites (ou frames de animação) para uma movimentação mínima do personagem, requerendo muito trabalho na parte artística do projeto.

Produzindo ferramentas

No decorrer da produção de nossa implementação do Asteróides sentimos falta de uma maneira mais fácil de se preparar os gráficos, tanto na confecção dos sprites quanto do plano de fundo, ou background.

Editor Ninpo em funcionamento

Ninpo Map Editor (beta 1)

A primeira ferramenta a ser desenvolvida foi o Ninpo Map Editor. É um programa destinado a ler imagens, no formato BMP paletizado, para depois gerar tiles dessa imagem. A partir destes tiles podemos criar mapas destinados ao nosso jogo facilmente, utilizando a interface gráfica do editor de mapas.

Tanto o mapa quanto os tiles podem ser exportados em formato C header, ou seja: código-fonte C. O Ninpo Map Editor foi desenvolvido em Java, visto a facilidade de se criar uma interface gráfica e a possibilidade de se gerar um programa multi-plataforma.

No site do projeto na SourceForge (http://sf.net/projects/gbagame) pode ser baixado o Ninpo Map Editor. Note que o programa, embora utilizável, carece de algumas funcionalidades, como poder salvar uma tarefa no meio do trabalho. Mas já se pode transformar uma imagem BMP paletizada em um arquivo contendo um vetor de pixels (ou seja, os tiles) e um vetor com a paleta, além de ser possível, a apartir desta imagem original, construir um mapa, em que cada tiles corresponde a algum tile da fiogura original, algo muito útil para se produzir backgrounds.

Algoritmos e implementações

Cálculo de Colisão

O cálculo utilizado para verificarmos se houve ou não colisão é muito simples. Para cada objeto temos definidos uma "caixa de colisão", que engloba toda a área do sprite, que representa graficamente o objeto, que desejamos que seja "sólido".

Uma maneira de delimitarmos esta caixa é estabelecermos uma largura e uma altura convenientes, alinhados sempre ao centro do objeto, e utilizar a seguinte função:

u8 colideObjeto(pObjeto obA, pObjeto obB){

	s16 leftA, leftB;
	s16 rightA, rightB;
	s16 topA, topB;
	s16 bottomA, bottomB;	

        leftA = obA->posX;
        leftB = obB->posX;
        rightA = obA->posX + obA->caixaLargura;
        rightB = obB->posX + obB->caixaLargura;
        topA = obA->posY;
        topB = obB->posY;
        bottomA = obA->posY + obA->caixaAltura;
        bottomB = obB->posY + obB->caixaAltura;
    
        if (bottomA < topB) return FALSE;
        if (topA > bottomB) return FALSE;

        if (rightA < leftB) return FALSE;
        if (leftA > rightB) return FALSE;

	return TRUE;
}
    

O que a função acima faz é simplesmente verificar se, dados dois objetos, a intersecção entre as áreas das caixas de colisão é nula ou não. Note que o cálculo verifica somente isto: intersecção entre caixas (ou retângulos). Caso seja necessário algum outro tipo de figura geométrica para representarmos a área de colisão de um objeto, uma função diferente terá de ser usada.

Já para limitarmos a movimentação do sprite pelo espaço de jogo, decidiu-se que cada tile que constitui o mapa possui uma característica que define se ele é chão, teto, parede, chão e parede etc. Ao acompanharmos a posição de nosso personagem principal é possível verificar quais os tiles que o cercam, e decidir o que ocorrerá de acordo com estas informações.

Scrolling

O scrolling se trata da movimentação da tela como um todo. É isto que dá a impressão de estarmos seguindo adiante no jogo, quando na verdade o personagem está parado em relação à tela enquanto todo o resto se movimenta. O GameBoy Advance fornece atributos muito simples para efetuarmos esta movimentação.

Existe embutido no sistema o conceito de background, que consiste em uma imagem de tamanho fixado entre 256x256 até 512x512 pixels. Após carregarmos esta imagem nos endereços de memória de vídeo podemos movimentá-la através de registradores mapeados em pontos específicos da memória, efetuando o desejado scrolling:

#define REG_BG0HOFS    *(u16*)0x4000010     //Background 0 Horizontal Offset
#define REG_BG0VOFS    *(u16*)0x4000012     //Background 0 Vertical Offset
#define REG_BG1HOFS    *(u16*)0x4000014     //Background 1 Horizontal Offset
#define REG_BG1VOFS    *(u16*)0x4000016     //Background 1 Vertical Offset
#define REG_BG2HOFS    *(u16*)0x4000018     //Background 2 Horizontal Offset
#define REG_BG2VOFS    *(u16*)0x400001A     //Background 2 Vertical Offset
#define REG_BG3HOFS    *(u16*)0x400001C     //Background 3 Horizontal Offset
#define REG_BG3VOFS    *(u16*)0x400001E     //Background 3 Vertical Offset
    

O problema nesta abordagem simples é justamente no tamanho fixo do background. Embora possamos configurá-lo para que sua imagem se repita tanto na vertical quanto na horizontal (tal como uma colcha de retalhos em que cada retalho retrata a mesma imagem), isto se torna uma limitação séria para nosso jogo, pois desejamos maior liberdade para desenharmos mapas e estágios tão grandes e diversificados quanto quisermos.

A imagem do background dividido em tiles

Background dividido em tiles

Desta maneira optamos por utilizar um modo de vídeo em que a imagem de background é fixada em 256 por 256 pixels. Efetuando as contas, isto nos dá um background formado por 32x32 tiles de 8x8 pixels:

Para conseguirmos obter estágios de tamanho arbitrário, cujo background não fosse apenas uma imagem se repetindo, resolvemos trocar fatias de tiles ainda não visíveis do background, devido ao posicionamento do personagem, por fatias de tiles da imagem que deve aparecer em um momento subseqüente, inferido pela movimentação do personagem (para frente, para trás etc.). Isto é possível pela relação entre os tamanhos do mapa do background, 256x256 no nosso caso, e do tamanho da tela, 240x160, dando uma margem de 2 colunas de tiles 8x8 na vertical e 12 linhas de tiles na horizontal não visíveis na tela.

Scrolling do background

Na figura acima podemos observar a tela do jogo à esquerda e o background, à direita, sendo atualizado. É nítida, na imagem, a faixa de transição entre uma figura e outra. À medida que o personagem anda para a direita, uma faixa, ou coluna, de tiles, logo à direita da área de transição é atualizada.

A fim de trocar as faixas do background, utilizamos uma função que recebe um mapa e o índice da coluna a ser trocada na área de memória especificada.

void swapBackgroundTileColumn(pBg bg, u8 c, u16 map[]){
	int i;
	for (i = 0; i < 32; i++){
		bg->mapData[i*32 + c] = map[i*32 + c] | (bg->palNumber << 12);
	}
}
    

Animação e movimentação do Sprite

A animação de um sprite é controlada por um timer. Este é incrementado a cada vez que os elementos da tela são desenhadas. Assim que o timer alcança um tempo de update, o tempo é zerado e a animação é atualizada para seu próximo frame. Uma função é responsável por trocar na memória a informação sobre qual vetor de tiles deve ser utilizado para desenhar a personagem no frame especificado.

u8 nukenin_idle_frames[] = { 0, 1, 2 };
u8 nukenin_idle_total = 3;
u8 nukenin_idle_update = 4;
u8* nukenin_idle(int t){
	switch ( t ){
		case 0: return (u8*) nukenin_idle_0_tile;
		case 1: return (u8*) nukenin_idle_1_tile;
		case 2: return (u8*) nukenin_idle_2_tile;
	}
	return 0;
};
    

O código acima mostra um exemplo de controle de animação, feita para quando o personagem principal está parado. A animação possui três frames, cada um espera um tempo de 4 iterações para que seja atualizado para o próximo desenho. À partir desses dados, a área de memória do Gameboy responsável pelos tiles dos sprites é atualizada, e a animação correspondente à ação do personagem é mostrada.

Frames de animação do Nukenin

Animação do personagem principal

Transformação Linear Afim

Para efetuarmos a rotação dos sprites (e também do background, se ele for do tipo conveniente), utilizamos a chamada matriz de transformação afim P.

Calculamos P a partir de duas outras matrizes: a matriz de rotação R e a matriz de escalonamento E. Estudando um pouco de álgebra linear (MAC300 - Métodos Numéricos em Algébra Linear no IME) temos os meios de calcular estas matrizes [VJIN]:

R(α) =
  cos(α)   -sin(α)  
sin(α)   cos(α)
,
E(sx, sy ) =
  sx   0  
0   sy
(1)

Para obtermos uma matriz A que faz o escalonamento e depois a rotação basta efetuar o seguinte cálculo:

A = R(α) · E-1(sx, sy ) =
  sx · cos(α)   - sy · sin(α)  
sx · sin(α)   sy · cos(α)
(2)

Mas para utilizarmos efetivamente a matriz P em nosso projeto, devemos saber como o hardware do GameBoy intrepretará estas informações. Isto porque basta fornecermos os elementos de P nos lugares "certos" para que a rotação/escalonamento do sprite ocorra. Temos, por fim, que a matriz P utilizada é a inversa da matriz A calculada em (2), ou seja:

P = A-1 =
  cos(α)   -sin(α)  
sx   sx
sin(α)   cos(α)
sy   sy
=
  pa   pb  
pc   pd
(3)

Resta colocar o algoritmo no GBA, ou seja, transformá-lo em linguagem C. Tomemos como exemplo a rotação de um sprite. Como visto na descrição dos sprites (seção 3), a estrutura encarregada de caracterizá-los encontra-se na OAM (vide tabela 1).

Mas, diferente das outras características do sprite, os quatro elementos da matriz P são distribuídos em quatro diferentes entradas do vetor OAMEntry. Como temos apenas 128 possíveis entradas neste vetor, podemos então definir 32 matrizes de transformação afim diferentes.

O código abaixo ilustra a utilização da OAM na configuração dos elementos de P. Repare que a cada 64bits (ou uma entrada em OAMEntry), os últimos 16 bits correspondem a um elemento de P.

typedef struct tagOAM_AFF_ENTRY
{
        u16 fill0[3]; /* Primeira entrada no OAMEntry */
        s16 pa;
        u16 fill1[3]; /* Segunda entrada no OAMEntry */ 
        s16 pb;
        u16 fill2[3]; /* Terceira entrada no OAMEntry */
        s16 pc;
        u16 fill3[3]; /* Quarta entrada no OAMEntry */
        s16 pd;

} OAMAffEntry, *pOAMAffEntry;
     

Para finalmente efetuarmos a rotação, após termos o sprite definido com todos os atributos da entrada na OAM configurados, basta aplicarmos a função:

void rotateSprite(OAMentry* sprite, 
                    OAMAffEntry* aff_entry, 
		    int angle, 
		    s32 x_scale, 
		    s32 y_scale)
{	
	aff_entry->pa = (x_scale *  cos(angle)) >> 8; 
	aff_entry->pb = (x_scale * -sin(angle)) >> 8; 
	aff_entry->pc = (y_scale *  sin(angle)) >> 8; 
	aff_entry->pd = (y_scale *  cos(angle)) >> 8; 

}
    

Utilizamos cálculo em ponto fixo, ou cálculo inteiro, pela incapacidade do processador ARM7 efetuar multiplicações e divisões com desempenho satisfatório. Assim, ao invés de utilizarmos realmente uma função que calcule o seno e o cosseno, temos dois vetores com os valores do seno e do cosseno mapeados para cada angulo:

    
const s32 SIN[360] = {
    0,    4,    8,   13,   17,   22,   26,   31,   35,   40,   44,   48, 
   53,   57,   61,   66,   70,   74,   79,   83,   87,   91,   95,  100, 
  104,  108,  112,  116,  120,  124,  127,  131,  135,  139,  143,  146, 
...
...
}

const s32 COS[360] = {
  256,  255,  255,  255,  255,  255,  254,  254,  253,  252,  252,  251, 
  250,  249,  248,  247,  246,  244,  243,  242,  240,  238,  237,  235, 
  233,  232,  230,  228,  226,  223,  221,  219,  217,  214,  212,  209, 
...
...
}
    

O que as funções sen() e cos() fazem é apenas consultar estes vetores e devolver o valor mapeado.

Visível no código acima está o tipo do conteúdo dos vetores: números inteiros. Como obtemos os valores corretos do seno/cosseno então? Tomando cos(0) como exemplo, temos que COS[0] = 256. Agora efetuamos o cálculo 256/28, que dá 1, ou, escovando bits, 256>>8.

Portanto, como os valores de seno e cosseno não estão representados em ponto flutuante, e sim em ponto fixo, devemos adaptar quaisquer números a serem operados com eles. Justificando o bitshift para esquerda em x_scale<<8 e o bitshift para direita no final de cada operação (((x_scale << 8 ) * cos(angle)) >> 8)). Este último bitshift converte a valor em ponto fixo para o respectivo valor em inteiro.

7. Finalizando

Ao final de um ano programando neste projeto conseguimos criar um jogo funcional, mas, infelizmente, faltando elementos que fizessem com que o jogo se tornasse mais "jogável", como um sistema de som, animações para o caso dos personagens receberem dano, mais fases e mais personagens inimigos.

Abaixo estão alguns screenshots do jogo em pleno funcionamento no emulador VisualBoy Advance. As figuras foram criadas com a versão do jogo de novembro de 2005.

telas do jogo em funcionamento

A ROM do jogo está na seção 2, junto de outros arquivos desenvolvidos neste projeto.

[voltar]

8. Referências

CESÁRIO, G., França, F., Scardua, W.: SpaceWarz Advance. (http://www.linux.ime.usp.br/~cef/mac499-04/monografias/fefranca/, /gabriel/, /articuno/)

CRAWFORD, Chris: The Art of Computer Game Design (http://www.vancouver.wsu.edu/fac/peabody/game-book/Coverpage.html)

DEVKITPRO: devkitARM toolchain for homebrew game development (http://www.devkitpro.org)

GRUBER, Diana: Action Arcade Adventure Set (http://www.fastgraph.com/makegames/sidescroller/)

HAPP, Tom.: CowBite Virtual Hardware Specifications. (http://www.cs.rit.edu/~tjh8300/CowBite/CowBiteSpec.htm)

HARBOUR, J.: Programming the Nintendo Game Boy Advance: The unofficial guide. (http://www.jharbour.com)

KERNIGHAN, B., Ritchie, D.: C: A linguagem de programação padrão ANSI. São Paulo, 1989. Ed. Campus.

ROGERS, Jason: PERN - The Nintendo Reverse Engineering Project. (http://www.thepernproject.com)

SINGH, Amit: UNIX® on the Game Boy Advance. (http://www.kernelthread.com/publications/gbaunix/)

GNUARM: Documentação oficial. (http://www.gnuarm.com/support.html)

NINTENDO: Corporate History (http://www.nintendo.com/corp/history.jsp)

VIJN, Jasper: TONC GBA Programming (http://user.chem.tue.nl/jakvijn/tonc/)

[voltar]

Parte II - Considerações pessoais

1. Desafios

Garça branca na chuva, Koson

O primeiro e principal desafio lançado foi exatamente o objetivo do projeto: criar um jogo completo. Mesmo tendo noção da dificuldade de tal empreendimento, tínhamos esperança de ter uma versão jogável ao fim do ano. Este objetivo foi alcançado em parte, pois muita coisa acabou não sendo implementada, tal como o sistema sonoro. Isto se deve em parte à falta de especificação do projeto no início. O que o jogo deve realmente conter? O que é realmente necessário e o que poderia ser considerado supérfluo? Isto faz diferença no longo prazo, pois tendíamos a nos perder em meio a alguns problemas de implementação de recursos que às vezes poderia ter sido deixado de lado.

Um motivo para este problema pode ser o fato de que, na realidade, o objetivo não era apenas criar um jogo, e sim criar um jogo e aprender a programar jogos. Isto validava qualquer tentativa de se inserir extras no projeto, tal como um efeito gráfico interessante, mas não vital, pois estaríamos aprendendo algo no decorrer do trabalho. Isto pode ser visto em nosso implementação de um background com rotação no Asteróides, efeito deixado de lado no jogo Nukenin.

O primeiro problema encontrado de ordem prática foi com nossa quota de espaço em disco. Ela não é suficiente para a instalação de muita coisa, e cada um dos arquivos utilizados é imprescindível para o projeto, tais como o GCC e o GNUARM toolchain, que já somam mais de 100MB. Assim colocamos o material no diretório /tmp, que não é considerado como espaço de nossa conta, e criamos um script que roda pela Rede Linux procurando por algum computador cujo diretório /tmp contenha o GNUARM. Achado o arquivo, o script copia o GNUARM e o descompacta automaticamente na máquina desejada. Isto foi deixado de lado posteriormente com a adoção, em meados de julho, do devkitARM, que, devido ao menor tamanho (cerca de 45MB), podia ser instalado em nossa conta.

No fim das contas, pudemos produzir uma versão demo de um jogo com boa qualidade. Mas permaneceu o desejo de termos um jogo completo criado por nós mesmos.

[voltar]

2. Disciplinas relacionadas ao projeto

As disciplinas cursadas no BCC mais relevantes para o projeto podem ser vistas abaixo:

As disciplinas de base, tanto matemáticas quanto computacionais, foram importantes para incutir as idéias de raciocínio abstrato e de formalização de idéias, gerando um pensamento um pouco mais ordenado. Isto foi importante tanto no projeto quanto em outras atividades, acadêmicas ou não.

Mesmo assim, o maior problema, sob meu ponto de vista, é a demasiada ênfase dada na formação acadêmica e científica do aluno, em detrimento do ensino de disciplinas voltadas mais ao desenvolvimento de sistemas e projetos grandes. Se é desejável que o aluno tenha discernimento, um bom raciocínio lógico e que saiba embasar suas afirmações de modo racional e científico, também é necessário uma preparação mais diversificada de qualidade, para podermos atuar tanto na área científica quanto no desenvolvimento de programas e projetos de médio e grande porte.

Uma parte deste material que considero importante - ao lado do conteúdo das indispensáveis disciplinas de base, como Análise de Algoritmos, Autômatos, Álgebra Linear e outras - é dada em Programação Orientada à Objetos (Design Patterns) e Engenharia de Software, embora nesta última a abordagem e forma como foi dado o curso se mostrou um tanto deficiente, enfocando detalhes de várias metodologias, sem o aprofundamento e utilização de nenhum.

[voltar]

3. Trabalho em equipe

Um dos heróis do Suikoden, Utagawa Kuniyoshi

Com nosso grupo sendo constituído por duas pessoas, algumas atividades acabaram sendo executadas por apenas um dos membros. O Edgar é o principal responsável pela produção e manipulação das imagens pertencentes ao jogo, como o desenho de tais imagens, digitalização e transformação em BMPs paletizadas. À mim coube a elaboração da documentação do projeto, que corresponde a boa parte da seção técnica e descritiva da monografia, READMEs, comentários do código fonte etc.

Mas esta divisão não era de forma nenhuma absoluta, pois muitas vezes eu tinha de criar figuras e imagens para servir de plano de fundo no jogo ou de ilustração na monografia, apresentação e pôster. Durante os trabalhos, como explicado na primeira parte deste documento, foi utilizado o método de programação pareada na maior parte do projeto.

[voltar]

4. O Futuro

Uma versão testável do jogo está pronta, mas não pararemos por aí. Pelo menos uma versão com alguns inimigos mais "espertos" e um estágio com início, meio e fim deve ficar pronta. Falta também inserir cenas de transição, ou animação, durante o jogo, para dar uma emoção maior ao jogador, além da sensação de que o jogo parece "de verdade".

Para tanto será preciso esmiuçar mais ainda alguns pormenores de nosso programa como um todo, assim como o hardware do GameBoy. Para se ter uma idéia, excluindo o uso da memória de vídeo, da memória RAM de 256KB - que parece à primeira vista muito pouco - utilizamos não mais que 2KB, mostrando que o jogo tem ainda muito a crescer e se desenvolver, e o aparelho, muito a ser aproveitado.

[voltar]

5. Conclusão

"Terminou o drama. Por que, pois, alguém ainda se adianta?
Porque um sobreviveu ao naufrágio"
Herman Melville - Moby Dick
Corvo em galho com neve, Koson

Programar em um aparelho diferente de um PC comum, ter de lidar com suas características peculiares de hardware, precisar conhecer este hardware tão bem quanto a biblioteca C, escovar bits e programar usando macros, tudo isto foi uma novidade para mim, novidades estranhas à primeira vista, mas que acabaram se mostrando muito divertidas.

Poder carregar seu jogo no bolso e mostrá-los para os amigos, isto também é muito gratificante, pois você obtém, além de alguns elogios, um feedback instantâneo de sua obra, podendo considerar estas observações no desenvolvimento posterior do projeto.

Apesar de tudo isto, não posso deixar de dizer que às vezes eu sentia muita falta de um Sistema Operacional a realizar operações básicas para mim, tal como uma alocação de memória durante um malloc, ou mesmo de dar avisos de segmentation fault, pois o jogo simplesmente roda no GBA, mostrando defeitos muitas vezes complicados de se depurar, principalmente no início do projeto, quando não tínhamos na cabeça como o aparelho funcionava em suas entranhas.

Mas passado o começo complicado, e com a máquina produtiva azeitada, o trabalho passou a fluir melhor, compensando os esforços e fios de cabelos arrancados inicialmente. Só espero que mais pessoas criem jogos para o GBA no IME, talvez implementados em um sistema 3D :-).

[voltar]

Apêndice

1. Ilustrações

As ilustrações no início das seções tratam-se de peças da arte Ukiyo-e, em que as figuras são entalhadas em madeira para depois serem impressas em papel. Esta arte é produzida desde o século XVII, tendo como motivos principais paisagens, peças teatrais e personagens míticos/históricos.

Literalmente, Ukiyo-e significa "figuras do mundo flutuante". Algumas das imagens foram aproveitadas na elaboração dos backgrounds do jogo.

Os nomes e artistas das figuras apresentadas seguem abaixo:

[voltar]

2. Material disponível

O material produzido durante este projeto pode ser obtido logo abaixo:

Pretendemos também disponibilizar mais materiais no site do projeto na Sourceforge:

http://www.sourceforge.net/projects/gbagame

[voltar]




THE END
thank you for playing!

Valid CSS Brasão da USP Logo do Eclipse Brasão do IME (busto do Arquimedes) SourceForge.net Logo Nedstat counter [5.dez.2005]