This second example is divided in 2 versions, t3h.fab, loads a dual 32x15 metatiles screen made with YY-CHR and MapFab. Displays it, and lets you scroll both horizontally and vertically.
The second version (t3v.fab) loads a dual 16x30 metatiles screen made with YY-CHR and MapFab. Displays it, and lets you scroll both horizontally and vertically.
Here you can download these examples.
mapfab(raw, "t3h.mapfab", "chr", "palette", "metatiles", "level")
vars /game_vars
CCC/levels lvl1_mt_map
UU my_x_scroll=0
UU my_y_scroll=0
/*
Loads a 2 screen horizontal 32x15 metatiles screen made with MapFab.
Its only argument is a pointer to the levels struct
made with the macros included in the earlier mapfab()
*/
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
// 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
CC/metatiles mt_nw=mymetatiles
CC/metatiles mt_ne=mt_nw
mt_ne+=mt_size
CC/metatiles mt_sw=mt_ne
mt_sw+=mt_size
CC/metatiles mt_se=mt_sw
mt_se+=mt_size
CC/metatiles 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
// upload first namespace data
for UU y=0; y<15; y+=1
for UU x=0; x<16; x+=1
U my_metatile=my_lvl_pointer{(y<<5)+x}
U nw_tile=mt_nw{my_metatile}
U ne_tile=mt_ne{my_metatile}
{PPUDATA}(nw_tile)
{PPUDATA}(ne_tile)
for UU x=0; x<16; x+=1
U my_metatile=my_lvl_pointer{(y<<5)+x}
U sw_tile=mt_sw{my_metatile}
U se_tile=mt_se{my_metatile}
{PPUDATA}(sw_tile)
{PPUDATA}(se_tile)
//upload first attributes data, we need to OR it together
//for every 2x2 metatiles block (https://www.nesdev.org/wiki/PPU_attribute_tables)
for UU y=0; y<15; y+=2
for UU x=0; x<16; x+=2
U my_metatile=my_lvl_pointer{(y<<5)+x}
U my_attr=mt_attr{my_metatile}
//
my_metatile=my_lvl_pointer{(y<<5)+x+1}
my_attr|=(mt_attr{my_metatile}<<2)
//
my_metatile=my_lvl_pointer{(y<<5)+x+32}
my_attr|=(mt_attr{my_metatile}<<4)
//
my_metatile=my_lvl_pointer{(y<<5)+x+33}
my_attr|=(mt_attr{my_metatile}<<6)
{PPUDATA}(my_attr)
ppu_reset_addr($2400) // second screen https://www.nesdev.org/wiki/Mirroring
// upload second namespace data
for UU y=0; y<15; y+=1
for UU x=16; x<32; x+=1
U my_metatile=my_lvl_pointer{(y<<5)+x}
U nw_tile=mt_nw{my_metatile}
U ne_tile=mt_ne{my_metatile}
{PPUDATA}(nw_tile)
{PPUDATA}(ne_tile)
for UU x=16; x<32; x+=1
U my_metatile=my_lvl_pointer{(y<<5)+x}
U sw_tile=mt_sw{my_metatile}
U se_tile=mt_se{my_metatile}
{PPUDATA}(sw_tile)
{PPUDATA}(se_tile)
//upload second attributes data, we need to OR it together
//for every 2x2 metatiles block (https://www.nesdev.org/wiki/PPU_attribute_tables)
for UU y=0; y<15; y+=2
for UU x=16; x<32; x+=2
U my_metatile=my_lvl_pointer{(y<<5)+x}
U my_attr=mt_attr{my_metatile}
//
my_metatile=my_lvl_pointer{(y<<5)+x+1}
my_attr|=(mt_attr{my_metatile}<<2)
//
my_metatile=my_lvl_pointer{(y<<5)+x+32}
my_attr|=(mt_attr{my_metatile}<<4)
//
my_metatile=my_lvl_pointer{(y<<5)+x+33}
my_attr|=(mt_attr{my_metatile}<<6)
{PPUDATA}(my_attr)
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
ppu_reset_scroll_16(my_x_scroll, my_y_scroll)
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
my_x_scroll-=1
if pads[0].held & BUTTON_RIGHT
my_x_scroll+=1
if pads[0].held & BUTTON_UP
if my_y_scroll.a == 0
my_y_scroll.b^=1
my_y_scroll.a=239
else
my_y_scroll.a-=1
if pads[0].held & BUTTON_DOWN
if my_y_scroll.a == 239
my_y_scroll.a=0
my_y_scroll.b^=1
else
my_y_scroll+=1
/* 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
You can scroll with the pad. Notice how vertical mirroring (for horizontal scrolling) is selected by default, to show the double (32x15) horizontal screen made in mapfab.
As you can see in the move_player function, the Y coordinate scrolling is not quite what you would expect. It's made of 9 bits, being the 9th bit the one that selects the namespace and the rest 8 bit the scrolling inside that namespace. Those 9 bits are sent to the PPU using the ppu_reset_scroll_16 function.
mapfab(raw, "t3v.mapfab", "chr", "palette", "metatiles", "level")
vars /game_vars
CCC/levels lvl1_mt_map
UU my_x_scroll=0
UU my_y_scroll=0
/*
Loads a 2 screen vertical 16x30 metatiles screen made with MapFab.
Its only argument is a pointer to the levels struct
made with the macros included in the earlier mapfab()
*/
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
// 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
CC/metatiles mt_nw=mymetatiles
CC/metatiles mt_ne=mt_nw
mt_ne+=mt_size
CC/metatiles mt_sw=mt_ne
mt_sw+=mt_size
CC/metatiles mt_se=mt_sw
mt_se+=mt_size
CC/metatiles 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
// upload first namespace data
for UU y=0; y<15; y+=1
for UU x=0; x<16; x+=1
U my_metatile=my_lvl_pointer{(y<<4)+x}
U nw_tile=mt_nw{my_metatile}
U ne_tile=mt_ne{my_metatile}
{PPUDATA}(nw_tile)
{PPUDATA}(ne_tile)
for UU x=0; x<16; x+=1
U my_metatile=my_lvl_pointer{(y<<4)+x}
U sw_tile=mt_sw{my_metatile}
U se_tile=mt_se{my_metatile}
{PPUDATA}(sw_tile)
{PPUDATA}(se_tile)
//upload first attributes data, we need to OR it together
//for every 2x2 metatiles block (https://www.nesdev.org/wiki/PPU_attribute_tables)
for UU y=0; y<15; y+=2
for UU x=0; x<16; x+=2
U my_metatile=my_lvl_pointer{(y<<4)+x}
U my_attr=mt_attr{my_metatile}
//
my_metatile=my_lvl_pointer{(y<<4)+x+1}
my_attr|=(mt_attr{my_metatile}<<2)
//
my_metatile=my_lvl_pointer{(y<<4)+x+32}
my_attr|=(mt_attr{my_metatile}<<4)
//
my_metatile=my_lvl_pointer{(y<<4)+x+33}
my_attr|=(mt_attr{my_metatile}<<6)
{PPUDATA}(my_attr)
ppu_reset_addr($2800) // second screen https://www.nesdev.org/wiki/Mirroring
// upload second namespace data
for UU y=15; y<30; y+=1
for UU x=0; x<16; x+=1
U my_metatile=my_lvl_pointer{(y<<4)+x}
U nw_tile=mt_nw{my_metatile}
U ne_tile=mt_ne{my_metatile}
{PPUDATA}(nw_tile)
{PPUDATA}(ne_tile)
for UU x=0; x<16; x+=1
U my_metatile=my_lvl_pointer{(y<<4)+x}
U sw_tile=mt_sw{my_metatile}
U se_tile=mt_se{my_metatile}
{PPUDATA}(sw_tile)
{PPUDATA}(se_tile)
//upload second attributes data, we need to OR it together
//for every 2x2 metatiles block (https://www.nesdev.org/wiki/PPU_attribute_tables)
for UU y=15; y<30; y+=2
for UU x=0; x<16; x+=2
U my_metatile=my_lvl_pointer{(y<<4)+x}
U my_attr=mt_attr{my_metatile}
//
my_metatile=my_lvl_pointer{(y<<4)+x+1}
my_attr|=(mt_attr{my_metatile}<<2)
//
my_metatile=my_lvl_pointer{(y<<4)+x+16}
my_attr|=(mt_attr{my_metatile}<<4)
//
my_metatile=my_lvl_pointer{(y<<4)+x+17}
my_attr|=(mt_attr{my_metatile}<<6)
{PPUDATA}(my_attr)
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
ppu_reset_scroll_16(my_x_scroll, my_y_scroll)
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
my_x_scroll-=1
if pads[0].held & BUTTON_RIGHT
my_x_scroll+=1
if pads[0].held & BUTTON_UP
if my_y_scroll.a == 0
my_y_scroll.b^=1
my_y_scroll.a=239
else
my_y_scroll.a-=1
if pads[0].held & BUTTON_DOWN
if my_y_scroll.a == 239
my_y_scroll.a=0
my_y_scroll.b^=1
else
my_y_scroll+=1
/* 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
Again, you can scroll through the screens with the pad. This time, horizontal mirroring (for vertical scrolling) is selected in the configuration file below, to show the double (16x30) vertical screen made in mapfab.
mapper = NROM
mirroring = H
output = t3v.nes
nesfab-dir = ../
input = lib/nes.fab
input = lib/palette.fab
input = t3v.fab
input = chr.macrofab
input = palette.macrofab
input = metatiles.macrofab
input = level.macrofab