DevKitAdv 主要包括两部分,一是GCC++编译器,二是 GBA库.
GCC++编译器功能和我们常用的VC差不多,只不过少了个编辑源代码的文本编辑器(至少我没发现,我用的是EditPlus,UltraEdit也可以),还有就是--不支持类(class),真是让人头痛,只能用struct来替代.它的作用是把我们写的代码编译成二进制的可执行文件,当然这个可执行文件是相对GBA和GBA模拟器而言的.就象Windows里的EXE文件无法在Mac机上使用是一样的道理;
GBA库提供了图像,控制及声音一系列的函数,和GCC++配合使用. 下载地址:
http://occultforces.mine.nu/~darkfader/gba/files/devkitadv.zip
二. DevKitAdv 的安装
没啥好说的,解压后就可以直接使用,编译时设置DevKitAdv的路径就可以了,建议做一个批处理文件,比如 go.bat
set PATH=d:\\devkitadv\\bin;%PATH% cmd (win98是command)
三. 最简单的 GBA 程序 (t1)
// main.c
// 一些基本数据类型 typedef unsigned char u8; typedef unsigned short u16; typedef unsigned long u32;
#define REG_DISPCNT *(u16*)0x04000000 // 显示寄存器地址 #define VRAM 0x06000000 // 图像缓冲区地址 #define M5_VRAM 0x0600A000 // M5缓冲区地址 #define BACKBUFFER 0x010 // 双缓冲/背缓冲地址 #define PALETTE 0x5000000 // 调色板地址
#define MODE_3 0x03 // 240*160 15bits/单缓冲区 #define MODE_4 0x04 // 240*160 8bits/双缓冲区 #define MODE_5 0x05 // 160*128 15bits/双缓冲区 #define BG2_ENABLE 0x0400 // BG_2
#define SetMode(Mode) REG_DISPCNT=(Mode) // 设置显示模式的宏定义
// ----------- 主程序 ------------ int main() {
//设置屏幕模式,这里使用MODE_4 SetMode (MODE_4 | BG2_ENABLE);
}
1.MODE_5和MODE_3都是16bits,但MODE_3只有单缓冲,制作动画效果肯定没双缓冲好,因此排除MODE_3;
2.MODE_4是8bits,理论上256色对于掌机够用了,虽然16bits真彩的诱惑没有人想抗拒,可MODE_5只有160*128咧,在实际应用中建议还是使用MODE_4. 很简单吧--的确是的,现在要用GCC编译它: gcc -lm -o main.elf main.c
objcopy -v -O binary main.elf main.bin
你会看目录下多了个\"main.bin\这个就是能在GBA模拟器上执行的二进制文件!
教程中t1-t7目录为源程序目录,里面有个make.bat,修改代码后直接执行它就可以编译,但要注意我的devkitadv是装在D:,你要是装在别的盘就得改一下make.bat的path参数.
四. 在MODE_4背景层画图的 GBA 程序 (t2)
在GBA的MODE_4里画一幅图要经过3个步骤:
1. 把原始256色图像文件转换成 *.h / *.c 的数据文件,我们用的是 < BMP2GBA > ,这里以\"image.bmp\"为例,转换后我们就得到了一个\"image.h\"文件;
2. 在程序开头#include \"image.h\这样就能在程序中使用\"image.h\"定义的调色板和图像数据;
3. 在程序中把\"image.h\"定义的调色板和图像数据写入MODE_4背景层的调色板和图像缓冲区.
另外,GBA还有专为精灵设置的物体层,它的用法和背景层一样,只是功能有点不一样,地址是0x06000000.有关用这里就不详细说了,大家可以把精灵数据直接输出到物体缓冲区就可以了. 下面是源程序: ... ...
// 包含图像调色板和数据的头文件 #include \"gfx/image.h\"
// ----------- 全局变量 -------- // 系统调色板
u16* palette_mem=(u16*)PALETTE; // 图像缓冲区
u16* video_buffer=(u16*)VRAM;
// ----------- 函数定义 ---------
// MODE_4绘图函数
void Draw(u16* src_palette,u16* src_data,u16* dst_palette,u16* dst_data);
// ----------- 主程序 ------------ int main() {
// 设置屏幕模式,这里使用MODE_4 SetMode (MODE_4 | BG2_ENABLE);
// 在背景层画图,Palette和Data是在\"image.h\"定义的调色板和图像数据数组名 Draw(Palette,Data,palette_mem,video_buffer); }
// MODE_4绘图函数
void Draw(u16* src_palette,u16* src_data,u16* dst_palette,u16* dst_data) {
int loop,x,y;
// 写入目的调色板
for(loop = 0; loop < 256; loop++) dst_palette[loop] = src_palette[loop];
// 写入图像缓冲区 for(x = 0; x < 120; x++) {
for(y = 0; y < 160; y++) {
dst_data[(y) *120 + (x)]=src_data[(y) *120 + (x)]; } } }
编译后得到main.bin,然后在GBA模拟器里运行,就可以得到这样的结果:
大家看了可能有点失望~是啊,256色用来看PPMM实在... ...下一步我们就使用MODE_5来画16bits真彩的MM!
五. 在MODE_5画图的 GBA 程序 (t3)
在GBA的MODE_5里画一幅图也要经过相似3个步骤,只不过不需要调色板数据:
1. 把原始真彩图像文件转换成 *.h / *.c 的数据文件,我们用的是 < Targa2GBA > ,这里以\"image.bmp\"(240*160)为例,DOS窗口下进Targa2GBA目录,输入\"t2g mode5 image.bmp image.h\转换后我们就得到了一个\"image.h\"文件;
2. 在程序开头#include \"image.h\这样就能在程序中使用\"image.h\"定义的图像数据; 3. 在程序中把\"image.h\"定义的图像数据写入图像缓冲区. 下面就是源程序:
// 包含图像数据的头文件 #include \"gfx/image.h\"
// ----------- 全局变量 -------- // 图像缓冲区
u16* video_buffer=(u16*)VRAM;
// ----------- 函数定义 --------- // MODE_5绘图函数
void Draw(int x,int y,int w,int h,u16 *src_data,u16 *dst_data);
// ----------- 主程序 ------------ int main() {
// 设置屏幕模式,这里使用MODE_5 SetMode (MODE_5 | BG2_ENABLE);
// 在背景层画图,image是在\"image.h\"定义的图像数据数组名 Draw(0,0,240,160,image,video_buffer); }
// MODE_5绘图函数
void Draw(int x,int y,int w,int h,u16 *src_data,u16 *dst_data) {
int i,o,idst;
// 把源图像数据复制到图像缓冲区的指定地方 idst =(y*160)+x; for (i=0;i if (*src_data != 0) { dst_data[idst] = *src_data; } idst++; src_data++; } idst += (160-w); } } 编译后运行结果: 呵呵,是不是很棒?这是24bits \"image.bmp\"(240*160) 在MODE_5 16bits下的显示结果,240*160的图像到了MODE_5下的160*128,也就是MODE_5的满屏显示画面只有这么大. 六. 全屏显示的 MODE_5 GBA 程序 (t4) 由于GBA不支持线性的图像变换,因此得到的结果会产生一些马赛克的现象,现在还是附上这个变换函数和最终结果,其实质量还是可以接受的,大家可以试试使用这个新的MODE_5. // 切换到新MODE_5全屏模式,page为缓冲区,原理是把显示寄存器数据X,Y交换,得到128*160的显示,GBA就会全屏显示. void SetFlipMode(int page) { u16 *ioreg=(u16*)0x4000000; *ioreg=5+((page&1)>>4)+(1>>10); ioreg[0x10]=0; ioreg[0x11]=256; ioreg[0x12]=128; ioreg[0x13]=0; } int main() { // 设置屏幕模式,这里使用MODE_5 SetMode (MODE_5 | BG2_ENABLE); // 切换模式 SetFlipMode(0); // 在背景层画图,image是在\"image.h\"定义的图像数据数组名 Draw(0,0,240,160,image,video_buffer); return(0); } Come on,来看看结果: 此时就是新MODE_5下的全屏模式,图像被翻转并放大,因为MODEL_5的分辨率是160*128,而上面的程序画的是240*160的图像,所以有部分就看不到了. 如果要象MODEL_4那样正常显示我们的图像,那么只有对原图像文件做点手脚才行了,下面就是处理新MODEL_5下图像文件的步骤: 1.把原图像顺时针旋转90度; 2.再将图像水平翻转; 3.调整图像大小至 160*128. 这里是示意图: 原图 1 2 3 OK,把步骤3后的图像编译进bin就可以得到: 大家可以看到有轻微的马赛克,但比起256色的效果还是要好一些,到底使用什么模式还得看做的游戏类型,如果是一些经典RPG/ACT/SLG等还是建议用MODE_4. 七. GBA的双缓冲显示(t5) 大家在做上面MODEL_5的程序时一定会发现图像在闪动(第六节的240*160的MM象被破了相...),而MODEL_4下却比较稳定--这是因为MODEL_5下要处理16bits(实质上是15bits)的图像,数据量比MODEL_4下的8bits大很多,在没使用双缓冲的情况下,图像填充时就会造成闪烁,这就是为什么我们抛弃了MODEL_3的原因... 原理也很简单,图像在背缓冲区里填充好之后再直接输出到前缓冲区显示,程序里就是一个 \"等待同步-> 交换缓冲\" 的过程: ... ... // ----------- 全局变量 -------- // 图像缓冲区 u16* video_buffer=(u16*)M5_VRAM; // ----------- 函数定义 --------- ... ... // 等待缓冲区数据同步 void WaitSync (); // 交换缓冲区内容 void SwapScreen (); // ----------- 主程序 ------------ int main() { // 设置屏幕模式,这里使用MODE_5 SetMode (MODE_5 | BG2_ENABLE); while(1) { // 在背景层画图,image是在\"image.h\"定义的图像数据数组名 Draw(0,0,240,160,image,video_buffer); WaitSync(); SwapScreen(); } } // 等待缓冲区数据同步 void WaitSync () { while (*(volatile u16*)0x4000006<160) {}; } // 交换缓冲区 void SwapScreen () { if (REG_DISPCNT & BACKBUFFER) { REG_DISPCNT &= ~BACKBUFFER; video_buffer = (u16*) M5_VRAM; } else { REG_DISPCNT |= BACKBUFFER; video_buffer = (u16*) VRAM; } } 八. GBA 的按键输入(t6) 讲了老半天的图像,虽说是对着MM,但大家一定也有点烦了,我们现在就换个方向,来看看GBA的控制. ... ... // 按键控制 #define KEY_A 1 #define KEY_B 2 #define KEY_SELECT 4 #define KEY_START 8 #define KEY_RIGHT 16 #define KEY_LEFT 32 #define KEY_UP 64 #define KEY_DOWN 128 #define KEY_R 256 #define KEY_L 512 volatile u32* KEYS = (volatile u32*)0x04000130; // 包含图像调色板和数据的头文件 #include \"gfx/image.h\" // ----------- 全局变量 -------- // 图像缓冲区 u16* video_buffer=(u16*)M5_VRAM; // 图像显示坐标 int img_x,img_y; // ----------- 函数定义 --------- // 按键控制 void KeyAction(); ... ... // ----------- 主程序 ------------ int main() { // 设置屏幕模式,这里使用MODE_5 SetMode (MODE_5 | BG2_ENABLE); while(1) { // 处理按键事件 KeyAction(); // 在背景层画图,image是在\"image.h\"定义的图像数据数组名 Draw(img_x,img_y,96,64,image,video_buffer); WaitSync(); SwapScreen(); } } // 处理按键事件 void KeyAction() { // 上方向键 if(! ( (*KEYS) & KEY_UP) ) { img_y+=5; } // 下方向键 if(! ( (*KEYS) & KEY_DOWN) ) { img_y-=5; } } 简单吧,这个例子响应上下按键来控制图像上下移动.我们没有先清除背缓冲就在里面填充图像数据,所以你可以看到图像移动后,原来位置上的图像仍在背缓冲里,这样可以很清楚的看到双缓冲的工作过程. 什么?太丑了,那好,加一个背景就OK了嘛... ...后一幅是带ALPHA. #include \"bg.h\" ... ... // 先画背景 Draw(0,0,160,128,bg,video_buffer); // 再画MM Draw(img_x,img_y,96,64,image,video_buffer); ... ... 九. 简单声音输出(t7) Simple is the Best(简洁至上),这里我们使用一个现成的声音模块(Troff Player,by Vova & Serge).这里还要用到一个转换工具< MOD2GBA >,用来把MOD音乐文件转换成GBA的 *.c / *.h 声音数据文件.MOD和MIDI差不多,但支持更多更强的效果. // MOD数据文件 #include \"song_data.h\" // MOD播放函数文件 #include \"modplayer.h\" // ----------- 主程序 ------------ int main() { //设置屏幕模式,这里使用MODE_4 SetMode (MODE_4 | BG2_ENABLE); // 初始化声音(声道数,音量) InitSound(2, 7); // 初始化音乐(节拍,循环) InitSong(20000, 0); while(1) { // 更新音乐播放状态 UpdateSong(); } } OK,就这么EZ. 十. 用图块建立可滚动/缩放/旋转的背景(t8) 这一节主要是源程序中注释为主,这里就不详细说明了.\"gba.h\"包含基本宏定义,\"maths.h\"是sin/cos乘256后的值数组,\"main.h\"包括了我们定义背景结构及操作背景的函数. 程序中的地图背景是由不同的图块所构成,而这些图块统一紧挨着放在一个图像文件,这样每个图块就会有一个索引号;地图信息只要记录这张地图里共有多少个 单位(图块)以及每个单位对应的图块索引号就OK了,在例子中\"gfx/tiles.h\"就是图块大本营,而\"gfx/level1.h\"则是图块索引排列表.地图工具为\"map editor beta 4\". 背景的滚动/缩放/旋转是通过一系列的简单数学计算,修改GBA系统提供的一些背景属性来完成,因为是由硬件来完成背景的操作(MODE_1),所以速度很快,我还有个MODE_5下直接修改像素点位置来完成旋转的例程,待会儿大家可以比较一下. 十一. MODE_5下的背景旋转(t9) 这个例子通过计算旋转后像素的新位置来完成旋转,原理很简单,可以直接应用到精灵上. // ----------- 主程序 ------------ int main() { // 角度 int i=0; // 设置屏幕模式,这里使用MODE_5 SetMode (MODE_5 | BG2_ENABLE); while(1) { // 在背景层画图,image是在\"image.h\"定义的图像数据数组名 Draw(0,0,128,128,bg,video_buffer); // 将旋转后的背景数据写入图像缓冲 RotateBG(bg,video_buffer,i++); if (i==360) i=0; } } // 旋转背景 void RotateBG(u16 *src,u16 *dst,int angle) { int x,y,ys,yc,tx,ty; unsigned int srcptr=0,dstptr =0; // 计算旋转后像素的指针 for (y=-80;y<80;y++) { ys = y * Sin [angle]; yc = y * Cos [angle]; for (x=-80;x<80;x++) { tx =(((x*Cos [angle]) - ys))>>7; ty =(((x*Sin [angle]) + yc))>>7; srcptr = (((ty<<7)+tx+(160*80+80)) & 16383); dst [dstptr] = src [srcptr]; dstptr++; } } } 十二. GBA下的中文显示 呵呵,英文的还没来得及写就先把中文搞掂了~~~~ 这里所做的程序核心技术来自于DOS下的中文显示技术.其实和老外用位图来显示26个英文字母原理大同小异,我们使用16*16点阵字库来完成中文的显示. 大家一定知道在输入法里有一个从没用过区位(内码)输入法,这个输入法使用4个数字来完成中文输入----估计没有谁会用它来和MM聊天.中文字库其实也就可以看成一张很大的位图,上面按一定顺序画满了常用的汉字,而区位(内码)输入法的4个数字代表了一个汉字在字库里所在的区和具体位置. 举个例子,\"说\"字的区位码是\"4321\当我们选择区位输入而且在记事本里输入\"4321\输入法程序就会马上到字库里4321区域,然后把4321区域的点阵(位图)数据传给系统显示出来,这样\"说\"字就出来了. 在GBA里显示中文是一样的原理,但由于GBA没有文件输入/输出函数,我们不得不先把二进制的点阵字库转换成数组存储数据,包括区位码和点阵(位图)数组.这里就不详细说明原理了,我为大家做好了一个 转换工具----\" HZK2GBA \"(汉字库->GBA).附带源程序^_^. 你首先得把你所有的文字做成一个文本文件,比如\" zk.txt \然后执行\" hzk2gba zk.txt \"来得到\" GBAZK.h \"文件,这就是GBA可用的字库数组头文件. 这样做的目的是不用把字库里所有的汉字送入GBA,只留下我们用到的汉字,可以很大的节省空间和内存. 接下来的工作就是利用得到的\" GBAZK.h \"在GBA里写下你想写的话. ... ... #define RGB(r,g,b) ((r)+(g<<5)+(b<<10)) // 设置GBA的色彩 #define MAX 10240 // 最大字符数 // 包含中文字库数据的头文件 #include \"gfx/GBAZK.h\" // ----------- 全局变量 -------- // 图像缓冲区 u16* video_buffer=(u16*)VRAM; // ----------- 主程序 ------------ int main() { // 设置屏幕模式,这里使用MODE_5 SetMode (MODE_5 | BG2_ENABLE); // 写入字符 DrawText(\"水银教程\ } //- 写中文字符函数 ---------------- void DrawText(unsigned char *str,int x,int y,int r,int g,int b) { int i,j,k,n; // 字符所在区,位,实际位置 int qu, wei, location; while(*str) { // 得到单字的区位码 qu = *(str++)-0xa0; wei = *(str++)-0xa0; location = qu*94+wei; // 在字库里查找 for(n=0 ;n // 根据字符数据画出字符 for(i=0;i<16;i++) for(j=0;j<2;j++) for(k=0;k<8;k++) if(convert(ZKDATA[n*32+i*2+j],7-k)) DrawPixel(x+j*8+k,y+i,r,g,b); x+=16; } } } } int convert(u16 str,int n) { return((str>>n)&0x1); } //- 画点函数 --------------------- void DrawPixel(int x, int y, int r, int g, int b) { video_buffer[x+y*160] = RGB(r,g,b); } 就这么多了,字体样式的问题得等下一步解决TrueType字体显示才行,字体大小嘛,大家自己用背景/物体层的缩放或是直接缩放像素的方法试试.至于英文的显示~~~~~ ^_* ,好困噢~~~~ 下回见!!!!! 下面我将一一讲解上篇的程序 前三行: typedef unsigned char u8; typedef unsigned short u16; typedef unsigned long u32; 定义GBA程序自己的数据类型,因为这样我们就很容易知道每个数据在GBA中占的字节数. #define REG_DISPCNT *(u16*)0x04000000 // 显示寄存器地址 REG_DISPCNT是设置显示模式的寄存器.GBA中有mode0-mode5六个显示模式.我们这个程序的显示模式是mode4,也就是240x160的8位色(256色)的双缓冲显示模式.比如mode3,就是240x160的16位真彩色的单缓冲显示模式. #define VRAM 0x06000000 // 图像缓冲区地址 // 调色板地址 #define PALETTE 0x5000000 VRAM就是GBA的显示内存,简单地说VRAM中的数据就是屏幕上的图象.跟我们的平常用的计算机一样的.PALETTE就是调色板地址,简单地说就是装图像调色板的地方. #define MODE_4 0x04 // mode4标志 // BG_2标志 #define BG2_ENABLE 0x0400 MODE_4和BG2_ENABLE都是设置REG_DISPCNT的值. 我们再看看main函数主要做了些什么. 首先是SetMode (MODE_4 | BG2_ENABLE); 把显示模式设置成mode_4.然后是执行Draw函数,把我们刚刚转换的helloworld.h里面的图像数据和调色板数据复制指定的内存去.在Draw这个函数里面,都是使用了for循环一个一个地把调色板数据和图象数据复制到内存中去的. 前面提到过,GBA中MODE4支持的是240*160并且256色的显示方式,那么显示内存VRAM中一个字节就对应屏幕上的一点的颜色.这么算来,整张240*160大小的屏幕就应该有240*160=38400个字节.所以我们在helloworld.h中的helloworld_gfx有38400/2=19200个元素(一个u16是占两个字节).而且我们还可以通过公式来计算得到屏幕中一点对应在VRAM的地址.这样就可以得到下面的写点函数.特别指出的是,对于VRAM的访问只能是u16大小的数据类型,而不能是u8(包括char).而一个u16类型的数据表示着两个点的信息,低8位对应的是前面的点,高8位对应的是后面的点. 所以我们需要一些算法来实现对屏幕中单独一个点的读写,请看下面的写点函数 void SetPixel(int x,int y,u8 color) { register u16 *tc; if(x&1) // 如果x是奇数 *tc=((*tc&0x00FF)+(color<<8)); *tc=(*tc&0xFF00)+color; tc= video_buffer+y*120+x/2; else } 其中video_buffer就是显示内存VRAM的地址.屏幕上的点与VRAM中的值是按线性一一对应的.VRAM中第1个字节的信息就是屏幕中(0,0)这个点的颜色在调色板上的引索,第2个字节就是屏幕(1,0)的颜色信息,第3个字节就是(2,0)的颜色信息,一次类推.到第241个字节就是屏幕(0,1)的颜色信息.然后第242就是(1,1)的信息...由此关系,我们可以找出公式,在屏幕上(x,y)点的信息应该是储存于VRAM中第 y*240+x 个字节里,那么我们如果要画(x,y)这一点,就只需要修改VRAM中第y*240+x个字节的信息就OK了.可是这里又有个问题困扰着我们,那就是上面黑体字写的,GBA中对于VRAM的访问不能是单单的一个字节的访问,而必须是16位两个字节的访问.这也就是说你每次写点的同时会影响到旁边的点的信息.还好,我们可以通过简单的位运算可以在避开影响旁边点的同时,完成对(x,y)的写点工作.代码已经在上面了.由于*tc是16位的指针,所以tc加1就等于移动了16位两个字节的距离.所以在代码中,tc=video_buffer+ y*120+ x/2 而不是前面所说的tc=video_buffer+ y*240 +x.至于后面的代码我也就不多讲了,如果你觉得有问题,那么应该去查看一下C语言书籍. 通过这个函数,你就可以在屏幕上画线,画圆,画矩型等,甚至还可以写汉字. 4.更多的资料和信息 GBA上的mode4下第一个程序虽然做出来了,但是想要真正地做GBA开发那还远远不够的.GBA里面支持Bg(Background)背景,OBJ(Object)精灵等东西,那些才是GBA游戏中常用的技术.通过Bg和OBj才能进一步使用GBA硬件上的Alpha混合,Face in/out,马塞克等特效支持.可惜我不可能在这里把这些东西都详细讲解出来.你可以到www.gpgame.net网站上找到我写的十分详细的GBA资料,名字叫做GBA探索日记.同时也可以到www.gbadev.org去查看许多国外的教程.www.gbadev.org是个十分齐全的GBA开发网站,你可以在上面找很多国外的朋友写的demo,开发工具,源代码,文章等等.虽然目前国内还没有正式的GBA开发网站,但是可以到http://www.wodenstar.com/cgdn/bbs/index.php 论坛上找到很多已经在做GBA开发的朋友.在这里我要提醒大家的是,开发GBA程序其实是很简单的事情,困难的是资料的缺乏,尤其是中文资料.最后我把我的联系方式告诉大家,如果大家在做GBA过程有什么问题,希望我能帮上你. 3.第一个GBA程序 现在我们开始我们的第一个GBA程序.在这个程序中我们将在屏幕上显示一张的图片. 首先你建立一张240*160大小,256色的bmp图片,比如这样: 名字就叫helloworld.bmp.打开刚才下载的Kaleid_1-2-3程序.选中菜单File中Open,打开这个helloworld.bmp文件,再选中菜单File中Convert and Save.出现对话框: 如图一样选择各项选择,特别注意的是一定要选择\"BitMap Graphics(Mode 4)\"和\"Save As C Source\",并且去掉\"#include mode0-2的BG与VRAM GBA的VRAM总共96KB,在mode3-5中,VRAM跟计算机的显示内存一样.屏幕上一个点对应一个显示内存地址. 可以看看下面两个函数,都是在mode4下的写点函数. void PlotPixel(int x,int y, unsigned short int c) { m_VideoBuffer[(y) * 120 + (x)] = (c); } void SetPixel(int x,int y,u8 color) { register u16 *tc; tc=m_VideoBuffer+y*120+x/2; if(x&1) *tc=((*tc&0x00FF)+(color<<8)); else *tc=(*tc&0xFF00)+color; } 由于GBA支持的最小数据传输单位为16位,而16位在mode4(256色)下为两个点的信息. 所以上面的PlotPixel是对两个点的写法.而SetPixel是通过一些简单的位移运算来实现对单个点的写入. 特别指出的是一个VRAM里的u16的值低8位对应的是前面的点,高8位对应的是后面的点. 说了这么多mode4的,下面看看GBA最精彩的部分mode0-2 设置REG_DISPCNT这个寄存器 不说多了,它就是指定你要显示的模式和支持的BG. 比如看这个的语句: *(vu16 *)REG_DISPCNT = DISP_MODE_1 | DISP_BG0_ON ; 这段语句就是说让GBA支持MODE1的显示模式,并支持BG0 当然你也可以 *(vu16 *)REG_DISPCNT = DISP_MODE_1 | DISP_BG0_ON | DISP_BG1_ON |DISP_BG2_ON; 但是你不能把BG3也加进来.因为mode1只能支持BG0,BG1,BG2. mode0-2都是运用tile(图块)先来组成整个屏幕的. 而mode0-2每个都有不同的BG 一个屏幕可以由多个BG组合显示出来.比如你可以安排地面为一个BG,天空为一个BG等等. 每个BG对应一个寄存器 Text BG:就是不能旋转的,似乎只能使用16色.当然,如果使用16色的调色板,当然可以使用最多到16个调色板. Rotation BG:就是可以旋转的,放大的.只能使用256色.如果使用了256色的调色板,就不能使用16色的调色板了.因为GBA的BG调色板就只有256x2=512那么大. 再看看不同模式下的BG分配 mode 0: 4个Text BG mode 1: Text BG: BG0 BG1 Rotation BG: BG2 mode 2: Raotation BG: BG2,BG3 接下来就看看控制这些BG的寄存器: 1.Text BG 在AgbLib任天堂提供的官方开发包里面是这么写出它的寄存器 #define REG_BASE 0x04000000 // Registers #define REG_BG0CNT (REG_BASE + 0x8) // BG 0 Control #define REG_BG1CNT (REG_BASE + 0x8) // BG 1 Control 2.Rotation BG 在AgbLib任天堂提供的官方开发包里面是这么写出它的寄存器 #define REG_BASE 0x04000000 // Registers #define REG_BG2CNT (REG_BASE + 0x8) // BG 0 Control #define REG_BG3CNT (REG_BASE + 0x8) // BG 1 Control Poriority是指的是显示的优先权. 0最高,4最低. 再看看关于BG大小的问题. Screen Size Setting Size Screen Data Size Screen Data 00 01 10 11 为了显示一张BG. 你可以通过AgbLib中提供的bmp2map.exe来把一张bmp文件转换成GBA可以用的BG数据. BG数据分三块. 1.Character Data 前面已经说了.mode0-2下的GBA屏幕是由图块组成的.这些图块都是8x8大的. 这些图块有些人把它叫做Tile,也有些人把它叫做Character(AgbLib里面就是这么叫的). 这些数据就是保存这些8x8图块的图象数据的. 2.Map Data 256×256 512×256 256×512 512×512 2 Kbytes 128×128 4 Kbytes 256×256 4 Kbytes 512×512 256 Bytes 1 Kbyte 4 Kbytes Text Screen Rotation/Scaling Screen Screen Screen 8 Kbytes 1024×1024 16 Kbytes Map Data可以说是图块的引索.它是记录Character Data的在屏幕中的排布的数据. 3.Palatte Data 这个不用说了,就是调色板 在显示一张BG前,应该把上面的上部分数据传输到GBA内存中. 看看下面的代码吧: DmaArrayCopy(3,e1_Character, BG_VRAM, DmaArrayCopy(3,e1_Map, 32); 32); 32); BG_VRAM+0x8000, BG_PLTT+32*2, DmaArrayCopy(3,e1_Palette, DmaArrayCopy是个数据传输的函数.当然,你也可以使用for(...)一个字节一个字节地写.但是GBA里有3个DMA通道(就是不经过CPU,专门用来传输大量数据的专线),它应该是就像memcpy那样的功能. 上面的代码就是将e1图像文件Character,Map,Palette传输到GBA中对应的内存. 特别指出的是在使用16色的调色板的时候,你如果用bmp2map.exe来转换.那么就要注意这个程序中-p这个参数.它指出了这个Map用的哪个Paletee.比如我上面的那个e1的BG就是使用了-p2,即使用的第2个调色板.那么它在GBA中对应的内存地址应该是BG_PLTT+32*2(16色的调色板总共占32个字节). 你可能要问BG_VRAM,BG_PLTT是什么. #define PLTT 0x05000000 #define BG_PLTT (PLTT + 0x0) #define VRAM 0x06000000 #define BG_VRAM (VRAM + 0x0) 它们其实就是VRAM,Palette在内存中的指定位置. Map Data,Character Data都是放在VRAM中的. 在mode0-2中,VRAM被分成许多Block,每个2KB(0x800).Character Data比较大一点,它一个要占8个Block(16KB).Map Data比较小一点,它一个占一个Block. 还是看看这张十分有用图片 Character Base Block的值为0-3 Screen Base Block(就是装Map Data的)的值为0-31 当然,不能让他们重复,否则先写的数据就被覆盖了. 再看看设置BG的寄存器的代码吧: #define TEXTBG_SIZE_256x256 #define TEXTBG_SIZE_256x512 #define TEXTBG_SIZE_512x256 #define TEXTBG_SIZE_512x512 #define ROTBG_SIZE_128x128 #define ROTBG_SIZE_256x256 #define ROTBG_SIZE_512x512 0x0 0x8000 0x4000 0xC000 0x0 0x4000 0x8000 #define ROTBG_SIZE_1024x1024 0xC000 DmaArrayCopy(3,e1_Character, BG_VRAM, DmaArrayCopy(3,e1_Map, 32); 32); 32); BG_VRAM+0x8000, BG_PLTT+32*2, DmaArrayCopy(3,e1_Palette, *(vu16 *)REG_BG0CNT = BG_COLOR_16 | TEXTBG_SIZE_256x256 | BG_PRIORITY_0 | 16 << BG_SCREEN_BASE_SHIFT | 0 << BG_CHAR_BASE_SHIFT; 你对应前面的图看看. 16是Screen Base Block的值,0是Character Base Block的值. 0x8000就是Base Block 16的对应偏移址,0是Base Block 0的对应偏移址 GBA硬件上对有很多对BG处理的支持,比如旋转,移动,放大,Alpha,马塞克. 我们先看看BG的移动. 这同样也是通过对GBA的寄存器的写入来实现的. 在BG2(Rotation BG)中有两个寄存器控制它的位置.REG_BG2X,REG_BG2Y 还是看看它们的定义 #define REG_BASE 0x04000000 // Registers #define REG_BG2X (REG_BASE + 0x28) // BG 2 Start X Coordinate #define REG_BG2Y (REG_BASE + 0x2c) // BG 2 Start Y Coordinate 它们是只能写的寄存器,你可不要去读它们,否则读不出来什么有用的数据的. 但是你可以去写它们: s32 x=10<<8; s32 y=10<<8; *(s32 *)REG_BG2X=x; *(s32 *)REG_BG2Y=y; 需要记住的它们都是32位的数据,而且是有符号的. x增大,BG向左移,x减小,BG向右移. y增大,BG向上移,y减小,BG想下移. 当x或y每改变256,BG移动1个像素. 所以上面的x,y都使用了<<8 GBA探索日记(二) OBJ及OAM OBJ就是指的一个个的精灵,或者说就是GBA中的小块小块的图片.它其实跟BG差不多.只是它要小一些.它也算个图层.比如GBA中的RPG游戏就喜欢把一个个的人物用OBJ来实现.这样方便控制他们的位置.当然,从GBA探索日记(-)中也可以看到BG的控制其实也是很方便的.而OBJ和BG的差别就是大小. 设置显示模式 如果你的程序里要用OBJ那么, 在设置显示模式的时候需要把DISP_OBJ_ON和DISP_OBJ_CHAR_1D_MAP加进去 *(vu16 *)REG_DISPCNT = DISP_MODE_1 | DISP_OBJ_ON | DISP_OBJ_CHAR_1D_MAP; #define DISP_OBJ_CHAR_1D_MAP 0x0040 // OBJ Character Data 1D Mapping #define DISP_OBJ_ON 0x1000 // OBJ ON 下面就直接来看看OBJ在GBA中怎么实现的. 首先要说明的就是OBJ在GBA中显示也是由一个个8x8的小图块拼成,跟BG一样. 共有下面12种大小. 0000: 8 x 8 1000: 8 x 16 0001: 16 x 16 1001: 8 x 32 0010: 32 x 32 1010: 16 x 32 0011: 64 x 64 1011: 32 x 64 0100: 16 x 8 0101: 32 x 8 0110: 32 x 16 0111: 64 x 32 OBJ的数据分三部分. 1.Character 它跟BG的数据格式是一样的,都是由8x8的小图块的图像数据.而且你也可以通过转换BG的工具来转换OBJ的Character.所以说BG和OBJ的数据结构基本是一样的.不过所有的OBJ总共的Character不能超过32K. 256色的OBJ能有512个Characters,每个Characters占8*8=64bytes.而16色的Characters能有1024个.每个Character占8*8/2=32Bytes.似乎比一个BG的256个Character多?但是BG可以有多个,不同的BG可以有不同的Character,而所有OBJ只能共用这512个Charater. 2.Palette 调色板就不用说了,它跟BG一样,支持一个256色调色板或16个16色的调色板. 3.OAM OAM就是OBJ Attribure Memory的缩写.它就是控制OBJ属性的数据部分.比如OBJ的位置,大小等数据部分就放在这里.每个OBJ的OAM占8个字节,OAM是从内存中07000000h到070003FFh,共1024=8*128,所以OAM总共能有128个. 如何创建一个OBJ呢? 前面我就说过通过转换BG的工具也可以用于转换OBJ. 以前转换BG我建议使用的是AgbLib里的bmp2map.exe,同样,转换OBJ也同样用bmp2map.exe这个工具. 比如我把Character Data放到sprite_gfx这个数组里,把Palette放到sprite_pal里面.(OBJ中没有Map Data,你的转换时可以把Map data也转换进来,只不过后面用不着) 然后在程序里写入: DmaArrayCopy(3, sprite_gfx,OBJ_MODE1_VRAM,32); DmaArrayCopy(3, sprite_pal,OBJ_PLTT,32); 把sprite_gfx,sprite_pal拷贝到指定内存中去. 这里又设计到OBJ_MODE1_VRAM和OBJ_PLTT两个内存地址 还是看看AgbLib中是怎么定义它们的吧. #define PLTT 0x05000000 // Palette RAM #define PLTT_END (PLTT + 0x400) #define BG_PLTT (PLTT + 0x0) // BG Palette RAM #define BG_PLTT_END (PLTT + 0x200) #define OBJ_PLTT (PLTT + 0x200) // OBJ Palette RAM #define OBJ_PLTT_END (PLTT + 0x400) #define VRAM 0x06000000 // VRAM #define VRAM_END (VRAM + 0x18000) #define BG_VRAM (VRAM + 0x0) // BG Character/Screen RAM #define OBJ_MODE0_VRAM (VRAM + 0x10000) // OBJ Character RAM #define OBJ_MODE1_VRAM (VRAM + 0x10000) #define OBJ_MODE2_VRAM (VRAM + 0x10000) #define OBJ_MODE3_VRAM (VRAM + 0x14000) #define OBJ_MODE4_VRAM (VRAM + 0x14000) #define OBJ_MODE5_VRAM (VRAM + 0x14000) #define OBJ_VRAM_END (VRAM + 0x18000) 这里我把BG中的地址也包含近来了,你可以对比一下BG和OBJ中的地址. 然后就是设置OAM(OBJ Attribure Memory). 这里先看看官方文档中是怎么说这个OAM的数据结构的. 特别指出的是上面表格中Character Name是指这个OBJ的起始Character的引索 我将介绍两中方法来设置. 第一种方法很直观,也很简单.这是我在网上看到最多的. 首先定义了数据结构 typedef struct tagOAMEntry { u16 attribute0; u16 attribute1; u16 attribute2; u16 attribute3; }OAMEntry, *pOAMEntry; //attribute0 #defines #define ROTATION_FLAG #define SIZE_DOUBLE #define MODE_NORMAL 0x100 0x200 0x0 0x400 0x800 #define MODE_TRANSPARENT #define MODE_WINDOWED #define MOSAIC #define COLOR_16 #define COLOR_256 #define SQUARE #define TALL #define WIDE //attribute1 #defines #define ROTDATA(n) 0x1000 0x0000 0x2000 0x0 0x4000 0x8000 ((n)<<9) 0x1000 #define HORIZONTAL_FLIP #define VERTICAL_FLIP #define SIZE_8 0x2000 0x0 0x4000 0x8000 0xC000 #define SIZE_16 #define SIZE_32 #define SIZE_64 //atrribute2 #defines #define PRIORITY(n) #define PALETTE(n) 然后写入代码: OAMEntry sprites[128]; // 这里把总共128个OBJ都定义了,不过我们现在只使用第一个 void InitializeSprites() { } void CopyOAM() { } u16 spriteY=100; u16 spriteX=100; u16 loop; u16* temp; u16* _OAM = (u16*)0x7000000; temp = (u16*)sprites; for(loop = 0; loop < 128*4; loop++) { } _OAM[loop] = temp[loop]; u16 loop; for(loop = 0; loop < 128; loop++) { } sprites[loop].attribute0 = 160; //y to > 159 sprites[loop].attribute1 = 240; //x to > 239 ((n)<<10) ((n)<<12) InitializeSprites(); sprites[0].attribute0 = COLOR_256 | SQUARE | spriteY; sprites[0].attribute1 = SIZE_16 | spriteX; sprites[0].attribute2 = 0 | PRIORITY(0); 由于这个OBJ的Character是从OBJ_MODE1_VRAM的首地址开始写入的,所以它的Character Name就应该是0.所以sprites[0].attribute2 = 0 | PRIORITY(0); 由于GBA中16色的OBJ和256色的OBJ中对于Characters的寻址方式是一样的.那么无论是16色的还是256色的OBJ,它们的起始Characters都应该是OBJ的VRAM+Character Name*32(虽然256色下的Character占64Bytes). 比如看看下面的例子: 如果传输256色的sprite的Character Data到OBJ_MODE1_VRAM+N*32的地址 DmaArrayCopy(3,sprite_gfx,OBJ_MODE1_VRAM+N*32,32); 那么它对应的的attribute2中的Character Name应该是N. sprites[0].attribute2 = N; N的范围是0-1023 对于attribute3,我们现在暂时不用. 接下来就是把sprites写到OAM中 要在程序的主循环中不断更新OAM while(1) { } // 等待图像缓冲同步 WaitSync(); CopyOAM(); 第二种方法是AgbLib里面使用的方法. 比如我要显示一个是OBJ 就直接定义个 vu32 bg_oam_buffer[2]; 然后同样要写Character和Palette数据进去 DmaArrayCopy(3, sprite_gfx,OBJ_MODE1_VRAM,32); DmaArrayCopy(3, sprite_pal,OBJ_PLTT,32); 设置OAM的时候就这样写 bg_oam_buffer[0] = OAM_COLOR_256 | OAM_SQUARE |OAM_SIZE_16x16 | (100) << OAM_H_POS_SHIFT | (72) << OAM_V_POS_SHIFT; bg_oam_buffer[1] = 0x0400; 下面给出AgbLib中对于上面的宏的定义: #define OAM_V_POS_SHIFT 0 #define OAM_H_POS_SHIFT 16 #define OAM_COLOR_16 0x00000000 // Select 16 #define OAM_COLOR_256 0x00002000 // Select 256 colors #define OAM_SIZE_8x8 0x00000000 // OBJ 8 x 8 dot #define OAM_SIZE_16x16 0x40000000 // OBJ 16 x 16 dot #define OAM_SIZE_32x32 0x80000000 // OBJ 32 x 32 dot #define OAM_SIZE_64x64 0xc0000000 // OBJ 64 x 64 dot #define OAM_SIZE_16x8 0x00004000 // OBJ 16 x 8 dot #define OAM_SIZE_32x8 0x40004000 // OBJ 32 x 8 dot #define OAM_SIZE_32x16 0x80004000 // OBJ 32 x 16 dot #define OAM_SIZE_64x32 0xc0004000 // OBJ 64 x 32 dot #define OAM_SIZE_8x16 0x00008000 // OBJ 8 x 16 dot #define OAM_SIZE_8x32 0x40008000 // OBJ 8 x 32 dot #define OAM_SIZE_16x32 0x80008000 // OBJ 16 x 32 dot #define OAM_SIZE_32x64 0xc0008000 // OBJ 32 x 64 dot 你可以看出. bg_oam_buffer[0]其实就是第一种方法的attribute0,attribute1的集合. bg_oam_buffer[1]就是第一种方法的attribute2,attribute3的集合. 然后同样地在程序住循环里不断更新写入OAM while(1) { } 这是OAM的定义: #define OAM 0x07000000 // OAM #define OAM_END (OAM + 0x400) DmaCopy(3, bg_oam_buffer, OAM, 8 , 32); 因篇幅问题不能全部显示,请点此查看更多更全内容