"Sub Sprites" in Sonic 1

Discussion in 'Engineering & Reverse Engineering' started by Novedicus, Sep 9, 2018.

  1. Ralakimus


    People suck Tech Member
    "What are these 'sub sprites' you speak of" you may ask. In Sonic 2 and 3K, if you set a bit in an object's "render flags", you were able to sacrifice a bit of the object's RAM to display multiple sprites in one object. With this, you could save quite a number of object slots.

    For example, in Sonic 1, each log in a bridge was an individual object, so it would potentially use up several object slots, depending on how long the bridge was. However, in Sonic 2, it would only use 1 or 2 objects and just draw multiple sprites for them.

    [​IMG] [​IMG]

    However, this is not a feature in Sonic 1. So, here, I will show you how you can implement such a feature. For this, I will primarily be using Sonic 2 as an example for simplicity's sake. With this, it would also open up the possibility of porting other objects from Sonic 2 and 3K to Sonic 1 without needing to make them use more object slots (such as the invincibility stars). You can also optimize certain objects, such as the swinging chained platforms or the Green Hill boss' swinging ball, by making the chains sub sprites (which is what Sonic 2 does).

    I will be using the Sonic 2 GitHub disassembly and the Sonic 1 GitHub disassembly (the sprite rendering routine (BuildSprites) in that isn't documented well as of September 8, 2018, so currently it's very similar to the 2005 Hivebrain version, even has the same labels).

    The purpose of this tutorial is to just port the sub sprite functionality to Sonic 1. There will be no changes to the mappings format and no other features or optimizations will be implemented.

    How Does It Work?​​

    In Sonic 2, if you set bit 6 of the render flags SST in an object, it'll enable sub sprites for the object. For that object, it will render a "main sprite" and then its sub sprites. To set it all up, you set the main sprite's size and frame ID, and then the number of sub sprites you want to render. Then, you can set the sub sprites' position and frame ID.

    These specific properties you set DO override some standard object SSTs. Specifically, the last byte in x_pos, the last 2 bytes in y_pos, x_vel , y_vel, intertia (for Sonic and Tails), y_radius, priority, anim_frame, anim, anim_frame_duration, collision_property, status, routine, and subtype. What is overridden really depends on the amount of sub sprites you have set to display (for example, only having 2 sub sprites will not override anim_frame_timer and onward).

    Take a look here:

    [68k]mainspr_mapframe = $B
    mainspr_width = $E
    mainspr_childsprites = $F ; amount of child sprites
    mainspr_height = $14
    sub2_x_pos = $10 ;x_vel
    sub2_y_pos = $12 ;y_vel
    sub2_mapframe = $15
    sub3_x_pos = $16 ;y_radius
    sub3_y_pos = $18 ;priority
    sub3_mapframe = $1B ;anim_frame
    sub4_x_pos = $1C ;anim
    sub4_y_pos = $1E ;anim_frame_duration
    sub4_mapframe = $21 ;collision_property
    sub5_x_pos = $22 ;status
    sub5_y_pos = $24 ;routine
    sub5_mapframe = $27
    sub6_x_pos = $28 ;subtype
    sub6_y_pos = $2A
    sub6_mapframe = $2D
    sub7_x_pos = $2E
    sub7_y_pos = $30
    sub7_mapframe = $33
    sub8_x_pos = $34
    sub8_y_pos = $36
    sub8_mapframe = $39
    sub9_x_pos = $3A
    sub9_y_pos = $3C
    sub9_mapframe = $3F
    next_subspr = $6[/68k]

    One thing that Sonic 2 does to avoid issues with SSTs is to load an object whose purpose is only to hold these sub sprite properties and to display the sprites, while the main object handles all the programming and stuff, and when it needs to handle sprites, it sets the properties in the other object. Let's take a look at Obj15 in Sonic 2 for example. It starts with this:

    btst #6,render_flags(a0)
    bne.w +
    moveq #0,d0
    move.b routine(a0),d0
    move.w Obj15_Index(pc,d0.w),d1
    jmp Obj15_Index(pc,d1.w)
    ; ---------------------------------------------------------------------------
    move.w #$200,d0
    bra.w DisplaySprite3[/68k]

    It checks if the "sub sprite enable" flag is set and if so, make it so that it just runs DisplaySprite3, which is a routine that takes a precalculated offset for the object display queue instead of using the priority SST. Like I explained, the priority SST can be overridden by sub sprite data, so this routine exists, so it doesn't have to use the SST. However, if that flag is not set, it will then go to the normal Obj15 code, where upon initialization, it will load an Obj15 with that flag set.

    Depending on how you use your SSTs, you may not need to do something like that, but that is an option just in case.

    So, now let's go ahead an implement this, shall we? First thing you should do is define the sub sprite SSTs from above somewhere in your disassembly.

    Now, once you have the SSTs defined, let's get into the code. The magic really happens in "BuildSprites" (it's the same in both Sonic 1 and 2 disassemblies), so let's go there. It's in the main ASM file for all disassemblies. Do note that you'll want to change any RAM names or SST names to the ones defined in your disassembly (i.e. render_flags -> obRender for S2 GitHub to S1 GitHub).

    Also keep in mind that ASM68K does not support temporary labels (those "+" or "-" labels you may see occasionally in the Sonic 2 disassembly). You'll need to replace them with actual labels.

    Here is where we need to make our first change:

    [68k] move.b obRender(a0),d0
    move.b d0,d4
    andi.w #$C,d0
    beq.s loc_D6DE[/68k]

    This code grabs the render flags and does flag checks. In Sonic 2, this piece of code looks like this:

    [68k] move.b render_flags(a0),d0
    move.b d0,d4
    btst #6,d0 ; is the multi-draw flag set?
    bne.w BuildSprites_MultiDraw ; if it is, branch
    andi.w #$C,d0 ; is this to be positioned by screen coordinates?
    beq.s BuildSprites_ScreenSpaceObj ; if it is, branch[/68k]

    As you may see, Sonic 2 adds 2 additional lines between the ANDI instruction and the MOVE instruction. Those 2 lines of code check the "sub sprite enable" flag and branches to "BuildSprites_MultiDraw" if it is set. You'll want to add that in to Sonic 1's BuildSprites.

    In Sonic 2, "BuildSprites_MultiDraw" is located right after before the routine that copies mapping data to the sprite buffer. In Sonic 1, that routine is called "sub_D750", so right before that routine, create the "BuildSprites_MultiDraw" label.

    And now for the actual sub sprite rendering code. In Sonic 2, it starts off with:

    [68k] move.l a4,-(sp)
    lea (Camera_X_pos).w,a4
    movea.w art_tile(a0),a3
    movea.l mappings(a0),a5
    moveq #0,d0[/68k]

    If you notice, it store the address of start of the main camera's position data in a4. However, if we take a look at how Sonic 1 handles grabbing the camera position data, we'll see this:

    [68k]movea.l BldSpr_ScrPos(pc,d0.w),a1[/68k]

    Which is located directly after the code that grabs the render flags and checks it. What it's doing is choosing which camera in which the object will be drawn relative to. Which camera it picks depends on bits 2 and 3 in the render flags, which is exactly what that piece of code from before was doing (with both bits cleared just making it branch to loc_D6DE, meaning it should not be relative to any camera).

    The main issue here is that in Sonic 2, if an object is set to handle sub sprites, it is forced to be drawn relative to a camera, as it does not check those 2 bits in the render flags. For the sake of simplicity and staying true to the original functionality in Sonic 2, we will not handle multiple camera rendering for sub sprites.

    Next up, we have this bit of code. Go ahead and implement this.

    [68k] ; check if object is within X bounds
    move.b mainspr_width(a0),d0 ; load pixel width
    move.w x_pos(a0),d3
    sub.w (a4),d3
    move.w d3,d1
    add.w d0,d1
    bmi.w BuildSprites_MultiDraw_NextObj
    move.w d3,d1
    sub.w d0,d1
    cmpi.w #320,d1
    bge.w BuildSprites_MultiDraw_NextObj
    addi.w #128,d3[/68k]

    Here, it checks the main sprite's X position to see if it's on screen horizontally. You may notice it's very similar to this in Sonic 1:

    [68k] move.b obActWid(a0),d0
    move.w obX(a0),d3
    sub.w (a1),d3
    move.w d3,d1
    add.w d0,d1
    bmi.w loc_D726
    move.w d3,d1
    sub.w d0,d1
    cmpi.w #$140,d1
    bge.s loc_D726
    addi.w #$80,d3[/68k]

    The main difference really are the labels it branches to and the fact that it doesn't use the regular width SST ("obWidth"), but rather "mainspr_width".

    Next up is this. Go ahead and implement this.

    [68k] ; check if object is within Y bounds
    btst #4,d4
    beq.s +
    moveq #0,d0
    move.b mainspr_height(a0),d0 ; load pixel height
    move.w y_pos(a0),d2
    sub.w 4(a4),d2
    move.w d2,d1
    add.w d0,d1
    bmi.w BuildSprites_MultiDraw_NextObj
    move.w d2,d1
    sub.w d0,d1
    cmpi.w #224,d1
    bge.w BuildSprites_MultiDraw_NextObj
    addi.w #128,d2
    bra.s ++
    move.w y_pos(a0),d2
    sub.w 4(a4),d2
    addi.w #128,d2
    andi.w #$7FF,d2
    cmpi.w #-32+128,d2
    blo.s BuildSprites_MultiDraw_NextObj
    cmpi.w #32+128+224,d2
    bhs.s BuildSprites_MultiDraw_NextObj

    Basically like the previous set of code, but it checks the Y position. One main difference, however, is that it handles another flag from the render flags, bit 4. This bit, if clear, makes it so that it doesn't take into account the height of the sprite/object when checking if it's onscreen vertically or not. Again, you may notice it's similar to this piece of code from Sonic 1:

    [68k] btst #4,d4
    beq.s loc_D6E8
    moveq #0,d0
    move.b obHeight(a0),d0
    move.w obY(a0),d2
    sub.w 4(a1),d2
    move.w d2,d1
    add.w d0,d1
    bmi.s loc_D726
    move.w d2,d1
    sub.w d0,d1
    cmpi.w #$E0,d1
    bge.s loc_D726
    addi.w #$80,d2
    bra.s loc_D700
    move.w obY(a0),d2
    sub.w obMap(a1),d2
    addi.w #$80,d2
    cmpi.w #$60,d2
    blo.s loc_D726
    cmpi.w #$180,d2
    bhs.s loc_D726


    Pretty much the same deal as the previous set of code, different labels and the fact it uses "mainspr_height" instead of the normal height SST ("obHeight"). And if you notice, Sonic 1 does not have a "andi.w #$7FF,d2" instruction for this kind of code. If you really want to keep true to Sonic 1, go ahead and remove that instruction from the piece of code from Sonic 2.

    After that, it draws the main sprite with this piece of code:

    [68k] moveq #0,d1
    move.b mainspr_mapframe(a0),d1 ; get current frame
    beq.s +
    add.w d1,d1
    movea.l a5,a1
    adda.w (a1,d1.w),a1
    move.w (a1)+,d1
    subq.w #1,d1
    bmi.s +
    move.w d4,-(sp)
    bsr.w ChkDrawSprite ; draw the sprite
    move.w (sp)+,d4

    Keep in mind that there really isn't a "ChkDrawSprite" in Sonic 1, and the fact that Sonic 1 handles sprite limits differently than Sonic 2. So, a change you'll want to make is to create a label in the sub_D750 routine between "movea.w obGfx(a0),a3" and "btst #0,d4", since in Sonic 2, if the sprite limit has not been reached, it would branch to to that "btst #0,d4". Once you have made the label, change the branch to "ChkDrawSprite" to that new label.

    Another change that we need to make to that piece of code is to change how it gets the number of sprites to draw. In Sonic 2, the number of sprite pieces in a frame is a word, but in Sonic 1, it's a byte, so you'll want to change the ".w" extension in "move.w (a1)+,d1" to a ".b" extension. To keep true to how Sonic 1 handles this kind of thing, apply the same change to "add.w d1,d1" and "subq.w #1,d1".

    For reference, take a look at this piece of code from Sonic 1:

    [68k] move.b obFrame(a0),d1
    add.b d1,d1
    adda.w (a1,d1.w),a1
    move.b (a1)+,d1
    subq.b #1,d1
    bmi.s loc_D720[/68k]

    After all that, we can finally render some sub sprites!

    So, it begins with:

    [68k] ori.b #$80,render_flags(a0) ; set onscreen flag
    lea sub2_x_pos(a0),a6
    moveq #0,d0
    move.b mainspr_childsprites(a0),d0 ; get child sprite count
    subq.w #1,d0 ; if there are 0, go to next object
    bcs.s BuildSprites_MultiDraw_NextObj[/68k]

    Here, it sets the sprite as "on screen", like it would normally, and then it prepares to get the sub sprite data, and then gets the number of sub sprites to draw.

    Next up is this:

    [68k]- swap d0
    move.w (a6)+,d3 ; get X pos
    sub.w (a4),d3
    addi.w #128,d3
    move.w (a6)+,d2 ; get Y pos
    sub.w 4(a4),d2
    addi.w #128,d2
    andi.w #$7FF,d2
    addq.w #1,a6
    moveq #0,d1
    move.b (a6)+,d1 ; get mapping frame
    add.w d1,d1
    movea.l a5,a1
    adda.w (a1,d1.w),a1
    move.w (a1)+,d1
    subq.w #1,d1
    bmi.s +
    move.w d4,-(sp)
    bsr.w ChkDrawSprite
    move.w (sp)+,d4
    swap d0
    dbf d0,- ; repeat for number of child sprites[/68k]

    This goes through each available sub sprite, sets it up properly, and then draws it. You may notice the "ChkDrawSprite" there. Apply the same change we made to the previous set of code that had that. Also, here:

    [68k] move.b (a6)+,d1 ; get mapping frame
    add.w d1,d1
    movea.l a5,a1
    adda.w (a1,d1.w),a1
    move.w (a1)+,d1
    subq.w #1,d1
    bmi.s +[/68k]

    This may looks familar. Apply the same changes as before here, too.

    And finally, it ends with this:

    movea.l (sp)+,a4
    bra.w BuildSprites_NextObj[/68k]

    Which just goes back to the main sprite rendering loop. "BuildSprites_NextObj" is called "loc_D726" in Sonic 1.

    But, we are not quite finished yet!
    I did mention a "DisplaySprite3" before, did I not?

    In Sonic 2, it looks like this:

    lea (Sprite_Table_Input).w,a1
    adda.w d0,a1
    cmpi.w #$7E,(a1)
    bhs.s return_16542
    addq.w #2,(a1)
    adda.w (a1),a1
    move.w a0,(a1)


    Go ahead and implement this routine under the "DisplaySprite1" routine (located in "_incObj/sub DisplaySprite.asm") in the GitHub disassembly, or "DisplaySprite2" routine in the 2005 Hivebrain disassembly.

    And that should be it!

    So, before we say we are all done and whatnot, you may want to test that you implemented this right. So, I have created a test object here. Here's the code:

    [68k]; ---------------------------------------------------------------------------
    ; Test object that tests out sub sprites
    ; ---------------------------------------------------------------------------

    btst #6,1(a0) ; Is this object set to render sub sprites?
    bne.s OT_SubSprs ; If so, branch
    moveq #0,d0
    move.b $24(a0),d0 ; Go to current object routine
    move.w OT_Routines(pc,d0.w),d0
    jmp OT_Routines(pc,d0.w)

    move.w #$200,d0 ; Display sprites
    jmp DisplaySprite3

    ; ---------------------------------------------------------------------------

    dc.w OT_Init-OT_Routines
    dc.w OT_Main-OT_Routines

    ; ---------------------------------------------------------------------------
    ; Initialization
    ; ---------------------------------------------------------------------------

    addq.b #2,$24(a0) ; Set as initialized
    jsr FindFreeObj ; Find a free object slot
    bne.s OT_NoFreeObj
    move.w a1,$3E(a0) ; Set as child object
    move.b #[ID],(a1) ; Load test object
    move.b #%01000100,1(a1) ; Set to render sub sprites
    move.w #$780,2(a1) ; Base tile ID
    move.l #Map_Sonic,4(a1) ; Mappings
    move.b #$30,mainspr_width(a1) ; Set main sprite width
    move.b #$30,mainspr_height(a1) ; Set main sprite height
    move.b #4,mainspr_childsprites(a1) ; Set number of child sprites
    move.w 8(a0),8(a1) ; Set position
    move.w $C(a0),$C(a1)


    ; ---------------------------------------------------------------------------
    ; Main
    ; ---------------------------------------------------------------------------

    movea.w $3E(a0),a1 ; Get child object

    moveq #0,d6
    move.b ($FFFFD01A).w,d6 ; Get frame to use
    move.b d6,mainspr_mapframe(a1) ; Set main sprite frame

    moveq #0,d5
    move.b mainspr_childsprites(a1),d5 ; Get number of sub sprites
    subq.b #1,d5
    bmi.s OT_NoSubSprs ; If there are none, branch
    lea sub2_x_pos(a1),a2 ; Get sub sprite data

    move.b $26(a0),d0 ; Get sine and cosine of the current angle
    jsr CalcSine
    asr.w #3,d0 ; Get Y position
    add.w $C(a0),d0
    asr.w #3,d1 ; Get X position
    add.w 8(a0),d1
    move.w d1,(a2)+ ; Set X position
    move.w d0,(a2)+ ; Set Y position
    move.w d6,(a2)+ ; Set map frame
    addi.b #$40,$26(a0) ; Next angle to use
    dbf d5,OT_SetSubSprs ; Loop until every sub sprite is set

    addq.b #1,$26(a0) ; Increment angle

    In the 2005 Hivebrain disassembly, "FindFreeObj" is "SingleObjLoad" and change "[ID]" to whatever object ID you set this object to.

    Go ahead and put it in your disassembly and then spawn it in a level. You should see something like this:

    If you do, great!, You are all set! If not, go back and see what you need to fix. Any "branch out of range" errors or "illegal value" errors you may get are easy to fix and solutions for them are on this very site somewhere.

    Enjoy your sub sprites.
  2. RetroKoH


    Project Sonic 8x16
    Well done! I reckon something like this could also be done for monitors to have the icons come from the box instead of having another object just for the icons (Those objects handle the powerup itself IIRC, but that code can easily be integrated into the monitor object proper)
  3. Advanced?


    Sonic 1 Hack
    I'm having trouble implementing it, can you tell me what's wrong? (Using S2 Constants)

    Code (Text):
    1. ; ---------------------------------------------------------------------------
    2. ; Subroutine to    convert    mappings (etc) to proper Megadrive sprites
    3. ; ---------------------------------------------------------------------------
    5. ; ||||||||||||||| S U B    R O U T    I N E |||||||||||||||||||||||||||||||||||||||
    8. BuildSprites:                ; XREF: TitleScreen; et al
    9.         lea    (Sprite_Table).w,a2 ; set address for sprite table
    10.         moveq    #0,d5
    11.         lea    (Sprite_Table_Input).w,a4
    12.         moveq    #7,d7
    14. loc_D66A:
    15.         tst.w    (a4)
    16.         beq.w    loc_D72E
    17.         moveq    #2,d6
    19. loc_D672:
    20.         movea.w    (a4,d6.w),a0
    21.         tst.b    (a0)
    22.         beq.w    loc_D726
    23.         bclr    #7,1(a0)
    24.         move.b    1(a0),d0
    25.         move.b    d0,d4
    26.         btst    #6,d0                ; is the multi-draw flag set?
    27.         bne.w    BuildSprites_MultiDraw        ; if it is, branch
    28.         andi.w    #$C,d0
    29.         beq.s    loc_D6DE
    30.         movea.l    BldSpr_ScrPos(pc,d0.w),a1
    31.         moveq    #0,d0
    32.         move.b    width_pixels(a0),d0
    33.         move.w    x_pos(a0),d3
    34.         sub.w    (a1),d3
    35.         move.w    d3,d1
    36.         add.w    d0,d1
    37.         bmi.w    loc_D726
    38.         move.w    d3,d1
    39.         sub.w    d0,d1
    40.         cmpi.w    #$140,d1
    41.         bge.s    loc_D726
    42.         addi.w    #$80,d3
    43.         btst    #4,d4
    44.         beq.s    loc_D6E8
    45.         moveq    #0,d0
    46.         move.b    x_radius(a0),d0
    47.         move.w    y_pos(a0),d2
    48.         sub.w    4(a1),d2
    49.         move.w    d2,d1
    50.         add.w    d0,d1
    51.         bmi.s    loc_D726
    52.         move.w    d2,d1
    53.         sub.w    d0,d1
    54.         cmpi.w    #$E0,d1
    55.         bge.s    loc_D726
    56.         addi.w    #$80,d2
    57.         bra.s    loc_D700
    58. ; ===========================================================================
    60. loc_D6DE:
    61.         move.w    x_sub(a0),d2
    62.         move.w    x_pos(a0),d3
    63.         bra.s    loc_D700
    64. ; ===========================================================================
    66. loc_D6E8:
    67.         move.w    y_pos(a0),d2
    68.         sub.w    4(a1),d2
    69.         addi.w    #$80,d2
    70.         cmpi.w    #$60,d2
    71.         bcs.s    loc_D726
    72.         cmpi.w    #$180,d2
    73.         bcc.s    loc_D726
    75. loc_D700:
    76.         movea.l    4(a0),a1
    77.         moveq    #0,d1
    78.         btst    #5,d4
    79.         bne.s    loc_D71C
    80.         move.b    mapping_frame(a0),d1
    81.         add.b    d1,d1
    82.         adda.w    (a1,d1.w),a1
    83.         move.b    (a1)+,d1
    84.         subq.b    #1,d1
    85.         bmi.s    loc_D720
    87. loc_D71C:
    88.         bsr.w    sub_D750
    90. loc_D720:
    91.         bset    #7,1(a0)
    93. loc_D726:
    94.         addq.w    #2,d6
    95.         subq.w    #2,(a4)
    96.         bne.w    loc_D672
    98. loc_D72E:
    99.         lea    $80(a4),a4
    100.         dbf    d7,loc_D66A
    101.         move.b    d5,($FFFFF62C).w
    102.         cmpi.b    #$50,d5
    103.         beq.s    loc_D748
    104.         move.l    #0,(a2)
    105.         rts
    106. ; ===========================================================================
    108. loc_D748:
    109.         move.b    #0,-5(a2)
    110.         rts
    111. ; End of function BuildSprites
    113. ; ||||||||||||||| S U B    R O U T    I N E |||||||||||||||||||||||||||||||||||||||
    115. BuildSprites_MultiDraw:
    116.             move.l    a4,-(sp)
    117.             lea    (Camera_X_pos).w,a4
    118.             movea.w    art_tile(a0),a3
    119.             movea.l    mappings(a0),a5
    120.             moveq    #0,d0
    122.     ; check if object is within X bounds
    123.             move.b    mainspr_width(a0),d0    ; load pixel width
    124.             move.w    x_pos(a0),d3
    125.             sub.w    (a4),d3
    126.             move.w    d3,d1
    127.             add.w    d0,d1
    128.             bmi.w    BuildSprites_MultiDraw_NextObj
    129.             move.w    d3,d1
    130.             sub.w    d0,d1
    131.             cmpi.w    #320,d1
    132.             bge.w    BuildSprites_MultiDraw_NextObj
    133.             addi.w    #128,d3
    135.     ; check if object is within Y bounds
    136.             btst    #4,d4
    137.             beq.s    +
    138.             moveq    #0,d0
    139.             move.b    mainspr_height(a0),d0    ; load pixel height
    140.             move.w    y_pos(a0),d2
    141.             sub.w    4(a4),d2
    142.             move.w    d2,d1
    143.             add.w    d0,d1
    144.         bmi.w    BuildSprites_MultiDraw_NextObj
    145.             move.w    d2,d1
    146.             sub.w    d0,d1
    147.             cmpi.w    #224,d1
    148.             bge.w    BuildSprites_MultiDraw_NextObj
    149.             addi.w    #128,d2
    150.             bra.s    ++
    151. +
    152.             move.w    y_pos(a0),d2
    153.             sub.w    4(a4),d2
    154.             addi.w    #128,d2
    155.             cmpi.w    #-32+128,d2
    156.             blo.s    BuildSprites_MultiDraw_NextObj
    157.             cmpi.w    #32+128+224,d2
    158.             bhs.s    BuildSprites_MultiDraw_NextObj
    159. +
    160.             moveq    #0,d1
    161.             move.b    mainspr_mapframe(a0),d1    ; get current frame
    162.             beq.s    +
    163.             add.w    d1,d1
    164.             movea.l    a5,a1
    165.             adda.w    (a1,d1.w),a1
    166.             move.b   (a1)+,d1
    167.             subq.w    #1,d1
    168.             bmi.s    +
    169.             move.w    d4,-(sp)
    170.             bsr.w    ChkDrawSprite    ; draw the sprite
    171.             move.w    (sp)+,d4
    172. +
    173.             ori.b    #$80,render_flags(a0)    ; set onscreen flag
    174.             lea    sub2_x_pos(a0),a6
    175.             moveq    #0,d0
    176.             move.b    mainspr_childsprites(a0),d0    ; get child sprite count
    177.             subq.w    #1,d0        ; if there are 0, go to next object
    178.             bcs.s    BuildSprites_MultiDraw_NextObj
    179. -            swap    d0
    180.             move.w    (a6)+,d3    ; get X pos
    181.             sub.w    (a4),d3
    182.             addi.w    #128,d3
    183.             move.w    (a6)+,d2    ; get Y pos
    184.             sub.w    4(a4),d2
    185.             addi.w    #128,d2
    186.             andi.w    #$7FF,d2
    187.             addq.w    #1,a6
    188.             moveq    #0,d1
    189.             move.b    (a6)+,d1    ; get mapping frame
    190.             add.w    d1,d1
    191.             movea.l    a5,a1
    192.             adda.w    (a1,d1.w),a1
    193.             move.w    (a1)+,d1
    194.             subq.w    #1,d1
    195.             bmi.s    +
    196.             move.w    d4,-(sp)
    197.             bsr.w    ChkDrawSprite
    198.             move.w    (sp)+,d4
    199. +
    200.             swap    d0
    201.             dbf    d0,-    ; repeat for number of child sprites
    203. BuildSprites_MultiDraw_NextObj:
    204.             movea.l    (sp)+,a4
    205.             bra.w    loc_D726
    207. DrawSprite_Done:
    208.         rts
    210. ; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
    212. ; sub_1680A:
    213. ChkDrawSprite:
    214.     cmpi.b    #80,d5        ; has the sprite limit been reached?
    215.     blo.s    DrawSprite_Cont    ; if it hasn't, branch
    216.     rts    ; otherwise, return
    217. ; End of function ChkDrawSprite
    219. ; ||||||||||||||| S U B    R O U T    I N E |||||||||||||||||||||||||||||||||||||||
    222. sub_D750:                ; XREF: BuildSprites
    223.         movea.w    2(a0),a3
    224.         cmpi.b    #80,d5
    225.         bhs.s    DrawSprite_Done
    226. ; loc_1681C:
    227. DrawSprite_Cont:
    228.         btst    #0,d4
    229.         bne.s    loc_D796
    230.         btst    #1,d4
    231.         bne.w    loc_D7E4
    232. ; End of function sub_D750
    235. ; ||||||||||||||| S U B    R O U T    I N E |||||||||||||||||||||||||||||||||||||||
    238. sub_D762:                ; XREF: sub_D762; SS_ShowLayout
    239.         cmpi.b    #$50,d5
    240.         beq.s    locret_D794
    241.         move.b    (a1)+,d0
    242.         ext.w    d0
    243.         add.w    d2,d0
    244.         move.w    d0,(a2)+
    245.         move.b    (a1)+,(a2)+
    246.         addq.b    #1,d5
    247.         move.b    d5,(a2)+
    248.         move.b    (a1)+,d0
    249.         lsl.w    #8,d0
    250.         move.b    (a1)+,d0
    251.         add.w    a3,d0
    252.         move.w    d0,(a2)+
    253.         move.b    (a1)+,d0
    254.         ext.w    d0
    255.         add.w    d3,d0
    256.         andi.w    #$1FF,d0
    257.         bne.s    loc_D78E
    258.         addq.w    #1,d0
    260. loc_D78E:
    261.         move.w    d0,(a2)+
    262.         dbf    d1,sub_D762
    264. locret_D794:
    265.         rts
    266. ; End of function sub_D762
    268. ; ===========================================================================
    270. loc_D796:
    271.         btst    #1,d4
    272.         bne.w    loc_D82A
    274. loc_D79E:
    275.         cmpi.b    #$50,d5
    276.         beq.s    locret_D7E2
    277.         move.b    (a1)+,d0
    278.         ext.w    d0
    279.         add.w    d2,d0
    280.         move.w    d0,(a2)+
    281.         move.b    (a1)+,d4
    282.         move.b    d4,(a2)+
    283.         addq.b    #1,d5
    284.         move.b    d5,(a2)+
    285.         move.b    (a1)+,d0
    286.         lsl.w    #8,d0
    287.         move.b    (a1)+,d0
    288.         add.w    a3,d0
    289.         eori.w    #$800,d0
    290.         move.w    d0,(a2)+
    291.         move.b    (a1)+,d0
    292.         ext.w    d0
    293.         neg.w    d0
    294.         add.b    d4,d4
    295.         andi.w    #$18,d4
    296.         addq.w    #8,d4
    297.         sub.w    d4,d0
    298.         add.w    d3,d0
    299.         andi.w    #$1FF,d0
    300.         bne.s    loc_D7DC
    301.         addq.w    #1,d0
    303. loc_D7DC:
    304.         move.w    d0,(a2)+
    305.         dbf    d1,loc_D79E
    307. locret_D7E2:
    308.         rts
    309. ; ===========================================================================
    311. loc_D7E4:                ; XREF: sub_D750
    312.         cmpi.b    #$50,d5
    313.         beq.s    locret_D828
    314.         move.b    (a1)+,d0
    315.         move.b    (a1),d4
    316.         ext.w    d0
    317.         neg.w    d0
    318.         lsl.b    #3,d4
    319.         andi.w    #$18,d4
    320.         addq.w    #8,d4
    321.         sub.w    d4,d0
    322.         add.w    d2,d0
    323.         move.w    d0,(a2)+
    324.         move.b    (a1)+,(a2)+
    325.         addq.b    #1,d5
    326.         move.b    d5,(a2)+
    327.         move.b    (a1)+,d0
    328.         lsl.w    #8,d0
    329.         move.b    (a1)+,d0
    330.         add.w    a3,d0
    331.         eori.w    #$1000,d0
    332.         move.w    d0,(a2)+
    333.         move.b    (a1)+,d0
    334.         ext.w    d0
    335.         add.w    d3,d0
    336.         andi.w    #$1FF,d0
    337.         bne.s    loc_D822
    338.         addq.w    #1,d0
    340. loc_D822:
    341.         move.w    d0,(a2)+
    342.         dbf    d1,loc_D7E4
    344. locret_D828:
    345.         rts
    346. ; ===========================================================================
    348. loc_D82A:
    349.         cmpi.b    #$50,d5
    350.         beq.s    locret_D87C
    351.         move.b    (a1)+,d0
    352.         move.b    (a1),d4
    353.         ext.w    d0
    354.         neg.w    d0
    355.         lsl.b    #3,d4
    356.         andi.w    #$18,d4
    357.         addq.w    #8,d4
    358.         sub.w    d4,d0
    359.         add.w    d2,d0
    360.         move.w    d0,(a2)+
    361.         move.b    (a1)+,d4
    362.         move.b    d4,(a2)+
    363.         addq.b    #1,d5
    364.         move.b    d5,(a2)+
    365.         move.b    (a1)+,d0
    366.         lsl.w    #8,d0
    367.         move.b    (a1)+,d0
    368.         add.w    a3,d0
    369.         eori.w    #$1800,d0
    370.         move.w    d0,(a2)+
    371.         move.b    (a1)+,d0
    372.         ext.w    d0
    373.         neg.w    d0
    374.         add.b    d4,d4
    375.         andi.w    #$18,d4
    376.         addq.w    #8,d4
    377.         sub.w    d4,d0
    378.         add.w    d3,d0
    379.         andi.w    #$1FF,d0
    380.         bne.s    loc_D876
    381.         addq.w    #1,d0
    383. loc_D876:
    384.         move.w    d0,(a2)+
    385.         dbf    d1,loc_D82A
    387. locret_D87C:
    388.         rts
    389. ; ---------------------------------------------------------------------------
    390. ; Subroutine to    check if an object is on the screen
    391. ; ---------------------------------------------------------------------------
    393. ; ||||||||||||||| S U B    R O U T    I N E |||||||||||||||||||||||||||||||||||||||
    396. ChkObjOnScreen:
    397.         move.w    x_pos(a0),d0    ; get object x-position
    398.         sub.w    (Camera_X_pos).w,d0 ; subtract screen x-position
    399.         bmi.s    NotOnScreen
    400.         cmpi.w    #320,d0        ; is object on the screen?
    401.         bge.s    NotOnScreen    ; if not, branch
    403.         move.w    y_pos(a0),d1    ; get object y-position
    404.         sub.w    (Camera_Y_pos).w,d1 ; subtract screen y-position
    405.         bmi.s    NotOnScreen
    406.         cmpi.w    #224,d1        ; is object on the screen?
    407.         bge.s    NotOnScreen    ; if not, branch
    409.         moveq    #0,d0        ; set flag to 0
    410.         rts
    411. ; ===========================================================================
    413. NotOnScreen:                ; XREF: ChkObjOnScreen
    414.         moveq    #1,d0        ; set flag to 1
    415.         rts
    416. ; End of function ChkObjOnScreen
    419. ; ||||||||||||||| S U B    R O U T    I N E |||||||||||||||||||||||||||||||||||||||
    422. ChkObjOnScreen2:
    423.         moveq    #0,d1
    424.         move.b    width_pixels(a0),d1
    425.         move.w    x_pos(a0),d0
    426.         sub.w    (Camera_X_pos).w,d0
    427.         add.w    d1,d0
    428.         bmi.s    NotOnScreen2
    429.         add.w    d1,d1
    430.         sub.w    d1,d0
    431.         cmpi.w    #320,d0
    432.         bge.s    NotOnScreen2
    434.         move.w    y_pos(a0),d1
    435.         sub.w    (Camera_Y_pos).w,d1
    436.         bmi.s    NotOnScreen2
    437.         cmpi.w    #224,d1
    438.         bge.s    NotOnScreen2
    440.         moveq    #0,d0
    441.         rts
    442. ; ===========================================================================
    444. NotOnScreen2:                ; XREF: ChkObjOnScreen2
    445.         moveq    #1,d0
    446.         rts
    447. ; End of function ChkObjOnScreen2
    Last edited: Oct 27, 2019
  4. Ralakimus


    People suck Tech Member
    Well, what errors do you get?

    I'm gonna guess it's those +/- labels. As I said in the guide:
    Assuming you are using the ASM68K version of the Sonic 1 disassembly.
  5. Advanced?


    Sonic 1 Hack
    I'm using the AS version. The game builds, but the sub sprites don't display.
  6. Ralakimus


    People suck Tech Member
    What's "Camera_X_pos" equated to?
  7. Advanced?


    Sonic 1 Hack
  8. Ralakimus


    People suck Tech Member
    Ok, looking through it again, I see this towards the end:

    [68k] move.w (a1)+,d1
    subq.w #1,d1
    bmi.s +[/68k]

    It's supposed to be a move.b, not a move.w, since Sonic 1 uses a byte for the sprite piece count instead of a word like in Sonic 2.
  9. Advanced?


    Sonic 1 Hack
  10. Ralakimus


    People suck Tech Member
    Okay, over a year later, it has been brought to my attention, that fucking bit 6 in the render flags SST is used to handle Sonic on loops in stock Sonic 1. I never went through a loop when testing this, so I never noticed.

    To fix that, find another flag in RAM to use for Sonic's loop flag.