雷叔的白日梦 - 关于邪恶的HBlank



雷精灵
2009-05-04 18:36:27

官方游戏能做到的,我为什么做不到?——雷叔

雷叔早就堕落了。从他开始拥有一台任天堂主机的时候,是的,从那时起,他就堕落了。
如同饥饿的人扑在刚出炉的面包上一样,如同吸血鬼扑在白净细嫩的处女脖颈上一样,如同丧尸扑在惊慌失措的R.P.D.警员身上一样,如同爆气的八神超必杀“八稚女”扑在随便谁身上一样,我记得很清楚,那个时候,雷叔就是那个样子扑在任天堂系列主机上的。

当雷叔发现,GBA其实就是一个ARM的单片机的那一瞬间,我清楚地从他的那双眼睛中看到了饥饿和欲望的光芒。是的。就是饥饿和欲望。对未知领域——呃,精确地说,是对马上就要揭开那最后一块遮羞布但目前还是未知领域——的饥饿和欲望。

……
好吧,随便你怎么想,反正我就是这么描述的……

当雷叔发现,DS其实就是GBA穿了个马甲的那一瞬间,我又一次地从他那双眼睛中看到了饥饿和欲望的光芒。不过,这次的光芒中,还有一丝喜悦和……嗯,怎么描述呢?大概应该算是“庆幸”吧?——参杂在里面。

——庆幸什么?我猜,大概是因为听说DS和GBA非常相似,于是就有种“轻车熟路”的感觉了吧……嗯,“违和感”。就像——
“哎呀,几位爷,快里面请!——刘叔,w叔,牛叔,六面叔,快下来陪几位爷~~~”
“少废话!我听说你们这里新来了个头牌叫掌家叔的……快叫她下来陪我家少爷!”
“哎呀!真不巧,掌家叔今天不见客。”
“连本少爷也不见?”
“哎呀!这不是雷少爷吗!”
“行了!别嚷嚷,老样子,让她下来陪本少爷!”

当年2叔曾经写过一个DEMO,只使用1个图层,却能做出视差卷轴效果。当时雷叔对这种技术极为好奇。
2叔只说了一个词“HBlank”。
雷叔查找资料,终于在GBA中实现了这种效果。后来2叔把源代码给雷叔看,两人的实现方法几乎相同。
现在看来,这其实是一种很简单的技术。

GBA/DS在扫描屏幕的时候,水平方向上并不是只扫描240/256个像素就跳到下一行去扫描,而是继续往后扫描,一直扫描227/355个像素点为止。多出来的那部分像素点,由于在屏幕上显示不出来,因此相当于空消耗时间而不会对屏幕内容有任何影响。如果在这块时间内对屏幕进行一些邪恶的操作,则不会影响已经扫描完的内容,只会有可能影响到将要扫描的内容。
比如说:屏幕按照某固定速度进行卷轴。如果在HBlank中更改卷轴速度,则扫描完的部分将不会受到影响,未扫描的部分自然以新的速度进行卷轴。于是此时屏幕就被撕裂开了。这就是只用一个BG实现视差卷轴的基本原理。

雷叔:首先我需要注册一个HBlank的中断服务,每当HBlank发生的时候,程序进入中断服务。然后在中断服务中更改卷轴速度,或者卷轴偏移量就行了。
我:没错。GBA/DS中,卷轴寄存器中存放的是偏移量。
雷叔:很好。那就这么做吧。每个HBlank读取VCount值,获得当前扫描的是第几条扫描线,也就是y坐标。然后根据计算当前扫描线的卷轴偏移量,最后把结果赋给卷轴寄存器。
我:想法很正确,不过有一点需要注意。
雷叔:注意什么?
我:HBlank中断非常短。不要进行过于复杂的计算。否则有可能导致开始扫描下一扫描线的时候,甚至后一个中断到来的时候,当前中断服务还没响应完。
雷叔:那也很简单,预先把所有扫描线的卷轴偏移量都计算好,保存到某一数组中,HBlank的时候直接写值即可,无需计算了。
我:嗯。这样就很好了。当然,如果不是每个扫描线都需要改变卷轴速度的话,那么这个数组可以减小一些。
[code=c]
#include
#include
#include
#include
#include
#include "main.h"
#include "HsText.h"
#include "efs_lib.h"
#include "HsEncoding.h"

//****************************资源********************************************//

//****************************变量********************************************//
#define FRAMECOUNT_B 4
#define FRAMECOUNT_H 4

const u16 textPalette[]={
RGB5(0, 0, 0), // Transparent
RGB5(31,31, 31),// White
RGB5(31,0, 0), // Red
RGB5(0, 31, 0), // Green
RGB5(0, 0, 31),// Blue
RGB5(31,31, 0), // Yellow
RGB5(31,0, 31),// Purple
RGB5(0, 31, 31),// Cyan
RGB5(0, 0, 0), // Black
};

const static u8 gFrm[]={
1,40,48,64,96,104,
};

int x[6];

