Jump to content

Game Engine Building #4: Sprite Collisions


Recommended Posts


Last time we added weapons to our playable character, but they didn't do anything.  This time we are going to add some depth to our game by adding impacts between:

1.  NPCs and PCs
2.  Our projectiles and NPCs
3.  Our whoop-ass stick and NPCs.


First, we need to define what we do when we register a hit.  In practice, this would be a fairly complicated routine involving death checks, hit points, moving, etc., but here we are going to keep it simple and you can add stuff as you see fit.  This is the subroutine that we are going to jump to when we register a hit:


direction_change:                 ;this is just a random routine to make something happen when a collision occurs
  LDX enemy_number
  LDA random_direction
  STA enemy_direction,x


All we do here is if there is a hit, make the enemy change direction.  That's it.  Just take the random direction we have stored in our random direction counter and store it in the active enemy direction.  It should be noted that in some cases the random direction you input into your character's direction could very well be the direction he has been traveling.  You could add code to fix this if you like. 


Next, we need to define the area that we will use when we are looking for hits (or collisions).  Consider the picture below:


I highly recommend making a picture like this anytime you define collision coordinates.  It makes it easy to see and keeps you "organized". 

First, draw out your playable character in all of its shooting poses.  It is pretty obvious here, but as your characters get bigger and more complex, this will be harder to visualize.  After we draw our pictures, we can see that there are 4 views for each weapon we need to consider.  In this case, the 4 directions of the D-PAD. 

Then, draw an 8x8 pixel grid (or however big your weapon sprite is) as shown at the bottom of the picture.  Then draw the various views of the weapon sprite area THAT YOU WANT TO REGISTER A HIT on top of this grid.  Then draw a box around the sprites in the empty space of the grid as shown in this picture.  This is your "HIT" area. 

Remember that the sprites on the NES are referenced from the upper left hand corner.  So, we need to figure out what the offsets are in all four directions for each view.  Sounds complicated, but it's really pretty simple.  We'll start with the arrow facing up. 

Here, we see that our blue box extends all the way over the top of the last row of pixels.  So, since we are referencing our collision coordinates to the top of the sprite, we would basically have the vertical arrow sprite location + 0 = top collision coordinate of the arrow.  Then, we see that the box extends all the way past the bottom row of pixels, so we have vertical arrow sprite location + 8 = bottom collision coordinate of the arrow.  Then, we see that we skip one pixel from the left side of the arrow sprite, so we would have the horizontal arrow sprite location + 1 = left collision coordinate of the arrow.  The we skip 6 pixels before the right side of the box, or horizontal arrow sprite location + 6 = right collision coordinate of the arrow.

That really makes it sound more difficult than it is.  Basically we just count the pixels above and to the left of the blue lines.  So, for each direction we would have:

Up View:  0,8,1,6
Down View:  0,8,1,6
Right View:  1,6,0,8
Left View:  1,6,0,8


Now, we've defined the offsets required for each direction that our little homie can shoot.  Now...what the hell do we do with it??

Well, if we think back, we remember that we have a loop running for each of our enemies.  We can either re-compute these coordinates for each enemy (each loop), or we can compute it once and store it somewhere in RAM.  It's really up to you, but here we are going to store it in RAM.  To do this, we will need to define some new variables:


arrow_vertical  .rs 2    ;the vertical collision coordinates we define for our arrow
arrow_horizontal  .rs 2  ;horizontal coordinates


Next, we basically just say that for each one of these new variables, we take the sprite location, add the appropriate offset we defined above, and store it in one of these variables.  Simple, no?  Well, I'm having a hard time putting it into words, so let's look at the subroutine:



  LDY arrow_direction
  LDA Weapons_RAM+4              ;transfer arrow collision coordinates
  ADC arrow_a,y
  STA arrow_vertical
  LDA Weapons_RAM+4
  ADC arrow_b,y
  STA arrow_vertical+1
  LDA Weapons_RAM+7
  ADC arrow_c,y
  STA arrow_horizontal
  LDA Weapons_RAM+7
  ADC arrow_d,y
  STA arrow_horizontal+1


All it is is load the sprite location, add a number, and dump it in the variables.  However, since we have 4 different views to work with, we can't just add a constant and make it work right.  We need to add some sort of look up table that stores the offset data that the program can access.  Here, arrow_a is the offset for the top of the blue box in the picture for the up, down, right, left directions, respectively.  arrow_b is the bottom, arrow_c is the left, and arrow_d is the right.  If you study the offsets we came up with and the following table, you'll see the relationship between the two.  Basically the transposition.  If we put this into a neat table, we get:


