NESFab Tutorial Part 2

Exploring Nametable Mirroring and scroll

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.

First version. t2h.fab

 
  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
  
version 1 screen

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.

Second version. t3v.fab

 
  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
version 2 screen