FILE* tmpFile=NULL;
struct stat tmpStat;
void* tmpBuffer=NULL;
//****************************函数定义****************************************//
int main(void){
int i=0;

irqSet(IRQ_HBLANK,HBlank);
irqEnable(IRQ_HBLANK);

videoSetMode(MODE_0_2D);
vramSetBankA(VRAM_A_MAIN_BG);
dmaCopy(textPalette,BG_PALETTE,sizeof(textPalette));
int textgroundID=Hs_TileTextInit(3,1,30,0);
bgSetPriority(textgroundID,0);

Hs_ClearTileTextBG();
Hs_TileTextOut("正在初始化文件系统…请稍候。",12,36,1);
swiWaitForVBlank();
if(!EFS_Init(EFS_AND_FAT|EFS_DEFAULT_DEVICE,NULL)){
Hs_ClearTileTextBG();
Hs_TileTextOut("初始化文件系统失败!",12,12,2);
Hs_TileTextOut("ROM可能没有打正确的DLDI补丁!",12,24,5);
Hs_TileTextOut("按任意键或点击屏幕关闭DS主机。",12,36,5);
swiWaitForVBlank();
while(TRUE){
swiWaitForVBlank();
scanKeys();
if(keysDown())DSShutDown();
}
}

Hs_ClearTileTextBG();
Hs_TileTextOut("正在初始化代码页…请稍候。",12,36,1);
swiWaitForVBlank();
if(!Hs_InitEncoding()){
Hs_ClearTileTextBG();
Hs_TileTextOut("初始化代码页失败!",12,12,2);
Hs_TileTextOut("代码页数据文件可能已经丢失!",12,24,5);
Hs_TileTextOut("按任意键或点击屏幕关闭DS主机。",12,36,5);
swiWaitForVBlank();
while(TRUE){
swiWaitForVBlank();
scanKeys();
if(keysDown())DSShutDown();
}
}

Hs_ClearTileTextBG();
Hs_TileTextOut("系统初始化成功!",12,36,3);
swiWaitForVBlank();
if(TRUE){
char tmpString[256];
memset(tmpString,0,sizeof(tmpString));
strcpy(tmpString,"ROM路径为");
char tmpString2[256];
char tmpString3[256];
memset(tmpString2,0,256);
memset(tmpString3,0,256);
UTF8_UTF16(efs_path,strlen(efs_path),tmpString2);
UTF16_GBK(tmpString2,256,tmpString3);
strcat(tmpString,tmpString3);
Hs_TileTextOut_ACL(tmpString,12,12,7,256-12,256-6);
swiWaitForVBlank();
for(i=0;i<0x100;i++){
swiWaitForVBlank();
scanKeys();
if(keysDown())break;
}
}

/**Main Screen*/
videoSetMode(MODE_0_2D|DISPLAY_BG_EXT_PALETTE|DISPLAY_SPR_EXT_PALETTE);
vramSetBankE(VRAM_E_LCD);

tmpFile=fopen("/Pokemon_Pal.bin","rb");
fstat(fileno(tmpFile),&tmpStat);
tmpBuffer=calloc(1,tmpStat.st_size);
fread(tmpBuffer,tmpStat.st_size,1,tmpFile);
DC_FlushAll();
fclose(tmpFile);
void* Pokemon_Pal_bin=tmpBuffer;
int Pokemon_Pal_bin_size=tmpStat.st_size;
tmpFile=NULL;
dmaCopy((void*)Pokemon_Pal_bin,(void*)(VRAM_E_EXT_PALETTE[1]),Pokemon_Pal_bin_size);
vramSetBankE(VRAM_E_BG_EXT_PALETTE);
vramSetBankA(VRAM_A_MAIN_BG);
tmpFile=fopen("/Pokemon_Map.bin","rb");
fstat(fileno(tmpFile),&tmpStat);
tmpBuffer=calloc(1,tmpStat.st_size);
fread(tmpBuffer,tmpStat.st_size,1,tmpFile);
DC_FlushAll();
fclose(tmpFile);
void* Pokemon_Map_bin=tmpBuffer;
int Pokemon_Map_bin_size=tmpStat.st_size;
tmpFile=NULL;
tmpFile=fopen("/Pokemon_Tiles.bin","rb");
fstat(fileno(tmpFile),&tmpStat);
tmpBuffer=calloc(1,tmpStat.st_size);
fread(tmpBuffer,tmpStat.st_size,1,tmpFile);
DC_FlushAll();
fclose(tmpFile);
void* Pokemon_Tiles_bin=tmpBuffer;
int Pokemon_Tiles_bin_size=tmpStat.st_size;
tmpFile=NULL;
dmaCopy((void*)Pokemon_Map_bin,(void*)(SCREEN_BASE_BLOCK(31)),Pokemon_Map_bin_size);
for(i=0;i>1;i++)BG_MAP_RAM(31)[i]|=(1<<12);
dmaCopy((void*)Pokemon_Tiles_bin,(void*)(CHAR_BASE_BLOCK(0)),Pokemon_Tiles_bin_size);

int backgroundID=bgInit(0,BgType_Text8bpp,BgSize_B8_512x256,31,0);
dmaCopy(textPalette,BG_PALETTE,sizeof(textPalette));
textgroundID=Hs_TileTextInit(3,1,30,0);

bgSetPriority(backgroundID,3);
bgSetPriority(textgroundID,0);

/**Main Sprite*/
oamInit(&oamMain,SpriteMapping_1D_128,true);
vramSetBankF(VRAM_F_LCD);

tmpFile=fopen("/Bike_Pal.bin","rb");
fstat(fileno(tmpFile),&tmpStat);
tmpBuffer=calloc(1,tmpStat.st_size);
fread(tmpBuffer,tmpStat.st_size,1,tmpFile);
DC_FlushAll();
fclose(tmpFile);
void* Bike_Pal_bin=tmpBuffer;
int Bike_Pal_bin_size=tmpStat.st_size;
tmpFile=NULL;
tmpFile=fopen("/Haruka_Pal.bin","rb");
fstat(fileno(tmpFile),&tmpStat);
tmpBuffer=calloc(1,tmpStat.st_size);
fread(tmpBuffer,tmpStat.st_size,1,tmpFile);
DC_FlushAll();
fclose(tmpFile);
void* Haruka_Pal_bin=tmpBuffer;
int Haruka_Pal_bin_size=tmpStat.st_size;
tmpFile=NULL;
dmaCopy((void*)Bike_Pal_bin,(void*)VRAM_F_EXT_PALETTE,Bike_Pal_bin_size);
dmaCopy((void*)Haruka_Pal_bin,(void*)(VRAM_F_EXT_PALETTE+1),Haruka_Pal_bin_size);
vramSetBankF(VRAM_F_SPRITE_EXT_PALETTE);
vramSetBankG(VRAM_G_MAIN_SPRITE);
u16* gfxOffset_B=oamAllocateGfx(&oamMain,SpriteSize_64x32,SpriteColorFormat_256Color);
u16* gfxOffset_H=oamAllocateGfx(&oamMain,SpriteSize_64x64,SpriteColorFormat_256Color);
tmpFile=fopen("/Bike_Sprite.bin","rb");
fstat(fileno(tmpFile),&tmpStat);
tmpBuffer=calloc(1,tmpStat.st_size);
fread(tmpBuffer,tmpStat.st_size,1,tmpFile);
DC_FlushAll();
fclose(tmpFile);
void* Bike_Sprite_bin=tmpBuffer;
int Bike_Sprite_bin_size=tmpStat.st_size;
tmpFile=NULL;
tmpFile=fopen("/Haruka_Sprite.bin","rb");
fstat(fileno(tmpFile),&tmpStat);
tmpBuffer=calloc(1,tmpStat.st_size);
fread(tmpBuffer,tmpStat.st_size,1,tmpFile);
DC_FlushAll();
fclose(tmpFile);
void* Haruka_Sprite_bin=tmpBuffer;
int Haruka_Sprite_bin_size=tmpStat.st_size;
tmpFile=NULL;
dmaCopy((void*)Bike_Sprite_bin,(void*)gfxOffset_B,Bike_Sprite_bin_size/FRAMECOUNT_B);
dmaCopy((void*)Haruka_Sprite_bin,(void*)gfxOffset_H,Haruka_Sprite_bin_size/FRAMECOUNT_H);
oamSet(&oamMain,1,
(SCREEN_WIDTH-64)>>1,SCREEN_HEIGHT-96,
0,0,SpriteSize_64x32,SpriteColorFormat_256Color,gfxOffset_B,-1,
false,false,false,false,false);
oamSet(&oamMain,0,
(SCREEN_WIDTH-64)>>1,SCREEN_HEIGHT-128+8,
0,1,SpriteSize_64x64,SpriteColorFormat_256Color,gfxOffset_H,-1,
false,false,false,false,false);

int Frame=0;
int px=0;
while(TRUE){
swiWaitForVBlank();

scanKeys();
if(keysHeld() & KEY_LEFT)px--;
if(keysHeld() & KEY_RIGHT)px++;

// 视差卷轴特效
Frame++;
if(Frame%8==0)x[0]++;
if(Frame%4==0)x[1]++;
if(Frame%2==0)x[2]++;
if(Frame%2==0)x[4]+=2;else x[4]++;
if(Frame%8==0)x[5]+=4;else x[5]+=2;

dmaCopy(
(void*)(Bike_Sprite_bin+Bike_Sprite_bin_size*((Frame>>3)%FRAMECOUNT_B)/FRAMECOUNT_B),
(void*)gfxOffset_B,
Bike_Sprite_bin_size/FRAMECOUNT_B);
dmaCopy(
(void*)(Haruka_Sprite_bin+Haruka_Sprite_bin_size*((Frame>>3)%FRAMECOUNT_H)/FRAMECOUNT_H),
(void*)gfxOffset_H,
Haruka_Sprite_bin_size/FRAMECOUNT_H);

oamSet(&oamMain,1,
((SCREEN_WIDTH-64)>>1)+(px>>1),SCREEN_HEIGHT-96,
0,0,SpriteSize_64x32,SpriteColorFormat_256Color,gfxOffset_B,-1,
false,false,false,false,false);
oamSet(&oamMain,0,
((SCREEN_WIDTH-64)>>1)+(px>>1),SCREEN_HEIGHT-128+8,
0,1,SpriteSize_64x64,SpriteColorFormat_256Color,gfxOffset_H,-1,
false,false,false,false,false);


oamUpdate(&oamMain);

if((keysHeld() & KEY_UP) || (keysDown() & KEY_X)){
complexWinFX--;
if(complexWinFX<0){
gTransitionType=rand()&3;
complexWinFX=64;
}
}
if((keysHeld() & KEY_DOWN) || (keysDown() & KEY_B)){
complexWinFX++;
if(complexWinFX>64){
gTransitionType=rand()&3;
complexWinFX=0;
}
}
}
oamFreeGfx(&oamMain,gfxOffset_B);
oamFreeGfx(&oamMain,gfxOffset_H);

free(Pokemon_Pal_bin);
free(Pokemon_Map_bin);
free(Pokemon_Tiles_bin);
free(Bike_Pal_bin);
free(Bike_Sprite_bin);
free(Haruka_Pal_bin);
free(Haruka_Sprite_bin);
Pokemon_Pal_bin=NULL;
Pokemon_Map_bin=NULL;
Pokemon_Tiles_bin=NULL;
Bike_Pal_bin=NULL;
Bike_Sprite_bin=NULL;
Haruka_Pal_bin=NULL;
Haruka_Sprite_bin=NULL;

return 0;
}
//============================================================================//
static void HBlank(){
vu16 Hframe=REG_VCOUNT&511;

int i=0;
for(i=0;i<6;i++){
if(Hframe==gFrm[i]){
// bgScroll(backgroundID,-x[i],0);
// bgUpdate();
REG_BG0HOFS=-x[i];
break;
}
}
}
//============================================================================//
void DSShutDown(){
fifoSendValue32(FIFO_PM,PM_REQ_LED|(1<<6));
}
[/code]

