This third code I want to show you is an example of how to make program an infinite horizontal scroll using NESFab. Here you can download the example.
Like the past two examples, its working is very simple. It loads a mapfab level composed of 4 horizontal 16x16 screens and scrolls arround them with the pad. The character data is made with YY-CHR english Next you have the whole code:
mapfab(raw, "t4.mapfab", "chr", "palette", "metatiles", "level")
ct UU SCROLL_AMOUNT=7 // the amount of pixels that will move when a key is pressed (1-8)
vars /game_vars
CCC/levels lvl1_mt_map // level 1 metatile map and its tiles correspondence
CC/metatiles mt_nw
CC/metatiles mt_ne
CC/metatiles mt_sw
CC/metatiles mt_se
CC/metatiles mt_attr
UU my_x_scroll=0 // this points to the current h. scroll value
UU maximum_x // this will point to the map maximum x scroll value
UU scr_bg_update_base_addr // addrs where to copy data during NMI
UU scr_attr_bg_update_base_addr
Bool scr_bg_update_needed=false
U[30] scroll_tiles // The data we'll upload to the PPU during NMI.
//U[30] scroll_tiles2
U[8] scroll_attrs
/*
Writes the initial 32x30 screen. Take note the mapfab is (16*4)H x 16V.
It has a 16th row of tiles that will never be seen to make multiplications
by its height a power of two (faster)
*/
fn load_screen(CCC/levels my_lvl_pointer)
U chr_bank = read U(my_lvl_pointer)
CCC/palettes pal=read CCC/palettes(my_lvl_pointer)
CC/metatiles mymetatiles=read CC/metatiles(my_lvl_pointer)
UU size = UU(read U(my_lvl_pointer)) // width in metatiles of screen
//after all these reads, now my_lvl_pointer points to the metatiles matrix
maximum_x=size<<4 // sets the maximum x value to scroll to
maximum_x-=256 //
// set palette
load_palette(pal)
ppu_upload_palette()
UU mt_size = UU(read U(mymetatiles)) // number of metatiles
if mt_size == 0
mt_size = 256
lvl1_mt_map=my_lvl_pointer // the map is set in columns. it's in level.macrofab
mt_nw=mymetatiles
mt_ne=mt_nw
mt_ne+=mt_size
mt_sw=mt_ne
mt_sw+=mt_size
mt_se=mt_sw
mt_se+=mt_size
mt_attr=mt_se
mt_attr+=mt_size
// now every pointer points where it should
ppu_reset_addr($2000) // first screen // https://www.nesdev.org/wiki/Mirroring
{PPUSTATUS}() // Reset the address latch before 'ppu_set_addr', just to be safe.
{PPUCTRL}(PPUCTRL_VRAM_32_DOWN) // Used to write columns of data.
// upload first namespace data
UU i=0
UU ii=0
for UU x=0; x<16; x+=1
ppu_reset_addr($2000+ii)
for UU y=0; y<16; y+=1
U my_metatile=my_lvl_pointer{i}
U nw_tile=mt_nw{my_metatile}
U sw_tile=mt_sw{my_metatile}
{PPUDATA}(nw_tile)
{PPUDATA}(sw_tile)
i+=1
i-=16
ii+=1
ppu_reset_addr($2000+ii)
for UU y=0; y<16; y+=1
U my_metatile=my_lvl_pointer{i}
U ne_tile=mt_ne{my_metatile}
U se_tile=mt_se{my_metatile}
{PPUDATA}(ne_tile)
{PPUDATA}(se_tile)
i+=1
ii+=1
//upload first attributes data, we need to OR it together
//for every 2x2 metatiles block (https://www.nesdev.org/wiki/PPU_attribute_tables)
i=0
ii=0
for UU a=0; a<8; a+=1
for UU attri=0; attri<32; attri+=8
ppu_reset_addr($2000+960+a+attri)
U my_metatile=my_lvl_pointer{i} //tl
U my_attr=mt_attr{my_metatile}
//
my_metatile=my_lvl_pointer{i+16} // tr
my_attr|=(mt_attr{my_metatile}<<2)
//
my_metatile=my_lvl_pointer{i+1} //bl
my_attr|=(mt_attr{my_metatile}<<4)
//
my_metatile=my_lvl_pointer{i+17} //br
my_attr|=(mt_attr{my_metatile}<<6)
//
{PPUDATA}(my_attr)
//
my_metatile=my_lvl_pointer{i+8} //tl
my_attr=mt_attr{my_metatile}
//
my_metatile=my_lvl_pointer{i+16+8} // tr
my_attr|=(mt_attr{my_metatile}<<2)
//
my_metatile=my_lvl_pointer{i+1+8} //bl
my_attr|=(mt_attr{my_metatile}<<4)
//
my_metatile=my_lvl_pointer{i+17+8} //br
my_attr|=(mt_attr{my_metatile}<<6)
//
{PPUDATA}(my_attr)
i+=2
ii+=32
i=ii
nmi main_nmi()
// Update OAM and poll the pads:
ppu_upload_oam_poll_pads(0)
// Turn on rendering:
{PPUMASK}(PPUMASK_ON | PPUMASK_NO_CLIP)
// Reset the scroll https://www.nesdev.org/wiki/PPU_scrolling
// and https://www.nesdev.org/wiki/Mirroring
if scr_bg_update_needed
nmi_update_screen_bg()
//
ppu_reset_scroll_16(my_x_scroll, 0)
mode main()
: nmi main_nmi
load_screen(@lev_level1) // the name lev_level1 is defined in the levels.macrofab
// Tell the NES to trigger NMI once per frame:
{PPUCTRL}(PPUCTRL_NMI_ON)
// Wait forever, one frame at a time:
while true
update_pads()
move_player()
update_sprites()
nmi // Wait for the next NMI
fn update_sprites()
// Our stack index into OAM:
U i = 0
// Push sprites
set_oam_x(i, 0) // x-position
set_oam_y(i, 0) // y-position
set_oam_p(i, 8) // empty tile
set_oam_a(i, 0) // options
i += 4
// Clear the remainder of OAM
hide_oam(i)
fn move_player()
/*
the X scroll runs smoothly but the Y scroll doesn't quite
work the way you'd expect. The 9th bit selects the namespace (screen)
and the 8 bit left (up to 239) the scroll inside that screen.
More info in https://www.nesdev.org/wiki/PPU_scrolling
*/
if pads[0].held & BUTTON_LEFT
if (my_x_scroll >= SCROLL_AMOUNT)
my_x_scroll-=SCROLL_AMOUNT
else
my_x_scroll=0
calc_left_scr_bg(my_x_scroll)
if pads[0].held & BUTTON_RIGHT
if ((my_x_scroll+SCROLL_AMOUNT) <= maximum_x)
my_x_scroll+=SCROLL_AMOUNT
else
my_x_scroll=maximum_x
calc_right_scr_bg(my_x_scroll)
/* This function sets a 9 bit scroll
instead of the 8 bit ppu_reset_scroll
from nes.fab library: https://www.nesdev.org/wiki/PPU_scrolling
*/
fn ppu_reset_scroll_16(UU x, UU y)
ppu_reset_scroll(x.a, y.a) // the 9th bits should go to ppuctrl right after
{PPUCTRL}(PPUCTRL_NMI_ON | (x.b & 1) | ((y.b & 1)<<1)) // Set PPU control register, we want NMIs
/*
This function calculates the BG piece to put into the namespace
after right scrolling, and its attributes (palettes).
*/
fn calc_right_scr_bg(UU x)
UU x_right=x+255
UU column=(x_right.a >> 3)
UU col_start_addr
UU attr_base_addr
// calculate the correct namespace
if (x_right.b & 1)
col_start_addr=$2400+column
attr_base_addr=$2400+960
else
col_start_addr=$2000+column
attr_base_addr=$2000+960
// calculate the 8x8 tiles that will patch the screen
UU mt_map_col_start=(x_right >> 4)
U y=0
UU map_mt_pointer=(mt_map_col_start << 4)
for U i=0; i<15; i+=1
U metatile=lvl1_mt_map{map_mt_pointer}
//select whether it's the left or right column of the 2x2 metatile
if (column & 1)
scroll_tiles[y]=mt_ne[metatile]
scroll_tiles[y+1]=mt_se[metatile]
else
scroll_tiles[y]=mt_nw[metatile]
scroll_tiles[y+1]=mt_sw[metatile]
y+=2
map_mt_pointer+=1
// calculate the addresses where data will be written
scr_bg_update_base_addr=col_start_addr
scr_attr_bg_update_base_addr=attr_base_addr
scr_attr_bg_update_base_addr+=(x_right.a >> 5)
// calculate the attributes data
UU base_mt_attr=(x_right >> 4)
base_mt_attr&=$fffe
base_mt_attr=(base_mt_attr<<4)
U iii=0
for UU i=0; i<15; i+=2
U my_metatile=lvl1_mt_map{base_mt_attr+i} //tl
U my_attr=mt_attr{my_metatile}
my_metatile=lvl1_mt_map{base_mt_attr+i+16} // tr
my_attr|=(mt_attr{my_metatile}<<2)
my_metatile=lvl1_mt_map{base_mt_attr+i+1} //bl
my_attr|=(mt_attr{my_metatile}<<4)
my_metatile=lvl1_mt_map{base_mt_attr+i+17} //br
my_attr|=(mt_attr{my_metatile}<<6)
scroll_attrs[iii]=my_attr
iii+=1
//
scr_bg_update_needed=true
/*
Copies to the adequate namespace addr the bg piece needed
This function is to be called when NMI takes place.
*/
fn nmi_update_screen_bg()
{PPUSTATUS}() // Reset the address latch just to be safe.
{PPUCTRL}(PPUCTRL_VRAM_32_DOWN) // Used to write columns of data. Addr incs in 32 every write.
// write the column to the namespace
ppu_reset_addr(scr_bg_update_base_addr)
for U y=0; y<30; y+=1
{PPUDATA}(scroll_tiles[y])
// now write the attrs. remember we are in addr+32 mode every write
U i=0
for UU attri=0; attri<32; attri+=8
ppu_reset_addr(scr_attr_bg_update_base_addr+attri)
{PPUDATA}(scroll_attrs[i])
{PPUDATA}(scroll_attrs[i+4])
i+=1
//
scr_bg_update_needed=false
fn calc_left_scr_bg(UU x)
UU column=(x.a >> 3)
UU col_start_addr
UU attr_base_addr
// calculate the correct namespace
if (x.b & 1)
col_start_addr=$2400+column
attr_base_addr=$2400+960
else
col_start_addr=$2000+column
attr_base_addr=$2000+960
// calculate the 8x8 tiles that will patch the screen
UU mt_map_col_start=(x >> 4)
U y=0
UU map_mt_pointer=(mt_map_col_start << 4)
for U i=0; i<15; i+=1
U metatile=lvl1_mt_map{map_mt_pointer}
//select whether it's the left or right column of the 2x2 metatile
if (column & 1)
scroll_tiles[y]=mt_ne[metatile]
scroll_tiles[y+1]=mt_se[metatile]
else
scroll_tiles[y]=mt_nw[metatile]
scroll_tiles[y+1]=mt_sw[metatile]
y+=2
map_mt_pointer+=1
// calculate the addresses where data will be written
scr_bg_update_base_addr=col_start_addr
scr_attr_bg_update_base_addr=attr_base_addr
scr_attr_bg_update_base_addr+=(x.a >> 5)
// calculate the attributes data
UU base_mt_attr=(x >> 4)
base_mt_attr&=$fffe
base_mt_attr=(base_mt_attr<<4)
U iii=0
for UU i=0; i<15; i+=2
U my_metatile=lvl1_mt_map{base_mt_attr+i} //tl
U my_attr=mt_attr{my_metatile}
my_metatile=lvl1_mt_map{base_mt_attr+i+16} // tr
my_attr|=(mt_attr{my_metatile}<<2)
my_metatile=lvl1_mt_map{base_mt_attr+i+1} //bl
my_attr|=(mt_attr{my_metatile}<<4)
my_metatile=lvl1_mt_map{base_mt_attr+i+17} //br
my_attr|=(mt_attr{my_metatile}<<6)
scroll_attrs[iii]=my_attr
iii+=1
//
scr_bg_update_needed=true
The code shares the basic operating structure than basic examples and has 4 major functions:
load_screen loads the level and sets some global variables that will be used by other functions.
calc_left_scr_bg and calc_right_scr_bg calculate the patch that will need to be written to the PPU memory during VBlank NMI when scrolling left or right.
nmi_update_screen_bg uploads the data to the PPU memory during VBlank. This function only uploads the data. It does not calculate it, which is done in the previous functions. This is so because VBlank is only 2273 cycles long and it's better to calculate the patch of data we need to write during the main game loop and leave the NMI to exclusively upload that data.
The data is uploaded using the PPUCTRL_VRAM_32_DOWN option, which writes data in columns, that is, every write increments the PPU memory address in 32. Perfect for writing columns of tiles.
The mapfab data is also read in colums, thanks to the level.macrofab:
#:name:#
#:chr_name:#
#:palette_name:#
#:metatiles_name:#
data /levels
[] lev_#name#
U(chr_#chr_name#_index) // The CHR banke
(@pal_#palette_name#) // Pointer to the palette
(@mt_#metatiles_name#) // Pointer to the metatiles
U(_width) // The width, in tiles.
(_column_major) // The actual level tiles
Notice the _column_major instead of the previous _row_major of the other examples. Also, the mapfab has 16 tiles per column, instead of the 15 that can be shown in a NES, to make multiplications faster using powers of two.