附录,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);

-传入一个完整的32byte的调色板指针

-复制32byte到缓冲区

-随时调用,但只在v-blank时候更新

pal_bg(bg_palette); //仅16字节,背景

pal_spr(sprite_palette); //仅限16个字节,sprites

-跟pal_all一样,但只有16个字节

pal_col(unsigned char index,unsigned char color);

-精灵或背景,设置任意1个调色板的1个颜色

-随时调用,但只在v-blank时候更新

-index = 0 – 31 (0-15 bg, 16-31 sprite)

#define RED 0x16

pal_col(0, RED); //将背景颜色设置为红色

pal_col(0, 0x30); //将背景颜色设置为白色= 0x30

pal_col() 旋转颜色(超级玛丽中的硬币变色) 或者闪烁的精灵

注意:调色板缓冲区设置为0x1c0-0x1df(参看示例代码)

PAL_BUF = $01c0,在crt0.s中定义

- 这是在硬件堆栈中。如果子程序调用深度超过16,它将开始覆盖缓冲区,可能导致错误的颜色或游戏崩溃

pal_clear(void);//将所有颜色设置为黑色,可随时执行

pal_bright(unsigned char bright); //使所有颜色变亮或变暗

- 0-8,4 =正常,3 2 1更暗,5 6 7更亮

- 0是黑色,4是正常,8是白色

pal_bright(4); //正常亮度

注意:在init期间必须至少调用一次 pal_bright() 它设置指向颜色的指针,需要为调色板更新设置颜色。(在crt0.s中)

Shiru在Chase源代码游戏中具有淡化功能

void pal_fade_to(unsigned to)