arrow_a:                         ;outlines of the arrowhead
  .db $00,$00,$01,$01
  .db $08,$08,$06,$06
  .db $01,$01,$00,$00
  .db $06,$06,$08,$08


Now, we've got a routine that we can put somewhere that will define the position of the arrow on the screen and give us the collision boundries to check against the bad guy locations. 

The exercise is exactly the same for the hammer/mace.  I'll leave that to you to figure out.  It is good practice.  The resulting table is in the program.

The exercise for the Playable Character sprites is exactly the same with one exception.  The playable character has the same "blue box" in every direction.  This saves us the trouble of messing with the look up table and allows us to hard code the location info.  However, if your character looks different in each direction, you may want to consider making a table for it as well. 


Now that we've defined how we are going to transfer the sprite collision data into RAM, we need to call it at the appropriate time in the main program.  It's really up to you when you want to call it as it doesn't matter too much, but here we are going to call them after we finish handling the weapons routines, but right before we run the UpdateENEMIES loop.  This sets us up nicely for what comes next.


I guess I should explain this here to avoid confusion.  We need to put our collision data variables in a certian order in RAM to make the following collsion routine work correctly.  Observe:


arrow_vertical  .rs 2         ;vertical position of the arrow for collision
hammer_vertical  .rs 2        ;hammer
PC_vertical  .rs 2            ;PC

arrow_horizontal  .rs 2       ;horizontal position of the arrow for collision
hammer_horizontal  .rs 2      ;hammer
PC_horizontal  .rs 2          ;PC


Messing up the order of these will cause you to waste time debugging.  You'll see why this is necessary in a second here.

Next we need to update our "UpdateENEMIES" loop to call for sprite collision detection.  It will be simplest to just show you:


  LDA #$01                         ;this loop updates the enemies one at a time in a loop
  STA enemy_number                 ;start with enemy one
  LDA #$02
  STA enemy_ptrnumber
  JSR enemy_update                 ;move the sprites based on which direction they are headed
  JSR Enemys_Animation             ;find out which frame the enemy animation is on
  JSR Enemys_Sprite_Loading        ;update the enemy meta tile graphics
  JSR update_enemy_sprites         ;update position


  LDY #$00            ;arrow       ;sprite collision detection routines
  JSR enemy_collision              ;note that 00, 02, and 04 are to specify the variables of each weapon
  LDY #$02            ;mace
  JSR enemy_collision
  LDY #$04            ;Playable Character
  JSR enemy_collision


  INC enemy_number                 ;incriment the enemy number for the next trip through the loop
  INC enemy_ptrnumber              ;these are addresses for the graphics data, so we need to keep it at 2x the enemy number
  INC enemy_ptrnumber
  LDA enemy_number
  CMP #$04                         ;if it is 4, we have updated enemies 0,1,2,3 so we are done
  BNE .loop

Here we see why we need to put our variables in the correct order.  We run the enemy_collision routine 3 times, one for each weapon type.  First, we use index $00 for the arrow, index $02 for the mace, and index $04 for the playable character collision detection. 

Now, we're all set up to write some sort of routine to check for collisions.


After we've defined all the stuff above, the actual detection routine, the "man meat" if you will, is pretty straightforward.  Remember the BCC and BCS commands we learned last time??  We need them here.  If you don't get it, go back and review or ask a search engine or something.  These are important.  Again, it's probably easiest if I just lay out the sub-routine and comment the hell out of it.  But, basically, all we are doing here is seeing if the "blue boxes" we defined above are intersecting with the "blue boxes" defined by the NPCs.  Note that this routine assumes that the NPCs are basically the same as the PC.  If you're enemies are of different styles, or have different looks depending on the direction they are traveling, it's not really any big deal, you just have to set up tables like we did before.  Just base them on the enemy_direction and/or define an "enemy_type" variable to guide your program to the correct "blue box" data.  Here, we are going to keep it simple.  Oh, and I lied above.  When we register a hit, we also have to tell the mace/arrow routines to dissapear the weapon sprites. 

I think that the following picture will help illustrate my little routine here.  "Steps" refer to the comparisions in the following routine.  You can see examples of where you would branch and where you would RTS (green boxes mean branch, red mean RTS).  I think that this figure will make it a little clearer.


See the following:


