It is currently Tue May 23, 2017 5:31 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 13 posts ] 
Author Message
PostPosted: Thu May 11, 2017 7:21 am 
Offline

Joined: Wed Sep 21, 2016 12:08 pm
Posts: 23
I am looking for insights on how to make single pixel changes to a background. If I have an 8 byte mask, I want to apply it to the 16 bytes (2 layers of 8) tile and get a new replacement tile. Think of it like BreakOut but on a pixel level. The challenge is that the NES isn't really designed to do this. Here's my initial thoughts, but I'm looking for insights and suggestions.

1. I'm going to have to use a board with CHR-RAM. This will be a new experience.
2. Unless I'm doing some hblank switching, I'm only going to be able to have at most 256 destructible tiles on the screen at once. Best case that's about 25% of the screen.
3. I need a efficient mapping from pixel (x,y) to tile address
4. I need to pull the 16 bytes of tile data from the CHR-RAM, put it in NES RAM, apply the mask, then write the data back. Either I split this up over two frames (one read, one write) or I somehow eleminate the need for the read from CHR-RAM altogether.
5. How does QIX do this?
6. How much memory is there in CHR-RAM compared to CHR-ROM? And given there is only 1 bit in the rendering mask to indicate the tile page (typically background or sprite), how could you set it to a different page other than 1 or 0?
7. If the background tile is temporarily in NES RAM, pixel level collision detections should be a simple masking check, correct? If my sprite is at (x,y) on the screen, I get the tile data for the background directly behind it. I then calculate what the offset (dx,dy) that sprite is from the upper left pixel in the tile. This gives me an " AND " mask which I can then apply to the tile data and see if there is overlap.


Last edited by Lucradan on Thu May 11, 2017 9:07 am, edited 1 time in total.

Top
 Profile  
 
PostPosted: Thu May 11, 2017 8:38 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 9642
Location: Rio de Janeiro - Brazil
I almost always copy my posts to the clipboard before submitting, to avoid this kind of problem, which has happened to me a few times.


Top
 Profile  
 
PostPosted: Thu May 11, 2017 8:39 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 1570
Location: DIGDUG
That's what I was going to say. Especially a long post, select all, copy. If it fails, try again, paste.

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Top
 Profile  
 
PostPosted: Thu May 11, 2017 10:31 am 
Offline

Joined: Wed Sep 21, 2016 12:08 pm
Posts: 23
I just replaced the content of the original post to reflect the question I wanted to ask. This time I typed it in Notepad then copy-paste


Top
 Profile  
 
PostPosted: Thu May 11, 2017 10:55 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 18338
Location: NE Indiana, USA (NTSC)
Lucradan wrote:
1. I'm going to have to use a board with CHR-RAM. This will be a new experience.

The most gentle way to go about this is to take some game you've already made that uses CHR ROM and hack it to copy data from PRG ROM into CHR RAM. Then animate one or more tiles.

Quote:
2. Unless I'm doing some hblank switching, I'm only going to be able to have at most 256 destructible tiles on the screen at once. Best case that's about 25% of the screen.

Games like Qix and Hatris don't either. I think among NES games, the only one that has unique destructible tiles across the whole screen is Videomation. (There were a couple more for the Famicom.) If you're willing to drop to 1bpp, you can cover the whole screen with two sets of 240 tiles, switching halfway down. Half the screen would use bitplane 0, the other half bitplane 1.

Quote:
3. I need a efficient mapping from pixel (x,y) to tile address

That depends on where you want to put the playfield. Would you reply attaching a mock screenshot?

Quote:
4. I need to pull the 16 bytes of tile data from the CHR-RAM, put it in NES RAM, apply the mask, then write the data back. Either I split this up over two frames (one read, one write) or I somehow eleminate the need for the read from CHR-RAM altogether.

For this, you'll probably want unrolled loop that writes 4 tiles and reads 4 other tiles. Then the loop would look like this:

  1. Active picture: Find 4 tiles to modify
  2. Vblank: Send sprite display list to OAM; read 4 tiles from CHR RAM
  3. Active picture: Modify those tiles; find 4 more tiles to modify
  4. Vblank: Send sprite display list to OAM; write those 4 modified tiles back; read 4 new tiles
  5. Active picture: Modify those tiles; find 4 more tiles to modify
  6. Vblank: Send sprite display list to OAM; write those 4 modified tiles back; read 4 new tiles
  7. Etc.

Quote:
5. How does QIX do this?

Qix has 8 KiB of WRAM at $6000-$7FFF, presumably to store a backup copy of the tiles that get copied into CHR RAM.

