It is currently Mon Jan 22, 2018 5:10 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 21 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Tue Dec 05, 2017 1:04 pm 
Offline
User avatar

Joined: Sun Sep 26, 2010 10:29 pm
Posts: 40
Hi,
I need some help with reading from PPU. I am following the Nesdoug tutorial series and want to code a collision check between
a sprite and a background tile. Thus Doug mentions that reading from PPU while doing background collision is quite ugly, I would
like to give it a shot. Here is my code


Code:
#define VOID_TILE         0x00
#define OBSTICLE_TILE      0x01
#define NAMETABLE         0x2000

#define BG_ADR(x,y)      ((y<<2)|(x>>3))
#define MSB(x)         (((x)>>8))
#define LSB(x)         (((x)&0xff))

...

void render_routine(void){
   wait_until_nmi();
   PPU_ADDRESS = MSB(NAMETABLE + BG_ADR(projectile_x, projectile_y));
   PPU_ADDRESS = LSB(NAMETABLE + BG_ADR(projectile_x, projectile_y));

   if(PPU_DATA == OBSTICLE_TILE){
      PPU_DATA = VOID_TILE;
   }
        reset_scrolling();
}

...


This code correctly detects, that the projectile hits a OBSTICLE_TILE, but renders the VOID_TILE to a complete different
place on the screen (but on the same line). Can somebody help? Also it would be interesing, if it is common style to avoid
PPU reading while doing background collision cheks and therefore maintain internal collision maps, like in Nesdoug's tutorial series.

Regards
Sebastian


Top
 Profile  
 
PostPosted: Tue Dec 05, 2017 1:11 pm 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 6645
Location: Seattle
1) PPU_DATA has a one-byte delay for reads. So you have to set the address, read, and then the next read has the value you cared about.
2) PPU_ADDR will automatically increment after every read or write. So you'd have to re-set the address between when you read from memory and when you wrote to it.


Top
 Profile  
 
PostPosted: Tue Dec 05, 2017 2:21 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10233
Location: Rio de Janeiro - Brazil
The problem is not that it's "ugly", it's just not practical. You can only access VRAM during vblank, which lasts about 8% of the time of a frame, so it simply doesn't make sense to cram VRAM updates and collision logic inside such a small slice of time, while a lot of the remaining 92% of the time goes to waste. This may work for really simple games where not much happens each frame, but as you add more objects and more action, this model quickly becomes unfeasible.


Top
 Profile  
 
PostPosted: Tue Dec 05, 2017 2:34 pm 
Offline
User avatar

Joined: Thu Sep 15, 2016 6:29 am
Posts: 482
Location: Denmark (PAL)
One thing you can do that is still ugly, but at least feasible, if you're set on doing it this way, is use cartridge PRG-RAM and buffer writes to the nametable in a section of RAM that you're expecting to be identical to your nametable data, and read from that instead of using the PPU registers. It will eat up a massive 960 bytes (per nametable) that could have been used better, but the effect will be similar to what you're looking for.

I actually did exactly this for my recent Donkey Kong port, as I wanted the game to behave exactly like the arcade game down to minor details in the techniques used. The original game reads directly from video ram, but the NES can't do that, so I write to the buffer area in PRG-RAM instead, and copy it over whenever I need it, either managed blocks during vblank, or while turning off rendering.


Top
 Profile  
 
PostPosted: Tue Dec 05, 2017 3:11 pm 
Offline
User avatar

Joined: Sun Jun 05, 2005 2:04 pm
Posts: 2146
Location: Minneapolis, Minnesota, United States
Sumez wrote:
One thing you can do that is still ugly, but at least feasible, if you're set on doing it this way, is use cartridge PRG-RAM and buffer writes to the nametable in a section of RAM that you're expecting to be identical to your nametable data, and read from that instead of using the PPU registers. It will eat up a massive 960 bytes (per nametable) that could have been used better, but the effect will be similar to what you're looking for.


I think the main thing that is impractical is not even the RAM that is consumed, but trying to maintain that area of PRG-RAM so that it is identical to the PPU data. If you update your code that writes to $2007 so that it also writes to RAM, your updates to RAM won't benefit from the PPU auto-increment. You'll need to manually increment your index or address variable every time you write a value to $2007.

This is assuming that you are wanting to have access to any tile displayed on the screen at all times, including the HUD and other objects. If you're not including things like the HUD in your RAM copy, and you're dumping just the tiles that make up the background of your level, I would argue that that's not really creating a mirror of what is displayed, it's just decompressing your level into RAM, which is a common practice.

EDIT:
Sumez wrote:
I actually did exactly this for my recent Donkey Kong port, as I wanted the game to behave exactly like the arcade game down to minor details in the techniques used. The original game reads directly from video ram, but the NES can't do that, so I write to the buffer area in PRG-RAM instead, and copy it over whenever I need it, either managed blocks during vblank, or while turning off rendering.


One thing I hadn't considered when writing my post originally was that you could use the RAM copy of the screen to actually perform writes to $2007. In that case, I could actually see this being useful.


Last edited by Celius on Tue Dec 05, 2017 3:23 pm, edited 1 time in total.

Top
 Profile  
 
PostPosted: Tue Dec 05, 2017 3:16 pm 
Offline
User avatar

Joined: Thu Sep 15, 2016 6:29 am
Posts: 482
Location: Denmark (PAL)
That's pretty much exactly how I used it in my example... though it wouldn't have been necessary at all if the game didn't have logic that tries reading from the nametable data several times during a frame.
I definitely wouldn't have done it like that if I had to design the game from the ground up. :) I like to keep my collision data fast to decompress on the fly. Different methods for different games, though.


Top
 Profile  
 