enemy_collision:              ;check collision with the arrows, mace, PC

  LDX enemy_number            ;load the enemy number for this time through the UpdateENEMIES loop
  LDA updateconstants,X       ;check the outline of the enemy vs. the weapon
  TAX                         ;change the constant to the X variable
  LDA sprite_RAM,X            ;load the vertical position of the dominate enemy sprite (refer to last week for "dominate")
  CLC                         ;make sure that the carry flag is clear
  ADC #$01                    ;add on the offset for the up direction for the NPC
  CMP arrow_vertical+1,y      ;compare the top of the NPC with the bottom of the weapon in question
  BCC .next                   ;if the top of the NPC box is above the bottom of the weapon box, branch to .next
  RTS                         ;otherwise, there is no collision, so RTS
  LDA sprite_RAM,X            ;load the vertical sprite position
  ADC #$0E                    ;add the offset for the bottom of the metasprite
  CMP arrow_vertical,y        ;compare with the top of the weapon blue box
  BCS .next1                  ;if the bottom of the NPC box is below the top of the weapons box, branch to .next1
  RTS                         ;otherwise, there is no collision, RTS

;inserting another comment here, so far, we have determined that at least 1 pixel of the defined collision boxes share the same vertical space on the screen.  Now, we need to check and see if they share the same horizontal space.

  LDA sprite_RAM+3,X          ;load the sprite horizontal position
  ADC #$03                    ;add the offset defined in our pictures with the blue box to get the left side of the metasprite
  CMP arrow_horizontal+1,y    ;compare this the the right side of the weapon sprite
  BCC .next2                  ;if the left side of the NPC box is to the left of the right side of the weapons box, branch to .next2
  RTS                         ;otherwise, there is no collision, RTS
  LDA sprite_RAM+3,X          ;load the enemy sprite horizontal position
  ADC #$0C                    ;add the defined offset to get the right side of the enemy metasprite
  CMP arrow_horizontal,y      ;compare to the left side of the weapons sprite
  BCS .next3                  ;if the right side of the NPC is to the right of the left side of the weapons box, branch to .next3
  RTS                         ;otherwise there is no hit, RTS

;inserting another comment here, in this previous snippet we determined that the weapons sprite and the enemy meta sprite share the same horizontal space on the screen. 

;However, the top part of this subroutine showed us that we were also occuping the same vertical space.  So, we know that if we are branching to .next3, the weapon sprite and the meta sprite are, in fact, occuping the same space on the screen!!

.next3                        ;now that we've confirmed a hit, what do we do?
  CPY #$00                    ;we need to decide what weapon hit the duder, so remember $00 is for arrows
  BNE .nexts                  ;did we go over this?  Branch Not Equal (i.e. not an arrow) to .nexts
  JMP arrow_hit               ;do the arrow collision stuff
  CPY #$02                    ;is it the mace?
  BNE .nexts1                 ;again, Branch Not Equal to .nexts1
  JMP mace_hit
  JMP PC_hit                  ;add more here if there are more weapons, or make a look up table, but all that's left is the PC impact, so jump to it!



  JSR direction_change

  LDA #$01                    ;set the mace timer to $01 to reset the mace graphics.
  STA B_timer



  LDA #$FE                    ;here we are just making the arrow inactive. 
  STA Weapons_RAM+4           ;see the arrow routine to see how this works.
  STA Weapons_RAM+7

  JSR direction_change



  JSR direction_change       




1.  Determine if the sprites are overlapping in the vertical direction.
        a.  If not, RTS.
        b.  If so, go on to #2.
2.  Determine if the sprites are overlapping in the horizontal direction.
        a.  If not, RTS.
        b.  If so, go on to #3.
3.  Determine which weapon we are attacking with.
4.  Do the collision shit for the various weapons. 

Easy as cake.  If you have any questions, feel free to PM me or post.  If you don't understand what we did here, we need to figure it out.  This is important for stuff to come later. 


Now we should have all the tools we need to make our program recognize sprite collisions.  Download the zip file and mess around with the ROM.  Change the offsets in the tables and see how it changes the collision areas.  Maybe even add some other thing for the program to do with each weapon hit, or add health, or add in your own weapons.  Whatever tickles your teabag.gif

Next we'll start working with backgrounds.  face-icon-small-happy.gif  Please let me know if you have any questions.  Good luck.

Sprite Collisions.zip

Collision Example.PNG

Edited by MRN
Fix pics.
  • Like 1
Link to comment
Share on other sites

  • Create New...