[attach]1126[/attach]

我:很好!就是这种效果。
雷精灵:不过有一个BUG。我暂且不说,我估计下面还会遇到。

GBA/DS中每渲染一次屏幕被称作“一帧”。但是由于HBlank的存在,导致“一帧”可以被拆开成若干“扫描线”。在HBlank中,只要进行了任何影响渲染的操作,都将导致复杂特效。比如:
如果在HBlank中改变窗口大小,那么,不同的扫描线就可以实现不同的窗口大小。假如窗口的大小是按照某种规律变化的,那么就可以做到复杂窗口特效。比如圆形窗口。
如果在HBlank中卷轴随着扫描线按照正弦波的规律改变,同时进行马赛克特效,则可以实现水波特效。当然,若要模拟一个比较像样的水波,可能还需要Fade特效。而且,算法也比较复杂。
如果在HBlank中每个扫描线更改不同调色盘,那么这就可以在普通的16色和256色BG中模拟出最高16*16*160=40960色BG(GBA下)/16*16*192=49152色BG(DS下)。嗯,可以说,这甚至已经比16bit的BG更加强大了。而且,也比16bit的BG更加节省内存。只需要付出一点点的代价,那就是代码的复杂程度和处理消耗的时间。由于DMA的速度非常地快,而且DMA会暂停CPU以保证DMA的过程,因此不会影响HBlank。唯独需要注意的是,在DS中双CPU的通信可能会有些意想不到的问题。
如果在HBlank中更改MAP和Tile,则可以彻底忽略GBA/DS中Tile模式下的Tile数量限制。换句话说,这就是无限Tile技术。

