Sprite objects are general use spritemaps, driven by an instruction set. Generally, they are utilized as 'particle effects', such as dust, explosions, or flashes, but they are also utilized by certain enemies to allow for additional sprite-maps to be drawn simultaneously without requiring additional enemies. If you're programming a custom item, enemy, or other such thing, sprite objects are a great, simple way to add graphical effects to your custom code.
To spawn a sprite object, you first need to set 4 addresses in ram with parameters for that sprite object:
$12: The X position to spawn your sprite object $14: The Y position to spawn your sprite object $16: the Sprite object ID (see table below) $18: The base tile number and palette bits to spawn your sprite object with
To actually spawn the sprite object, you need to call the routine in bank $B4 which does so, using the following operation:
For any sprite object that uses graphics from the game's uncompressed, general use sprite graphic set, you're most likely going to need to store zero to $18. In cases where sprite objects need to use different palettes or graphics sets, the value stored to $18 will vary.
In order to delete a sprite object, the instruction list of that sprite object needs to run the instruction $BD07. Otherwise, you can clear all of the sprite objects by calling the routine at $B4BD97.
|Sprite Object ID||Description||Base tile number / palette bits||Notes|
|01||Charge beam sparks||#$0000|
|02||Charge beam sparks + flash||#$0000|
|03||Super missile explosion||#$0000|
|05||Ice beam particles||#$0000|
|08||Some sort of energy shot, only 1 frame||#$0000|
|0C||Small explosion from the large enemy death AI||#$0000|
|0D||Small health pickup||#$0000|
|0E||Large health pickup||#$0000|
|10||Slower small health pickup||#$0000|
|11||Screw attack kill particles||#$0000|
|12||Large, fast dust||#$0000|
|13||Large, fast dust with a glitchy end frame||#$0000|
|14||Large, fast dust with an extended glitchy end frame||#$0000|
|15||Large, slow dust||#$0000|
|16||Glitched space pirate shot?||#$0000|
|17||Glitched space pirate shot?||#$0000|
|19||Save station electricity?||#$0000|
|1A||Slowly extending vertical shutter||#$0000|
|1B||Somewhat quickly contracting vertical shutter||#$0000|
|1C||Elevator platform, permanent||#$0000|
|1D||Large enemy explosion||#$0000|
|32||Metroid electricity||$0F96,X : ORA $0F98,X||Enemy DD7F must be allowed|
|34||Metroid shell||$0F96,X : ORA $0F98,X||Enemy DD7F must be allowed|
Within bank $B4, where the routines which create and clear sprite objects reside, you can also find the actual sprite object data. This data includes:
In order to create additional sprite objects without overwriting any data, you will need to repoint the Sprite Object pointer table. In order to do this:
At this point, all additional sprite objects and other modifications to your Sprite Object pointer table should be done to the newly repointed table. To avoid confusion, I suggest that you mark the old table as freespace by filling it with $FF
Please note that Sprite Object pointers are not called by their address in the rom, but are instead called by their index in the Sprite Object pointer table. You can find the index by dividing their offset from the start of the table by 2. For instance, a Sprite Object who's pointer is $64 bytes from the start of the table would be spawned using the index $32.
Sprite Object instructions are functionally identical to just about any other instruction formats in the rest of the rom. To put it simply, instruction lists are broken up into 4 or 3 byte chunks, containing 2 byte instructions and 1 byte arguments. These chunks follow two formats:
DW $TTTT, $PPPP ;wait $TTTT frames, then set the spritemap to $PPPP. $TTTT must be < $8000, or it will be parsed as a pointer. DW $PPPP, $AA ;run the code at $PPPP with the parameter $AA. $PPPP must be >= $8000, or it will be parsed as a frame delay.
This might seem complex at first, but in practice, it can be quite simple, especially considering that Sprite Objects rarely need to run code. This instruction list, for example, would run a simple animation:
DW $0004, $F800 ;Wait 4 frames and then set the spritemap to $F800 DW $0004, $F807 ;Wait 4 frames and then set the spritemap to $F807 DW $0002, $F80E ;Wait 2 frames and then set the spritemap to $F80E DW $0001, $F815 ;Wait 1 frame and then set the spritemap to $F815 DW $0002, $F80E ;Wait 2 frames and then set the spritemap to $F80E DW $0004, $F807 ;Wait 4 frames and then set the spritemap to $F807 DW $BD07 ;Run the code at $BD07, no argument. (This points to the 'delete Sprite Object' routine.)
Note: All relevant Sprite Object instructions can be found in PJ's bank log.
Custom instruction lists can be placed anywhere within the bank. In order to call these instruction lists with a Sprite Object, you must add a new pointer to your Sprite Object pointerlist, which points to the start of your new instruction list.
Sprite objects are not limited to single sprite tiles, but instead consist of several spritemaps, which are called by instructions and set to frame delays in order to create animated effects. In order to create visually unique sprite objects, you will need to create new spritemaps, or modify existing ones.
Within Super Metroid, spritemaps are formatted like so:
$NNNN ;Number of sprite entries within this spritemap $XX, $SS, $YY, $TT, $AA ;Sprite entry 0 $XX, $SS, $YY, $TT, $AA ;Sprite entry 1 ... ;Sprite entry... $XX ;X offset from the center of the spritemap $SS ;Size - +80 to set to 16x16, +01 to reverse the X offset. $YY ;Y offset from the center of the spritemap $TT ;Tile number. Counts from left to right, top to bottom. $AA ;Attributes. Starts at 20, +10 for layer priority, +40 for X flip, +80 for Y flip
Its important to note that spritemaps have NO terminator. Instead, the engine determines where and when to stop reading based on the initial 2 bytes of the spritemap.
Custom spritemaps for sprite objects can be placed anywhere within the bank. Assuming you really know what you're doing, you could also potentially write a custom instruction to load a spritemap from a long address, but without doing so, spritemap load instructions can only be preformed using short addresses.
Once you have assembled all of your desired spritemaps, and have written an instruction list which utilizes them, all that is left to do is add the entry to your Sprite Object Pointer table, and call it ingame. In order to add the new Sprite Object to your pointer table, simply add the address of your instruction list to the pointer table. Make note of this new pointer's location within the table, as you will need to use this index within the table to spawn your sprite object. Once again, this index can be calculated by dividing the new pointer's offset from the start of the table by 2.
In summary: Sprite Objects are driven and controlled by their Instruction Lists, which point to and animate their spritemaps.