- Know the basics / intermediate stuff of ASM.
Required: Know how LDA/STA/RTS/CLC/ADC/SEC/SBC/INC/DEC/CMP/basic branch commands & jump commands/stack/indexing work.
- Know how to do regular spriting (not a requirement, but it would help greatly)
- Have the patience to learn this type of spriting and to insert the sprites through a patch.
1. What exactly are the possibilities of cluster sprites?
Remember SMW's reappearing ghosts? The fact that there can be up to 8 1UPs in the bonus room? That you can place 2 Boo rings, and they all move seperately? The Sumo brother flames?
These are all cluster sprites. The main advantages of cluster sprites is that there can be up to 20 (!) different cluster sprites on the same screen, as opposed to 12 at max for regular sprites. Furthermore, cluster sprites cause minimal slowdown. Having 20 of them won't or will barely generate slowdown, whereas putting more than 6 regular sprites usually already generates a lot of slowdown.
The feature of cluster sprites had been discovered by Roy, 26th-27th August 2009. So, by the time of writing this, it is indeed relatively new.
2. So, how do I create and insert these cluster sprites?
Well, first you need the following things:
- Like stated before, ASM knowledge.
- Custom Cluster Sprite patch, can be found in the Patches section.
- Sprite Tool (optional, but for the method we're going to use now, a requirement)
As you might've guessed by now, cluster sprites are in NO MEANS regular sprites.
You cannot insert them through Lunar Magic, for example. You need to generate them with ASM routines - right now, we're going to use a custom sprite's init code to do just that.
In that init, you can generate up to 20 of them cluster sprites. Of course, you don't HAVE to generate 20. It can be any arbitrary number between 0 (why would you want that, though) and 20.
I'll explain how to do that, but first let me mention the most important tables regarding cluster sprites:
$1892-$18A5 : Cluster sprite type. This basically functions as the sprite number for your cluster sprite.
Setting it to 00 means no cluster sprite for that slot. Values 01-08 are the regular cluster sprites, used in SMW. (Check the RAM map for more information on those.)
Values 09-88 (could be expanded easily on a later patch, but I thought 128 slots was enough) are slots which you could use for the custom cluster sprites.
Values 89-FF are actually invalid at the time of writing this, but they probably are a copy of 09-7F. (Since I made the patch work like that.) Basically there's no reason to use them though.
$1E02-$1E15 : A free cluster sprite table. Basically, you can use this table for just about anything.
SMW's cluster sprites and spike hell use it as Y low position.
We will use it as Y position here too, but you're not restricted to do it within your own sprites.
The rest of the tables I'll mention are also free.
$1E16-$1E29 : Another free table. Often used as X low position.
$1E2A-$1E3D : Another free table. Often used as Y high position.
$1E3E-$1E51 : Another free table. Often used as X high position.
$1E52-$1E65 : Another free table. Used for a lot of things. Which things exactly? Check the RAM map.
$1E66-$1E79 : See $1E52-$1E65.
$1E7A-$1E8D : See $1E52-$1E65.
$1E8E-$1EA1 : See $1E52-$1E65.
As you can see, there aren't many "predefined" tables. No ground interaction, no X or Y speed, heck, not even X or Y position have predefined RAM tables.
You all have to set it up yourself per sprite.
The generating sprite.
The generating sprite is the one you insert through Sprite Tool.
We'll take the spike_hell.asm from the cluster sprite patch:
!SpikeCount = $13 ; Amount of spikes to fall down, -1. Values outside of 00-13 are not recommended. ;--------------; ; Init Routine ; ;--------------; print "INIT ",pc PHY ; \ Wrapper. PHB ; | PHK ; | PLB ; / LDY #!SpikeCount Loop: LDA #$09 ; \ Custom cluster sprite 09. STA $1892,y ; / LDA InitXY,y ; \ Initial X and Y position of each spike. PHA ; | Is relative to screen border. AND #$F0 ; | STA $1E16,y ; | Store into X pos. PLA ; | ASL ; | ASL ; | ASL ; | ASL ; | STA $1E02,y ; | Store into Y pos. DEY ; | Loop until all slots are done. BPL Loop ; / LDA #$01 ; \ Run cluster sprite routine. STA $18B8 ; / PLB ; PLY ; RTL ; Return. InitXY: db $06,$45,$9E,$E2,$A7,$BC,$59,$40,$61,$F5,$D6,$24,$7B,$33,$C6,$0B,$00,$39,$70,$A1 ; Initial X and Y position table of sprites. ; Relative to screen border. ; Format: $xy ;--------------; ; Main Routine ; ;--------------; print "MAIN ",pc RTL
We only want to generate each spike once, right? Therefore, we should only be putting this code in the INIT. Leave the MAIN code as-is.
As you can see in this sprite, we're using a loop. This is handy, because the INIT only runs once, and we want to generate all cluster sprites at the same time.
Rather important note: Cluster sprites ONLY HAVE A MAIN ROUTINE, so if you want to put something initial for them, do it inside this generating sprite, like I did with initial X and Y position.
So we LDY #SpikeCount. #SpikeCount is the amount of spikes to generate - 1, which in this case is set to be #$13 ($14, thus 20 sprites).
LDA #$09 STA $1892,y sets the sprite number to the cluster sprite table. After my explaination on $1892,y, this should be obvious by now. Change #$09 to the number you inserted the cluster sprite as. (You can see it in the label inside the patch 'ClusterX', where X = cluster sprite number.)
The routine after that simply stores the values from a table to the cluster sprite's X and Y positions. I made the routine work like this to compress the table as much as possible. Saves another few bytes, I guess.
Now we keep running that code for each cluster sprite that is going to be generated, until the loop is done.
Last, but certainly not least, LDA #$01 STA $18B8 enables cluster sprites to be processed. If you do not set this, then the cluster sprites will simply not show up. So it's very important to keep in mind.
The actual cluster sprite. (Patch)
An important note is that cluster sprites use the Y index for their tables inside the custom cluster sprite patch. So do : LDA $1E02,y, NOT LDA $1E02,x.
So, like with normal sprites, do not touch this index, unless you really have to. (In that case, preserve Y.)
Every cluster sprite is different. You can tell that from normal custom sprites already - for example, Maxx's Newbie Boss is in no means similar to edit1754's dynamic Ptooie.
But what most sprites (and thus also cluster sprites) share is:
- Graphics. (OAM.)
These three things cover what I'm going to explain now. They're basically the fundament for what we would call a sprite. (There are exceptions to this, of course.)
How to set up movement, graphics and interaction:
You will need to create X and Y position routines. (Eventually X and Y speed, I'll explain that in a bit.)
These X and Y position registers should affect graphics and interaction too, of course. For example, if the sprite's Y position (relative to the screen border) is $10, then the graphical Y position should also be $10, and not something like $B8.
So, how exactly will you set these up? It's quite simple really.
Let's use $1E02-$1E15 for setting the Y position up, and $1E16-$1E29 for the X position.
LDA $1E02,y ; \ Move cluster sprites 2 pixels per frame. CLC ; | ADC #$02 ; | STA $1E02,y ; /
This will make all cluster sprites of the same type move with speed = 2 pixels per frame. But what if you want to move each single cluster sprite with a different speed? It's quite simple - you read from a table instead:
SpeedTable: db $01,$02,$FF,$FE,$F9,$05,$01,$03,$06,$FD,$04,$02,$FE,$FE,$01,$08,$FA,$FC,$03,$04 LDA $1E02,y ; \ Move cluster sprites seperately. x pixels per frame, depending on the table. CLC ; | ADC.w SpeedTable,y ; | Different speed for each cluster sprite slot. STA $1E02,y ; /
In the same way, you could affect $1E16,y or in fact any table, if you want to do so.
So, now you know how to increment $1E02,y for all cluster sprites of that type, or for each slot seperately. But does this make it an Y position RAM address yet? No! You still need to make it store it's Y position (relative to screen border) to OAM, and if you want to make it interact - which you usually do - you will need it in your interaction routines.
Here's how you do it in the graphics routine. It's actually rather simple. From Spike Hell:
OAMStuff: db $40,$44,$48,$4C,$D0,$D4,$D8,$DC,$80,$84,$88,$8C,$B0,$B4,$B8,$BC,$C0,$C4,$C8,$CC LDX.w OAMStuff,y ; Get OAM index for each cluster sprite. LDA $1E02,y ; \ Copy Y position relative to screen Y to OAM Y. SEC ; | SBC $1C ; | STA $0201,x ; / LDA $1E16,y ; \ Copy X position relative to screen X to OAM X. SEC ; | SBC $1A ; | STA $0200,x ; /
Comments should be obvious enough. You subtract $1C from $1E02,y and $1A from $1E16,y to get Y and X positions relative to the screen border.
It's recommendable to make it relative to the screen border - otherwise you might get some weird result.
$0200,x and $0201,x are the X and Y position of the OAM tile. We use the $02xx area here because that's a trend extended sprites usually follow anyway. On top of that, there's usually much more space you can use in this area.
LDX.w OAMStuff,y gets each cluster sprite's OAM slot. This is a rather messy way to do it, but it's effective for most OAM slots.
You really have to go and look for free OAM slots, though - I did that for spike hell. Usually those slots aren't used, not even by extended sprites.
So far I found a few score sprites that use it, though.
Making a routine that gets a free OAM slot out of the blue is a little bit more complicated, since there's no such thing as an OAM index RAM table for cluster sprites, unlike regular sprites. That doesn't mean it's impossible, though.
I may add some more on this part in the future, if I - or someone else - find out how to do it, without having side-effects. (A simple loop won't work, for example.)
Now we're busy with the OAM routine anyway, let's finish it.
LDA #$E0 ; \ Tile = #$E0. STA $0202,x ; / (Spike.) LDA Properties,y ; \ Properties per spike, some are rising so this needs a seperate table. STA $0203,x ; / PHX TXA LSR LSR TAX LDA #$02 STA $0420,x PLX
If you know how to work with OAM in normal spriting, this should be easy to follow. We use tile E0 -> that goes to $0202,x, which holds the tile value.
Then, we have the Properties table. It doesn't need to be a table, but for Spike Hell, I made it a table, because some of these sprites are upside down. If you want to make them all use the same properties, just use a direct value like:
LDA #$35 STA $0203,y
Then we store the tile size to $0420,x. 00 = 8x8, 02 = 16x16. "$0420,x?", you might think. "Most custom sprites use $0460,x, don't they?" Yes, that's true. That's because they use OAM $0300-$03FF, so they will need the second half ($0460-$049F) of the tile size table. $0200-$02FF will thus require $0420-$045F.
And that's all regarding OAM.
Interaction is probably the most difficult part for any sprite - cluster sprites are no exception. They don't have clipping values or anything like that, so you all need to do it "manually".
Quoting the following routine directly from Spike Hell:
!YInteractSmOrDu = $20 ; How many pixels to interact with small or ducking Mario, vertically. !YInteractSmNorDu = $30 ; How many pixels to interact with powerup Mario (not ducking), vertically. LDA $94 ; \ Sprite <-> Mario collision routine starts here. SEC ; | X collision = #$18 pixels. (#$0C left, #$0C right.) SBC $1E16,y ; | CLC ; | ADC #$0C ; | CMP #$18 ; | BCS Immobile ; / LDA #!YInteractSmOrDu ; Y collision routine starting here. LDX $73 BNE StoreToNill LDX $19 BEQ StoreToNill LDA #!YInteractSmNorDu StoreToNill: STA $00 LDA $96 SEC SBC $1E02,y CLC ADC #$20 CMP $00 BCS Immobile JSL $00F5B7 ; Hurt Mario if sprite is interacting. Immobile: ; OAM routine starts here.
This is typically what a Mario <-> cluster sprite interaction routine would look like. Optionally you could add a LDA $187A (on Yoshi) check in, but I decided it was not necessary in Spike Hell.
What the first part does is simple. It subtracts the cluster sprite X position from Mario's X position (low).
Then it adds #$0C to the result and compares it to #$18. If the result is equal to or higher than #$18, it means Mario is not interacting with the sprite.
The CLC ADC #$0C CMP #$18 means that the sprite is supposed to have an interaction field of $0C pixels on both left and right.
The Y interaction routine is a little bit more complicated, but should certainly not be too hard to follow.
By default, it loads the value for when Mario is small or ducking (#$20).
This is because the interaction field is 2 blocks. (Small / Ducking Mario = 1 block + the spike = 1 block.)
If he's neither, then the other value will be loaded (#$30).
This is because the interaction field is 3 blocks. (Big Mario = 2 blocks, the spike = 1 block.)
We then subtract cluster sprite Y position from Mario's Y low position, add #$20 and then check with either #$20 or #$30, depending on if Mario was either small or ducking, or neither one of them.
If the result was equal to or bigger than the comparison, Mario is not interacting with the sprite.
However, if it was smaller, then Mario was interacting with the sprite.
Here is where we throw JSL $00F5B7 in - a routine to hurt Mario.
Alternatively, you could use JSL $00F606 to kill Mario.
To kill the sprite instead, use LDA #$00 STA $1892,y. (This will make him disappear instantly.)
Say you want to get a 1UP when collecting the sprite. How do you do it? It's rather simple.
LDA #$00 STA $1892,y PHY TYX JSL $02AD34 LDA #$0D STA $16E1,y LDA $1E02,x SEC SBC #$08 STA $16E7,y LDA $97 STA $16F9,y LDA $1E16,x STA $16ED,y LDA $95 STA $16F3,y LDA #$30 STA $16FF,y PLY
At LDA #$00 STA $1892,y, we kill the sprite.
Then, we preserve Y and get the cluster sprite index into X, because we need to have it set in X for the score sprite set-up routine.
When we prepared an index for the score sprite, we store the appropiate values.
Score sprite type = #$0D. (1UP. Higher values for more lifes or a mess, lower values for score additions.)
Make Y position 8 pixels higher than the cluster sprite's Y position. ($16E7,y.)
Since this cluster sprite doesn't have a high Y position, just make the Y high relate to Mario's Y high. ($16F9,y.)
The same method is used with X position, with the only difference that the X coördinate is the same for the score sprite as it is for the cluster sprite.
Then we set up a speed (#$30), and pull Y back. There - we just made a collectable 1UP cluster sprite.
Say, you want to make the cluster sprite hurt you when you're underneath it, but when you're on top of it, you can stomp on it and bounce off the top. (Eventually killing the sprite.)
How do you do this? Well, it's not very hard.
The interaction routine will look like this:
LDA $94 ; \ Sprite <-> Mario collision routine starts here. SEC ; | X collision = #$18 pixels. (#$0C left, #$0C right.) SBC $1E16,y ; | CLC ; | ADC #$0C ; | CMP #$18 ; | BCS ImmoJump ; / LDA #!YInteractSmOrDu ; Y collision routine starting here. LDX $73 BNE StoreToNill LDX $19 BEQ StoreToNill LDA #!YInteractSmNorDu StoreToNill: STA $00 LDA $96 SEC SBC $1E02,y CLC ADC #$20 CMP $00 BCS ImmoJump LDA $7D ; \ If Mario falling, kill sprite. CMP #$10 ; | BPL KillSpriteInstead ; / JSL $00F5B7 ImmoJump: JMP Immobile KillSpriteInstead: LDA #$00 STA $1892,y JSL $01AA33 JSL $01AB99 PHY TYX JSL $02AD34 LDA #$05 STA $16E1,y LDA $1E02,x SEC SBC #$08 STA $16E7,y LDA $97 STA $16F9,y LDA $1E16,x STA $16ED,y LDA $95 STA $16F3,y LDA #$30 STA $16FF,y PLY Immobile:
The first part until ADC #$20 CMP $00 is no different from the original routine, so that part has already been explained.
We don't need to do it again, I just included it so you would see the complete picture.
Next, we check if Mario has a reasonable falling speed. If that is not the case, hurt Mario.
However, if he is falling at a passable rate, go to the routine that will eventually kill the sprite.
We see this at LDA #$00 STA $1892,y - erasing the cluster sprite. JSL $01AA33 makes Mario boost up, JSL $01AB99 will display contact graphics.
Next, we see the score sprite generating routine again. This is nothing new, except for the fact we're using a different value now - #$05.
This will add up 100 points to your total.
There are many, many more codes I could add up to this list, like how to only make it die on spin-jump or star kill.
However, I want to make this one the last section: each cluster sprite having a different kind of interaction.
Let's take Spike Hell as example. Say, you want to make the falling spikes vulnerable to any kind of jump, but the rising spikes should be completely invincible.
How do you do that? Quite simple.
You add up a table somewhere at the beginning of the sprite, which consists of 20 bytes - 1 byte per slot.
It could be done with a lesser amount of bytes, but for the sake of simplicity - hey, this is a tutorial after all - let's keep it at 20.
Invincible: ; These two tables should be at the top of the sprite. Running tables is a bad idea. >_> <_< db $01,$00,$00,$00,$00,$01,$00,$00,$00,$00,$00,$00,$01,$00,$00,$00,$00,$00,$00,$00 Properties: db $B5,$35,$35,$35,$35,$B5,$35,$35,$35,$35,$35,$35,$B5,$35,$35,$35,$35,$35,$35,$35 ; Properties table, per sprite. YXPPCCCT. ;etc. CLC ADC #$20 CMP $00 BCS ImmoJump LDA $7D CMP #$10 BPL KillSpriteInstead HurtMarioAnyway: JSL $00F5B7 ImmoJump: JMP Immobile KillSpriteInstead: LDA.w Invincible,y ; \ If the value is not zero, spike is a rising spike. BNE HurtMarioAnyway ; / Hurt Mario in that case. LDA #$00 STA $1892,y ;etc.
Properties: was a table which was already in there.
By that table, we found out which spikes were rising ($B5 -> Y flip bit set.), and which ones were not. ($35 -> Y flip bit clear.)
For rising spikes, we set each value to $01, and for falling spikes, we set each value to $00.
Now when we read from that table, Mario will be hurt if the table value is $01 (rising), but the sprite will be killed if the table value is $00 (falling).
That's all for interaction. I believe I have said enough. Too much, if you will.
Other random, but rather handy codes:
Cluster sprites stop reappearing at generator 2 or level end.
Put the following code after the OAM routine:
LDA $18BF ; \ If generator 2 is active... ORA $1493 ; / ...or level is ending... BEQ ReturnToTheRTSCommandMyChocolate ; Change BEQ to BRA if you don't want it to disappear at generator 2, sprite D2. LDA $0201,x CMP #$F0 ; As soon as the spike is off-screen... BCC ReturnToTheRTSCommandMyChocolate LDA #$00 ; KILL IT! KILL IT!!. STA $1892,y ;
More to follow maybe.
tl;dr This was the cluster spriting tutorial. I hope you enjoyed reading through it, even if not completely.
If you know any part which I could give a touch-up, please do tell.
I myself plan to add more to this tutorial in the future, when I get more experienced with cluster spriting.
At the time of writing, cluster spriting is still not very much utilized, so there may be a lot to come yet.
Also, I and several other staff members have agreed on the fact that cluster sprites may be submitted to the sprite section. If you have anything to show - don't keep it to yourself, submit it!