Actions

SCHG How-to

Difference between revisions of "Work with Objects"

From Sonic Retro

(Created page with '{{GuideBy|Malevolence|MarkeyJester}} I decided I wanted to start some tutorials on objects because there aren't too many hacks with new objects. I figure I can start off with th…')
 
m
 
(7 intermediate revisions by 4 users not shown)
Line 7: Line 7:
 
So, you want to start creating objects using the sonic engine? (We'll be using S1's but most of the ideas cover S1 and S2) Let's see firstly how most objects start out. I'll be using Hivebrain's 2005 disassembly for these examples:
 
So, you want to start creating objects using the sonic engine? (We'll be using S1's but most of the ideas cover S1 and S2) Let's see firstly how most objects start out. I'll be using Hivebrain's 2005 disassembly for these examples:
  
<asm>; ---------------------------------------------------------------------------
+
<syntaxhighlight lang="asm">; ---------------------------------------------------------------------------
 
; Object 18 - platforms (GHZ, SYZ, SLZ)
 
; Object 18 - platforms (GHZ, SYZ, SLZ)
 
; ---------------------------------------------------------------------------
 
; ---------------------------------------------------------------------------
  
Obj18: ; XREF: Obj_Index
+
Obj18:
moveq #0,d0
+
moveq #0,d0
move.b $24(a0),d0
+
move.b $24(a0),d0
 
move.w Obj18_Index(pc,d0.w),d1
 
move.w Obj18_Index(pc,d0.w),d1
jmp Obj18_Index(pc,d1.w)</asm>
+
jmp Obj18_Index(pc,d1.w)</syntaxhighlight>
  
 
First you see a little description of the object (commented out of course), followed by the object's code itself. It starts off by making sure there is currently no value in d0. After that it puts the value of the routine counter into d0 (whatever's in $24(a0) is in d0 such as 0, 2, 4, ect...). From there it moves the index's value with the value of d0 into d1, followed by jumping to the correct routine based on d1.
 
First you see a little description of the object (commented out of course), followed by the object's code itself. It starts off by making sure there is currently no value in d0. After that it puts the value of the routine counter into d0 (whatever's in $24(a0) is in d0 such as 0, 2, 4, ect...). From there it moves the index's value with the value of d0 into d1, followed by jumping to the correct routine based on d1.
Line 22: Line 22:
 
Routines are used to organize where certain code is and to be able to branch to those sections easily. Most objects used $24(a0) as the main routine counter and $25 as a secondary, but any scratch ram (ram not used by the object) can be used as a routine counter. The value in the routine counter needs to be even in order to work.
 
Routines are used to organize where certain code is and to be able to branch to those sections easily. Most objects used $24(a0) as the main routine counter and $25 as a secondary, but any scratch ram (ram not used by the object) can be used as a routine counter. The value in the routine counter needs to be even in order to work.
  
<asm>;===========================================================================
+
<syntaxhighlight lang="asm">;===========================================================================
Obj18_Index: dc.w Obj18_Main-Obj18_Index; Jumped to if #0 is in $24(a0)
+
Obj18_Index: dc.w Obj18_Main-Obj18_Index ; Jumped to if #0 is in $24(a0)
dc.w Obj18_Solid-Obj18_Index; Jumped to if #2 is in $24(a0)
+
dc.w Obj18_Solid-Obj18_Index ; Jumped to if #2 is in $24(a0)
dc.w Obj18_Action2-Obj18_Index; Jumped to if #4 is in $24(a0)
+
dc.w Obj18_Action2-Obj18_Index ; Jumped to if #4 is in $24(a0)
dc.w Obj18_Delete-Obj18_Index; Jumped to if #6 is in $24(a0)
+
dc.w Obj18_Delete-Obj18_Index ; Jumped to if #6 is in $24(a0)
dc.w Obj18_Action-Obj18_Index; Jumped to if #8 is in $24(a0)
+
dc.w Obj18_Action-Obj18_Index ; Jumped to if #8 is in $24(a0)
; ===========================================================================</asm>
+
; ===========================================================================</syntaxhighlight>
  
  
 
So, when this section is run (as soon as the object is loaded):
 
So, when this section is run (as soon as the object is loaded):
  
<asm>Obj18_Main: ; XREF: Obj18_Index
+
<syntaxhighlight lang="asm">Obj18_Main: ; XREF: Obj18_Index
addq.b #2,$24(a0)</asm>
+
addq.b #2,$24(a0)</syntaxhighlight>
  
 
When it gets to the next rts instead of going to Obj18_Main again, it will skip over that code and go to Obj18_Solid. Be warned if you put an odd value into the routine counter it won't work properly or if you put a number greater then the amount of routines, your game will crash.
 
When it gets to the next rts instead of going to Obj18_Main again, it will skip over that code and go to Obj18_Solid. Be warned if you put an odd value into the routine counter it won't work properly or if you put a number greater then the amount of routines, your game will crash.
Line 41: Line 41:
 
