One tip I'll give you is to move only ONE axis, then check for collisions on that axis, then move the other axis and check for collisions on that axis. Do NOT move in both directions and then check for collisions. I don't know if you planned to do it that way, but I wasted about a day or so trying to figure out how to determine whether I've landed on the top of a corner or hit the side. It doesn't work.
mikejmoffitt wrote:
I usually define a few constants that dictate points for collision relative to an object's coordinates.
This will work if all of your objects have the same hitbox size, but if your objects vary, and you want them to use the same routine, you're going to need variables for each object to define the size of it's hitbox. I have a hitbox width and height variable for each object, and with that, I can find any side, any corner, even divide them by half to find the center.
So what I do is a little like this:
First you're going to have to save a few variables. Save your X position and Y position before movement is applied. You'll need this for a few things. You don't need one of these variables for every object though because you only need it for the object being processed, so one temp variable for all objects will suffice.
Next we need to apply Xspeed to Xpos. Depending on your engine, how this is implemented will vary, but if you're going to scroll, and you want smooth acceleration, jump arcs, etc, you'll probably want a 16-bit speed and 24-bit position.
Just to save a little bit of unnecessary work, I check to see if the Xposition has changed, and if not, I skip over the collision detection. It's not feasible to do this for the Y axis in my game because of gravity, but that's neither here nor there. I also have to do this check to change my sprite's direction too if that matters for your game.
Now, we've confirmed X axis movement, so let's see if we need to check the right or left side. Subtract your last X position from the current X position. You don't need to do this with your full 16 or 24 bit position value, just the one that corresponds to a pixel. Like, I have xPosHi, which corresponds to how many screens the object has moved over, xPosLo, which corresponds to pixels, and xPosSub which corresponds to sub-pixel positions. I just use xPosLo for this check. If the result of that subtraction is positive, your object has moved right, so check the right side. If negative, you've moved left so check the left side.
Edit: I was thinking for a minute that this wouldn't work and was going to change but I think I was wrong about that. 0 - $FF = 1 which is positive which means you moved right. $FF - 0 = $FF is negative which means you moved left. I'm still editing this though because I checked my code and realized what I did was actually a little different. I subtract the xPos to see if the object has moved, like I said, but in that instance, I BEQ, meaning, jump over collision code if my X pixel position hasn't changed. When I get into the collision routine, I'm actually using an object's speed to see if they're moving left or right. So I just wanted to note that there are options to use speed for some checks too. I believe I used speed in my collision check since it's a separate routine, it's quicker to do just one LDA and get the answer. However, I can't go by speed to see if an object has moved due to subpixel movements.
Second Edit: After typing this I went back over my code and realized there is a mistake in the way I'm doing it. Checking X speed for knowing which side to check won't work. I've never actually seen any errors with the program, however, an object could have an X speed of 0 and still be moving left, and I can't do a secondary check on subpixel speed because that can be negative and still moving right. So, I hope I didn't confuse you with the edits. Basically, ignore the edits asides from seeing what not to do, because I'm pretty sure the original method I described of checking current X position against last X position is the only way to reliably do this. I've got to change mine now.
Now, there are different ways to do the actual check. I'll just kind of give you a general idea.
Some people like to check multiple points on their object. Mine calculates how many tiles need to be checked for the hitbox height. I don't believe the way that I did this is very standard, however I've tested it and it saves cycles on reading from the metatiles.
So, you know now you want to check the right side, calculate the right side position using hitbox size variables or constants, now you're going to have to math it up to find where those pixels fall on the map. This varies greatly depending on your engine, but if you said 16x16, then you've basically got to divide your positions by 16 to find the tile they occupy. This becomes a bit more complicated in an actual game situation though.
Have you done metatiles yet? This part will be almost exactly the same as the math to locate your metatiles.
Here's what I do. I don't expect that you'll be able to copy this but I'm hoping it gives you an idea. My map is arranged in columns, so first it finds the map column then uses the Y position to find how deep into that column to go.
Code:
LDA currentObjectBackgroundCheckXHi ; Temp variable for X high byte (screen #)
LSR
LSR
LSR ; 1 screen = 16 metatiles. Divide by 8 though because each pointer is two bytes
TAY
LDA currentObjectBackgroundCheckXLo ; Temp variable for X low byte (pixel #)
LSR
LSR
LSR
AND #%00011110 ; Round to multiple of 2
STA collisionMapPointerLo
LDA currentObjectBackgroundCheckXHi
ASL
ASL
ASL
ASL
ASL ; Multiply high byte by 32 to get offset for low byte from screen shifts. One screen corresponds to 16 metatiles in my engine, which each have a two byte pointer, therefore, 32
ORA collisionMapPointerLo
CLC
ADC #<MapStart ; Add the offset to the map location
STA collisionMapPointerLo
TYA
ADC #>MapStart ; Use carry in case this addition wraps
STA collisionMapPointerHi
CheckMetatileCollisionHorizontalLoop:
LDA (collisionMapPointerLo), y
STA collisionColumnPointerLo
INY
LDA (collisionMapPointerLo), y
STA collisionColumnPointerHi
Y axis part, for me, is:
Code:
LDA currentObjectBackgroundCheckYLo
LSR
LSR
LSR
LSR
STA temporaryCollision
LDY currentObjectBackgroundCheckYHi
LDA MetatileCollisionHiYOffset, y ; Add 16 if Y hi address is in second page
ORA temporaryCollision
TAY
That LDA from MetatileCollisionHiYOffset references this table:
Quote:
MetatileCollisionHiYOffset:
.db $00,$10
This part is very specific to my engine, since I use SMB3 style scrolling which limits me to two screens vertically, so an object Y position high byte will never been anything other than a 0 or 1.
MottZilla wrote:
Just because the sprite/character is 16x24 doesn't mean that its collision box needs to be that. I know there are atleast some games that use a 16x16 collision box even though the character is taller but I can't name an example at the moment.
Danmaku SHMUPs use a hitbox significantly smaller than the player's ship to allow for complicated bullet dodging.