一个精灵占16字节
16*16由4个精灵组成
nes为8像素宽一个精灵,横向32个 纵向30个

pal_col(0,0×16); //将背景设置为红色

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

发表评论

邮箱地址不会被公开。 必填项已用*标注