Collision Cleanup

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Collision Cleanup
by on (#59892)
I have been working on the collision for my demo, and I have been trying to get my head around some of the methods I have read about on the forum but I can't seem to get what they are saying.

Code:

   LDY $0200
   CPY #$26
   BEQ StartRightMove
   CPY #$27
   BEQ StartRightMove
   CPY #$28
   BEQ StartRightMove
   CPY #$46
   BEQ StartRightMove
   CPY #$47
   BEQ StartRightMove
   CPY #$48
   BEQ StartRightMove
   CPY #$66
   BEQ StartRightMove
   CPY #$67
   BEQ StartRightMove
   CPY #$68
   BEQ StartRightMove
   CPY #$86
   BEQ StartRightMove
   CPY #$87
   BEQ StartRightMove
   CPY #$88
   BEQ StartRightMove
   CPY #$A6
   BEQ StartRightMove
   CPY #$A7
   BEQ StartRightMove
   CPY #$A8
   BEQ StartRightMove
   CPY #$C6
   BEQ StartRightMove
   CPY #$C7
   BEQ StartRightMove
   CPY #$C8
   BEQ StartRightMove
   JMP ReadRightDone



That is about the best I could come up with for my collision detection with background tiles. My map is arranged in a grid pattern like so:

xx[][]xx[][]xx[][]xx
xx[][]xx[][]xx[][]xx
xxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxx
xx[][]xx[][]xx[][]xx
xx[][]xx[][]xx[][]xx

My code as is reads the Y position of the top left portion of the sprite and determines whether or not it is able to move based on the position of the blocks. If anyone could give me an idea of how to make a better collision detection system or even just point out a tutorial, I would be appreciative.

by on (#59894)
Man, you don't want to hardcode the collision data in the game logic... This is not how real games work. A real game engine is generic, and it behaves differently based on the data it's given.

What you want to do is store a table/array indicating what is solid and what isn't, in a way that is easy to look up. Then based on your character's coordinates you are gonna read this table/array and decide whether to allow or deny movements requested by the player.

This way you can even add new maps later, you just have to indicate what's solid and what isn't in this new map, and the same logic can be used for all maps. If you continue to do it the hardcoded way you are gonna have to write different pieces of logic for each level/map...

by on (#59895)
One of the proper ways to address the collision between the background and characters is to store an array containing physics data. Then you check the position the character wishes to move to, and allow or disallow the movement based on your collision map.

Say you have a 8x8 tile size like the NES uses for a BG tile. To represent a whole nametable with collision data you would need 32x30 bytes.

To check collision you would need to convert the position of a character (like the player) to an index in this array to check. You would divide the X by 8 to find out which 8x8 section the point you are checking. Same with Y. Hope that gives you some idea. You definitely don't make a special code routine to check hundreds of possible values like that.

by on (#59896)
Lets say that tile #1 is a 'solid' tile.
(Assume the Y is the same for the tile and player)


The x position of BG tile #1 is 0, so it occupies 0-7, correct?

If my character's x position is 8, that would mean decrementing his x position just once would put him in a collision.

So would my code be something along the lines of:


Load Current X Position
Load Current y position
Decrement X

$2000 + (Y x 32) + x

Load the tile number of that tile
Compare it with any of the tiles that I deem as solid (in this case lets just say 23)
Since it would be 23, I would then skip over my movement code and continue on.



I think I have a rough idea of how to do this, but before I try to write up a test for this, can someone clarify any possible misconceptions I am having with my above statements.

by on (#59897)
Yeah, it's kinda like you described, but there are some things to consider.

First, you shouldn't read the tile data from VRAM for collision purposes, because VRAM can only be read during VBlank and it would suck if your game logic had to wait for VBlank before taking decisions (it would slow down your game a lot). So it's better to store the collision map somewhere in PRG-ROM, so that you have free access to it. Of course that in order to draw the screen you must have used data from the ROM, so you probably already have a copy of that data accessible in the ROM.

Second, the player's coordinates have to go through a conversion before being used for checking tiles, because sprite coordinates are measured in pixels, while map coordinates are measured in tiles.

Also, the direction of the movement is important, because you have to compensate for the dimensions of the player. Say, if you player's coordinates indicate the top left corner of the sprite, when you move to the right you have to add the width of the sprite to the X coordinate before using it to check the map, because when moving to the right it's the right side of the sprite that collides with the map, not the left side.

Collision detection with the map usually works like this in professional games:

First you move the character, without worrying about collision at all. This might put the character inside a wall, but it doesn't matter because the collision works by ejecting the character from the wall if that's the case.

Now you check for solid tiles only in the direction the player has moved. This will save you some time, because it's obviously impossible to hit a wall on the left if you moved right. If you allow movement in both axis at the same time, you'll perform one horizontal verification (either left or right) and one vertical verification (either up or down).

Now, say I have a small map looks like this (it has 8x4 blocks of 16x16 pixels each):

Code:
+----+----+----+----+----+----+----+----+
|00  |01  |02  |03  |04  |05  |06  |07\\|
|    |    |    |    |    |    |    |\\\\|
+----+----+----+----+----+----+----+----+
|08  |09  |0A  |0B  |0C  |0D  |0E  |0F\\|
|    |    |    |    |    |    |    |\\\\|
+----+----+----+----+----+----+----+----+
|10  |11  |12  |13  |14  |15  |16  |17\\|
|    |    |    |    |    |    |    |\\\\|
+----+----+----+----+----+----+----+----+
|18\\|19\\|1A\\|1B\\|1C\\|1D\\|1E\\|1F\\|
|\\\\|\\\\|\\\\|\\\\|\\\\|\\\\|\\\\|\\\\|
+----+----+----+----+----+----+----+----+

The blocks filled with "/" are solid, the empty ones aren't. Here's the map again, now with my player on it:

Code:
+----+----+----+----+----+----+----+----+
|00  |01  |02  |03  |04  |05  |06  |07\\|
|    |    |    |    |    |   o----+|\\\\|
+----+----+----+----+----+---|P   |+----+
|08  |09  |0A  |0B  |0C  |0D | L  ||0F\\|
|    |    |    |    |    |   |  A ||\\\\|
+----+----+----+----+----+---|   Y|+----+
|10  |11  |12  |13  |14  |15 +----+|17\\|
|    |    |    |    |    |    |    |\\\\|
+----+----+----+----+----+----+----+----+
|18\\|19\\|1A\\|1B\\|1C\\|1D\\|1E\\|1F\\|
|\\\\|\\\\|\\\\|\\\\|\\\\|\\\\|\\\\|\\\\|
+----+----+----+----+----+----+----+----+

His top left corner has an "o" because that's the point indicated by his coordinates. Now, say the person playing the game pressed "right". We just move the player right:

Code:
+----+----+----+----+----+----+----+----+
|00  |01  |02  |03  |04  |05  |06  |07\\|
|    |    |    |    |    |    |o----+\\\|
+----+----+----+----+----+----+|P   |---+
|08  |09  |0A  |0B  |0C  |0D  || L  |F\\|
|    |    |    |    |    |    ||  A |\\\|
+----+----+----+----+----+----+|   Y|---+
|10  |11  |12  |13  |14  |15  |+----+7\\|
|    |    |    |    |    |    |    |\\\\|
+----+----+----+----+----+----+----+----+
|18\\|19\\|1A\\|1B\\|1C\\|1D\\|1E\\|1F\\|
|\\\\|\\\\|\\\\|\\\\|\\\\|\\\\|\\\\|\\\\|
+----+----+----+----+----+----+----+----+

That got him into a wall, but the game engine doesn't know that yet. What we have to do now is add the width of the player to his X coordinate so that we find the X coordinate of his top right corner, because he moved right:

Code:
+----o
|P   |
| L  |
|  A |
|   Y|
+----+

Now we have to convert those coordinates into map coordinates. Since the player's coordinates are measured in pixels, and my map uses 16x16-pixel blocks, to convert pixel coordinates into block coordinates we just have to divide them by 16. In assembly this is easily done by shifting a number to the right 4 times. Now we use a formula similar to the one I gave you for locating tiles in the name tables: Y * (map width) + X

Using that formula we will get the index of the block we have to check (in this example it would be $07). But checking this block isn't enough, we have to check the ones that are below it as well, because the player is much taller than a block. For that we must find the coordinates of the bottom right corner of the sprite, so that we know where it ends:

Code:
+----+
|P   |
| L  |
|  A |
|   Y|
+----o

Just add the sprite's height to the Y coordinate. Now we make the same conversion as before and see that this point is over block number $17. We have to check all 3 blocks, $07, $0f and $17 in order to decide what to do with the character. If all 3 blocks are empty (not solid), we just leave the player where he is. Now if any one of those blocks is solid, we must push him back to the left, "ejecting" him from the wall:

Code:
+----+----+----+----+----+----+----+----+
|00  |01  |02  |03  |04  |05  |06  |07\\|
|    |    |    |    |    |    +----o\\\\|
+----+----+----+----+----+----|P   |----+
|08  |09  |0A  |0B  |0C  |0D  | L  |0F\\|
|    |    |    |    |    |    |  A |\\\\|
+----+----+----+----+----+----|   Y|----+
|10  |11  |12  |13  |14  |15  +----o17\\|
|    |    |    |    |    |    |    |\\\\|
+----+----+----+----+----+----+----+----+
|18\\|19\\|1A\\|1B\\|1C\\|1D\\|1E\\|1F\\|
|\\\\|\\\\|\\\\|\\\\|\\\\|\\\\|\\\\|\\\\|
+----+----+----+----+----+----+----+----+

Remember, all the blocks between the top right corner and the bottom right corner of the character must be checked, or else it might end up missing a solid block.

by on (#59938)
Quote:
His top left corner has an "o" because that's the point indicated by his coordinates.


When you input the coordinates for a tile, it is positioned by the bottom left hand corner, so why would the top right indicate the character's position?


Quote:
Remember, all the blocks between the top right corner and the bottom right corner of the character must be checked, or else it might end up missing a solid block.


Note: When I say 'collision block', I mean one of the 16x16 areas like 00 or 1A in your diagrams.

If I have a character that is composed of 4 tiles:


[1][2]
[3][4]

Also, when checking for collision when a character is in a position that is 'spanning' more than 1 collision block, which blocks would be best for checking.

My guess would be 1, 3, and the top of 1, that *should* cover all possible collision blocks that the character could enter, right?

Code:
+----+----+----+----+----+----+----+----+
|00  |01  |02  |03  |04  |05  |06  |07\\|
|    |    |    |    |    |    +----o\\\\|
+----+----+----+----+----+----|P   |----+
|08  |09  |0A  |0B  |0C  |0D  | L  |0F\\|
|    |    |    |    |    |    |  A |\\\\|
+----+----+----+----+----+----|   Y|----+
|10  |11  |12  |13  |14  |15  +----o17\\|
|    |    |    |    |    |    |    |\\\\|
+----+----+----+----+----+----+----+----+
|18\\|19\\|1A\\|1B\\|1C\\|1D\\|1E\\|1F\\|
|\\\\|\\\\|\\\\|\\\\|\\\\|\\\\|\\\\|\\\\|
+----+----+----+----+----+----+----+----+


One final thought. The code I have setup already seems to be working, and it is generic to the point that I could easily set it as a subroutine that all movements (up, down, left, and right) could use for their collision checks with minor setups for the variables in between. I know how branches have the limit where you can only have a branching statement so far from the actual spot you are branching from and I was wondering if JSRs had this limitation. I would suspect they wouldn't but I seem to find some problems with everything I though I knew so far so I wouldn't be surprised if I am wrong.

by on (#59939)
Branch has only an 8bit value which is signed to redirect flow. Jumps like JSR can jump anywhere between $0000 to $FFFF. If a branch won't do you could do a branch for the inverse condition to skip over a JMP to the code needed as JMP has no limit to where you can jump to.

For collision for your game it is up to you how you want to check the collision of objects and from what points. Generally in an action game you'll need to check one to two points for feet and head and two or more maybe for sideways movement. But that's just a general idea, you'll have to figure out what works best for you.

You can google general "game collision detection" techniques for 2D games to get more information too. It's not really a NES specific issue so help is widely available.

by on (#59941)
I have been working on collision for about a week now and still haven't gotten it straightened out. I have gone over Tokumaru's diagrams and information and it makes complete sense. I wrote out some code that *should* be working but just isn't. I have spent the entire day and still haven't reached that aha! moment.

I have gotten the collision checking to work the where if I have a setup like so:

1 -- xxxxxxxxxxxxxxxxxxxxx
2 -- []xx[]xx[]xx[]xx[]xx[]xx
3 -- xxxxxxxxxxxxxxxxxxxxx
4 -- []xx[]xx[]xx[]xx[]xx[]xx

X = blank space
[] = solid object

Whenever the character is in row 1 or 3, he can move freely (I only put the code in the left movement while I work on it) so whenever I am in rows 2 or 4 I cannot move to the left at all, even if it has space where it could still move to.

As I said, I have literally spent my entire day attempting to get this implementation to work and yet it just fails. I have been going over every possible problem I could imagine and try to figure out if that is occurring and if so, how to fix it. I have enclosed a the source and pretty much everything I have here: [Removed URL of an archive containing an NES game published by a division of Konami --MOD]

I really can't figure out what is going wrong. I have a feeling it has to do with the math part like the dividing by 16 or something.

by on (#59944)
First: You have a commercial rom in that zip. (even though it's slightly hacked, it's not good to put on this forum)

Other forum members: Please check that my info on $2002, and .rs is correct. Otherwise, correct me. I don't want to be "the blind leading the blind." :lol:

67726e: Get ready for a BOOK! (Make sure to read all the comments I put in the code blocks too, there's more info there.)

In any case: It may help you to write a flowchart of how your code is running. Something much more readable than the actual code, but still gets the idea across. This post is going to help you get your code working first. There are also some bad practices, but I'll get to them later.

Starting from your main game loop (The Forever Label):

Forever:
Check if player can move.
If not, jmp to forever.

(all buttons do nothing except for the dpad, so I'll skip 'em)

Read Up.
If pressed, subtract one from player's y position unless he is outside the top of the maze.

Read Down
If pressed, add one to player's y position unless he is outside the bottom of the maze.

(Here's where it gets weird)

Read Left.
Start collision checking (Here? Why? If left is not pressed, the collision detection routine will never be run since the BEQ ReadLeftDone above it skips it. So...the player can move freely through any wall as long as left isn't pressed)

I am now going to break away from my flowchart, because there are actual coding mistakes here. Even assuming the collision detection was in the right place, it wouldn't work.

Code:
; Start of checking for collsion
   LDA $0203;This can never be less than #$18. Because you keep the player from going further left when it is equal to that.
   LSR A;Can't be less than #$0C
   LSR A;Can't be less than #$06
   LSR A;Can't be less than #$03
   LSR A;Can't be less than #$01
   STA XBLOCK;Hmm... This can never be 0.
   LDA $0200;Can't be less than #$27 because you keep the player from going up in this case.
   LSR A;Can't be less than #$13
   LSR A;Can't be less than #$09
   LSR A;Can't be less than #$04
   LSR A;Can't be less than #$02
   STA YBLOCK;This one can't be 1 OR 0.



That's a problem. You can't access the first two rows, and the first column of your CollisionTable. This can be fixed by subtracting those values so that #$18, #$27 (the coordinates where your playfield starts) becomes #$00, #$00 (so you can look up the right values in the collision array.)

The quickfix is something like this:


Code:
; Start of checking for collsion
   LDA $0203;This can never be less than #$18. Because you keep the player from going further left when it is equal to that.
   SEC
   SBC #$18
   LSR A
   LSR A
   LSR A
   LSR A
   STA XBLOCK
   LDA $0200
   SEC
   SBC #$27
   LSR A;
   LSR A;
   LSR A;
   LSR A;
   STA YBLOCK


Another good exercise (like making a flowchart) is thinking through which values are possible on a given routine. (As I did above) It helped me realize some parts of your data were inaccessible.

Moving on.

Code:
   LDA #$00
   LDX #$00;Load A and X with Zero. Fine.
CollisionRoutine:
   CLC
   ADC #13;Add the width of the playfield. Not... fine. At least not here. This makes part of your CollisionTable inacessible again. Let's say YBLOCK = 0. You'd be checking row 1. It's always gonna be offset by one, so the 0 row is never accessible. It's also one off every other time.
   INX ;Actually... if YBLOCK is 0 there's more wrong than that. This would make X = 1.
   CPX YBLOCK ;Which would not be equal to YBLOCK. It would have to loop all the way to #$FF, then back to #$00 before it stopped adding #13.
   BNE CollisionRoutine
   CLC
   ADC XBLOCK
   STA COLLISIONBLOCK;
   
   LDX COLLISIONBLOCK;I'll
   LDY CollisionTable, x
   CPY #1
   BEQ ReadLeftDone


The quick fix for this?

Code:
   LDA #$00
   LDX YBLOCK
   BEQ CollisionRoutine.XAdd;This way it doesn't add 13 when it doesn't need to.
CollisionRoutine:
   CLC
   ADC #13
   DEX;Why? I've reversed your logic. Rather than starting at 0 and INX'ing to YBLOCK, I'm starting at YBLOCK and DEX'ing to 0.
   BNE CollisionRoutine
CollisionRoutine.XAdd:
   CLC
   ADC XBLOCK
   STA COLLISIONBLOCK;
   
   LDX COLLISIONBLOCK
   LDY CollisionTable, x
   CPY #1
   BEQ ReadLeftDone


Finally, the .rs command doesn't work like you think it does. (It may not work like I think it does either, since I don't use it. Another forum member will cover my behind if I'm wrong) .rs reserves a number of bytes, it doesn't set a variable to the number you give you it.

Code:
LIVES            .rs 3      ; set default # of lives


This does NOT set LIVES to 3, it reserves 3 bytes where the .rsset counter currently is.

Code:
   .rsset $0000;Sets the counter to location $0000
LIVES            .rs 3; Reserves RAM location $0000, $0001, $0002 for use with LIVES. the rs counter location is now $0003.


That said, the final thing I did to your code to make collision detection work completely (At least while traveling left, since I didn't move your collision detection routine from where it is. Also note it ONLY checks the player's top left point, so parts of him can still pass through blocks. But it DOES work for just that point.) was this:

Code:
         ; Player Data
XBLOCK            .rs 1
YBLOCK            .rs 1
COLLISIONBLOCK      .rs 1


There are obviously problems with the other .rs uses, but I couldn't really fix them without completely breaking your code.

Here's what I believe happens.

Code:
.rsset $0000  ;;start variables at RAM location 0
   
NMI_COUNTER         .rs 0      ;All references to NMI_COUNTER are assembled to $00. The rsset counter is not incremented.
ALLOW_MOVE         .rs 0      ;All references to ALLOW_MOVE are assembled to $00. The rsset counter is not incremented. This means NMI_COUNTER and ALLOW_MOVE refer to the SAME RAM location (!)
ALLOW_ANIM         .rs 1;All References to ALLOW_ANIM are assembled to $00. (The same location as the above variables) the rsset counter is incremented by one to $0001
LIVES            .rs 3      ; All references to LIVES are assembled to $01. The rsset counter is incremented by three to $0004
SCORE            .rs 0      ;All references to SCORE become $04. Counter not incremented
PLAYING            .rs 0      ;All references to  PLAYING become $04 (The same location as score) The counter is not incremented.


So the fix I did above just made sure XBLOCK, YBLOCK and COLLISIONBLOCK all referred to different RAM locations. I couldn't fix your NMI_COUNTER and ALLOW_MOVE being the same RAM location without putting more thought into it than I want to right now. (Sorry, this is a LONG post, and this is actually the last part I've written. This has been like three hours or so of writing... :shock: I may make another post with a fix for this too when I'm less tired.)

It is up to you to figure out how to make collision detection work for the other directions. But now I will cover some other things in your code, that either confused me, or just aren't as well done as they could be.

I could be wrong on this, so correct me if I'm wrong other forum members.

Code:
VBLANKWAIT2:
   BIT $2002            ;This counts as a read of PPU status. So it resets the high/low latch. Other forum members correct me if I'm wrong of course.
   BPL VBLANKWAIT2
   
LOADPALLETES:
   LDA $2002               ; There is no need for this, because of that.
   LDA #$3F


Later, something similar.

Code:
LOADBKG:
   LDA $2002;;Each write to 2006, switches whether high/low will be written to. Since you're done it twice above, You're back where you want to be. This isn't needed.
   LDA #$20
   STA $2006


It happens at least one other time. Interestingly enough you don't do it before writing to the scroll register ($2005, which uses the same latch), but you don't need to. But... it doesn't hurt to keep them there, really (just uses some extra bytes and cycles at startup). I say again, if I'm wrong about the above please let me know other forum people, since I'd need to do some stuff in my own games. :D And we might want to clarify the wiki if I'm right, since I can see where he got the idea.



Code:
FOREVER:
   LDY #$01
   CPY ALLOW_MOVE
   BEQ LATCHCONTROLLER
   JMP FOREVER


Loading a value will change the zero bit. So...

Code:
FOREVER:
   LDY ALLOW_MOVE
   BNE LATCHCONTROLLER;It's faster by a little bit, since you're not comparing. But the branch has to be reversed.
   JMP FOREVER


Of course you'll still need to cmp/cpy if you have more than two possibilities. But if you only have two, and one of them is zero, this is fine.

One other thing. One of the things people hate about NESASM is you have to specify zero page RAM locations to get any gain from them. You do this by putting < before a zero page RAM location at each use. Otherwise, it's slower than it should be, and uses 1 more byte than it should. (the same as regular RAM)

Code:
FOREVER:
   LDY <ALLOW_MOVE
   BNE LATCHCONTROLLER;It's faster by a little bit, since you're not comparing.
   JMP FOREVER


Yes... every time you use a zero page RAM location. I didn't do that on any of the fixed code other than this, but I totally should've.

Another subtle optimization tip

Code:
StartUpMove:
   LDY $0200
   CPY #$27
   BEQ ReadUpDone
   LDX #$00
MoveUpLoop:
   LDA $0200, x    ; load sprite X position
   SEC             ; make sure carry flag is set
   SBC #$01        ; A = A - 1
   STA $0200, x    ; save sprite X position
   INX
   INX
   INX
   INX
   CPX #$10;Can we get rid of this cmp?
   BNE MoveUpLoop
ReadUpDone:


Loop optimization is a very good skill to have when working on more complex games.

Code:
StartUpMove:
   LDY $0200
   CPY #$27
   BEQ ReadUpDone
   LDX #$10;Very similar to what I did with your collision check loop way above in this post. I'm loading the number and going to 0, rather than loading 0 and going to the number
MoveUpLoop:
   LDA $0200, x    ; load sprite X position
   SEC             ; make sure carry flag is set
   SBC #$01        ; A = A - 1
   STA $0200, x    ; save sprite X position
   DEX;DEX instead of INX like above
   DEX
   DEX
   DEX;When the result of this DEX is < 0, it will continue to the label ReadUpDone. The program becomes a little faster since it's doing less by not cmp'ing
   BPL MoveUpLoop;BPL instead. This is so it will still do the zeroth sprite. This can be done for all your directions.
ReadUpDone:


TAX is always faster than storing and loading the accumulator, and assuming COLLISIONBLOCK (which is RAM) isn't used for anything else (it's not), then... you can use it for something else. Essentially it's wasted RAM, and the code is slower/uses more bytes than it could.


Code:
   ;STA COLLISIONBLOCK;Commented out. It's not needed.
   TAX
   ;LDX COLLISIONBLOCK;Commented out. It's not needed.
   LDY CollisionTable, x
   CPY #1
   BEQ ReadLeftDone


Similar to something above, loading a value changes the zero status bit.

Code:
   LDY CollisionTable, x
   ;CPY #1;No need for this.
   BNE ReadLeftDone;But reverse the branch condition


Edit: OH MY GOODNESS, I FORGOT THIS WHICH IS ACTUALLY IMPORTANT!

Code:
NMI_COUNT:   
   INC NMI_COUNTER
   LDY NMI_COUNTER
   CPY #60
   BNE CONTINUE_NMI
   ;JSR CLEAR_NMI_COUNTER;This is NOT the proper way to use JSR.
 ;JSR would jump to a bit of reused code that would END IN RTS.
;If you do not RTS from a JSR you leave data on your stack which will eventually overflow.
;This is BAD

;JSRs are used to go someplace, then return (By hitting an RTS in the code that was jsr'd to). If you don't need to return use JMP.

;JMP is probably what you were looking for.
;But JMP CLEAR_NMI_COUNTER there would be useless.
;Because there's no need to JMP someplace if the program will go there immediately after the JMP anyway.
;What happens if NMI_COUNTER is equal to 60? It goes down.
;What's right beneath that branch? The label we're JMP'ing to.
;So no need for it.

CLEAR_NMI_COUNTER:
   LDY #$00
   STY NMI_COUNTER
   INC ALLOW_MOVE

CONTINUE_NMI:
   LDA #$00
   STA $2003               ; set low byte (00) of the RAM address
   LDA #$02
   STA $4014               ; set low byte (02) of the RAM address & start transfer
   LDA #$00
   STA $2005
   STA $2005
   

   RTI                      ; return from interrupt


Which... brings me to a similar point in another location.

Code:
MoveLeftLoop:
   LDA $0203, x    ; load sprite X position
   SEC             ; make sure carry flag is set
   SBC #$01        ; A = A - 1
   STA $0203, x    ; save sprite X position
   INX
   INX
   INX
   INX
   CPX #$10
   BNE MoveLeftLoop
   ;JMP ReadLeftDone;You're JMP'ing to a label immediately after the JMP instruction. There's no need. So it's commented out. This is true for all your direction press code, not just left.
ReadLeftDone:


Actually while I'm still here, one more loop optimization thing (This uses my revised code, but the fix holds true for what you originally had):

Code:
   LDA #$00
   LDX YBLOCK
   BEQ CollisionRoutine.XAdd;This way it doesn't add 13 when it doesn't need to.

CollisionRoutine:
   CLC;CLC, clears the carry bit.
;But once it's clear it won't be set by addition until the accumulator wraps around.
;If you're sure that won't happen (after all your playfield is only 11x13=143, less than 255), you can get away with clearing it once.
;ALWAYS CLEAR IN CODE WHERE YOU DON'T KNOW THE STATUS BEFOREHAND THOUGH

;It's faster since you save cycles during each additional loop. Say YBLOCK was 11. You clear the carry flag 10 more times than you need to.
   ADC #13
   DEX
   BNE CollisionRoutine
CollisionRoutine.XAdd:
   CLC
   ADC XBLOCK
   STA COLLISIONBLOCK
   
   LDX COLLISIONBLOCK
   LDY CollisionTable, x
   CPY #1
   BEQ ReadLeftDone


So to make it not clear during the loop:

Code:
   CLC;I put it before the routine, so it's not run multiple times during the loop
   LDA #$00
   LDX YBLOCK
   BEQ CollisionRoutine.XAdd;This way it doesn't add 13 when it doesn't need to.

CollisionRoutine:
   ADC #13
   DEX
   BNE CollisionRoutine
CollisionRoutine.XAdd:
   ADC XBLOCK
   STA COLLISIONBLOCK
   
   LDX COLLISIONBLOCK
   LDY CollisionTable, x
   CPY #1
   BEQ ReadLeftDone


But you can certainly feel free to always CLC in a loop. And of course ALWAYS do it when you're not %100 sure of the status of it before an addition somewhere in your code. Most of these later tips are just to get you to think more about the code you're writing, and to think through how to make things faster, but you can ignore almost everything after I finished getting the code working. (EXCEPT THE JSR THING!)

Edit: I've been writing this for hours now, might as well keep going. A good idea to get used to subroutines might be writing subroutines for each joypad button. Because the way you have it now, all code that needs to check for a button needs to be placed in between those code blocks, and that will get messy and hard to read FAST.

If you had subroutines for the joypad buttons you could do stuff like this:

Code:
   JSR p2ap;Jump to a subroutine that checks if player two's a button is pressed
   BNE skipscreenadd;Branch to "skipscreenadd" if it's not being pressed


You could make your collision detection a subroutine so you could use it for the player and all monsters in the maze. (Or at least use it for all the directions without copying and pasting the whole thing). Try it out, if you don't understand how to make a subroutine, or how to make one work, ask! I'll help! (And I'll try to write less than this)

That concludes the book. I'll edit this if I'm corrected about .rs/$2002 (or anything else, just so someone doesn't have to read later in the thread to find out I'm wrong.

by on (#59946)
This doesn't strictly relate to collision, but I think I should explain it anyways. In your routines where you move a sprite, I notice you are referring directly to a hardware sprite value. It is important to know that most games do not alternate existing values in the dedicated OAM page (Like in your example, $200-$2FF) to move or change sprites, and they most certainly will not use hardware sprite coordinates, tile values, or attributes for game logic purposes (with the exception of sprite #0). Like for example, they don't move a sprite to the left by subtracting 1 from $203 like you do. Instead, it's better practice to define entities like the player, or an enemy that each have properties including X/Y coordinates and a sprite map (a definition of the arrangement of hardware sprites that make up an entity) among other things. Then you have a routine that takes these entities, and defines how and where sprites will be arranged on screen to display them given their coordinates and sprite map (often other information is required to effectively draw meta-sprites). One reason for this is so you do not need to ever know what exact hardware sprites are currently drawing an entity, which greatly reduces complexity when there are a lot of entities on screen. In my current project, there are often 4-8 enemies, the player, and multiple bullets from the player and each enemy that need to be handled, so if I needed to know exactly which hardware sprites made up each object, it would be a huge pain to try and code that.

This is also done for sprite-cycling purposes, as we often end up with more than 8 hardware sprites per scanline, and we want them to flicker so we can get a chance to see them all.

You also won't need to perform the same subtraction or addition to X/Y coordinates of the sprites that make up an entity. You modify one universal set of entity coordinates, and your code will place the sprites on screen that make up that entity according to those coordinates.

But this also applies to collision; not just displaying sprites. If you define an entity with a bounding box (this would be constructed of X coordinates of left and right edges, and Y coordinates of top and bottom edges), you can see if that entire entity collides with something; you don't have to check if individual sprites collided with anything. The concept of creating objects/entities rather than just modifying sprite coordinates and making sounds directly based on player input is very important to understand. There's a visual model of the way a program should be set up I forget where it is, but it explains basically what I was saying. I think tokumaru is the one that showed it to me, so maybe he still knows where it is. Not saying you -have- to code things that way, but it's highly recommended.

EDIT: And although most of us don't care, distributing a copy of Bomberman with your source is ill-advised and probably illegal. I really doubt you need to worry at the moment about this, but I'm just saying for future reference.

by on (#59952)
@Kasumi

I did mod that ROM and I was sort of trying to get an idea of how they did collision but to no avail. Also, whats the problem with me even having it? I thought that from a legal perspective, you are allowed to have a ROM of a game if you actually own the game (which I only have ROMs of games that I actually have physical copies of.) or do the people on this forum just not like people who mod ROMs?

by on (#59955)
67726e wrote:
I did mod that ROM and I was sort of trying to get an idea of how they did collision but to no avail. Also, whats the problem with me even having it? I thought that from a legal perspective, you are allowed to have a ROM of a game if you actually own the game

It's not a problem with having the ROM but of including it in the zip file.

by on (#59956)
Oh, I didn't even think of that. I'll make sure to be more vigilant next time. Oh, and if anyone wants to look at my source that I provided, I'm uploading a new folder without it.

Sorry... :oops:

by on (#59958)
Hudson Soft is part of Konami now, and it appears Konami is kind of a stickler about copying. Konami even sued a company that made a game with the same rules as Dance Dance Revolution but didn't copy the graphics or music because Konami holds a patent on that play style.

by on (#59960)
I did perform a little experiment involving my scoring code and the .rs part, and I figured out that in fact .rs doesn't store value, it (R)eserves (S)pace. Glad you thought of that, otherwise I would have had some seriously screwy code later in my 6502 life.

With the JSR problem in my NMI, I don't know what was going on there. It might have been one of those moments at two in the morning when I suddenly attain lysdexia :lol:

Quote:
;JMP ReadLeftDone;You're JMP'ing to a label immediately after the JMP instruction. There's no need. So it's commented out. This is true for all your direction press code, not just left.
ReadLeftDone:


That was actually something I just missed. There was a part of the collision code that I was trying as a fix that went in between ReadLeftDone and the JMP so I had to have the JMP so it wouldn't do the code.

Quote:
You could make your collision detection a subroutine so you could use it for the player and all monsters in the maze. (Or at least use it for all the directions without copying and pasting the whole thing). Try it out, if you don't understand how to make a subroutine, or how to make one work, ask! I'll help! (And I'll try to write less than this)


That was the plan. I just had it in that place while trying to get it to actually do something useful.

Quote:
You also won't need to perform the same subtraction or addition to X/Y coordinates of the sprites that make up an entity. You modify one universal set of entity coordinates, and your code will place the sprites on screen that make up that entity according to those coordinates.


I was trying that earlier when I was just starting on the game, but as I realize thanks to you bringing it up and Kasumi talking about my error with the .rs, I realized why it wasn't working to begin with.

Alright, so I really have to thank all of you for helping me with this as I figure out how sucky I am with 6502.

And once again.. sorry for the Bomberman mishap. I will make sure to not let anything like that happen again.