Basic Questions and Answers Thread

Discussion in 'Discussion & Q&A' started by Malevolence, Jul 7, 2009.

  1. MainMemory

    MainMemory Well-Known Member Member

    Joined:
    Mar 29, 2011
    Messages:
    922
    It wouldn't be too difficult to change over to the Sonic 2 object layout format, just a matter of changing which bits the object manager checks for certain things. Transferring your layouts might be a bit involved, unless someone's written a specific tool, you'd have to run each level through LevelConverter, make sure "Convert objects as-is" is checked and "Convert known objects only" is unchecked, then grab the Objects.bin from each folder and replace the proper file with it.
     
  2. RouRouRou

    RouRouRou Ain't no fun if the aliens can't have none. Member

    Joined:
    Nov 20, 2016
    Messages:
    97
    Removing the flag? I'm interested in that.
    Could someone please tell me where to find the code?

    I'd also like to know how to get these new objects to properly load in SonLVL, but that's another problem for another day.
     
  3. MainMemory

    MainMemory Well-Known Member Member

    Joined:
    Mar 29, 2011
    Messages:
    922
    Like I said, it would be a simple change to move the respawn flag from the ID byte to the Y position word and shift the X and Y flip flags over by one bit, to match Sonic 2's format. The code you need to edit is at loc_DA3C in S1 (any disassembly). The equivalent code in Sonic 2 is at ChkLoadObj.
     
  4. RouRouRou

    RouRouRou Ain't no fun if the aliens can't have none. Member

    Joined:
    Nov 20, 2016
    Messages:
    97
    Thanks, the 2005 disam has confusing labels, I'll try it out when I get time.
    Edit: I'll provide results as well.
    Edit 2: Let's just say that recently, there's been nothing worth showing off.
     
    Last edited: Sep 5, 2018
  5. Unused Account

    Unused Account Well-Known Member Member

    Joined:
    May 10, 2013
    Messages:
    154
    Probably a bit of a simple question, but I have no clue -
    How could I add custom art to Sonic 1? Wanted to just make some Sonic sprites and new tiles for GHZ, but I have no way to actually put them in the game. I found a tutorial for Sonic years ago, but I assume it's outdated and there's easier methods (I remember this tutorial taking ~20 minutes for a single sprite to be replaced).
     
  6. RouRouRou

    RouRouRou Ain't no fun if the aliens can't have none. Member

    Joined:
    Nov 20, 2016
    Messages:
    97
    There's a lot of methods for adding custom art to Sonic 1. For level art, the easiest would be using SonLVL's built in editor thst works like a paint program, (In my opinion) and for Sonic you could use SonMapED. Keep in mind you'll have to import the art, there's no built in editor.

    Of course, these are just my options and someone else will probably have spmething better.

    Edit: Typing on a phone is no fun.
    Edit 2: That's right, Flex 2 exists. I haven't used it though.
     
    Last edited: Sep 4, 2018
  7. MainMemory

    MainMemory Well-Known Member Member

    Joined:
    Mar 29, 2011
    Messages:
    922
    You could use Flex 2, which is basically SonMapEd but better, or SonLVL's SpritePlotter.NET if you just want to import a sprite sheet. Possibly both.
     
  8. Pacca

    Pacca Having an online identity crisis since 2019 Member

    Joined:
    Jul 5, 2014
    Messages:
    1,170
    Location:
    Limbo
    Does anyone have tips for optimizing Sonic 2s' TouchResponse routine? I'm trying to improve speed when multiple objects that use the routine are on screen, and it's taking up all the CPU time (for good reason, of course). Even just a little bit of a speed improvement might help drastically.

    EDIT: And is there any reason for there being a 'nop' right at the beginning of the routine? It seems completely pointless. It's not like it's trying to talk to the Z80 or anything along those lines...
     
    Last edited: Sep 5, 2018
  9. MarkeyJester

    MarkeyJester ♡ ! Member

    Joined:
    Jun 27, 2009
    Messages:
    2,870
    I'm surprised no-one's answered yet, usually they answer before I finish work and beat me to the punch. Lucky me this time, eh? d=

    There are a few things you could do to speed it up, if you're willing of course. I've worked up a few ideas you can use, or perhaps you could improve on them. Here are a few minors:

    Code:
    	subi.w	#8,d2
    	moveq	#0,d5
    	move.b	y_radius(a0),d5
    	subq.b	#3,d5
    This can be changed with:
    Code:
    		subq.w	#$08,d2				; get left position
    		move.w	#-3&$FF,d5			; load Y radius
    		add.b	y_radius(a0),d5			; '' (reduce by -3 for touching)
    Code:
    	move.w	#$10,d4
    This can be changed to a moveq.

    Code:
    	lea	(Object_RAM+$400).w,a1
    	move.w	#$6F,d6
    ; loc_3F5A0:
    Touch_Loop:
    	move.b	collision_flags(a1),d0
    	bne.w	Touch_Height
    ; loc_3F5A8:
    Touch_NextObj:
    	lea	next_object(a1),a1 ; load obj address ; goto next object
    	dbf	d6,Touch_Loop ; repeat 6F more times
    This can be rearranged such that the bne is a beq back to the loop, but... this probably won't benefit you if you have lots of objects with touching collision, this would only improve speed if it's the other way around, but it's something to consider if one is looking for the opposition to your problem, so I thought I'd mention it here.

    Here are a few majors which will require sacrificing of size in favour of speed:

    Code:
    Touch_Sizes:
    	dc.b   4,  4; 0
    	dc.b $14,$14
    	dc.b  $C,$14
    	etc...
    If you rearrange this table such that it's four words (yes, no kidding here), but have it so that it's width, then width x 2, then height, then height x 2:
    Code:
    Touch_Sizes:	dc.w	$0004,$0008,$0004,$0008
    		dc.w	$0014,$0028,$0014,$0028
    		dc.w	$000C,$0018,$0014,$0028
    		etc...
    This can allow some changes in code to speed up a little:

    Code:
    Touch_Height:
    	andi.w	#$3F,d0
    	add.w	d0,d0
    	lea	Touch_Sizes(pc,d0.w),a2
    	moveq	#0,d1
    	move.b	(a2)+,d1
    	move.w	x_pos(a1),d0
    	sub.w	d1,d0
    	sub.w	d2,d0
    	bcc.s	loc_3F5D6
    	add.w	d1,d1
    	add.w	d1,d0
    	bcs.s	Touch_Width
    	bra.w	Touch_NextObj
    ; ===========================================================================
    
    loc_3F5D6:
    	cmp.w	d4,d0
    	bhi.w	Touch_NextObj
    ; loc_3F5DC:
    Touch_Width:
    	moveq	#0,d1
    	move.b	(a2)+,d1
    	move.w	y_pos(a1),d0
    	sub.w	d1,d0
    	sub.w	d3,d0
    	bcc.s	loc_3F5F6
    	add.w	d1,d1
    	add.w	d1,d0
    	bcs.w	Touch_ChkValue
    	bra.w	Touch_NextObj
    ; ===========================================================================
    
    loc_3F5F6:
    	cmp.w	d5,d0
    	bhi.w	Touch_NextObj
    	bra.w	Touch_ChkValue
    Can be substituted for something along the lines of:

    Code:
    		lea	(Touch_Sizes).l,a3		; load touch sizes
    ^ to be loaded before the object checking loop, and left in a3 perminantly, you will also need to ensure that d0 is fully clear before entering the object checking loop too, and then...
    Code:
    	; --- Checking object ---
    
    		add.w	d0,d0				; multiply by size of long-word
    		add.w	d0,d0				; ''
    		move.w	d0,d1				; keep a copy for later
    		andi.w	#$003F<<2,d0			; get only the size
    		lea	(a3,d0.w),a2			; load correct size data
    
    		; X check
    
    		move.w	x_pos(a1),d0			; load object's X position
    		sub.w	(a2)+,d0			; get left position
    		sub.w	d2,d0				; minus character width
    		bcc.s	Touch_Left			; if character is to the left of the object, branch
    		cmp.w	(a2)+,d0			; check if the width of the object is enough to touch the character to its right
    		blo.s	Touch_Right			; if so, branch
    		bra.s	Touch_FindNew			; find next object
    
    Touch_Left:
    		cmp.w	d4,d0				; is the width of character enough to touch the object on its right?
    		bhi.s	Touch_FindNew			; if not, branch
    		addq.w	#$02,a2				; skip object's width
    
    Touch_Right:
    		; Y check
    
    		move.w	y_pos(a1),d0			; load object's Y position
    		sub.w	(a2)+,d0			; get top position
    		sub.w	d3,d0				; minus character height
    		bcc.s	Touch_Above			; if character is above the object, branch
    		cmp.w	(a2),d0				; check if the height of the object is enough to touch the character below
    		blo.s	Touch_Below			; if so, branch
    		bra.s	Touch_FindNew			; find next object
    
    Touch_Above:
    		cmp.w	d5,d0				; is the height of character enough to touch the object below?
    		bhi.s	Touch_FindNew			; if not, branch
    
    Touch_Below:
    Now, you may notice I kept a copy of the touch response ID inside d0, and put it into d1, this is for the next optimisation you can perform:
    Code:
    Touch_ChkValue:
    	move.b	collision_flags(a1),d1	; load touch response number
    	andi.b	#$C0,d1			; is touch response $40 or higher?
    	beq.w	Touch_Enemy		; if not, branch
    	cmpi.b	#$C0,d1			; is touch response $C0 or higher?
    	beq.w	loc_3F976		; if yes, branch
    	tst.b	d1			; is touch response $80-$BF ?
    	bmi.w	Touch_ChkHurt		; if yes, branch
    	; touch response is $40-$7F
    	move.b	collision_flags(a1),d0
    	andi.b	#$3F,d0
    	cmpi.b	#6,d0			; is touch response $46 ?
    	beq.s	Touch_Monitor		; if yes, branch
    	etc...
    	etc...
    Can be substituted for:
    Code:
    		move.l	TouchRoutines-4(pc,d1.w),-(sp)	; load routine
    		rts					; run routine
    
    TouchRoutines:	dc.l	Touch_Enemy		; 01
    		dc.l	Touch_Enemy		; 02
    		dc.l	Touch_Enemy		; 03
    		dc.l	Touch_Enemy		; 04
    		; etc...
    		; etc...
    Keeping in mind that this long-word address table will go up to FF, this will remove ALL compares and checks for the touch response ID and what type it is, since you'll jump directly to the routine you need without a check necessary. There "might" be a quicker method with this though:

    Code:
    		jmp	TouchRoutines-4(pc,d1.w)
    
    TouchRoutines:	bra.w	Touch_Enemy		; 01
    		bra.w	Touch_Enemy		; 02
    		bra.w	Touch_Enemy		; 03
    		bra.w	Touch_Enemy		; 04
    		; etc...
    		; etc...
    Not too sure, haven't bothered checking, but both will take about the same size, though the latter will limit the distance you can branch (but I doubt that'd be a problem. The bottom line is, you won't need to check for things like... if the ID is 06 for the monitors, since the 6th long-word will point directly to the routine responsible for handling the monitor as you require. You will have to restructure and make these routines as you need them of course, but it'll save you quite some time by comparison.

    Another solution to speed up time would be to remove the object checking loop, and replace it with a list reader. Though you will have to go through loads of objects and get the ones that put numbers into the touch response byte, to save themselves to the list. This will allow the 68k to directly read the object's address from this list, without having to check if it's touch response value is 00, since if it's in the list, it has to have a value other than 0, so no checking is required, the only checking needed will be if you've reached the end of the list.

    It's quite time consuming, but it's up to you and how desperate you are. Keep in mind, I have not checked the code above to see if it assembles or works or if there's something of Sonic 2 which may contradict the code, so you will have to experiment yourself.
     
    ProjectFM and Pacca like this.
  10. Pacca

    Pacca Having an online identity crisis since 2019 Member

    Joined:
    Jul 5, 2014
    Messages:
    1,170
    Location:
    Limbo
    So, I tried implementing the optimization for the object distance checking loop, and it doesn't quite function as expected. It sort of works, but only if your in a very precise location to the left of the object you want to collide with. The hitbox size seems accurate, and the routine works just fine if your to the left of the object, but in every other scenario, no collision occurs, even if your in what should be the objects hitbox.

    I made macro to make setting up Touch_Sizes for the new format easier, and it appears to compile exactly as intended. I also made the assumptions that Touch_FindNew should point to Touch_NextObj, and that Touch_Below should end in "bra.w Touch_ChkValue", as neither is properly defined in your code. I added " moveq #0,d0 ;clear d0" after Touch_Loop to ensure the upper portion of d0 is cleared before the collision ID is put in the lower byte, and put " lea (Touch_Sizes).l,a3 ; load touch sizes" right before Touch_Loop to ensure that a3 contains the proper pointer. I also went through the bother of making a "vanilla" copy of Touch_Sizes with a different name, since a routine related to boss collision uses the original list.

    Normally, I'd move on if one optimization doesn't work, but this is the most important one; this loop is the most obvious time eater in the entire routine, and shaving off any wasted cycles inside of it could be game changing for me.
     
    Last edited: Sep 6, 2018
  11. AURORA☆FIELDS

    AURORA☆FIELDS so uh yes Exiled

    Joined:
    Oct 7, 2011
    Messages:
    760
    I created a little speed optimization for Achi, to help me combat lag from Sonic 1. I created a similar concept to the Collision List from Sonic 3K into Sonic 1, and some optimizations for checking the object itself. Now, it is not exactly revolutionary, but I thought I'd share the code. The code here is for Sonic 1 Git disassembly, but should be easily adaptable. The collision list should always be exactly 1 word, + 2 * the number of (dynamic) object slots. For example, I did this:

    Code:
    CollisionList    rs.w $78    ; entries for collision response.
    Then, we need to reset the list at the start of a level, and with the player object. Under Level_SkipTtlCard, After this;

    Code:
            bsr.w    LZWaterFeatures
    Add the following:

    Code:
            move.w    #CollisionList+2,CollisionList.w    ; reset list pos
    Then, add the same at the end of Sonic_Normal. Do note you also need to make the jmp be jsr, and add a rts.

    Then, the actual code. Replace ReactToItem to @sizes: with:

    Code:
    AddToCollList:
            move.w    CollisionList.w,a1    ; get the collision list pos
            move.w    a0,(a1)+        ; put in the object there
            move.w    a1,CollisionList.w    ; save new pos
            rts
    
    ; ---------------------------------------------------------------------------
    ; Subroutine to react to obColType(a0)
    ; ---------------------------------------------------------------------------
    ReactToItem:
            move.w    obX(a0),d2    ; load Sonic's x-axis position
            move.w    obY(a0),d3    ; load Sonic's y-axis position
            subq.w    #8,d2
            moveq    #0,d5
            move.b    obHeight(a0),d5    ; load Sonic's height
            subq.b    #3,d5
            sub.w    d5,d3
            cmpi.b    #fr_Duck,obFrame(a0) ; is Sonic ducking?
            bne.s    @notducking    ; if not, branch
            addi.w    #$C,d3
            moveq    #$A,d5
    
        @notducking:
            move.w    #$10,d4
            add.w    d5,d5
    
            lea    CollisionList.w,a3    ; get list start
            move.w    (a3)+,d6        ; get list end
    
            sub.w    a3,d6            ; get the size of the list
            lsr.w    #1,d6            ; halve
            subq.w    #1,d6            ; sub 1 from it (dbf)
            bmi.s    @end            ; if no items, branch
    
    @loop:        move.w    (a3)+,a1        ; get next entry
            tst.b    obRender(a1)        ; check if onscreen
            bmi.s    @proximity        ; if so, check
    @next:        dbf    d6,@loop        ; loop
    
    @end        moveq    #0,d0
            rts
    And now, a bomb shell! For each object that needs to use collision response, you need a call to AddToCollList. Otherwise, no collision will be present whatsoever. A little bit about the code itself. It created an array of pointers to objects, and stores the list end pointer for any subsequent calls. This is really great, because you need to perform no magic to actually get the pointer when adding objects, significantly lowering overhead! It is also very easy to just grab the counter, subtract them from each other and shift down appropriately. One bonus is not having to check for the collision type either, making it substantially faster when looking for objects. Rest of the routine is practically the same as the original, because I didn't bother editing it.
     
    ProjectFM, Pacca and MarkeyJester like this.
  12. MarkeyJester

    MarkeyJester ♡ ! Member

    Joined:
    Jun 27, 2009
    Messages:
    2,870
    ^ that basically above is what I meant by a "list reader" (thank you Natsumi d=), as I said, you can remove the loop entirely so long as objects save themselves to a list which you can process later without checks.
    I did warn you and specifically said that I hadn't tested it. The code supplied was nothing more than a quickly written, untested piece of code for you to reference, I was hoping you'd mold it in successfully and fix any minor oddities with it...

    I believe you'll wanna change the two "cmp.w (a2)+,d0" with "add.w (a2)+,d0", I'm certain that's my fuck up, cmp performs a subtraction which is the exact opposite...

    Another thing would be these:
    Code:
    		sub.w	(a2)+,d0			; get left position
    		sub.w	d2,d0				; minus character width
    
    		sub.w	(a2)+,d0			; get top position
    		sub.w	d3,d0				; minus character height
    Swap with:
    Code:
    		sub.w	d2,d0				; minus character width
    		sub.w	(a2)+,d0			; get left position
    
    		sub.w	d3,d0				; minus character height
    		sub.w	(a2)+,d0			; get top position
    Wrong way around d=
    The Touch_FindNew is not exactly branching back to "Touch_NextObj", it branches back to just before the loop where there's a moveq #$00,d0, the reason is because I didn't want the moveq #$00,d0 inside the search loop, running over and over again, you only need it done once, so it's best to keep it out of the loop.
    I think the biggest hog of CPU would be the width/height checking, since it doesn't matter where on the screen the collide-able object is, it will always be checked against the character's position. So I don't think that small loop is the main culprit, but it's still worth removing or reducing if at all possible, so follow the linked list reader method Natsumi's shared.
     
    ProjectFM and Pacca like this.
  13. Pacca

    Pacca Having an online identity crisis since 2019 Member

    Joined:
    Jul 5, 2014
    Messages:
    1,170
    Location:
    Limbo
    Thanks! I don't have quite everything working yet, but at least the essential stuff is working. Initial testing suggest that Natsumis' suggestion works too, I'll just have to track down every time something writes to collision_flags. Thanks for all the help :3
     
  14. Tanman Tanner

    Tanman Tanner Well-Known Member Member

    Joined:
    Dec 23, 2016
    Messages:
    116
    Location:
    Buffalo, New York
    What's the label in Sonic 3/Sonic & Knuckles/Sonic 3 & Knuckles that determines the level order (i.e. AIZ, then HCZ, etc.)?
     
  15. MainMemory

    MainMemory Well-Known Member Member

    Joined:
    Mar 29, 2011
    Messages:
    922
    I think it's all hardcoded in the level events?
     
  16. Pacca

    Pacca Having an online identity crisis since 2019 Member

    Joined:
    Jul 5, 2014
    Messages:
    1,170
    Location:
    Limbo
    Hm, the collision detection code still seems to be somewhat off. It occasionally lets potential collisions slip by, typically when something is to the right or towards the bottom of the object. I tried to debug it myself (even pulled out my regen debugger, got myself in a stable position inside of what should be an enemy hitbox, then tracked down it's ID so I could walk through the exact collision that was errantly failing).

    The underlying problem is that I still don't properly understand how most of the branch instructions work (I only have bra, beq, bne, bgt, bge, blt, and ble truly pinned down), or even how to use comparison instructions other then cmp or sub. If that frustrates you to no end, believe me, I'm right there with you. I've been planning on making a "branchtest" gamemode that lets me play with them in real time, so I can finally figure them out, but I'm not sure if it's worth the dev time right now, given that the SHC is sneaking up on me...
     
  17. Tanman Tanner

    Tanman Tanner Well-Known Member Member

    Joined:
    Dec 23, 2016
    Messages:
    116
    Location:
    Buffalo, New York
    Hopefully it doesn't work like that, since I recall Sonic 1&2 had set level orders in a label, and could be altered without a hassle.
    Darn me for using Sonic 3 while most people are using 1 & 2. Well, I just gotta keep looking, right?
     
  18. faith

    faith Well-Known Member Member

    Joined:
    Aug 26, 2013
    Messages:
    1,209
    The act 1 to act 2 transitions are all done in the level background event routines. From zone to zone, it seems it's all handled in the cutscene objects. A quick way to find them is by searching "StartNewLevel", in which before it's called, d0 should contain the new zone and act ID to switch to.
     
  19. MarkeyJester

    MarkeyJester ♡ ! Member

    Joined:
    Jun 27, 2009
    Messages:
    2,870
    I've had time to have a thorough look now.

    So, firstly, the "move.w #-3&$FF,d5" wasn't assembling properly, AS was being a gay ass piece of shit as usual. So you'll have to use the manual version "move.w #$00FD,d5".

    When I asked you to swap these:
    Code:
    		sub.w	(a2)+,d0			; get left position
    		sub.w	d2,d0				; minus character width
    
    		sub.w	(a2)+,d0			; get top position
    		sub.w	d3,d0				; minus character height
    With:
    Code:
    		sub.w	d2,d0				; minus character width
    		sub.w	(a2)+,d0			; get left position
    
    		sub.w	d3,d0				; minus character height
    		sub.w	(a2)+,d0			; get top position
    Turns out, no, I was right the first time, it should've been (a2) first.

    The third and most important problem is:
    Code:
    	add.w	d0,d0				; multiply by size of long-word
    	add.w	d0,d0				; ''
    	move.w	d0,d1				; keep a copy for later
    	andi.w	#$003F<<2,d0			; get only the size
    It's actually 3 shifts, not 2, the size of each element in the table ended up being 8 not 4, so:
    Code:
    	lsl.w	#$03,d0				; multiply by 8
    	move.w	d0,d1				; keep a copy for later
    	andi.w	#$003F<<3,d0			; get only the size
    I've had the chance to check this carefully on a disassembly this time, so I can confirm it functions as it should.

    Sorry for the inconvenience, I've been too busy.
     
    Last edited: Sep 9, 2018
    Pacca likes this.
  20. Tanman Tanner

    Tanman Tanner Well-Known Member Member

    Joined:
    Dec 23, 2016
    Messages:
    116
    Location:
    Buffalo, New York
    Oh, THAT'S what that label does. I feel stupid now. Thank you!
     
    Last edited: Sep 10, 2018