雷叔:你知道我是为什么开始对HBlank有兴趣的吗?
我:不知道。
雷叔:其实这可以追溯到GBA时代。当时我玩《口袋妖怪》的时候,看到了一个技能“闪光术”。这种技能会在漆黑的洞窟中开出一个圆形的窗口。
我:哦!于是你就想:这是怎么实现的呢?
雷叔:没错。刚开始我认为是窗口特效。于是我就查找资料,寄存器的什么位是用来控制窗口形状的。
我:哈哈……结果你没找到,然后就认为是BG了。
雷叔:对。BG的话,非常容易实现。不过,我又注意到一件事情:那就是在使用“闪光术”的瞬间,圆形窗口逐渐变大,直到指定大小。
我:然后呢?
雷叔:当时我已经懂得了动态MAP和动态Tile技术,如果仔细做的话,用这两种技术都可以实现。于是在很长时间内,我都认为那是BG,不是窗口特效。
我:直到有一天,你在查看MAP的时候,发现不存在这个所谓的“圆形窗口层”。
雷叔:嗯。当时我相当惊异——真神奇啊!不是窗口,也不是BG,难道是精灵?结果OAM找了一圈也没找到所谓的“窗口精灵”。于是我就石化了……
我:那么,你又是什么时候才意识到它确实是窗口的呢?
雷叔:几分钟后。不晓得这到底是什么,于是我就开始开启/关闭VBA中的所有层和特效。然后我发现当关闭了窗口特效之后“闪光术”消失……
我:那你又是何时才知道原理的呢?
雷叔:最近。最近才悟出,“在HBlank中,只要进行了任何影响渲染的操作,都将导致复杂特效”。
我:那你又是何时才做出真正的圆形窗口特效的呢?
雷叔:在那之后,我就试着写了个DEMO。效果在DS模拟器上非常好。但是一上真机就出问题,当窗口的面积小于一定值的时候窗口无效。
我:是什么原因呢?
雷叔:当初我以为是乘方和开平方的缘故。因为HBlank中断不能有太复杂的运算,否则时间不够,于是我就拼命优化算法。最后连开平方都直接调用BIOS了。
我:然后?
雷叔:然后还不行。我感到很奇怪,于是又写了几个比较简单的复杂窗口特效比如百叶窗,结果发现也是在真机上有问题。我开始意识到这不是运算太复杂的缘故……事实上我到现在都不知道这到底是为什么,但是我却在w叔的指点下,成功地把这个问题回避过去了。

