Monday, September 25, 2017

Responsive object collisions, part 1

Previously, I talked about how there are two kinds of object collisions, which I named solid and responsive collision. We then saw how objects can opt into solid collision by calling a function such as SolidObjectFull, which does all the work of performing bound checks on the player and the solid obstacle, as well as affecting both objects' SSTs appropriately.

Responsive collision, on the other hand, can be attained by calling the Add_SpriteToCollisionResponseList function:
    lea     (Collision_response_list).w,a1
    cmpi.w  #$7E,(a1)           ; Is list full?
    bhs.s   locret_1041C        ; If so, return
    addq.w  #2,(a1)             ; Count this new entry
    adda.w  (a1),a1             ; Offset into right area of list
    move.w  a0,(a1)             ; Store RAM address in list

Much like with the Draw_Sprite function, responsive collision works in a subscription-based model: calling the function simply adds the object to a list of items to be processed in the future. The actual work of performing bound checks and producing the appropriate effects is deferred to the next frame, where it is done by the player object.

(Note that the collision response list is actually processed twice in a Sonic and Tails game, once per player object. This will become relevant later. Foreshadowing: the sign of a quality blog.)

For this reason, the collision parameters cannot be passed through registers; instead, a single byte of the solid object's SST is reserved for this purpose:
    collision_flags = $28 ; byte ; TT SSSSSS ; TT = collision type, SSSSSS = size
The decision to stuff everything in a single byte, presumably to save space on the SST, has a couple of consequences. First off, the collision_flags attribute carries information about both the collision size and the collision type. As a result, each time the game needs to read either value, it must clear out the remaining bits.
    movea.w (a4)+,a1                    ; Get address of first object's RAM
    move.b  collision_flags(a1),d0      ; Get its collision_flags
    bne.s   Touch_Width                 ; If it actually has collision, branch

    andi.w  #$3F,d0                     ; Get only collision size
    add.w   d0,d0                       ; Turn into index
    lea     Touch_Sizes(pc,d0.w),a2
    moveq   #0,d1
    move.b  (a2)+,d1                    ; Get width value from Touch_Sizes
    move.w  x_pos(a1),d0                ; Get object's x_pos
    sub.w   d1,d0                       ; Subtract object's width
Then, because six bits are obviously not enough to store the actual dimensions of the object's hitbox, the collision size is really just an index to a lookup table of hardcoded size definitions, stored in pairs of width by height.
    dc.b    4,   4
    dc.b  $14, $14
    dc.b   $C, $14
    dc.b  $14,  $C
    dc.b    4, $10
    dc.b   $C, $12
    dc.b  $10, $10
Collision type, on the other hand, is fairly straightforward. There are four collision types, and the appropriate behavior is selected by a series of branches; no fancypants arithmetic involved.
    move.b  collision_flags(a1),d1      ; Get its collision_flags
    andi.b  #$C0,d1                     ; Get only collision type bits
    beq.w   Touch_Enemy                 ; If 00, enemy, branch
    cmpi.b  #$C0,d1
    beq.w   Touch_Special               ; If 11, "special thing for starpole", branch
    tst.b   d1
    bmi.w   Touch_ChkHurt               ; If 10, "harmful", branch
    ...                                 ; If 01...
Next time, we'll continue our foray into responsive collision by taking a look at the implementation of each collision type.

No comments:

Post a Comment