Actions

SCHG How-to

Work with Objects

From Sonic Retro

Revision as of 16:03, 25 November 2010 by MarkeyJester (talk | contribs) (Lesson 3: Making Mappings: Correcting stuff)

(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:

<asm>; ---------------------------------------------------------------------------

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)</asm>

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.

<asm>;=========================================================================== 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)

===========================================================================</asm>


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

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

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:

<asm>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</asm>

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: 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)</asm>

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)

<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 jmp SpeedToPos ; update the object's position (move the object)</asm>

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:

<asm>ObjXX_Main: move.w #$100,$38(a0)</asm>

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: sub.w #1,$38(a0) ; subtracts from the timer beq.s ObjXX_Next ; tests if timer has hit 0 rts</asm>

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: neg.w $10(a0) move.w #$100,$38(a0) rts</asm>

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: <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”.

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>

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

<asm>Map_obj18: dc.w ObjectMap_01-Map_obj18 dc.w ObjectMap_02-Map_obj18 dc.w ObjectMap_03-Map_obj18 ObjectMap_01: dc.b $00

ObjectMap_02: dc.b $00

ObjectMap_03: dc.b $00</asm>

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:

<asm>ObjectMap_01: dc.b $01 dc.b $YY, $SS, $VV, $VV, $XX</asm>

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:

<asm>ObjectMap_01: dc.b $01 dc.b $08, $SS, $VV, $VV, $XX</asm>

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: <asm> dc.b $YY, $SS, $VV, $VV, $08</asm>

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: <asm> dc.b $YY, $SS, $VV, $VV, $XX</asm> 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.