[code=c]
#include
#include
#include
#include
#include
#include "main.h"
#include "HsText.h"
#include "efs_lib.h"
#include "HsEncoding.h"

//****************************资源********************************************//

//****************************变量********************************************//
#define FRAMECOUNT_B 4
#define FRAMECOUNT_H 4

const u16 textPalette[]={
RGB5(0, 0, 0), // Transparent
RGB5(31,31, 31),// White
RGB5(31,0, 0), // Red
RGB5(0, 31, 0), // Green
RGB5(0, 0, 31),// Blue
RGB5(31,31, 0), // Yellow
RGB5(31,0, 31),// Purple
RGB5(0, 31, 31),// Cyan
RGB5(0, 0, 0), // Black
};

const static u8 gFrm[]={
1,40,48,64,96,104,
};

int x[6];
int complexWinFX=64;
int gTransitionType=0;
u16 winX[192+1];

FILE* tmpFile=NULL;
struct stat tmpStat;
void* tmpBuffer=NULL;
//****************************函数定义****************************************//
int main(void){
int i=0;

irqSet(IRQ_HBLANK,HBlank);
irqEnable(IRQ_HBLANK);

videoSetMode(MODE_0_2D);
vramSetBankA(VRAM_A_MAIN_BG);
dmaCopy(textPalette,BG_PALETTE,sizeof(textPalette));
int textgroundID=Hs_TileTextInit(3,1,30,0);
bgSetPriority(textgroundID,0);

Hs_ClearTileTextBG();
Hs_TileTextOut("正在初始化文件系统…请稍候。",12,36,1);
swiWaitForVBlank();
if(!EFS_Init(EFS_AND_FAT|EFS_DEFAULT_DEVICE,NULL)){
Hs_ClearTileTextBG();
Hs_TileTextOut("初始化文件系统失败!",12,12,2);
Hs_TileTextOut("ROM可能没有打正确的DLDI补丁!",12,24,5);
Hs_TileTextOut("按任意键或点击屏幕关闭DS主机。",12,36,5);
swiWaitForVBlank();
while(TRUE){
swiWaitForVBlank();
scanKeys();
if(keysDown())DSShutDown();
}
}

Hs_ClearTileTextBG();
Hs_TileTextOut("正在初始化代码页…请稍候。",12,36,1);
swiWaitForVBlank();
if(!Hs_InitEncoding()){
Hs_ClearTileTextBG();
Hs_TileTextOut("初始化代码页失败!",12,12,2);
Hs_TileTextOut("代码页数据文件可能已经丢失!",12,24,5);
Hs_TileTextOut("按任意键或点击屏幕关闭DS主机。",12,36,5);
swiWaitForVBlank();
while(TRUE){
swiWaitForVBlank();
scanKeys();
if(keysDown())DSShutDown();
}
}

Hs_ClearTileTextBG();
Hs_TileTextOut("系统初始化成功!",12,36,3);
swiWaitForVBlank();
if(TRUE){
char tmpString[256];
memset(tmpString,0,sizeof(tmpString));
strcpy(tmpString,"ROM路径为");
char tmpString2[256];
char tmpString3[256];
memset(tmpString2,0,256);
memset(tmpString3,0,256);
UTF8_UTF16(efs_path,strlen(efs_path),tmpString2);
UTF16_GBK(tmpString2,256,tmpString3);
strcat(tmpString,tmpString3);
Hs_TileTextOut_ACL(tmpString,12,12,7,256-12,256-6);
swiWaitForVBlank();
for(i=0;i<0x100;i++){
swiWaitForVBlank();
scanKeys();
if(keysDown())break;
}
}

/**Main Screen*/
videoSetMode(MODE_0_2D|DISPLAY_BG_EXT_PALETTE|DISPLAY_SPR_EXT_PALETTE);
vramSetBankE(VRAM_E_LCD);
tmpFile=fopen("/Pokemon_Pal.bin","rb");
fstat(fileno(tmpFile),&tmpStat);
tmpBuffer=calloc(1,tmpStat.st_size);
fread(tmpBuffer,tmpStat.st_size,1,tmpFile);
DC_FlushAll();
fclose(tmpFile);
void* Pokemon_Pal_bin=tmpBuffer;
int Pokemon_Pal_bin_size=tmpStat.st_size;
tmpFile=NULL;
dmaCopy((void*)Pokemon_Pal_bin,(void*)(VRAM_E_EXT_PALETTE[1]),Pokemon_Pal_bin_size);
vramSetBankE(VRAM_E_BG_EXT_PALETTE);
vramSetBankA(VRAM_A_MAIN_BG);
tmpFile=fopen("/Pokemon_Map.bin","rb");
fstat(fileno(tmpFile),&tmpStat);
tmpBuffer=calloc(1,tmpStat.st_size);
fread(tmpBuffer,tmpStat.st_size,1,tmpFile);
DC_FlushAll();
fclose(tmpFile);
void* Pokemon_Map_bin=tmpBuffer;
int Pokemon_Map_bin_size=tmpStat.st_size;
tmpFile=NULL;
tmpFile=fopen("/Pokemon_Tiles.bin","rb");
fstat(fileno(tmpFile),&tmpStat);
tmpBuffer=calloc(1,tmpStat.st_size);
fread(tmpBuffer,tmpStat.st_size,1,tmpFile);
DC_FlushAll();
fclose(tmpFile);
void* Pokemon_Tiles_bin=tmpBuffer;
int Pokemon_Tiles_bin_size=tmpStat.st_size;
tmpFile=NULL;
dmaCopy((void*)Pokemon_Map_bin,(void*)(SCREEN_BASE_BLOCK(31)),Pokemon_Map_bin_size);
for(i=0;i>1;i++)BG_MAP_RAM(31)[i]|=(1<<12);
dmaCopy((void*)Pokemon_Tiles_bin,(void*)(CHAR_BASE_BLOCK(0)),Pokemon_Tiles_bin_size);
int backgroundID=bgInit(0,BgType_Text8bpp,BgSize_B8_512x256,31,0);

dmaCopy(textPalette,BG_PALETTE,sizeof(textPalette));
textgroundID=Hs_TileTextInit(3,1,30,0);

bgSetPriority(backgroundID,3);
bgSetPriority(textgroundID,0);

/**Main Sprite*/
oamInit(&oamMain,SpriteMapping_1D_128,true);
vramSetBankF(VRAM_F_LCD);
tmpFile=fopen("/Bike_Pal.bin","rb");
fstat(fileno(tmpFile),&tmpStat);
tmpBuffer=calloc(1,tmpStat.st_size);
fread(tmpBuffer,tmpStat.st_size,1,tmpFile);
DC_FlushAll();
fclose(tmpFile);
void* Bike_Pal_bin=tmpBuffer;
int Bike_Pal_bin_size=tmpStat.st_size;
tmpFile=NULL;
tmpFile=fopen("/Haruka_Pal.bin","rb");
fstat(fileno(tmpFile),&tmpStat);
tmpBuffer=calloc(1,tmpStat.st_size);
fread(tmpBuffer,tmpStat.st_size,1,tmpFile);
DC_FlushAll();
fclose(tmpFile);
void* Haruka_Pal_bin=tmpBuffer;
int Haruka_Pal_bin_size=tmpStat.st_size;
tmpFile=NULL;
dmaCopy((void*)Bike_Pal_bin,(void*)VRAM_F_EXT_PALETTE,Bike_Pal_bin_size);
dmaCopy((void*)Haruka_Pal_bin,(void*)(VRAM_F_EXT_PALETTE+1),Haruka_Pal_bin_size);
vramSetBankF(VRAM_F_SPRITE_EXT_PALETTE);
vramSetBankG(VRAM_G_MAIN_SPRITE);
u16* gfxOffset_B=oamAllocateGfx(&oamMain,SpriteSize_64x32,SpriteColorFormat_256Color);
u16* gfxOffset_H=oamAllocateGfx(&oamMain,SpriteSize_64x64,SpriteColorFormat_256Color);
tmpFile=fopen("/Bike_Sprite.bin","rb");
fstat(fileno(tmpFile),&tmpStat);
tmpBuffer=calloc(1,tmpStat.st_size);
fread(tmpBuffer,tmpStat.st_size,1,tmpFile);
DC_FlushAll();
fclose(tmpFile);
void* Bike_Sprite_bin=tmpBuffer;
int Bike_Sprite_bin_size=tmpStat.st_size;
tmpFile=NULL;
tmpFile=fopen("/Haruka_Sprite.bin","rb");
fstat(fileno(tmpFile),&tmpStat);
tmpBuffer=calloc(1,tmpStat.st_size);
fread(tmpBuffer,tmpStat.st_size,1,tmpFile);
DC_FlushAll();
fclose(tmpFile);
void* Haruka_Sprite_bin=tmpBuffer;
int Haruka_Sprite_bin_size=tmpStat.st_size;
tmpFile=NULL;
dmaCopy((void*)Bike_Sprite_bin,(void*)gfxOffset_B,Bike_Sprite_bin_size/FRAMECOUNT_B);
dmaCopy((void*)Haruka_Sprite_bin,(void*)gfxOffset_H,Haruka_Sprite_bin_size/FRAMECOUNT_H);
oamSet(&oamMain,1,
(SCREEN_WIDTH-64)>>1,SCREEN_HEIGHT-96,
0,0,SpriteSize_64x32,SpriteColorFormat_256Color,gfxOffset_B,-1,
false,false,false,false,false);
oamSet(&oamMain,0,
(SCREEN_WIDTH-64)>>1,SCREEN_HEIGHT-128+8,
0,1,SpriteSize_64x64,SpriteColorFormat_256Color,gfxOffset_H,-1,
false,false,false,false,false);

// 开启Win0窗口
REG_DISPCNT|=DISPLAY_WIN0_ON;
// 窗外
WIN_OUT=0;
// 窗内
WIN_IN=0xFFFF;

int Frame=0;
int px=0;
while(TRUE){
swiWaitForVBlank();

WIN0_Y0=0;
WIN0_Y1=192;
// 计算窗口x坐标数组
for(i=0;i<192;i++){
switch(gTransitionType){
case 0:{// 栅栏
int tmp=complexWinFX<<2;
tmp=tmp>255?0:255-tmp;
winX[i]=(((i>>3)&1)?0:(tmp<<8))|(((i>>3)&1)?255-tmp:255);
}break;
case 1:{// 圆
winX[i]=(((i&15)>=(complexWinFX>>2)?255:0)<<8)|255;
}break;
case 2:{// 百叶窗
int tmp=(complexWinFX*5>>1)*(complexWinFX*5>>1)-(i-95)*(i-95);
int x0;
int x1;
tmp=tmp<0?0:sqrt32(tmp);
x0=127-tmp<0?0:127-tmp;
x1=127+tmp>255?255:127+tmp;
winX[i]=(x0<<8)|x1;
}break;
case 3:{// 随机杂线
int tmp[192];
if(complexWinFX==64){
tmp[i]=rand()&255;
winX[i]=255;
}else if(complexWinFX==0){
tmp[i]=rand()&255;
winX[i]=(255<<8)|255;
}else{
winX[i]=((tmp[i]-(tmp[i]*complexWinFX>>6))<<8)|(tmp[i]+((255-tmp[i])*complexWinFX>>6));
}
}break;
}
}
winX[192]=winX[0];

scanKeys();
if(keysHeld() & KEY_LEFT)px--;
if(keysHeld() & KEY_RIGHT)px++;

// 视差卷轴特效
Frame++;
if(Frame%8==0)x[0]++;
if(Frame%4==0)x[1]++;
if(Frame%2==0)x[2]++;
if(Frame%2==0)x[4]+=2;else x[4]++;
if(Frame%8==0)x[5]+=4;else x[5]+=2;

dmaCopy(
(void*)(Bike_Sprite_bin+Bike_Sprite_bin_size*((Frame>>3)%FRAMECOUNT_B)/FRAMECOUNT_B),
(void*)gfxOffset_B,
Bike_Sprite_bin_size/FRAMECOUNT_B);
dmaCopy(
(void*)(Haruka_Sprite_bin+Haruka_Sprite_bin_size*((Frame>>3)%FRAMECOUNT_H)/FRAMECOUNT_H),
(void*)gfxOffset_H,
Haruka_Sprite_bin_size/FRAMECOUNT_H);

oamSet(&oamMain,1,
((SCREEN_WIDTH-64)>>1)+(px>>1),SCREEN_HEIGHT-96,
0,0,SpriteSize_64x32,SpriteColorFormat_256Color,gfxOffset_B,-1,
false,false,false,false,false);
oamSet(&oamMain,0,
((SCREEN_WIDTH-64)>>1)+(px>>1),SCREEN_HEIGHT-128+8,
0,1,SpriteSize_64x64,SpriteColorFormat_256Color,gfxOffset_H,-1,
false,false,false,false,false);

oamUpdate(&oamMain);

if((keysHeld() & KEY_UP) || (keysDown() & KEY_X)){
complexWinFX--;
if(complexWinFX<0){
gTransitionType=rand()&3;
complexWinFX=64;
}
}
if((keysHeld() & KEY_DOWN) || (keysDown() & KEY_B)){
complexWinFX++;
if(complexWinFX>64){
gTransitionType=rand()&3;
complexWinFX=0;
}
}
}
oamFreeGfx(&oamMain,gfxOffset_B);
oamFreeGfx(&oamMain,gfxOffset_H);

free(Pokemon_Pal_bin);
free(Pokemon_Map_bin);
free(Pokemon_Tiles_bin);
free(Bike_Pal_bin);
free(Bike_Sprite_bin);
free(Haruka_Pal_bin);
free(Haruka_Sprite_bin);
Pokemon_Pal_bin=NULL;
Pokemon_Map_bin=NULL;
Pokemon_Tiles_bin=NULL;
Bike_Pal_bin=NULL;
Bike_Sprite_bin=NULL;
Haruka_Pal_bin=NULL;
Haruka_Sprite_bin=NULL;

return 0;
}
//============================================================================//
static void HBlank(){
vu16 Hframe=REG_VCOUNT&511;

int i=0;
for(i=0;i<6;i++){
if(Hframe==gFrm[i]){
// bgScroll(backgroundID,-x[i],0);
// bgUpdate();
REG_BG0HOFS=-x[i];
break;
}
}

// 复杂窗口特效
*(vu16*)0x04000040=winX[Hframe+1];
}
//============================================================================//
void DSShutDown(){
fifoSendValue32(FIFO_PM,PM_REQ_LED|(1<<6));
}
[/code]