Alright, so you know the basis of routines, now where should we go? Well, some of the basic building blocks in objects are what are called SSTs. Every object in S1 and S2 is allotted $40 bytes of RAM to do whatever they want with, but some SSTs are already used for certain things (the game engine checks a few of the object's SSTs once each frame). Let's see an example:
 
Alright, so you know the basis of routines, now where should we go? Well, some of the basic building blocks in objects are what are called SSTs. Every object in S1 and S2 is allotted $40 bytes of RAM to do whatever they want with, but some SSTs are already used for certain things (the game engine checks a few of the object's SSTs once each frame). Let's see an example:
  
<asm>Obj18_Main: ; XREF: Obj18_Index
+
<syntaxhighlight lang="asm">Obj18_Main:
addq.b #2,$24(a0) ; adds to the routine so this isn't run again
+
addq.b #2,$24(a0) ; adds to the routine so this isn't run again
move.w #$4000,2(a0); moves #$4000 to the art tile's SST (it's 2 in S1 and S2)
+
move.w #$4000,2(a0) ; moves #$4000 to the art tile's SST (it's 2 in S1 and S2)
move.l #Map_obj18,4(a0); moves the mappings into the mapping's SST
+
move.l #Map_obj18,4(a0) ; moves the mappings into the mapping's SST
move.b #$20,$19(a0) ; width of object in pixels  
+
move.b #$20,$19(a0) ; width of object in pixels  
cmpi.b #4,($FFFFFE10).w ; check if level is SYZ
+
cmpi.b #4,($FFFFFE10).w ; check if level is SYZ
 
bne.s Obj18_NotSYZ
 
bne.s Obj18_NotSYZ
move.l #Map_obj18a,4(a0) ; SYZ specific code (use different mappings)
+
move.l #Map_obj18a,4(a0) ; SYZ specific code (use different mappings)
move.b #$20,$19(a0) ; this really isn't needed since $19(a0) already has #$20 in it from the code before</asm>
+
move.b #$20,$19(a0) ; this really isn't needed since $19(a0) already has #$20 in it from the code before</syntaxhighlight>
  
 
What this basically does is define the width of the object in pixels and loads the starting art tile and palette and mappings. Down more in the code you'll see:
 
What this basically does is define the width of the object in pixels and loads the starting art tile and palette and mappings. Down more in the code you'll see:
  
<asm>Obj18_NotSLZ:
+
<syntaxhighlight lang="asm">Obj18_NotSLZ:
move.b #4,1(a0); use screen coordinates (such as the ones you see in debug mode)
+
move.b #4,1(a0) ; use screen coordinates (such as the ones you see in debug mode)
move.b #4,$18(a0); set priority (if other objects have a priority of a number less then 4 then the other object will be seen over this one if they interact)
+
move.b #4,$18(a0) ; set priority (if other objects have a priority of a number less then 4 then the other object will be seen over this one if they interact)
move.w $C(a0),$2C(a0); store a copy of the y coordinate ($C(a0) is y coordingate in S1 and S2 and $2C(a0) is scratch ram)
+
move.w $C(a0),$2C(a0) ; store a copy of the y coordinate ($C(a0) is y coordingate in S1 and S2 and $2C(a0) is scratch ram)
move.w $C(a0),$34(a0); store another copy of the y coordinate
+
move.w $C(a0),$34(a0) ; store another copy of the y coordinate
move.w 8(a0),$32(a0); store a copy of the x coordinate
+
move.w 8(a0),$32(a0) ; store a copy of the x coordinate
move.w #$80,$26(a0); move #$80 into $26(a0) (to be used later)</asm>
+
move.w #$80,$26(a0) ; move #$80 into $26(a0) (to be used later)</syntaxhighlight>
  
 
This is a continuation of the loading of the object (the priority and using screen coordinates) and it saves the x and y pos and a value which will be used later. All you need to do to display an object it to fill in 1(a0), 2(a0), 4(a0) and jump to DisplaySprite.
 
This is a continuation of the loading of the object (the priority and using screen coordinates) and it saves the x and y pos and a value which will be used later. All you need to do to display an object it to fill in 1(a0), 2(a0), 4(a0) and jump to DisplaySprite.
Line 66: Line 66:
 
==Lesson 2: More Intricate Things With Objects==
 
==Lesson 2: More Intricate Things With Objects==
 
===Section A: Movement===
 
===Section A: Movement===
Welcome to lesson 2 of my object toturials, in this section we're going to talk about other things that can be done with objects. So say you have an object displaying now and would like it to move. What you'll have to do is set its X and/or Y speed which are the SSTs $10(a0) and $12(a0) consecutively and then simply call a SpeedtoPos (or ObjectMove in S2)
+
Welcome to lesson 2 of my object tutorials, in this section we're going to talk about other things that can be done with objects. So say you have an object displaying now and would like it to move. What you'll have to do is set its X and/or Y speed which are the SSTs $10(a0) and $12(a0) consecutively and then simply call a SpeedtoPos (or ObjectMove in S2)
  
<asm>           move.w #-$40,$10(a0) ; make the object have a speed which moves it to the left slowly
+
<syntaxhighlight lang="asm"> move.w #-$40,$10(a0) ; make the object have a speed which moves it to the left slowly
 
move.w #$400,$12(a0) ; make the object have a speed which moves it down quickly
 
move.w #$400,$12(a0) ; make the object have a speed which moves it down quickly
jmp SpeedToPos ; update the object's position (move the object)</asm>
+
jmp SpeedToPos ; update the object's position (move the object)</syntaxhighlight>
  
 
If SpeedToPos is not called the object will stay immobilized. Also as a note, if you have a positive speed in the Y speed SST, the object will move down and if you have a negative speed in the Y speed SST, the object will move up. This is just a simple explanation of movement and I will eventually cover things such as how to make objects move in circular motions, but for now it's not necessary.
 
If SpeedToPos is not called the object will stay immobilized. Also as a note, if you have a positive speed in the Y speed SST, the object will move down and if you have a negative speed in the Y speed SST, the object will move up. This is just a simple explanation of movement and I will eventually cover things such as how to make objects move in circular motions, but for now it's not necessary.
Line 77: Line 77:
 
Another pretty basic idea used quite frequently is timers. It's what the GHZ boss uses to turn around and go back and forth. To use a timer what you'll have to do is take an unused SST and make sure it's set aside. In this example code, let's use $38(a0). What you want to do is somewhere before the timer starts (say the main loading code) where the code won't be used again is fill this with a number:
 
Another pretty basic idea used quite frequently is timers. It's what the GHZ boss uses to turn around and go back and forth. To use a timer what you'll have to do is take an unused SST and make sure it's set aside. In this example code, let's use $38(a0). What you want to do is somewhere before the timer starts (say the main loading code) where the code won't be used again is fill this with a number:
  
<asm>ObjXX_Main:
+
<syntaxhighlight lang="asm">ObjXX_Main:
move.w #$100,$38(a0)</asm>
+
move.w #$100,$38(a0)</syntaxhighlight>
  
 
When you come to an area where you want to start counting down, you'll want to set up a code that gets repeated until the time is up, as in:
 
When you come to an area where you want to start counting down, you'll want to set up a code that gets repeated until the time is up, as in:
  
<asm>ObjXX_CountDown:
+
<syntaxhighlight lang="asm">ObjXX_CountDown:
sub.w #1,$38(a0) ; subtracts from the timer
+
sub.w #1,$38(a0) ; subtracts from the timer
beq.s ObjXX_Next ; tests if timer has hit 0
+
beq.s ObjXX_Next ; tests if timer has hit 0
rts</asm>
+
rts</syntaxhighlight>
  
 
Now, in ObjXX_Next you can increase the routine, change the speed/reverse it and you can reset the timer there as well so that it keeps changing speed:
 
Now, in ObjXX_Next you can increase the routine, change the speed/reverse it and you can reset the timer there as well so that it keeps changing speed:
  
<asm>ObjXX_Next:
+
<syntaxhighlight lang="asm">ObjXX_Next:
neg.w $10(a0)
+
neg.w $10(a0)
move.w #$100,$38(a0)
+
move.w #$100,$38(a0)
rts</asm>
+
rts</syntaxhighlight>
  
 
==Lesson 3: Making Mappings==
 
==Lesson 3: Making Mappings==
Ok, in this section we’re gonna cover the aspects of making object mappings, so what are mappings? Mappings are a way of presenting art tiles on an object in a certain way, now going back to “Section C: Displaying/Basic SSTs” in “Lesson 1” you’ll notice the line: <asm>move.l   #Map_obj18,4(a0)   ; moves the mappings into the mapping's SST</asm> This loads the mappings under the routine name “Map_obj18”.
+
Ok, in this section we’re gonna cover the aspects of making object mappings (Note: this is written for Sonic 1 primarily, however Sonic 2 and to an extend Sonic 3 (&K) mappings will be quite similar bar a few changes), so what are mappings? Mappings are a way of presenting art tiles on an object in a certain way, now going back to “Section C: Displaying/Basic SSTs” in “Lesson 1” you’ll notice the line: <syntaxhighlight lang="asm"> move.l #Map_obj18,4(a0) ; moves the mappings into the mapping's SST</syntaxhighlight> This loads the mappings under the routine name “Map_obj18”.
  
 
So, we need to make this routine and the mappings for it, now it doesn’t really matter where you put this but for now let’s put it directly under our object code (it just makes sense this way).
 
So, we need to make this routine and the mappings for it, now it doesn’t really matter where you put this but for now let’s put it directly under our object code (it just makes sense this way).
  
<asm>Map_obj18:</asm>
+
<syntaxhighlight lang="asm">Map_obj18:</syntaxhighlight>
  
 
Next we need an index for this routine similar to the one explain in “Lesson 1”
 
Next we need an index for this routine similar to the one explain in “Lesson 1”
  
<asm>Map_obj18:
+
<syntaxhighlight lang="asm">Map_obj18: dc.w ObjectMap_00-Map_obj18
dc.w ObjectMap_01-Map_obj18
+
dc.w ObjectMap_01-Map_obj18
dc.w ObjectMap_02-Map_obj18
+
dc.w ObjectMap_02-Map_obj18
dc.w ObjectMap_03-Map_obj18
+
ObjectMap_00: dc.b $00
ObjectMap_01: dc.b $00
 
  
ObjectMap_02: dc.b $00
+
ObjectMap_01: dc.b $00
  
ObjectMap_03: dc.b $00</asm>
+
ObjectMap_02: dc.b $00</syntaxhighlight>
  
And now to make the mappings, you’ll notice three sections that look like “ObjectMap_01: dc.b $00” under this is where our set of mappings are going to go, now let me just set one out for you and explain what’s what:
+
And now to make the mappings, you’ll notice three sections that look like “ObjectMap_00: dc.b $00” under this is where our set of mappings are going to go, now let me just set one out for you and explain what’s what:
  
<asm>ObjectMap_01: dc.b $01
+
<syntaxhighlight lang="asm">ObjectMap_00: dc.b $01
dc.b $vv, $ww, $xx, $yy, $zz</asm>
+
dc.b $YY, $SS, $VV, $VV, $XX</syntaxhighlight>
  
So, you’ll notice that first of all the $00 that was previously there is now a $01, this is because a line has been added below it “dc.b $vv, $ww, $xx, $yy, $zz” that is one map block, if you had two of these below, then you would put $02 at the top there, that top value indicates the number of map blocks to use, we’re only gonna use one for now, so $01 it is.
+
So, you’ll notice that first of all the $00 that was previously there is now a $01, this is because a line has been added below it “dc.b $YY, $SS, $VV, $VV, $XX” that is one sprite, if you had two of these below, then you would put $02 at the top there, that top value indicates the number of sprites to use in these mappings, we’re only gonna use one for now, so $01 it is.
  
Now to the actual mapping at hand, you notice I’ve put vv ww xx yy and zz, this is just to explain what each byte does, so lets take a look at vv, vv sets how many pixels up or down to present the tiles from where the object is, for example:
+
Now to the actual mapping, you notice I’ve put '''YY SS VV VV''' and '''XX''', this is just to explain what each byte does, so lets take a look at '''YY''', '''YY''' sets how many pixels up or down to present the tiles from where the object is, it is signed so negative values from FF down to 80 will move the sprite up while positive values from 00 to 7F will move the sprite down, for example:
  
<asm>ObjectMap_01: dc.b $01
+
<syntaxhighlight lang="asm">ObjectMap_00: dc.b $01
dc.b $08, $ww, $xx, $yy, $zz</asm>
+
dc.b $08, $SS, $VV, $VV, $XX</syntaxhighlight>
  
This will move the tile mappings down 8 pixels from where the object is, so if the object is on the Y axis of $0200, the tile mappings will present on the Y axis of $0208, simple.
+
This will move the tile mappings down 8 pixels from where the object is, so if the object is on the Y axis of $0200, the tile mappings will present on the Y axis of $0208.
  
Next let’s skip over to zz, this is the same as vv except it’s on the X axiz (left or right) not the Y axiz, so:
+
Next let’s skip over to '''XX''', this is the same as '''YY''' except it’s on the X axis (left or right) not the Y axis, so:
<asm>dc.b $vv, $ww, $xx, $yy, $08</asm>
+
<syntaxhighlight lang="asm"> dc.b $YY, $SS, $VV, $VV, $08</syntaxhighlight>
  
This will move the tile mappings right 8 pixels from where the object is, so if the object is on the X axis of $0340, the tile mappings will present on the X axis of $0348, simple.
+
This will move the tile mappings right 8 pixels from where the object is, so if the object is on the X axis of $0340, the tile mappings will present on the X axis of $0348.
  
Ok, now to ww, this is how the tiles should present them selves, in other words how the block is made, now lets say we have 16 tiles in VRam (from 00 to 0F), depending on what code is in ww, will change how those 16 tiles are stacked on each other:
+
Ok, now to '''SS''', this is the "shape" so to speak, it's how the tiles should present themselves (in other words how the block is made), now lets say we have 16 tiles in VRam (from 00 to 0F), depending on what code is in '''SS''' will change how those 16 tiles are stacked on each other:
  
 
So if we put $00 the tiles will map:  
 
So if we put $00 the tiles will map:  
Line 326: Line 325:
  
 
That should be self explanatory for you:
 
That should be self explanatory for you:
<asm>dc.b $vv, $ww, $xx, $yy, $zz</asm>
+
<syntaxhighlight lang="asm"> dc.b $YY, $SS, $VV, $VV, $XX</syntaxhighlight>
Next let's look at xx, this sets if it is flipped, mirrored, and or uses a certain palette line, now it works like this:
+
Next let's look at '''VV VV''', this is actually a word of data and is the V-Ram location to read the tiles for the sprite, it also has specific settings (if it is flipped, mirrored, and or uses a certain palette line, and if it's high or low plane), this is a "Map ID" and is used for map planes too, let's break '''VVVV''' up into sections of '''XYZZ''':
  
First, you need to set if it’s flipped or mirrored, if you put 08 in, the mappings with appear mirrored, if you put 10 in, the mappings will appear flipped, if you put 18 in, the mappings will appear both mirrored and flipped.
+
'''YZZ is broken into bits: ABBB BBBB BBBB'''
  
Now, you need to set what colour palette line to use, so the first line would be 00, the second line would be 20, third is 40, and forth 60.
+
B = the tile ID (or V-Ram location divided by 20 if you'd prefer)
 +
A = the mirror flag, if clear (bit 0), the "map tile"/"sprite" is normal, if set (bit 1), the "map tile"/"sprite" is mirrored.
  
So now you need to add these 2 codes together, for example, if I want the mappings to be flipped (10) and use the second colour palette line (20), add them together, you get 30, and then put it where xx is.
+
'''X is broken into bits: PCCF'''
  
Lets try another one, we want our mappings to be mirrored (08) and use the forth palette line (60), add them together, you get 68, and then put it where xx is, simple.
+
P = the plane flag, if clear (bit 0), the "map tile"/"sprite" is low plane, if set (bit 1), the "map tile"/"sprite" is high plane.
 +
CC = the palette line flag:
  
<asm>dc.b $vv, $ww, $xx, $yy, $zz</asm>
+
00 = line 0
 +
01 = line 1
 +
10 = line 2
 +
11 = line 3
  
And finally, yy, this is what tile to start mapping from, so lets say we set ww at 03 so the tiles will position them selves like this:
+
F = the flip flag, if clear (bit 0), the "map tile"/"sprite" is normal, if set (bit 1), the "map tile"/"sprite" is flipped.
{| border="1"
+
 
|00
+
And there you have it, that’s how to map art on an object.
|-
+
 
|01
+
==Lesson 4: Working with animations==
|-
+
 
|02
+
After getting an object working and individual mappings made, you may get the idea of wanting to animate several individual mappings in a specific order at a certain speed, luckily there just so happens to be routines in the engine to use and deal with animation scripts, in this section; we take a look at that.
|-
+
 
|03
+
As described in Lesson 1 by Malevolence, the object may call a routine known as “DisplaySprite”, in order to use animation scripts, just before the instruction calling “DisplaySprite”, the following will be used:
|}
+
 
 +
<syntaxhighlight lang="asm"> lea (Ani_obj18).l,a1 ; load animation script address to a1
 +
jsr AnimateSprite ; run routine to change the map frame ID using the scripts</syntaxhighlight>
 +
 
 +
This will ensure that the script is ran through “before” the display routine is called.
 +
 
 +
The next thing is the animation scripts and their format:
 +
 
 +
<syntaxhighlight lang="asm">Ani_obj18: dc.w ObjectAni_00-Ani_obj18
 +
dc.w ObjectAni_01-Ani_obj18
 +
dc.w ObjectAni_02-Ani_obj18
 +
dc.w ObjectAni_03-Ani_obj18
 +
ObjectAni_00: dc.b $02,$00,$01,$02,$FF
 +
ObjectAni_01: dc.b $02,$00,$01,$02,$FE,$02
 +
ObjectAni_02: dc.b $02,$02,$01,$00,$02,$02,$00,$01,$FF
 +
ObjectAni_03: dc.b $02,$00,$01,$02,$FD,$01
 +
even</syntaxhighlight>
 +
 
 +
Just like the mappings, the animations also have an index, and each entry is selected by the value in $1C(a-) of the object’s ram, so if 02 was moved to $1C(a-) of the object, then “ObjectAni_02” script would be used, if 00 was moved to $1C(a-), then “ObjectAni_00” script would be used.
 +
 
 +
In every script, the first byte is the delay (or better yet “speed”) of the animation, it sets how many frames to show the same map frame before it should move on to the next one, I’ve set all of them to 02, meaning it always waits 2 frames before it shows the next frame in the script.  00 is the fastest while FF is the slowest.
 +
 
 +
Every byte “after” the first one is the animation itself, if it is a positive number (from 00 to 7F) then it is a map frame number to use, for example, our mappings used in this tutorial, if the number is 00, then the mappings “ObjectMap_00” would be displayed, if the number is 02, then the mappings “ObjectMap_02” would be displayed.
 +
 
 +
So in the script “ObjectAni_02”, mappings 02 01 00 02 02 00 then 01 would be displayed in that order showing for over 2 frames each.
 +
 
 +
If however the number is negative (from 80 to FF) then it is a “flag”, flags are used to set how the animation will change, loop, stop, etc,  However there are only 6 flags used:
  
If we put $02 in yy instead of $00, it’ll position the tiles like this:
+
*FF, this resets the entire animation script, looping all of it over and over again,
{| border="1"
+
*FE, this jumps back through the script a certain number of bytes, for example in script “ObjectAni_01” you’ll see an “$FE,$02” indicating that it’ll move back 2 bytes in the script to the “$01” and loop.
|02
+
*FD, this sets the next animation script to use, for example in script “ObjectAni_03” you’ll see “$FD,$01” indicating to move 01 to $1C(a-) of the object, this will set “ObjectAni_01” to be ran next.
|-
+
*FC, this increases the routine counter $24(a-) by 02.
|03
+
*FB, this resets the “sub”-routine counter $25(a-) to 00.
|-
+
*FA, this increases the “sub”-routine counter $25(a-) by 02.
|04
+
That is all for animation scripts.
|-
+
==Note by DelayHacks==
|05
+
If you wanted to create object at the level, you need to add this object to objects' pointers (_inc/Object Pointers.asm):
|}
+
<syntaxhighlight lang="asm">
 +
dc.l Obj01, ObjectFall, ObjectFall, ObjectFall
 +
</syntaxhighlight>
 +
As you can see, there is free slots for objects, you can replace "ObjectFall" with your object's routine:
 +
<syntaxhighlight lang="asm">
 +
dc.l Obj01, ObjXX, ObjectFall, ObjectFall
 +
</syntaxhighlight>
 +
If you want to create object as the gameplay starts, you need to find free slots for object in Objects's ram (aka RAM from $D000 to $D800, every object has a $3F bytes).
  
As you may have noticed it mapped the tiles starting from tile number 02, instead of tile number 00, and there you have it, that’s how to map art on an object.
+
After you find one, you need to add this line into sonic's code:
 +
<syntaxhighlight lang="asm">
 +
move.b #$XX,$FFFFXXXX.w ; create object
 +
</syntaxhighlight>
 +
replace $XX with your object's id, also replace XXXX with RAM you find.
 
[[Category:SCHG How-tos|Work with Objects]]
 
[[Category:SCHG How-tos|Work with Objects]]

Latest revision as of 02:11, 29 September 2018

(Original guide by Malevolence and MarkeyJester)

I decided I wanted to start some tutorials on objects because there aren't too many hacks with new objects. I figure I can start off with the really basic things then move on to more intricate subjects such as bosses. For now here is the first tutorial on very basic object concepts:

Lesson 1: The Basic Object

Section A: Overview

So, you want to start creating objects using the sonic engine? (We'll be using S1's but most of the ideas cover S1 and S2) Let's see firstly how most objects start out. I'll be using Hivebrain's 2005 disassembly for these examples:

; ---------------------------------------------------------------------------
; Object 18 - platforms	(GHZ, SYZ, SLZ)
; ---------------------------------------------------------------------------

Obj18:
		moveq	#0,d0
		move.b	$24(a0),d0
		move.w	Obj18_Index(pc,d0.w),d1
		jmp	Obj18_Index(pc,d1.w)

First you see a little description of the object (commented out of course), followed by the object's code itself. It starts off by making sure there is currently no value in d0. After that it puts the value of the routine counter into d0 (whatever's in $24(a0) is in d0 such as 0, 2, 4, ect...). From there it moves the index's value with the value of d0 into d1, followed by jumping to the correct routine based on d1.

Section B: Routines

Routines are used to organize where certain code is and to be able to branch to those sections easily. Most objects used $24(a0) as the main routine counter and $25 as a secondary, but any scratch ram (ram not used by the object) can be used as a routine counter. The value in the routine counter needs to be even in order to work.

;===========================================================================
Obj18_Index:	dc.w Obj18_Main-Obj18_Index	; Jumped to if #0 is in $24(a0)
		dc.w Obj18_Solid-Obj18_Index	; Jumped to if #2 is in $24(a0)
		dc.w Obj18_Action2-Obj18_Index	; Jumped to if #4 is in $24(a0)
		dc.w Obj18_Delete-Obj18_Index	; Jumped to if #6 is in $24(a0)
		dc.w Obj18_Action-Obj18_Index	; Jumped to if #8 is in $24(a0)
; ===========================================================================


So, when this section is run (as soon as the object is loaded):

Obj18_Main:			; XREF: Obj18_Index
		addq.b	#2,$24(a0)

When it gets to the next rts instead of going to Obj18_Main again, it will skip over that code and go to Obj18_Solid. Be warned if you put an odd value into the routine counter it won't work properly or if you put a number greater then the amount of routines, your game will crash.

Section C: Displaying/Basic SSTs

Alright, so you know the basis of routines, now where should we go? Well, some of the basic building blocks in objects are what are called SSTs. Every object in S1 and S2 is allotted $40 bytes of RAM to do whatever they want with, but some SSTs are already used for certain things (the game engine checks a few of the object's SSTs once each frame). Let's see an example:

Obj18_Main:
		addq.b	#2,$24(a0)		; adds to the routine so this isn't run again
		move.w	#$4000,2(a0)		; moves #$4000 to the art tile's SST (it's 2 in S1 and S2)
		move.l	#Map_obj18,4(a0)	; moves the mappings into the mapping's SST
		move.b	#$20,$19(a0)		; width of object in pixels 
		cmpi.b	#4,($FFFFFE10).w	; check if level is SYZ
		bne.s	Obj18_NotSYZ
		move.l	#Map_obj18a,4(a0)	; SYZ specific code (use different mappings)
		move.b	#$20,$19(a0)		; this really isn't needed since $19(a0) already has #$20 in it from the code before

What this basically does is define the width of the object in pixels and loads the starting art tile and palette and mappings. Down more in the code you'll see:

Obj18_NotSLZ:
		move.b	#4,1(a0)	; use screen coordinates (such as the ones you see in debug mode)
		move.b	#4,$18(a0)	; set priority (if other objects have a priority of a number less then 4 then the other object will be seen over this one if they interact)
		move.w	$C(a0),$2C(a0)	; store a copy of the y coordinate ($C(a0) is y coordingate in S1 and S2 and $2C(a0) is scratch ram)
		move.w	$C(a0),$34(a0)	; store another copy of the y coordinate
		move.w	8(a0),$32(a0)	; store a copy of the x coordinate
		move.w	#$80,$26(a0)	; move #$80 into $26(a0) (to be used later)

This is a continuation of the loading of the object (the priority and using screen coordinates) and it saves the x and y pos and a value which will be used later. All you need to do to display an object it to fill in 1(a0), 2(a0), 4(a0) and jump to DisplaySprite.


Lesson 2: More Intricate Things With Objects

Section A: Movement

Welcome to lesson 2 of my object tutorials, in this section we're going to talk about other things that can be done with objects. So say you have an object displaying now and would like it to move. What you'll have to do is set its X and/or Y speed which are the SSTs $10(a0) and $12(a0) consecutively and then simply call a SpeedtoPos (or ObjectMove in S2)

		move.w	#-$40,$10(a0)	; make the object have a speed which moves it to the left slowly
		move.w	#$400,$12(a0)	; make the object have a speed which moves it down quickly
		jmp	SpeedToPos	; update the object's position (move the object)

If SpeedToPos is not called the object will stay immobilized. Also as a note, if you have a positive speed in the Y speed SST, the object will move down and if you have a negative speed in the Y speed SST, the object will move up. This is just a simple explanation of movement and I will eventually cover things such as how to make objects move in circular motions, but for now it's not necessary.

Section B: Timers

Another pretty basic idea used quite frequently is timers. It's what the GHZ boss uses to turn around and go back and forth. To use a timer what you'll have to do is take an unused SST and make sure it's set aside. In this example code, let's use $38(a0). What you want to do is somewhere before the timer starts (say the main loading code) where the code won't be used again is fill this with a number:

ObjXX_Main:
		move.w	#$100,$38(a0)

When you come to an area where you want to start counting down, you'll want to set up a code that gets repeated until the time is up, as in:

ObjXX_CountDown:
		sub.w	#1,$38(a0)	; subtracts from the timer
		beq.s	ObjXX_Next	; tests if timer has hit 0
		rts

Now, in ObjXX_Next you can increase the routine, change the speed/reverse it and you can reset the timer there as well so that it keeps changing speed:

ObjXX_Next:
		neg.w	$10(a0)
		move.w	#$100,$38(a0)
		rts

Lesson 3: Making Mappings

Ok, in this section we’re gonna cover the aspects of making object mappings (Note: this is written for Sonic 1 primarily, however Sonic 2 and to an extend Sonic 3 (&K) mappings will be quite similar bar a few changes), so what are mappings? Mappings are a way of presenting art tiles on an object in a certain way, now going back to “Section C: Displaying/Basic SSTs” in “Lesson 1” you’ll notice the line:
		move.l	#Map_obj18,4(a0)	; moves the mappings into the mapping's SST
This loads the mappings under the routine name “Map_obj18”.

So, we need to make this routine and the mappings for it, now it doesn’t really matter where you put this but for now let’s put it directly under our object code (it just makes sense this way).

Map_obj18:

Next we need an index for this routine similar to the one explain in “Lesson 1”

Map_obj18:	dc.w ObjectMap_00-Map_obj18
		dc.w ObjectMap_01-Map_obj18
		dc.w ObjectMap_02-Map_obj18
ObjectMap_00:	dc.b $00

ObjectMap_01: 	dc.b $00

ObjectMap_02: 	dc.b $00

And now to make the mappings, you’ll notice three sections that look like “ObjectMap_00: dc.b $00” under this is where our set of mappings are going to go, now let me just set one out for you and explain what’s what:

ObjectMap_00:	dc.b $01
		dc.b $YY, $SS, $VV, $VV, $XX

So, you’ll notice that first of all the $00 that was previously there is now a $01, this is because a line has been added below it “dc.b $YY, $SS, $VV, $VV, $XX” that is one sprite, if you had two of these below, then you would put $02 at the top there, that top value indicates the number of sprites to use in these mappings, we’re only gonna use one for now, so $01 it is.

Now to the actual mapping, you notice I’ve put YY SS VV VV and XX, this is just to explain what each byte does, so lets take a look at YY, YY sets how many pixels up or down to present the tiles from where the object is, it is signed so negative values from FF down to 80 will move the sprite up while positive values from 00 to 7F will move the sprite down, for example:

ObjectMap_00:	dc.b $01
		dc.b $08, $SS, $VV, $VV, $XX

This will move the tile mappings down 8 pixels from where the object is, so if the object is on the Y axis of $0200, the tile mappings will present on the Y axis of $0208.

Next let’s skip over to XX, this is the same as YY except it’s on the X axis (left or right) not the Y axis, so:

		dc.b	$YY, $SS, $VV, $VV, $08

This will move the tile mappings right 8 pixels from where the object is, so if the object is on the X axis of $0340, the tile mappings will present on the X axis of $0348.

Ok, now to SS, this is the "shape" so to speak, it's how the tiles should present themselves (in other words how the block is made), now lets say we have 16 tiles in VRam (from 00 to 0F), depending on what code is in SS will change how those 16 tiles are stacked on each other:

So if we put $00 the tiles will map:

00

If we put $01 the tiles will map:

00
01

If we put $02:

00
01
02

If we put $03:

00
01
02
03

If we put $04:

00 01


If we put $05:

00 02
01 03

If we put $06:

00 03
01 04
02 05

If we put $07:

00 04
01 05
02 06
03 07

If we put $08:

00 01 02

If we put $09:

00 02 04
01 03 05

If we put $0A:

00 03 06
01 04 07
02 05 08

If we put $0B:

00 04 08
01 05 09
02 06 0A
03 07 0B

If we put $0C:

00 01 02 03


If we put $0D:

00 02 04 06
01 03 05 07

If we put $0E:

00 03 06 09
01 04 07 0A
02 05 08 0B

If we put $0F:

00 04 08 0C
01 05 09 0D
02 06 0A 0E
03 07 0B 0F

That should be self explanatory for you:

		dc.b	$YY, $SS, $VV, $VV, $XX

Next let's look at VV VV, this is actually a word of data and is the V-Ram location to read the tiles for the sprite, it also has specific settings (if it is flipped, mirrored, and or uses a certain palette line, and if it's high or low plane), this is a "Map ID" and is used for map planes too, let's break VVVV up into sections of XYZZ:

YZZ is broken into bits: ABBB BBBB BBBB

B = the tile ID (or V-Ram location divided by 20 if you'd prefer) A = the mirror flag, if clear (bit 0), the "map tile"/"sprite" is normal, if set (bit 1), the "map tile"/"sprite" is mirrored.

X is broken into bits: PCCF

P = the plane flag, if clear (bit 0), the "map tile"/"sprite" is low plane, if set (bit 1), the "map tile"/"sprite" is high plane. CC = the palette line flag:

00 = line 0 01 = line 1 10 = line 2 11 = line 3

F = the flip flag, if clear (bit 0), the "map tile"/"sprite" is normal, if set (bit 1), the "map tile"/"sprite" is flipped.

And there you have it, that’s how to map art on an object.

Lesson 4: Working with animations

After getting an object working and individual mappings made, you may get the idea of wanting to animate several individual mappings in a specific order at a certain speed, luckily there just so happens to be routines in the engine to use and deal with animation scripts, in this section; we take a look at that.

As described in Lesson 1 by Malevolence, the object may call a routine known as “DisplaySprite”, in order to use animation scripts, just before the instruction calling “DisplaySprite”, the following will be used:

		lea	(Ani_obj18).l,a1			; load animation script address to a1
		jsr	AnimateSprite				; run routine to change the map frame ID using the scripts

This will ensure that the script is ran through “before” the display routine is called.

The next thing is the animation scripts and their format:

Ani_obj18:	dc.w	ObjectAni_00-Ani_obj18
		dc.w	ObjectAni_01-Ani_obj18
		dc.w	ObjectAni_02-Ani_obj18
		dc.w	ObjectAni_03-Ani_obj18
ObjectAni_00:	dc.b	$02,$00,$01,$02,$FF
ObjectAni_01:	dc.b	$02,$00,$01,$02,$FE,$02
ObjectAni_02:	dc.b	$02,$02,$01,$00,$02,$02,$00,$01,$FF 
ObjectAni_03:	dc.b	$02,$00,$01,$02,$FD,$01
		even

Just like the mappings, the animations also have an index, and each entry is selected by the value in $1C(a-) of the object’s ram, so if 02 was moved to $1C(a-) of the object, then “ObjectAni_02” script would be used, if 00 was moved to $1C(a-), then “ObjectAni_00” script would be used.

In every script, the first byte is the delay (or better yet “speed”) of the animation, it sets how many frames to show the same map frame before it should move on to the next one, I’ve set all of them to 02, meaning it always waits 2 frames before it shows the next frame in the script. 00 is the fastest while FF is the slowest.

Every byte “after” the first one is the animation itself, if it is a positive number (from 00 to 7F) then it is a map frame number to use, for example, our mappings used in this tutorial, if the number is 00, then the mappings “ObjectMap_00” would be displayed, if the number is 02, then the mappings “ObjectMap_02” would be displayed.

So in the script “ObjectAni_02”, mappings 02 01 00 02 02 00 then 01 would be displayed in that order showing for over 2 frames each.

If however the number is negative (from 80 to FF) then it is a “flag”, flags are used to set how the animation will change, loop, stop, etc, However there are only 6 flags used:

  • FF, this resets the entire animation script, looping all of it over and over again,
  • FE, this jumps back through the script a certain number of bytes, for example in script “ObjectAni_01” you’ll see an “$FE,$02” indicating that it’ll move back 2 bytes in the script to the “$01” and loop.
  • FD, this sets the next animation script to use, for example in script “ObjectAni_03” you’ll see “$FD,$01” indicating to move 01 to $1C(a-) of the object, this will set “ObjectAni_01” to be ran next.
  • FC, this increases the routine counter $24(a-) by 02.
  • FB, this resets the “sub”-routine counter $25(a-) to 00.
  • FA, this increases the “sub”-routine counter $25(a-) by 02.

That is all for animation scripts.

Note by DelayHacks

If you wanted to create object at the level, you need to add this object to objects' pointers (_inc/Object Pointers.asm):

	dc.l Obj01, ObjectFall,	ObjectFall, ObjectFall

As you can see, there is free slots for objects, you can replace "ObjectFall" with your object's routine:

	dc.l Obj01, ObjXX,	ObjectFall, ObjectFall

If you want to create object as the gameplay starts, you need to find free slots for object in Objects's ram (aka RAM from $D000 to $D800, every object has a $3F bytes).

After you find one, you need to add this line into sonic's code:

		move.b	#$XX,$FFFFXXXX.w	; create object

replace $XX with your object's id, also replace XXXX with RAM you find.