Your reset code (where you set the fixed $C000 mode) won't run if the cart boots in a bank without a reset stub. The reset stub is necessary precisely to guarantee that your reset code gets called.unregistered wrote:Before our game starts (is playable), it sets MMC1 into fixed-$C000 mode. So that would nullify the reset_stub's usefulness in each PRG bank, I think; but, that's just me thinking... am I right?
Can't you sacrifice 1 or 2 tiles to make room for the reset stub? Or implement some simple compression of the CHR data (e.g. good old RLE) to gain ~25% (~4KB) of the space back without sacrificing any tiles?
I just would like to not have to copy all of block 16, our fixed bank is $C000 in bank 15, into block 32, because then I'd have to change the code in both blocks when debugging. Do you understand my desire?
edit: Is block 16 the same as bank 15? Because the banks start with bank 0.
To avoid having to duplicate everything in bank 15 into bank 31, you could put only those subroutines that operate on data in the first outer bank into the first outer bank's fixed bank, and only those subroutines that operate on data in the second outer bank into the second outer bank's fixed bank. The practicality of that may depend on how complex your NMI handler is. Switching banks in NMI on MMC1 is very tricky. You might want to use a simplified NMI handler in the second outer bank.
The overall code might look like this:
Code: Select all
; This goes at $FFF0 in all 16K banks resetpart1: sei ldx #$FF stx resetpart1+2 ; Clear shift register; set fixed $C000 txs ; Initialize stack pointer jmp resetpart2 .addr nmi_handler, resetstub1, irq_handler ; This goes at the same place in both fixed banks, such as $FF80 resetpart2: inx ; now X is 0 stx $2000 ; disable NMI handler stx $2001 ; disable rendering ; Set CHR window to bank 0 stx $A000 stx $A000 stx $A000 stx $A000 stx $A000 ; Set mode to $0E: vertical mirroring, fixed $C000, 8K CHR lda #1 stx $8000 ; X writes a 0, A writes a 1 sta $8000 sta $8000 sta $8000 stx $8000 jmp resetpart3 ; And this goes in your primary fixed bank resetpart3: cld bit $2002 @vsync1: bit $2002 bpl @vsync1 @vsync2: bit $2002 bpl @vsync2 ; Hardware init continues here, including choosing a PRG bank ; at $8000 by writing to $E000
That would be pretty cool to be able to write something like .incbin ]16, $000, $4000, inside of block 32, with the "]" indicating that a block number follows. That's the only character not used in asm6, I think.
Guess that I don't have to put anything inside block 32 now because I don't need to set P bit of "CHR bank 0" to 1 now. ...Maybe I need to reread tokumaru's explaination and think about how that logic could affect the need of applying tepples' wisdom. Lunch time.
1.) How many points does one need to check if the sprite is 8 pixels wide and she is colliding into an 8 pixel wall, but she moves more than 8 pixels per frame sometimes. I been attempting to solve this for a while. Now, two points are checked and she used to never enter the wall, but she is ejected 8 pixels away from the wall every other time it checks collision.Kasumi[color=#FF8080], [url=https://forums.nesdev.com/viewtopic.php?p=91421&sid=afaed212db3f60a1730db7676f794c05#p91421]on page 32[/url],[/color] wrote:I don't quite see the relation to collision detection.My goal is to see this collision detection work well.
But sure, I can sure talk about it. It's actually really easy and fast as long as you don't handle slopes, and don't have things like tiles you can jump up through or tiles you can drop down through.
You only really need to check 6 points to be TOTALLY safe, or 4 if the shape of your character never changes and he is smaller than the size of the collision tile for both X and Y. If he's say... 24 pixels tall with 16x16 collision tiles you have to check one more point. You also need to check more if your character moves more pixels in a frame than the size of your tile. For instance, he's capable of moving 9 pixels in a frame, but your collision tiles are only 8 wide.
Obviously, you first gotta find out whether or not the tile you're working with is collision enabled. I can't especially help with that unless you make a post about your current tile format. Post about that, and I can write a book for you about that.
I personally keep 4 screens of 32x32 metatiles in a page of RAM, which are updated when the game scrolls to a new screen. This means all my screen decompression happens once and only on a frame when the character passes a screen boundary.
The collision detection and parts of my code that tell the PPU what to draw always pull from this RAM, where it is much easier to figure out the math for me than pulling directly from the data in ROM.
I load the 32x32 tile from RAM, I find out the 16x16 tile I want in it. Then I know if I should eject or not.
It works exactly like this:The tricky part is actually getting the 32x32 tiles into RAM in the first place, but you may not even need to do that depending on how your data is stored.
Code: Select all
;Ignore the <'s, it's a nesasm thing. lda <xhigh;Zero Page RAM I put any high X position into ror a;puts the lowest bit of the high x pos into the carry lda <yhigh;Zero Page RAM I put any high y position into ror a;puts the lowest bit of the high y pos into the carry ;and puts the x bit into the high bit of the accumulator ror a;Now the low x and y bits are in the high bits accumulator and #%11000000;Removes the other junk in the accumulator sta <reservedE;Temp RAM. The highest bits of it now contain ;which screen I'll read from RAM. Each screen is 64 bytes long, because it contains 8x8 32x32 metatiles. lda <xlow lsr a lsr a lsr a lsr a lsr a;divide by 32, since I'm trying to get a 32x32 metatile. ;The other bits of precision don't matter. ora <reservedE;This combines my "which screen" bits with the X offset in RAM. sta <reservedE lda <ylow lsr a lsr a and #%00111000;This takes away all unnecessary bits from ;my y position ora <reservedE;I know have the offset in RAM that I need to load my 32x32 tile from. tay;Y contains the address of the 32 x 32 metatile to look up lda $0500,y;loads 32 x 32 tay;Y contains the 32 x 32 tile number
I then use a series small series of branches to determine which part of the 32x32 tile I'm in. Topleft, Topright, bottomleft, or bottomright. Once I know that, I load the tile and use its format to determine whether it's a collision or not.
Here's how I eject when I know the current tile is collision enabled
You want to eject left while traveling right. So imagine the right point of your character has just entered the first pixel of a tile. It's anded position would be 0. In this case, you obviously want to eject one. If you're going super fast, you'll end up further in the tile, so the and will be higher and you'll always eject one more than that. Easy. jsr to the routine, subtract offset from your character's position. (It should be set to 0 if the collision routine detects the current tile is not a collision tile.) You can even use the offset variable the routine returns to find out whether or not there WAS a collision if you want to do that to stop a walking animation or something.
Code: Select all
;Ejecting up/left lda lowbyte and #%00001111 clc adc #$01 sta offset rts
This one is mostly the same, except it has an eor. Say you're traveling left and hit the first pixel of a tile. That's actually pixel $0F, but you only want to eject one! The eor makes $0F equal to one, then it works the same as before! As before, just write a 0 to offset if you've discovered the current tile is not solid. Jsr to routine, then add offset to player's position.
Code: Select all
;Ejecting down/right lda lowbyte and #%00001111 eor #%00001111 clc adc #$01 sta offset rts
This works for any size tile that's a power of two. Just change the ands and eor to the number of bits your tile takes up.
If you're traveling left, move the player using the current speed value. You may be in a wall. You check the top and bottom left points of your character, and eject right. (add offset) If you're traveling right, check the top and bottom right points of your character and eject left. (subtract offset) you'll never be in the wall for a frame like Mario, and this will certainly be fast enough for how few tiles you'll need to check.
For ejecting up while falling you check the bottom left and right points and subtract offset from the player's position.
2.) The code I was blessed to discover for
Code: Select all
lda altoX ;<our major X variable and #00000111b ;and with 7 because our collision deals with 8x8 tiles :) ;always clears bit3 ldx FORWARD ;<00 for left, 01 for right, and ff for neither ;it is verified that FORWARD won't be ff if this code is reached :) bne + ;if going right we don't want to invert bits 0 through 2 eor #00000111b + ;some other code here sta t16 ;a temp variable lda altoX ldx FORWARD beq + clc sbc t16 jmp ++ + sec adc t16 ++ sta altoX
3.) Your usage of ror to move the low bits of yhigh and xhigh into the carry is really brilliant Kasumi!
p.s. I hope I haven't posted this earlier.
edit: Sorry, I was wrong, we
edit2: Sorry, noticed that my summary of our code was wrong so created a strike-through and followed that with a valid summary, I think.
Say your tilesize is 16x16. You have a character that is 18 pixels tall. They can move through a single tile if they're positioned perfectly and you only check the top and bottom points.
(All example images in this post are zoomed to 400%) Black is one 16x16 tile, red is the character, and yellow are the points checked.
That image shows what happens if you only check the top and bottom points. If your character is tall enough, a tile can pass through their middle!
So you have to check the top point always. Then 16 pixels below that.
Note even when the top point is one above the tile, the collision still works. Move it down and it still works. This ensures there will always be at least ONE point for every tile the player might hit on the grid. You still need to check the bottom, though! (Unless the bottom is exactly 16 pixels below the top point.) Otherwise the character could move their bottom through tiles!
This image shows which points you'd have to check for characters of lots of heights:
Top. Then 16 pixels below that. (Then 16 pixels below that. As many times until 16 pixels below the last point is outside the character.) Then you have to check the bottom point. This will eliminate any blindspot.
Code: Select all
FCEUX 2.2.3 - Trace Log File f1 A:00 X:FF Y:00 S:C2 P:nvubdIzc $BFF0:78 SEI f1 A:00 X:FF Y:00 S:C2 P:nvubdIzc $BFF1:A2 FF LDX #$FF f1 A:00 X:FF Y:00 S:C2 P:nvubdIzc $BFF3:9A TXS f1 A:00 X:FF Y:00 S:FF P:NvubdIzc $BFF4:8E 00 80 STX $8000 = $00 f1 A:00 X:FF Y:00 S:FF P:NvubdIzc $BFF7:4C A0 FF JMP $FFA0 f1 A:00 X:FF Y:00 S:FF P:NvubdIzc $FFA0:00 BRK f1 A:00 X:FF Y:00 S:FC P:NvubdIzc $D1BA:00 BRK etc.
|iNES header| 4E 45 53 1A |Number of PRG-ROM blocks| 20 |Number of CHR-ROM blocks| 00 |ROM control bytes: Vertical mirroring, yes SRAM, no trainer, Mapper #1| 13 00 |Filler| 00 00 00 00 00 00 00 00
For my PRG-ROM blocks I just added 15 more blocks after the first 14 and started bank!15, the last bank in the the second outer bank, with .base $C000... and also had to change the .org $C000 in my main fixed bank, that follows it, to .base $C000.
Ohhh!! Maybe I need to place the other 15 blocks after my main fixed bank!
edit: Also want to say that now the nesdev wiki makes sense, to me, when it says, "Valid addresses are $0000-$3FFF," at the end of the PPUADDR section. Honestly, I always thought that was a mistake; but after writing to CHR-RAM ($0000-$1FFF) it is nice to know that that statement is valid when CHR-RAM is enabled. Want to write that here to help some others to understand that too.