[attach]1127[/attach][attach]1128[/attach][attach]1129[/attach][attach]1130[/attach]

w叔:你这种方法需要注册HBlank中断服务,然后在每个HBlank中传递数据。既然仅仅是向特定地址传递数据,为什么不使用HDMA呢?
雷叔:嗯……有道理!

于是雷叔被w叔怂恿,开始研究HDMA了。但是很不幸,直到现在他还没有成功启动HDMA……

什么是HDMA呢?HDMA是DMA的一种特定模式。在这种模式下,每当HBlank发生的时候,指定的DMA通道自动开始工作,把指定的数据传输到指定的地址中。在这个过程中完全无需人工干预。

首先需要设置一个DMA通道。根据GBATEK,HBlank对时间要求比较严格,那么就用优先级最高的DMA0吧。然后需要设置DMA控制字。
[code=c]
DMA_CR(0)=0;
DMA_SRC(0)=(uint32)(&winX[1]);
DMA_DEST(0)=(uint32)(0x04000040);
DMA_CR(0)=DMA_COPY_HALFWORDS|1|DMA_REPEAT|DMA_START_HBL|DMA_DST_RESET;
[/code]
注意看,这里面的设置是非常精心的。
DS的硬件在设计DMA通道的时候为了省事提供了一些很有用的状态,我们得把它们合理利用上。在HDMA这种模式下,我们要求每次HBlank到来的时候(DMA_REPEAT|DMA_START_HBL),DMA通道要向指定目标地址(DMA_DEST(0))传输指定源地址(DMA_SRC(0))中指定数量(1)的指定长度(DMA_COPY_HALFWORDS)的数据。每次传输都是传输到指定寄存器所以目标地址固定不变(DMA_DST_FIX或者DMA_DST_RESET都行)。
这个时候出现一个有趣的问题:当某一次HBlank到来之后,HDMA开始运作,数据开始传输。当传输完成之后,DMA通道,尤其是“Enable”和“Busy”状态位,是什么状态呢?
事实证明,Enable状态位有效,证明当前DMA通道仍然在“占用状态”;而Busy状态位也有效,证明当前DMA通道还处于“忙”状态。直到所有的数据全都传输完,也就是那几百条扫描线全都扫描完,或者更通俗地说,当前帧已经渲染完的时候,Busy才变成无效。至于Enable状态位,只有我们手动关闭才会变成无效。
由此可见,我们必须在每次准备开始进行HDMA的时候,先将DMA通道关闭(DMA_CR(0)=0;),才能保证下面要进行的DMA传输是有效的;而且在设置好DMA并启动DMA通道之后,绝对不要等待DMA完成(while(DMA_CR(0)&DMA_BUSY);)。很简单,DMA什么时候才算完成?整整一帧全部渲染完成才算完成。

