关于nes开发资料收集
一个精灵占16字节 16*16由4个精灵组成 nes为8像素宽一个精灵,横向32个 纵向30个
pal_col(0,0x16); //将背景设置为红色
pal_bg(palette); //设置调色板
ppu_wait_nmi();//等待绘制完成
oam_clear(); //清除所有精灵缓冲区
oam_meta_spr(BoxGuy2.x, BoxGuy2.y, BlueSpr);//绘制精灵
附录,neslib库
Shiru为NES开发编写了neslib代码。这些都是我所有工作方式的详细注释。我将在稍后添加示例代码。我主要使用的是neslib的略微修改版本
http://shiru.untergrund.net/files/nes/chase.zip
而且,这里还是示例代码
http://shiru.untergrund.net/files/src/cc65_nes_examples.zip
并且此链接具有neslib版本,可与cc65的最新版本(截至2016年)一起使用2.15版
http://forums.nesdev.com/viewtopic.php?p=154078#p154078
调色板
pal_all(const char * data);
const unsigned char game_palette [] = {…} //定义一个32字节的字符数组 pal_all(game_palette);
-传递指向32字节完整调色板的指针 -它将32字节的调色板复制到缓冲区 -可以随时执行,仅在v-blank期间更新
pal_bg(bg_palette); //仅16个字节,背景
pal_spr(sprite_palette); //仅16个字节, sprites-与pal_all相同,但16个字节
pal_col(unsigned char index,unsigned char color); -在任何调色板,BG或Sprite中仅设置1种颜色 -可以随时进行,仅在v-blank期间更新 -index = 0 – 31(0-15 bg,16-31 sprite)
#定义红色0x16 pal_col(0,红色); //将背景色设置为红色 pal_col(0,0x30); //将背景色设置为white = 0x30
pal_col()对于旋转颜色(SMB硬币)或使精灵闪烁可能很有用。 注意:在示例代码 PAL_BUF = $ 01c0中,在示例代码PAL_BUF = $ 01c0中将调色板缓冲区设置为0x1c0-0x1df-在 硬件堆栈中。如果子例程调用的深度超过16,它将开始覆盖缓冲区,可能导致颜色错误或游戏崩溃
pal_clear(void); //只需将所有颜色设置为黑色,即可随时进行
pal_bright(unsigned char bright); //使所有颜色变亮或变暗 – 0-8,4 =正常,3 2 1变深,5 6 7变亮 – 0为黑色,4为正常,8为白色 pal_bright(4); //正常
注意:在初始化期间必须至少调用一次pal_bright()(在crt0.s中)。它设置了一个指针,该指针需要设置颜色才能使调色板更新生效。
Shiru在Chase源代码game.c中具有淡入淡出功能。
void pal_fade_to(unsigned to)
{
if(!to) music_stop();
while(bright!=to)
{
delay(4);
if(bright<to) ++bright;
else --bright;
pal_bright(bright);
}
if(!bright)
{
ppu_off();
set_vram_update(NULL);
scroll(0,0);
}
}
**pal_spr_bright(unsigned char bright);
**-仅设置精灵亮度
pal_bg_bright(unsigned char bright); 设置BG亮度,使用0-8,与pal_bright()相同
ppu_wait_nmi(void); -等待下一帧
ppu_wait_frame(void); -对于NTSC电视,它每5帧等待一个额外的帧- 请勿使用,我将其删除-可能存在 屏幕分割问题
ppu_off(void); //关闭萤幕
ppu_on_all(void); //重新打开精灵和BG
ppu_on_bg(void); //仅打开BG,不影响子 画面ppu_on_spr(void); //仅打开精灵,不影响背景
ppu_mask(unsigned char mask); //手动设置2001寄存器,请参见nesdev wiki-可用于设置颜色强调或灰度模式
ppu_mask(0x1e); //正常,在 ppu_mask(0x1f)上显示;//灰度模式,在 ppu_mask(0xfe)上显示;//屏幕打开,所有颜色强调位都已设置,使屏幕变暗
ppu_system(void); //对于PAL返回0,对于NTSC返回!0
-在初始化期间,它会执行一些定时代码,并确定正在运行哪种电视系统。如果您希望针对每种类型的电视对节目进行不同的编程,则这是一种访问该信息的方法 - 用法类似于…… a = ppu_system();
精灵
oam_clear(void); //清除OAM缓冲区,使所有精灵消失
OAM_BUF = $ 0200,在crt0.s中定义
oam_size(unsigned char size); //将精灵大小设置为8×8或8×16模式
oam_size(0); // 8×8模式 oam_size(1); // 8×16模式
注意:在每个循环的开始,将sprid设置为0
sprid = 0;
然后每当您将一个精灵推送到OAM缓冲区时,它将返回下一个索引值(sprid)
oam_spr(unsigned char x,unsigned char y,unsigned char chrnum,unsigned char attr,unsigned char sprid); -返回sprid(OAM缓冲区的当前索引) -sprid是缓冲区中的精灵数量乘以4(每个精灵4个字节)
sprid = oam_spr(1,2,3,0,sprid); -这会将一个精灵放置在X = 1,Y = 2,使用图块#3,调色板#0,我们使用sprid来跟踪进入缓冲区的索引
sprid = oam_spr(1,2,3,0 | OAM_FLIP_H,sprid); //相同,但水平翻转精灵 sprid = oam_spr(1,2,3,0 | OAM_FLIP_V,sprid); //相同,但垂直翻转精灵 sprid = oam_spr(1,2,3,0 | OAM_FLIP_H | OAM_FLIP_V,sprid); //相同,但水平和垂直翻转精灵 sprid = oam_spr(1,2,3,0 | OAM_BEHIND,sprid); //精灵将在背景之后,但在通用背景色之前(第一个bg调色板条目)
oam_meta_spr(unsigned char x,unsigned char y,unsigned char sprid,const unsigned char * data); -返回sprid(OAM缓冲区的当前索引) -sprid是缓冲区中的精灵数量乘以4(每个精灵4个字节)
sprid = oam_meta_spr(1,2,sprid,metasprite1)
const unsigned char metasprite1 [] =…; //定义元角色,字符数组
这会将元精灵放置在x = 1,y = 2的相对位置
metasprite是sprite的集合 -您不能如此轻松地翻转它 -您可以使用nes屏幕工具制作metasprite- 它是每个瓦片4个字节的数组= -x偏移量,y偏移量,瓦片,属性(每个瓦片调色板/ flip) -您必须将指针传递到该数据数组 -数据集需要以128(0x80)终止-在 每个循环(帧)中,您将将精灵推入OAM缓冲区 -它们将自动转到OAM在v-blank期间(nmi代码的一部分)
oam_hide_rest(unsigned char sprid); -将其余的精灵从屏幕上 弹出-在每个循环结束时执行
-必要时,如果您没有在每个循环的开始时清除精灵 -如果屏幕上的精灵数量恰好为64,则sprid值将环绕为0,并且此函数会意外地将所有精灵推离屏幕(通过0会将所有子画面推离屏幕) -如果由于某种原因您传递了一个不能被4整除的值(例如3),则此函数将使游戏无限循环崩溃 -因此,使用oam_clear()可能更安全在每个循环的开始,不要调用oam_hide_rest()
音乐
注意,在crt0.s中,音乐数据包含在music_data:之后,并且初始化代码初始化音乐系统。
music_play(unsigned char song);**//向其发送一个歌曲编号,它设置指向歌曲开头的指针,将自动播放,并在v-blank音乐播放 (0)期间更新;//播放歌曲#0
music_stop(void); //停止播放歌曲,必须执行music_play()才能重新开始播放,这将开始播放歌曲
music_pause(unsigned char pause);//暂停一首歌曲,并在您暂停它的那一点取消暂停它
music_pause(1); //暂停 music_pause(0); //取消暂停
sfx_play(unsigned char sound,unsigned char channel); //将指针设置为声音fx的开始,它将自动播放
sfx_play(0,0); //播放声音效果#0,优先级#0
通道3的优先级高于2 、、、、、、 3> 2> 1>0。如果2种音效发生冲突,则会播放更高的优先级。
sample_play(unsigned char sample); //播放DMC音效
sample_play(0); //播放DMC样本#0
控制器
pad_poll(unsigned char pad); -读取控制器 -必须向其发送0或1,每个控制器发送一个- 每帧执行一次
pad1 = pad_poll(0); //读取控制器#1,存储在pad1中 pad2 = pad_poll(1); //读取contoller#2,存储在pad2中
pad_trigger(unsigned char pad); //仅获得新的按钮按下,而没有按下
a = pad_trigger(0); //读取控制器#1,仅在新按下此帧时返回 b = pad_trigger(1); //读取控制器2,仅在新按下此帧时返回
-这实际上调用了pad_poll(),但仅返回新的按动,不返回保持的按钮
pad_state(unsigned char pad); -获取上一次轮询而无需再次轮询 - 首先对每一帧执行pad_poll() -这样一来,您可以在所有帧中获得一致的值 -可以每帧多次执行此操作,并且仍然会获得相同的信息
pad1 = pad_state(0); //控制器#1,获取最后的轮询 pad2 = pad_state(1); //控制器#2,获取上次轮询
卷动
预计您将定义2个int(每个2个字节),即ScrollX和ScrollY。 您需要手动将它们设置为0到0x01ff(y为0x01df,只有240条扫描线,而不是256条) 在示例代码9中,shiru这样做了
– -y;
if(y <0)y = 240 * 2-1; //将Y保持在两个命名表的总高度之内
scroll(unsigned int x,unsigned int y); -设置x和y滚动。可以随时执行,直到下一个v-blank之前,数字不会进入2005寄存器 -高位更改基本名称表,寄存器2000(在下一个v-blank中)- 假设您正确设置了镜像,它将滚动进入下一个名称表。
滚动(scroll_X,scroll_Y);
*请注意,我不使用此滚动功能,但使用我自己的类似功能,无需将Y保持在0到$ 1df之间。
split(unsigned int x,unsigned int y); -等待精灵零命中,然后更改x滚动 -仅当您当前在OAM中的精灵位于零位置且屏幕上某个地方的非透明部分与a的非透明部分重叠时才起作用BG瓷砖。
-我不确定为什么要输入y,因为它不会更改y滚动 -实际上很难在屏幕中间进行y滚动更改,所以这可能是最好的- 警告:所有CPU时间之间函数调用和实际的分割点将被浪费! -请勿与此一起使用ppu_wait_frame(),否则可能会出现故障
瓷砖银行
-将2组256个图块加载到ppu,ppu地址为0-0x1fff -sprites和bg可以自由选择要使用的图块集,甚至都可以使用同一组图块
bank_spr(unsigned char n); //精灵集
bank_spr(0); //使用第一组拼贴 bank_spr(1); //使用第二组图块
bank_bg(unsigned char n); //哪一组背景瓷砖
bank_bg(0); //使用第一 组图块bank_bg(1); //使用第二组图块
rand8(void); //获得一个随机数0-255 a = rand8(); // a是char
rand16(void); //得到一个随机数0-65535 a = rand16(); // a是int
set_rand(unsigned int seed); //发送一个int(2个字节)作为rng的种子
-请注意,crt0初始化代码会自动将种子设置为0xfdfd- 如果随机性很重要,您可能想使用另一种种子方法,例如在标题屏幕上按下START时检查FRAME_CNT1
写入屏幕
set_vram_update(unsigned char * buf); -设置指向数组的指针(RAM中某个地方的VRAM更新缓冲区) -渲染为ON时,这就是进行BG更新的方式
用法… set_vram_update(Some_ROM_Array); //设置指向ROM中数据的指针
还… set_vram_update(NULL); -要禁用更新,请使用NULL指针调用此函数
vram缓冲区应这样填充…
非顺序: -非顺序表示将设置PPU地址,然后写入1字节 -MSB,LSB,1字节数据,重复- 顺序以0xff(NT_UPD_EOF)结尾
MSB = PPU地址的高字节 LSB = PPU地址的低字节
顺序: -sequential表示它将设置一个PPU地址,然后向ppu写1个以上的字节 -从左到右(或)从上到下 -MSB | NT_UPD_HORZ,LSB,字节数,字节列表,重复 或 -MSB | NT_UPD_VERT,LSB,字节数,字节列表,重复 -NT_UPD_HORZ,表示它将从左到右写入,环绕到下一行 -NT_UPD_VERT,表示将从上到下写入,但是有一个新地址到达屏幕底部后,需要进行设置,因为它永远不会包装到 以0xff(NT_UPD_EOF)结尾的序列上的下一列
#define NT_UPD_HORZ 0x40 =顺序 #define NT_UPD_VERT 0x80 =顺序 #define NT_UPD_EOF 0xff
从屏幕位置x = 1,y = 2开始从左到右的4个连续写入的示例, 图块编号为5,6,7,8 { MSB(NTADR_A(1,2))| NT_UPD_HORZ, LSB(NTADR_A( 1,2)), 4,4,// 4写 5,6,7,8,// tile#的 NT_UPD_EOF };
有趣的是,除非您发送这样的NULL指针,否则它将连续写入每个v-blank相同的数据…… set_vram_update(NULL); …尽管如此,可能并没有太大的不同。 数据集(又名vram缓冲区)不得大于256个字节,包括数据末尾的ff,并且不应超过…,我不知道,也许*个字节的数据到ppu,因为这种情况会发生在v空白期间而不是在渲染期间,时间非常有限。
*在发生不良事件之前,每帧最大v-ram变化(包括渲染)…
连续最大值= 97(无调色板更改此帧), 74(w调色板更改此帧)
非顺序最大值= 40(无调色板更改此帧), 31(w调色板更改此帧)
缓冲区只需要是… 3 * 40 + 1 = 121字节大小 …因为在v-blank期间它不能推入更多的字节。
(这尚未在硬件上进行过测试,仅在FCEUX上进行过测试)
//以下所有vram函数仅在禁用显示时有效
vram_adr(unsigned int adr); -设置PPU地址 (在后台设置用于写入图块的起点)
vram_adr(NAMETABLE_A); //从屏幕的 左上方开始vram_adr(NTADR_A(x,y)); vram_adr(NTADR_A(5,6)); //设置开始位置x = 5,y = 6
vram_put(unsigned char n); //在其中放1个字节 -use vram_adr(); 第一
vram_put(6); //将图块#6推到屏幕上
vram_fill(unsigned char n,unsigned int len); //重复相同的图块* LEN-使用vram_adr(); 首先- 可能必须使用vram_inc(); 首先(见下文)
vram_fill(1,0x200); // 1号图块被推送512次
vram_inc(unsigned char n); // ppu模式 vram_inc(0); //数据从左到右(绕到下一行)被推入vram vram_inc(1); //数据从上到下推入vram(仅适用于1列(30字节),然后您必须设置另一个地址)。 -如果需要更改方向,请在写入屏幕之前进行此操作
vram_read(unsigned char * dst,unsigned int size); - 从vram读取一个字节 -use vram_adr(); first -dst是您将在RAM中存储来自ppu的数据的位置,大小是多少字节
vram_read(0x300,2); //从vram读取2个字节,写入RAM 0x300
注意,不要从调色板中读取,只需在0x1c0使用调色板缓冲区
vram_write(unsigned char * src,unsigned int size); -将一些字节写入 vram-使用vram_adr(); first -src是指向要写入ppu的数据的指针 -size是要写入的字节数
vram_write(0x300,2); //从RAM 0x300向 vram写入2个字节vram_write(TEXT,sizeof(TEXT)); // TEXT []是要写入vram的字节数组。 (由于某种原因,这给了我一个错误,只传递一个数组名,必须转换为char 指针) vram_write((unsigned char )TEXT,sizeof(TEXT));
vram_unrle(const unsigned char * data); -将其传递给RLE数据的指针,它将全部推送到PPU。 -将压缩数据解压缩到vram- 这是您实际上最应该使用的方法-这是NES屏幕工具输出最好的方法。 vram_unrle(titleRLE);
用法: -首先,禁用渲染,ppu_off(); -设置vram_inc(0)并设置起始地址vram_adr()- 调用vram_unrle(); -然后重新打开渲染,ppu_on_all() -每帧仅加载1个名称表中的数据
注意: -nmi是在init中打开的,永远不会关闭
memcpy(void * dst,void * src,unsigned int len); -将数据从一个位置移动到另一位置…通常是从ROM到RAM
memcpy(update_list,updateListData,sizeof(updateListData));
memfill(void * dst,unsigned char value,unsigned int len); 用值填充内存
memfill(0x200,1,0x100); -使用图块#1填充0x200-0x2ff…即0x100字节的填充值
延迟(无符号字符帧);//等待#帧
delay(5); //等待5帧
在NESLIB.S中的ASM BITS上的技术说明:
-vram(位于调色板之外)仅在设置了VRAM_UPDATE + NAME_UPD_ENABLE时更新…
-ppu_wait_frame(或)ppu_wait_nmi,设置为“ UPDATE”
-set_vram_update,设置为“ ENABLE”
-set_vram_update(0 ); 禁用vram'UPDATE'-
我想您不能将指针设置为零页面地址0x0000,否则它将永远不会更新。
-music仅在设置FT_SONG_SPEED的情况下播放,播放将其设置,停止将其重置,暂停将其设置为负数(ORA#$ 80),取消暂停将其清除
相关地址:https://happysoul.github.io/nes/nesdoug nes模拟器:http://fceux.com/web/download.html
发表评论