Insights on Backgrounds Pixel Changes?
Moderator: Moderators
Insights on Backgrounds Pixel Changes?
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.
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.
Re: Insights on Backgrounds Pixel Changes?
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.
Re: Insights on Backgrounds Pixel Changes?
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
Re: Insights on Backgrounds Pixel Changes?
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
Re: Insights on Backgrounds Pixel Changes?
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.Lucradan wrote:1. I'm going to have to use a board with CHR-RAM. This will be a new experience.
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.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.
That depends on where you want to put the playfield. Would you reply attaching a mock screenshot?3. I need a efficient mapping from pixel (x,y) to tile address
For this, you'll probably want unrolled loop that writes 4 tiles and reads 4 other tiles. Then the loop would look like this: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.
- Active picture: Find 4 tiles to modify
- Vblank: Send sprite display list to OAM; read 4 tiles from CHR RAM
- Active picture: Modify those tiles; find 4 more tiles to modify
- Vblank: Send sprite display list to OAM; write those 4 modified tiles back; read 4 new tiles
- Active picture: Modify those tiles; find 4 more tiles to modify
- Vblank: Send sprite display list to OAM; write those 4 modified tiles back; read 4 new tiles
- Etc.
Qix has 8 KiB of WRAM at $6000-$7FFF, presumably to store a backup copy of the tiles that get copied into CHR RAM.5. How does QIX do this?
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.6. How much memory is there in CHR-RAM compared to CHR-ROM?
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.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?
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.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.
Re: Insights on Backgrounds Pixel Changes?
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.
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
Re: Insights on Backgrounds Pixel Changes?
Yup.Lucradan wrote:I'm going to have to use a board with CHR-RAM.
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.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.
Assuming 4 256-tile banks, and a name table sequentially filled with 0-255, 0-255, 0-255, 0-191:I need a efficient mapping from pixel (x,y) to tile address
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: Select all
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
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.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.
I'm not familiar with this game.How does QIX do this?
Bankswitching on the cartridge side.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?
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.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.
- rainwarrior
- Posts: 8734
- Joined: Sun Jan 22, 2012 12:03 pm
- Location: Canada
- Contact:
Re: Insights on Backgrounds Pixel Changes?
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.tepples wrote: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.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?
Re: Insights on Backgrounds Pixel Changes?
PowerMappers adds support for 32k CHR-RAM to the PowerPak.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.
Re: Insights on Backgrounds Pixel Changes?
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.
Re: Insights on Backgrounds Pixel Changes?
Or do it Oeka-kids(iNES 096₁₀) mapper style, that this happen automatically.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.
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.dustmop wrote:PowerMappers addssupport for 32k CHR-RAM to the PowerPak.
Re: Insights on Backgrounds Pixel Changes?
Ah, of course, that makes sense. I've never tried any more than 32k.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.
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.
Re: Insights on Backgrounds Pixel Changes?
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
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 105 times
-
- Destruction.asm
- (25.42 KiB) Downloaded 107 times