Quote:
6. How much memory is there in CHR-RAM compared to CHR-ROM?

Most NES games that use CHR RAM have 8 KiB of CHR RAM. Among licensed NES games, only Videomation has more (16 KiB). Oeka Kids games for Famicom have more, as do the unlicensed NES games RacerMate Challenge II and The Curse of Possum Hollow.

Quote:
And given there is only 1 bit in the rendering mask to indicate the tile page (typically background or sprite), how could you set it to a different page other than 1 or 0?

Mappers that support bankswitched CHR ROM also support bankswitched CHR RAM. On MMC3, for example, you'd write a window number (0 through 5) to $8000, then a 1K bank number (0 through 31) to $8001.

Quote:
7. If the background tile is temporarily in NES RAM, pixel level collision detections should be a simple masking check, correct? If my sprite is at (x,y) on the screen, I get the tile data for the background directly behind it. I then calculate what the offset (dx,dy) that sprite is from the upper left pixel in the tile. This gives me an " AND " mask which I can then apply to the tile data and see if there is overlap.

Whether you actually need pixel precision for collisions depends on your game design. Sometimes you can get away with much coarser granularity. This too will depend on the screenshot.


Top
 Profile  
 
PostPosted: Thu May 11, 2017 11:03 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 1570
Location: DIGDUG
How does QIX do this?

It uses CHR-RAM, and doesn't seem to ever read from the PPU, so somehow it's using RAM...maybe extra RAM at $6000-7fff to keep track of what part of the screen has been written.

Anyway, I did a modest move in Qix, and it split the CHR-RAM writes into 17 frames of writes.

It also switches pattern tables for BG halfway down each frame.

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Top
 Profile  
 
PostPosted: Thu May 11, 2017 11:03 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 9642
Location: Rio de Janeiro - Brazil
Lucradan wrote:
I'm going to have to use a board with CHR-RAM.

Yup.

Quote:
Unless I'm doing some hblank switching, I'm only going to be able to have at most 256 destructible tiles on the screen at once. Best case that's about 25% of the screen.

32KB RAM chips nowadays are easier to find than 8KB ones, so if you use a mapper with scanline IRQs you can easily have unique tiles for the entire screen by switching banks 3 times per frame. You can till pull off an entire screen of unique tiles using only 8KB of CHR-RAM if the image is only 2 colors, but there will only be 32 tiles left for sprites.

Quote:
I need a efficient mapping from pixel (x,y) to tile address

Assuming 4 256-tile banks, and a name table sequentially filled with 0-255, 0-255, 0-255, 0-191:

BankIndex: Y >> 6;
TileIndex: (Y << 2) AND %11100000 + (X >> 3);
TileRow: Y AND %00000111;
Plane0Address: TileIndex << 4 + TileRow;
Plane1Address: Plane0Address + 8;

NOTE: You can actually calculate the tile index differently if you arrange the relevant 5 bits of X and the relevant 3 bits of Y in different ways if it makes the calculation simpler, you just have to change the name table layout to reflect this.

To manipulate individual pixels of a row, you can use the index (X AND %00000111) to load a mask from the following tables:

Code:
PixelMasksSet:
   .db %10000000
   .db %01000000
   .db %00100000
   .db %00010000
   .db %00001000
   .db %00000100
   .db %00000010
   .db %00000001

PixelMasksClear:
   .db %01111111
   .db %10111111
   .db %11011111
   .db %11101111
   .db %11110111
   .db %11111011
   .db %11111101
   .db %11111110

Then you can OR the bytes from the pattern tables with the values from the first table to set bits, or AND them with the values from the second table to clear bits.

Quote:
I need to pull the 16 bytes of tile data from the CHR-RAM, put it in NES RAM, apply the mask, then write the data back. Either I split this up over two frames (one read, one write) or I somehow eleminate the need for the read from CHR-RAM altogether.

To eliminate the read you'd have to buffer all tiles in WRAM. There are mappers that allow you to have up to 32KB of WRAM.

Quote:
How does QIX do this?

I'm not familiar with this game.

Quote:
How much memory is there in CHR-RAM compared to CHR-ROM? And given there is only 1 bit in the rendering mask to indicate the tile page (typically background or sprite), how could you set it to a different page other than 1 or 0?

Bankswitching on the cartridge side.

Quote:
If the background tile is temporarily in NES RAM, pixel level collision detections should be a simple masking check, correct? If my sprite is at (x,y) on the screen, I get the tile data for the background directly behind it. I then calculate what the offset (dx,dy) that sprite is from the upper left pixel in the tile. This gives me an " AND " mask which I can then apply to the tile data and see if there is overlap.