PostPosted: Tue Dec 05, 2017 5:27 pm 
Offline
User avatar

Joined: Sat Sep 07, 2013 2:59 pm
Posts: 1558
-Basti- wrote:
Also it would be interesing, if it is common style to avoid
PPU reading while doing background collision cheks and therefore maintain internal collision maps, like in Nesdoug's tutorial series.

A general hint: You should usually separate your game logic from the graphical operations.

For example, when you have a character moving on screen, you should declare an x and y variable and not use the coordinate values from the actual hardware sprites for calculations.

Likewise, if your opponent is a background image, you should use general variables for collision checks instead of acually reading the PPU values and checking whether the empty background tile is set.

Not only does this free you from the restriction of only being able to read the PPU during vblank.
You can also access the variables much faster since you don't always have to set a register first.

_________________
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg


Top
 Profile  
 
PostPosted: Wed Dec 06, 2017 5:16 am 
Offline
User avatar

Joined: Sun Sep 26, 2010 10:29 pm
Posts: 40
Thank you all for your help. I think I will revise my thoughts and go back to collision maps. One more thing: how do scrolling games handle this? Do they switch between various collision maps according to the position of the player?

Regards
Sebastian


Top
 Profile  
 
PostPosted: Wed Dec 06, 2017 5:25 am 
Offline
User avatar

Joined: Thu Sep 15, 2016 6:29 am
Posts: 482
Location: Denmark (PAL)
The best practice I'd guess is having an algorithm that transforms an entity X/Y coordinate into a specific tile in your data, especially if you want to reuse the concept between both the player and enemies/other AI controlled stuff. The issue with scrolling games is of course that you'll be working with 16 bit coordinates on at least one axis.


Top
 Profile  
 
PostPosted: Wed Dec 06, 2017 7:44 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10233
Location: Rio de Janeiro - Brazil
-Basti- wrote:
Do they switch between various collision maps according to the position of the player?

That wouldn't work well when characters/objects cross the boundary between two screens, because they'd need simultaneous access to two separate collision maps.

Games that scroll usually work with a "cache" of the level map, at least 2 screens wide, so that the game has a little bit of space to work with past the edges of the screen (i.e. objects won't run into invalid collision data as they exit the screen). This cache, much like the name tables, is updated with new data as the camera moves, so the player is always surrounded by valid collision data.

Other games just have access to the complete level map at all times (i.e. the map is stored in a random-access-friendly format in ROM or decompressed in full to RAM), so that every piece of the level is accessible at any time.

Either way, scrolling games benefit greatly from using coordinates larger than 8 bits, since the space in which characters/objects live is larger than a single screen. These coordinates then have to be converted from level space to screen space for display purposes, via the following formula: SpriteX = ObjectX - CameraX


Top
 Profile  
 
PostPosted: Wed Dec 06, 2017 7:49 am 
Offline
User avatar

Joined: Fri Nov 19, 2004 7:35 pm
Posts: 3984
It just so happens that Dragon Quest 1 read all the tilemaps out of CHR-ROM.

_________________
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!


Top
 Profile  
 
PostPosted: Wed Dec 06, 2017 10:23 am 
Offline
User avatar

Joined: Sun Jun 05, 2005 2:04 pm
Posts: 2146
Location: Minneapolis, Minnesota, United States
Another disadvantage to using hardware tiles for collision detection is that if you do, you really can't repurpose a hardware tile. For example, you can't use the same tile to draw a real wall and a fake wall, even though the graphics are identical. Metatiles have more flexibility this way, and multiple metatile definitions typically takes up less space than multiple hardware tiles (you also have more room to define metatiles in PRG-ROM than you do hardware tiles in CHR-ROM).


Top
 Profile  
 
PostPosted: Wed Dec 06, 2017 4:03 pm 
Offline
User avatar

Joined: Sat Sep 07, 2013 2:59 pm
Posts: 1558
-Basti- wrote:
One more thing: how do scrolling games handle this? Do they switch between various collision maps according to the position of the player?

I guess this is done differently for different games.

My own game, "City Trouble", had an array with 34 values: The platform height for each of the 32 visible tiles + the tile left from the screen + the tile right from the screen. (If the scrolling doesn't align to 8 pixels, the part right from the screen is even visible.)

Whenever the scrolling aligned to 8 pixels, I shifted the values in the array to the left and loaded the rightmost position with the new value from the general level data.

_________________
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg


Top
 Profile  
 
PostPosted: Wed Dec 06, 2017 5:00 pm 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 1911
Location: DIGDUG
Your game (DRW) has auto-scrolling. So, load if scroll multiple of 8 makes sense here. Any game where scroll can shift 2 pixels in 1 frame might miss a load.

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


Top
 Profile  
 
PostPosted: Thu Dec 07, 2017 1:25 am 
Offline

Joined: Mon May 27, 2013 9:40 am
Posts: 384
DRW wrote:
Whenever the scrolling aligned to 8 pixels, I shifted the values in the array to the left and loaded the rightmost position with the new value from the general level data.


As a hint for future games, you can avoid having to shift the entire array if you use a circular array with a moving, "virtual" first index. To "shift" the whole array left or right you just move the origin. Of course, this puts some overhead in the functions which read from the array (you have to perform a substraction and a modulus - and if you are using powers of two / check + substraction if you are not), so it's a matter of measuring if the time gained from not having to shift an array is more than the extra time spent in the accesses.

In my situation, for example, it's quite useful. I keep a 16x16 metatile area as a collision map, and "scrolling it" would be overkill, so the circular array idea is most suitable.

_________________
http://www.mojontwins.com


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 21 posts ]  Go to page 1, 2  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: Bing [Bot] 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:  
Powered by phpBB® Forum Software © phpBB Group