{

if(!to) music_stop();

while(bright!=to)

{

delay(4);

if(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);

-设置背景亮度,使用0-8,与 pal_bright() 相同

ppu_wait_nmi(void);

- 等待下一帧

ppu_wait_frame(void);

-每5帧等一个额外的帧,对于NTSC电视

-不使用它,我删除它

-可能有错误的分屏

ppu_off(void);

-关闭屏幕

ppu_on_all(void);

-重新打开精灵和背景

ppu_on_bg(void);

-仅打开背景,不影响精灵

ppu_on_spr(void);

-只打开sprite,不影响背景

ppu_mask(unsigned char mask);

-手动设置2001寄存器,请参阅nesdev wiki

-可用于设置颜色强调或灰度模式

ppu_mask(0x1e); //正常模式,屏幕打开

ppu_mask(0x1f); //灰度模式,屏幕打开

ppu_mask(0xfe); //屏幕打开,所有颜色强调位设置,使屏幕变暗

ppu_system(void); //PAL返回0,NTSC返回0

- 在init期间,它会执行一些定时代码,并确定正在运行的电视系统类型。这是一种访问该信息的方法,如果你想对每种类型的电视进行不同的编程

-例如: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 mode

oam_size(1); // 8×16 mode

注意:在每个循环开始时,将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,使用tile#3,palette#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)

metasprite1[] = …; //metasprite的定义,chars数组

-metasprite是精灵的集合

-你不能轻易翻转它

-你可以使用nes屏幕工具制作metasprite

-每个tile为4个字节的数组=

-x offset,y offset,tile,attribute(每个tile调色板/flip)

-你必须传递一个指向这个数据数组的指针

-数据不能超过 128 (0x80)

-每个循环(帧)期间你将精灵推送到OAM缓冲区

-在v-blank期间自动转到OAM(nmi代码的一部分)

oam_hide_rest(unsigned char sprid);

-在每个循环结束时将其余的精灵移出屏幕

-这是必需做的,如果你没有在每个循环开始时清除精灵

-如果屏幕上的精灵数量正好是64,那么值会回到0,这个函数会意外地将所有精灵移出屏幕(通过0会将所有精灵移出屏幕)

-如果由于某种原因你传递了一个不能被4整除的值(如3),这个函数会在无限循环中使游戏崩溃

-在每个循环开始时调用 oam_clear(), 不要再用 oam_hide_rest()

music_play(unsigned char song);

-发送一个歌曲编号,它设置一个指向歌曲开头的指针,在v-blank他将自动播放

music_play(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); //设置一个指向音效开头的指针,它将自动播放

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

pad1 = pad_poll(1); //读取控制器#2,存储在pad2中

pad_trigger(unsigned char pad); //新按下的按钮,而不是按住的状态

a = pad_trigger(0); //取控制器#1,仅在此帧新按下

b = pad_trigger(1); //取控制器#2,仅在此帧新按下

-这实际上调用了pad_poll() //但只返回新的按键,而不是按住的按钮

pad_state(unsigned char pad);

-get last poll without polling again

-do pad_poll() first, every frame

-this is so you have a consistent value all frame

-can do this multiple times per frame and will still get the same info

pad1 = pad_state(0); // controller #1, get last poll

pad2 = pad_state(1); // controller #2, get last poll

注意:按钮定义与我使用的按钮定义相反,因为它们以向右移位而不是向左移动存储

// scrolling //

你预先定义了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滚动。可以随时执行,这些数字不会转到2005寄存器直到下一个v-blank

-高位更改基本名称表,寄存器2000 (在下一个v-blank)

-假设您已正确设置镜像,它将滚动进入下一个nametable

scroll(scroll_X,scroll_Y);

*注意,我不使用这个滚动功能,但我自己的类似功能,你不需要将Y保持在0和$ 1df之间。

split(unsigned int x,unsigned int y);

-waits for sprite zero hit, then changes the x scroll

-will only work if you have a sprite currently in the OAM at the zero position, and it’s somewhere on-screen with a non-transparent portion overlapping the non-transparent portion of a BG tile.

-i’m not sure why it asks for y, since it doesn’t change the y scroll

-it’s actually very hard to do a mid-screen y scroll change, so this is probably for the best

-warning: all CPU time between the function call and the actual split point will be wasted!

-don’t use ppu_wait_frame() with this, you might have glitches

*note I changed split in my example code to only take 1 arguement (x).

Tile banks

-there are 2 sets of 256 tiles loaded to the ppu, ppu addresses 0-0x1fff

-sprites and bg can freely choose which tileset to use, or even both use the same set

bank_spr(unsigned char n); // which set of tiles for sprites

bank_spr(0); // use the first set of tiles

bank_spr(1); // use the second set of tiles

bank_bg(unsigned char n); // which set of tiles for background

bank_bg(0); // use the first set of tiles

bank_bg(1); // use the second set of tiles

rand8(void); // get a random number 0-255

a = rand8(); // a is char

rand16(void); // get a random number 0-65535

a = rand16(); // a is int

set_rand(unsigned int seed); // send an int (2 bytes) to seed the rng

-note, crt0 init code auto sets the seed to 0xfdfd

-you might want to use another seeding method, if randomness is important, like checking FRAME_CNT1 at the time of START pressed on title screen


set_vram_update(unsigned char *buf);

-sets a pointer to an array (a VRAM update buffer, somewhere in the RAM)

-when rendering is ON, this is how BG updates are made

usage…

set_vram_update(Some_ROM_Array); // sets a pointer to the data in ROM

(or)

memcpy(update_list,updateListData,sizeof(updateListData));

– copies data from ROM to a buffer, the buffer is called ‘update_list’

set_vram_update(update_list); // sets a pointer, and a flag to auto-update during the next v-blank

also…

set_vram_update(NULL);

-to disable updates, call this function with NULL pointer

The vram buffer should be filled like this…

Non-sequential:

-non-sequential means it will set a PPU address, then write 1 byte

-MSB, LSB, 1 byte data, repeat

-sequence terminated in 0xff (NT_UPD_EOF)

MSB = high byte of PPU address

LSB = low byte of PPU address

Sequential:

-sequential means it will set a PPU address, then write more than 1 byte to the ppu

-left to right (or) top to bottom

-MSB|NT_UPD_HORZ, LSB, # of bytes, a list of the bytes, repeat

or

-MSB|NT_UPD_VERT, LSB, # of bytes, a list of the bytes, repeat

-NT_UPD_HORZ, means it will write left to right, wrapping around to the next line

-NT_UPD_VERT, means is will write top to bottom, but a new address needs to be set after it reaches the bottom of the screen, as it will never wrap to the next column over

-sequence terminated in 0xff (NT_UPD_EOF)

#define NT_UPD_HORZ 0x40 = sequential

#define NT_UPD_VERT 0x80 = sequential

#define NT_UPD_EOF 0xff

Example of 4 sequential writes, left to right, starting at screen position x=1,y=2

tile #’s are 5,6,7,8

{

MSB(NTADR_A(1,2))|NT_UPD_HORZ,

LSB(NTADR_A(1,2)),

4, // 4 writes

5,6,7,8, // tile #’s

NT_UPD_EOF

};

Interestingly, it will continually write the same data, every v-blank, unless you send a NULL pointer like this…

set_vram_update(NULL);

…though, it may not make much difference.

The data set (aka vram buffer) must not be > 256 bytes, including the ff at the end of the data, and should not push more than…I don’t know, maybe * bytes of data to the ppu, since this happens during v-blank and not during rendering off, time is very very limited.

* Max v-ram changes per frame, with rendering on, before BAD THINGS start to happen…

sequential max = 97 (no palette change this frame),

74 (w palette change this frame)

non-sequential max = 40 (no palette change this frame),

31 (w palette change this frame)

the buffer only needs to be…

3 * 40 + 1 = 121 bytes in size

…as it can’t push more bytes than that, during v-blank.

(this hasn’t been tested on hardware, only FCEUX)

// all following vram functions only work when display is disabled

vram_adr(unsigned int adr);

-sets a PPU address

(sets a start point in the background for writing tiles)

vram_adr(NAMETABLE_A); // start at the top left of the screen

vram_adr(NTADR_A(x,y));

vram_adr(NTADR_A(5,6)); // sets a start position x=5,y=6

vram_put(unsigned char n); // puts 1 byte there

-use vram_adr(); first

vram_put(6); // push tile # 6 to screen

vram_fill(unsigned char n,unsigned int len); // repeat same tile * LEN

-use vram_adr(); first

-might have to use vram_inc(); first (see below)

vram_fill(1, 0x200); // tile # 1 pushed 512 times

vram_inc(unsigned char n); // mode of ppu

vram_inc(0); // data gets pushed into vram left to right (wraping to next line)

vram_inc(1); // data gets pushed into vram top to bottom (only works for 1 column (30 bytes), then you have to set another address).

-do this BEFORE writing to the screen, if you need to change directions

vram_read(unsigned char *dst,unsigned int size);

-reads a byte from vram

-use vram_adr(); first

-dst is where in RAM you will be storing this data from the ppu, size is how many bytes

vram_read(0x300, 2); // read 2 bytes from vram, write to RAM 0x300

NOTE, don’t read from the palette, just use the palette buffer at 0x1c0

vram_write(unsigned char *src,unsigned int size);

-write some bytes to the vram

-use vram_adr(); first

-src is a pointer to the data you are writing to the ppu

-size is how many bytes to write

vram_write(0x300, 2); // write 2 bytes to vram, from RAM 0x300

vram_write(TEXT,sizeof(TEXT)); // TEXT[] is an array of bytes to write to vram.

(For some reason this gave me an error, passing just an array name, had to cast to char * pointer)

vram_write((unsigned char*)TEXT,sizeof(TEXT));

vram_unrle(const unsigned char *data);

-pass it a pointer to the RLE data, and it will push it all to the PPU.

-this unpacks compressed data to the vram

-this is what you should actually use…this is what NES screen tool outputs best.

vram_unrle(titleRLE);

usage:

-first, disable rendering, ppu_off();

-set vram_inc(0) and vram_adr()

-wait for start of frame, with ppu_wait_nmi();

-call vram_unrle();

-then turn rendering back on, ppu_on_all()

-only load 1 nametable worth of data, per frame

NOTE:

-nmi is turned on in init, and never comes off

memcpy(void *dst,void *src,unsigned int len);

-moves data from one place to another…usually from ROM to RAM

memcpy(update_list,updateListData,sizeof(updateListData));

memfill(void *dst,unsigned char value,unsigned int len);

-fill memory with a value

memfill(0x200, 0, 0x100);

-to fill 0x200-0x2ff with zero…that is 0x100 bytes worth of filling

delay(unsigned char frames); // waits a # of frames

delay(5); // wait 5 frames

TECHNICAL NOTES, ON ASM BITS IN NESLIB.S:

-vram (besides the palette) is only updated if VRAM_UPDATE + NAME_UPD_ENABLE are set…

-ppu_wait_frame (or) ppu_wait_nmi, sets ‘UPDATE’

-set_vram_update, sets ‘ENABLE’

-set_vram_update(0); disables the vram ‘UPDATE’

-I guess you can’t set a pointer to the zero page address 0x0000, or it will never update.

-music only plays if FT_SONG_SPEED is set, play sets it, stop resets it, pause sets it to negative (ORA #$80), unpause clears that bit