把上面那个代码片断加到VBlank或者主循环的swiWaitForVBlank();之后,就可以将HBlank中断服务去掉了。

我:我注意到一件有趣的事情。无论是否使用HBlank中断服务,你往寄存器里面传输的数据,总是当前VCount的值+1。
雷叔:没错。分析一下,当VCount=0的时候,或者说第0个HBlank到来的时候,第0条扫描线已经渲染完成了。所以这个时候进行操作,肯定是针对第1条扫描线了。因此自然就是数组的第1号元素。所以HBlank中断里面写成了*(vu16*)0x04000040=winX[Hframe+1];,HDMA里面写成了DMA_SRC(0)=(uint32)(&winX[1]);。同时,我在定义winX这个数组的时候,故意将其定义成192+1个元素这么大。……不过,哪怕定义成192个元素其实也无所谓,因为最后一条扫描线的HBlank到来的时候,整个屏幕已经渲染完成,紧接着就是VBlank发生了。那么这条扫描线也就无所谓再设置什么了。顺便一说,VBlank到来之后中HBlank就停止了,证明在这一帧中它的使命已经完成。
我:由此看来,第0条扫描线将永远不会被复杂特效所影响?
雷叔:正解。除非你的最后一条扫描线和第0条扫描线设置相同。这也就是为什么我令winX[192]=winX[0];的缘故。