Yes, you can do it like this. I advise against using sprite coordinates and dimensions in favor of logical object coordinates and bounding boxes though.


Top
 Profile  
 
PostPosted: Thu May 11, 2017 11:19 am 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 5175
Location: Canada
tepples wrote:
Lucradan wrote:
And given there is only 1 bit in the rendering mask to indicate the tile page (typically background or sprite), how could you set it to a different page other than 1 or 0?

Mappers that support bankswitched CHR ROM also support bankswitched CHR RAM. On MMC3, for example, you'd write a window number (0 through 5) to $8000, then a 1K bank number (0 through 31) to $8001.

Just to point out, this requires emulator support for the latest version of the iNES format to specify the CHR-RAM size. Emulators that are up to date are okay with this, but if you want your game to run on older emulators, PowerPak, etc. this isn't really an option.


Top
 Profile  
 
PostPosted: Thu May 11, 2017 11:57 am 
Offline
User avatar

Joined: Wed Oct 16, 2013 7:55 am
Posts: 114
rainwarrior wrote:
Emulators that are up to date are okay with this, but if you want your game to run on older emulators, PowerPak, etc. this isn't really an option.


PowerMappers adds support for 32k CHR-RAM to the PowerPak.


Top
 Profile  
 
PostPosted: Thu May 11, 2017 3:03 pm 
Offline

Joined: Wed Sep 21, 2016 12:08 pm
Posts: 23
Thank you everyone. I should have enough to build a small test case with a few destructible tiles. Give me a day or two and I'll see what I come up with.


Top
 Profile  
 
PostPosted: Thu May 11, 2017 4:47 pm 
Offline
User avatar

Joined: Sat Jul 12, 2014 3:04 pm
Posts: 791
tokumaru wrote:
so if you use a mapper with scanline IRQs you can easily have unique tiles for the entire screen by switching banks 3 times per frame.

Or do it Oeka-kids(iNES 096₁₀) mapper style, that this happen automatically.

dustmop wrote:
PowerMappers adds support for 32k CHR-RAM to the PowerPak.
I think it supports any size up to the PowerPak's CHR limit, that is, 512k, since thefox's code is reading the header and configuring appropriately. But, I'm just inferring.


Top
 Profile  
 
PostPosted: Thu May 11, 2017 5:54 pm 
Offline
User avatar

Joined: Wed Oct 16, 2013 7:55 am
Posts: 114
Myask wrote:
I think it supports any size up to the PowerPak's CHR limit, that is, 512k, since thefox's code is reading the header and configuring appropriately. But, I'm just inferring.


Ah, of course, that makes sense. I've never tried any more than 32k.

Back to the OP's topic: I was thinking of an approach that could be very cheap and easy, but only if there's a small number of actors that could collide with the destructible pixels. For example, in something like Space Invaders, where there's only 3 or less bullets that can destroy the environment, the main thread could pre-calculate the offsets, bit masks, and ppu addresses. Then the NMI would read in a single byte from CHR-RAM, apply the mask and check for collision, and if it's successful, could write back the newly updated CHR-RAM byte and assign a value that could be handled in the next main thread frame to react to the collision.

This wouldn't work if you wanted to check collision against many pixels in the background at once, but I don't know what your use case is. If this works, you don't need extra WRAM, and the work to be done in the NMI is very minimal. I think something like this approach could also work for a QIX-like game were there's a bunch of pixel-sized objects moving around.


Top
 Profile  
 
PostPosted: Wed May 17, 2017 5:07 pm 
Offline

Joined: Wed Sep 21, 2016 12:08 pm
Posts: 23
I finished a small test program for the concept. It only has 16 breakable tiles for the moment, but I hope to increase this to at least 240. It's also not as robust or as efficient as I'd like, but it's good for now.

Here is a general steps of how it works.

1. Change Bullet Position
2. Find Breakable Tile that is behind bullet
3. Find the bits on the high/low parts of the tile at that bullet position.
4. If either bit is true, there is a collision, otherwise return to 1
5. Set up the Mask buffer. The buffer for a 8 byte pixel mask has a size of 32 bytes and overlays at most 4 background tiles (UL, UR, LL, LR)
6. Apply the Mask Buffer to the background tiles UL, UR, LL, LR in NES RAM
7. Reset Bullet Position
8. At next NMI, copy changed bytes from tiles in NES RAM into PPU RAM
9. Return to 1


Attachments:
Destruction.nes [16.02 KiB]
Downloaded 7 times
Destruction.asm [25.42 KiB]
Downloaded 2 times
Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 13 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 7 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron
Powered by phpBB® Forum Software © phpBB Group