💃 Animation Systems
What are they and how they play?
Essentials
Running our newly registered animations requires an object that may combine multiple animations, transition between them and keep them in sync between server and clients.
This is where AnimationSystem
comes into play.
🪨 IAnimatedObject
Your animated object must implement IAnimatedObject
.
HammerAnimations provides you with two presets to use:
IAnimatedEntity
for entities with animation systemIAnimatedTile
for block entities (or tile entities) with animation system Implementing your own object type does mean that you're going to need to registerIObjectSource
from HammerLib.
If you're using HammerMultipart, there is PartSourceType
provide IObjectSource<PartEntity>
using PartSourceType.of(PartEntity part)
method.
Any animation system has one or more animation layers inside it, used to play and update animations.
🥬 Animation Layers
Each animation layer stores active animation, while also having some configuration on its part.
☕ Creating animation system
To create and store a new instance of AnimationSystem
for your object, create it as following:
protected final AnimationSystem animations = AnimationSystem.create(this);
To update your newly created system, you must update it every game tick.
In our example we are using TileSyncableTickable
as base, so we're going to add ticking code into update
method.
@Override
public void update()
{
super.update();
animations.tick();
}
⚙️ Configuring
To configure the animation system, our object must override setupSystem
method.
🧱 AnimationSystem.Builder
Defaults:
- canSync: true
- autoSync: false
AnimationSystem.Builder has the following methods:
disableSync()
- completely disable all synchronization of this AnimationSystem;autoSync(boolean autoSync)
- toggle if animation system should sync to clients when animation on any given layer changes. Does not matter whendisableSync()
is called;addLayers(AnimationLayer.Builder... layers)
- register multiple animation layers into the system;
🧱 AnimationLayer.Builder
AnimationLayer.Builder is created with AnimationLayer.builder(String name)
method
It is recommended to use layer names provided by CommonLayerNames
when possible!
Defaults:
- query: new Query()
- mask: none (permit all bone transforms)
- weight: 1
- blendMode: ADD
- allowAutoSync: true
- persistent: true
AnimationLayer.Builder provides following methods:
query(Query query)
- used for providing additional information to animations using formulas;mask(ILayerMask mask)
- restricts animations playing on this layer based on a bone name predicate;weight(float weight)
- adjusts the weight of all animations played on this layer;blendMode(BlendMode blendMode)
- changes how animation from this layer blends with all layers prior to current one; The options for blending are:BlendMode.OVERRIDE
- Replace bone transforms from previous layer (this also counts weights from both layer and animation, lerping the transforms) with this layer. Affects only those bones which are currently being animated.BlendMode.ADD
(default) - Adds the bone transforms on top of all previous layers. This is generally the recommended behavior to use.BlendMode.SUBTRACT
- Subtracts the bone transforms. This is kind of like setting negative weight to the layer. More of a proof of concept, but may be useful to some.
allowAutoSync(boolean allowAutoSync)
andpreventAutoSync()
- determines if this layer should cause animation system to sync when a different animation is started on this layer. Only matters if the AnimationSystem owning this layer has synchronization AND auto-sync enabledpersistent(boolean persistent)
andnonPersistent()
- determines if the animation layer should be stored to disk. This also prevents it from being synchronized when the animation system performs sync.
☕ Writing setupSystem
Here is an example of animation system which has unsynced ambient layer, as well as synced action layer:
@Override
public void setupSystem(AnimationSystem.Builder builder)
{
builder.addLayers(
AnimationLayer.builder(CommonLayerNames.AMBIENT).preventAutoSync(),
AnimationLayer.builder(CommonLayerNames.ACTION)
).autoSync();
}
✅ Complete example
package com.storyteam.storytelling.content.tiles;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import org.zeith.hammeranims.api.animsys.*;
import org.zeith.hammeranims.api.animsys.layer.AnimationLayer;
import org.zeith.hammeranims.api.tile.IAnimatedTile;
import org.zeith.hammerlib.tiles.*;
public class MyTile
extends TileSyncableTickable
implements IAnimatedTile
{
protected final AnimationSystem animations = AnimationSystem.create(this);
public MyTile(BlockEntityType<?> type, BlockPos pos, BlockState state)
{
super(type, pos, state);
}
@Override
public void update()
{
super.update();
animations.tick();
}
@Override
public void setupSystem(AnimationSystem.Builder builder)
{
builder.addLayers(
AnimationLayer.builder(CommonLayerNames.AMBIENT).preventAutoSync(),
AnimationLayer.builder(CommonLayerNames.ACTION)
).autoSync();
}
@Override
public AnimationSystem getAnimationSystem()
{
return animations;
}
@Override
public void saveAdditional(CompoundTag nbt)
{
super.saveAdditional(nbt);
nbt.put("Animations", animations.serializeNBT());
}
@Override
public void load(CompoundTag nbt)
{
super.load(nbt);
animations.deserializeNBT(nbt.getCompound("Animations"));
}
}
TileSyncableTickable
and TileSyncable
both use NBTSerializationHelper for reading&writing data.
Instead of writing saveAdditional(CompoundTag nbt)
and load(CompoundTag nbt)
methods, you can put an annotation onto the animation system field:
@NBTSerializable("Animations")
protected final AnimationSystem animations = AnimationSystem.create(this);
Now our tile is set up with animation system and is ready to run all of your wonderful animations!
⏯️ Playing animations
Now that we have our animation system, as well as animations, they can be started in a multitude of ways.
We're going to be starting them via startAnimationAt(String layer, ConfiguredAnimation|IAnimationSource animation)
method.
animations.startAnimationAt(CommonLayerNames.AMBIENT, ModAnimations.YOUR_ANIMATION_IDLE);
This is going to start the idle animation on the AnimationSystem
(you can put it into tick function).
The animation itself will not restart unless it is a different animation from currently running animation, or the ConfiguredAnimation has important
flag set to true.
If we'd like to restart or configure the animation, you're going to need to configure the animation source:
animations.startAnimationAt(CommonLayerNames.AMBIENT, ModAnimations.YOUR_ANIMATION_IDLE.configure());
There are a few calls that could be chained after .configure()
, you can see them here.