Editing Objects - RetroKoH/S1Fixed GitHub Wiki
Objects in Sonic 1 are pretty straight-forward to modify once you get the hang of ASM editing. In stock Sonic 1, Object RAM starts at $FFFFD000, and each object consists of $40 (64) bytes of RAM. This guide will introduce you to the basics of where to find objects, and how to understand the basics of how they work.
Object Pointers
A file containing a list of pointers to every single object in the game can be found in _inc/Object Pointers.asm. Each object is tied to a single-byte index number ranging from 00-FF (though only $8C indices are used in stock Sonic 1). If you are editing the base game, it's important to note that the following object ID values are either unused, point to objects that can be removed, or point to objects that go unused in the final game. These pointers can be used for new objects that you may either want to create on your own, or port over from other games.
Index Number | Notes |
---|---|
02 | Unused (Used for Tails in later games) |
03 | Unused (Used for the Collision Swapper in later games, and in One Two-Eight) |
04 | Unused |
04 | Unused |
04 | Unused |
04 | Unused |
10 | Unused (Used in the prototype for animation testing. Only an rts in the final. |
19 | Unused (Used in the prototype for the GHZ Checkered Ball. Only an rts in the final. |
1D | "Magic Switch" (Unused in the final game) |
21 | HUD (If using the HUD Manager, you can remove this) |
24 | Buzz Bomber Missile Explosion (Unused in the final game) |
25 | Rings (If you are using a Rings Manager and plan to remove Debug Mode, this object is not needed) |
29 | Points (If you want to free VRAM or even remove score, you can replace this object) |
3F | Boss/Bomb Explosion (This could be a subtype of the standard explosion if you need an object index) |
45 | Sideways Stomper (Unused in the final game) |
4A | Special Stage Entry Effect (Unused in the final game) |
4F | Unused (Used in the prototype for the Splats badnik. Only an rts in the final. |
7C | Giant Ring Flash (This could be integrated into the Giant Ring object (Obj4B) if you need an object index) |
There may be others. I'll update this list as I go on.
Object Status Table (Object RAM)
Every object in RAM in stock Sonic 1 (and S1Fixed) consists of $40 (64) bytes of data. Each byte serves a different purpose as it relates to how the object functions in the game. Some of the bytes' purposes are identical across all objects, while others are not. Many objects also have a slew of "scratch RAM" that you can make use of to add greater functionality to them.
Universal OST Bytes
These OST bytes serve the same purpose across every object in the game, and shouldn't be used outside of their intended purpose. Doing so can cause significant issues.
$00 - obID
This is the index number of the object. In sonic.asm, there is a function called ExecuteObjects that goes through the entirety of Object RAM and runs the code of every object loaded in RAM. To do so, it checks for these ID numbers. If it reads 00, it assumes there is no object, and moves on to the next space. Otherwise it pulls this value and runs a check against the Object Pointers list noted above, to find the address of the code to run. If an objects index number is changed at any point, either through a coded instruction or in real-time via RAM modding, it will begin running completely different code. In some instances, this may be desired, but in most cases, this can be troublesome.
$01 - obRender
This is a bitfield which tells the game whether or not this object's sprite should be rendered on-screen or not, and how it should do so. Each bit provides a different function. I'll break this down in the future, but do note that this changes across games, and functionality in S1Fixed is slightly different than in stock Sonic 1.
$02-03 - obGfx
These two bytes tell the game where the object's starting art tile offset is in VRAM, as well as which palette line it should utilize for rendering. A full breakdown of this, along with the accompanying macro, is coming soon.
$04-07 - obMap
These four bytes contain a full address pointing to the location of this object's sprite mappings. Unless your object will NEVER be displayed on screen in some manner, it's not advised to change this value during runtime. Only set this in the object's code to a label pointing to sprite mappings.
$08-09 - obX
These two bytes are the integer portion of the object's current x-position within the "room" you are currently in. Said "room" almost always refers to the stage you're currently playing in, though there are exceptions.
$0A-0B - obXSub
These two bytes are the fraction, or subpixel, portion of the object's current x-position. Now, numbers in this game do not use actual floating point numbers, so the way that fractions work is like so. Let's say obX is set to $0014 (20 in decimal), and obXSub is set to $4000. Because obXSub is 2 bytes, it ranges from $0000 to $FFFF, for $10000 possible values. Now, $4000/$10000 is equal to 1/4, or 0.25. Therefore, when we combine all 4 bytes into $00144000, that means the object's x-position is equal to 20.25. The subpixel value is not used when placing the object on screen, but as it is incremented and it overflows, the integer portion gets incremented as a result, allowing it to work like a fraction.
$0C-0D - obY
These two bytes are the integer portion of the object's current y-position within the "room".
$0E-0F - obYSub
These two bytes are the fraction, or subpixel, portion of the object's current y-position. The prior explanation applies here too.
$18 - obPriority
This OST is only one byte in stock Sonic 1, but has been extended to 2 bytes in S1Fixed ($18-19)
This byte is used by DisplaySprite to determine the drawing priority level of the object's sprite. Sprites with a lower priority value will be drawn ABOVE sprites with a higher priority value.
$19 - obActWid
This OST has been moved to $23 in S1Fixed (due to obPriority being extended to 2 bytes)
Labeled as "action width" in the disassembly. I'll provide more info later.
$1A - obFrame
The current sprite mapping frame being displayed.
OST Bytes used by most objects, including Sonic
These OST bytes serve the same purpose with most objects, including Sonic. In some cases, they can be repurposed, but this usually shouldn't be done unless you know what you are doing.
$10 - obVelX
These two bytes are used by the subroutines SpeedToPos or ObjectFall (or other similar subroutines) to adjust the object's x-position smoothly, giving it a sense of horizontal "motion".
$12 - obVelY
These two bytes are used by the subroutines SpeedToPos or ObjectFall (or other similar subroutines) to adjust the object's y-position smoothly, giving it a sense of vertical "motion". In ObjectFall, this value gets increased gradually as a means to emulate gravity.
$16 - obHeight
This byte determines the object's vertical radius, used primarily for terrain collision. Despite its name, it only determines half of the object's collision height. To be more accurate, it determines the length of the object's vertical collision detection sensor line.
$17 - obWidth
This byte determines the object's horizontal radius, used primarily for terrain collision. Despite its name, it only determines half of the object's collision width. To be more accurate, it determines the length of the object's horizontal collision detection sensor line.
$1B - obAniFrame
This byte is used by objects that utilize the AnimateSprite subroutine to determine the object's current frame index in its animation script. It's important to note that this doesn't directly determine the object's sprite mapping frame to be displayed. It is instead used to find the sprite mapping frame to be displayed, in accordance with the object's animation script.
$1C - obAnim
This byte is used by objects that utilize the AnimateSprite subroutine to determine which of the object's loaded animations should be executed.
$1D - obPrevAni
This byte is used by objects that utilize the AnimateSprite subroutine. It stores the previously run animation, allowing the subroutine to determine whether or not a new animation is being run or not. If so, it usually resets the object's animation variables accordingly.
$1E - obTimeFrame
$1F - obDelayAni
$22 - obStatus
This byte is a bitfield used by most objects to determine various aspects about its current in-game state. The manner in which the bits are utilized depends on the object in question.
$24 - obRoutine
This byte is used by nearly all objects as a means of program control. When an object's code is run by ExecuteObjects, it's commonplace that this byte will be used in a lookup table to determine which segment of the object's code should be run at that moment. As a result of being utilized in lookup tables that use word-length pointers, obRoutine is always set to an even value.
$26 - obAngle
This byte is used by many objects in subroutines involving angular movement. The most obvious example of this is Sonic, who runs and rolls along all manner of slopes throughout the game. Some objects use this byte in different manners.
OST Bytes used by many non-player objects
These OST bytes serve the same purpose with various objects, but NOT Sonic. Again, there are some cases where they can be repurposed, but this usually shouldn't be done unless you know what you are doing. Sonic uses equivalent OST bytes for different purposes.
$20 - obColType
This OST byte is used by ReactToItem to determine the type of collision hitbox an object has, as well as its bounding box size (via a lookup table). If this byte is set to 00, then Sonic is unable to collide with this item in a meaningful way.
$21 - obColProp
This OST byte is an additional collision-based property. The most common use of this byte is by boss objects as hit points. Every boss takes 8 hits before being defeated, so this byte is set to 8 when the boss battle starts, and the battle ends when this ticks down to 0.
$23 - obRespawnNo
This OST has been moved to $14 in S1Fixed, due to being extended to 2 bytes for use with the S3K Object Manager This OST variable serves the same purpose in stock Sonic 1, and S1Fixed, but its execution is slightly different. I'll cover this more in-depth later.
$25 - ob2ndRout
This byte is used by some objects as a secondary routine counter. It works exactly like obRoutine, and is always set to an even value.
$25 - obSolid
Removed in S1Fixed This byte was used in stock Sonic 1 as a status flag for solid objects. S1Fixed does away with this variable completely, following suit with Sonic 2 and Sonic 3K.
$28 - obSubtype
This byte is like a secondary obID value, so much so that this value is stored next to the ID value in an object's data in the object layout data. It denotes which variation of an object that this instance is. For example, Green Hill Zone's Newtron badnik makes use of this byte to determine whether it is a blue Newtron (which glides along the ground) or a green Newtron (which fires a shot, then disappears).
More to come later.
Porting Objects to/from S1Fixed
There are a few things to make note of when porting objects to/from S1Fixed, and other engines, especially those built off of Sonic 2 or Sonic 3:
- S1Fixed uses Sonic 2 format sprites (as of the time of writing). Adjustments to certain object mechanics such as crumbling ledges, as well as object sprite mappings, will need to be adjusted accordingly.
- Aspects like Solidity only account for one player character, as opposed to two, like in Sonic 2 or Sonic 3K.
- Objects use a single byte ID instead of an address. Certain aspects like subtypes and routine handling will be different in this engine compared to Sonic 3K.