后记:
当雷叔攻克了HDMA之后,我又从他那双邪恶的眼睛中看到了令人不寒而栗的光芒。嗯……就像——
啊~~~太爽了!原来这种姿势是这么爽啊!
嗯~~~雷少爷啊,下次~~~我们玩点更有趣的花样~~~怎么样?
好啊!你果真是天生尤物……
嗯~~~讨厌啦~~~~

……
我刚才什么都没说啊!你们什么都没看见啊!偷窥别人那啥是不道德的行为啊!

[[i] 本帖最后由 雷精灵 于 2009-5-5 10:32 编辑 [/i]]


掌叔
2009-05-04 20:01:39

终于出现在雷叔的小说中了!不过这个身份惨了点~还好有刘叔,w叔,牛叔,六面叔陪我

⊙﹏⊙b


whm3d
2009-05-06 10:13:19

我来了!学习学习!


whm3d
2009-05-06 10:14:15

雷叔对自行车这图,情有独钟!


13727190919
2009-07-11 13:16:57

支持下,看看!!!!!


cs3230524
2009-07-15 16:18:50

学习学习!!


t236846681
2009-07-23 10:58:56

学习中。。。。。。。ARM语言,是汇编吗,我是学ASP.NET的。。。。


掌叔
2009-07-23 11:01:03

[quote]学习中。。。。。。。ARM语言,是汇编吗,我是学ASP.NET的。。。。
[size=2][color=#999999]t236846681 发表于 2009-7-23 10:58[/color] [url=http://www.yayabo.cn/redirect.php?goto=findpost&pid=1860&ptid=612][img]http://www.yayabo.cn/images/common/back.gif[/img][/url][/size][/quote]
c
c++
汇编
都可以