-
Notifications
You must be signed in to change notification settings - Fork 1
Sprite Handling
Procedural modification of sprites is one of the primary goals Procedurline was designed for. As such, it provides a lot of different flexible ways sprite processing can be performed.
Fundamentally, sprite (animation) processing focuses on passing sprite animations through a data processor before they get displayed. This gives processors the ability to modify all animation frames, in addition to all metadata. There are two distinct kinds of sprite processing:
-
static processing: processing with the end goal of creating a new, separate
Sprite
instance which can then be used instead of loading them from disk. This method of processing is used for e.g. custom game element sprites. -
dynamic processing: processing which targets already existing sprites with the goal of modifying what is shown on screen. They don't create new
Sprite
instances, and one can target every sprite in the game. This method of processing is used for e.g. skinmods.
Static sprite processing takes the form of custom sprites, while dynamic processing is achieved using the global sprite animation mixer (DynamicAnimationMixer
) and animation processor (DynamicAnimationProcessor
) contained in the SpriteManager
(accessed using ProcedurlineModule.SpriteManager
).
To properly track sprites, Procedurline needs to assign each sprite a so called sprite ID. Each sprite should have an ID representing what "it is", like e.g. a player sprite. This ID shouldn't be unique though - multiple instances of the same underlying sprite should have the same ID. It is then used when caching sprite animations, which are only cached for all sprites with a particular ID (this is achieved using SpriteScopeKey
s). As such, animation processors / mixers don't have to worry about accidentally polluting another sprite's animation cache when only targeting a single specific sprite ID - all sprite IDs effectively behave like they have their own animation cache.
For sprites created from SpriteBank
s, Procedurline uses the sprite ID used to create the sprite, which is also tracked across sprite clones / copies. Custom sprites can specify an arbitrary sprite ID for themselves. A sprite's sprite ID can be obtained using SpriteManager.GetSpriteID
. If a sprite doesn't have an ID, it will be ignored by Procedurline, and can't be assigned a SpriteHandler
.
SpriteAnimationData
is to Sprite.Animation
what TextureData
is to actual textures. Instances of it contain an array of TextureData
instances as frames, in addition to animation metadata. The sprite manager contains methods to convert a SpriteAnimationData
instance to and from a Sprite.Animation
.
This class is usually used for data processors which modify sprite animations. However, sometimes, one doesn't wish to create a completely new animation, which comes with additional resource usage as completely new textures have to be created. In these cases, the processors instead use Sprite.Animation
as their data class. These processors are referred to as "mixers", and they should swap out the animation reference with another Sprite.Animation
instance (or null
to signify "no animation") to effectively "mix in" another animation into the sprite.
To implement its sprite processing stack, Procedurline needs to keep track of some data per sprite. This is done using a SpriteHandler
instance, which is attached to every active sprite in the scene. These sprite handlers keep track of the current status of animation processing, cache a processor task for every animation so that the actual processor don't get invoked every time, and handle sprite animation invalidation.
Procedurline automatically attaches a sprite handler to all sprites in the scene. Sometimes though, Procedurline doesn't notice a sprite, because e.g. another entity/component is proxying for it. In this case, SpriteManager.CreateSpriteHandler
can be used to still assign a handler to this sprite. You are then responsible yourself for disposing it once the sprite isn't used anymore.
The entry point to dynamic animation processing is the DynamicAnimationMixer
, which is a CompositeAsyncDataProcessor<Sprite, string, Sprite.Animation>
. Every animation displayed by a sprite with an attached SpriteHandler
first gets passed through this animation mixer. As such, you can modify all sprite animations in the game from here. Procedurline automatically registers default scopes (including $DYNAMIC
) on all keys passing through this mixer.
Procedurline also invokes the DynamicAnimationProcessor
at order=0
, by registering a wrapped SpriteAnimationDataProcessor
. It obtains the current animation's SpriteAnimationData
, passes it through the processor, and then, if the data was modified, creates a new animation using the modified data, which is then used for all further animation mixers. This created sprite animation is separately cached in a special SpriteAnimationCache
, whose texture scope is /GLOBAL/DYNANIMCACHE
, and is accessible using the DynamicAnimationCache
field.