-
Notifications
You must be signed in to change notification settings - Fork 13
Necromancer
Veil includes an animation API, named Necromancer. Necromancer provides a framework for models and animations, with a focus on a procedural workflow.
Skeletons are the basis of Necromancer, acting as a container for Bone
s. Skeletons do very little on their own - they require a Skin
to be rendered, and an Animator
to be puppeteered.
Skeletons only have their animation data calculated each tick, and the bones automatically interpolate between these values. Support is planned for variable update times, depending on entity distance and performance.
Skeletons may have their bones initialized in the constructor, though they may also be added dynamically if needed.
The buildRoots()
function should be called after adding bones!
An example Skeleton class:
public class ExampleSkeleton extends Skeleton<ExampleEntity> {
protected final Bone Head, Body, LeftLeg, RightLeg;
public ExampleSkeleton(ExampleEntity parent) {
super();
this.Body = new Bone("Body");
this.Body.setInitialTransform(0F, 16F, 0F, new Quaternionf().rotationZYX(0F, 0F, 0F));
this.addBone(Body);
this.Head = new Bone("Head");
this.Head.setInitialTransform(0F, 8F, 0F, new Quaternionf().rotationZYX(0F, 0F, 0F));
this.addBone(Head);
this.LeftLeg = new Bone("LeftLeg");
this.LeftLeg.setInitialTransform(-8F, -8F, 0F, new Quaternionf().rotationZYX(1.5F, 0F, 0F));
this.addBone(LeftLeg);
this.RightLeg = new Bone("RightLeg");
this.RightLeg.setInitialTransform(8F, 8F, 0F, new Quaternionf().rotationZYX(1.5F, 0F, 0F));
this.addBone(RightLeg);
this.Body.addChild(Head);
this.Body.addChild(RightLeg);
this.Body.addChild(LeftLeg);
this.buildRoots();
}
}
An Animator
puppeteers a Skeleton, updating the transformations of bones and the like. It does this through its animate()
function, as well as applying given animations and constraints each tick.
An Animation
applies a transformation to the bones of a skeleton, using its apply()
method. Animations should be statically created, and shared between animators which utilize them. An animation can be provided to an animator using animator.addAnimation(animation, priority)
or animator.addTimedAnimation(animation, priority, length)
. An animation is designed to work procedurally, with custom animations generating their transformations via code.
A subclass of Animation
, which transforms a Skeleton
based off keyframes. These are currently a work-in-progress!
Used by the Animator to control attributes of an Animation before applying it to the Skeleton, such as an animation's time or blend factor. Returned by animator.addAnimation(animation, priority)
. The entry's time is not automatically updated, and must be set by the Animator. For entries with a given length and automatically updated time, see TimedAnimationEntry
.
A subclass of AnimationEntry
, these are used to execute one-shot animations with a known length, such as attacks. Returned by animator.addTimedAnimation(animation, priority, length)
. A TimedAnimationEntry's time variable is automatically updated.
Constraints are applied by an Animator after any Animations, and adjust the pose in order to satisfy them. Currently, these are a work-in-progress, and no constraints are implemented by the library.
public class ExampleAnimator extends Animator<ExampleEntity, ExampleSkeleton> {
final AnimationEntry<ExampleEntity, ExampleSkeleton> walk;
public ExampleAnimator(ExampleEntity entity, ExampleSkeleton skeleton) {
this.walk = this.addAnimation(WalkAnimation.INSTANCE, 0);
}
public void animate(ExampleEntity entity) {
super.animate(entity);
// walk
WalkAnimation walkState = entity.walkAnimation;
this.walk.setTime(walkState.position());
this.walk.setMixFactor(walkState.speed());
// idle
skeleton.Body.y += Mth.sin(entity.tickCount * 0.05F) * 2;
}
static class WalkAnimation extends Animation<ExampleEntity, ExampleSkeleton> {
static final INSTANCE = new WalkAnimation();
public void apply(ExampleEntity entity, ExampleSkeleton skeleton, float blendFactor, float time) {
skeleton.LeftLeg .rotateDeg(45 * Mth.sin(time) * blendFactor, Direction.Axis.Z);
skeleton.RightLeg.rotateDeg(45 * -Mth.cos(time) * blendFactor, Direction.Axis.Z);
}
}
}
Skins are laid atop Skeletons, and are what is actually drawn to the screen. Currently, they contain a list of Meshes and the identifier of their associated Bone, which they inherit the transform of.
SkeletonParent
is an interface, providing functionality for attaching a skeleton to an Entity (support for block entities is also planned.) When implemented by an entity, Necromancer will automatically attach a Skeleton and Animator, generated using the factories specified in the entity's NecromancerEntityRenderer.
An extension of vanilla's EntityRenderer
, built for entities implementing SkeletonParent. This automatically handles attaching Skeletons and Animators to entities, drawing the Skeleton through a provided Skin, and entity render layers.
An equivalent of vanilla's EntityRenderLayer
, for entities implementing SkeletonParent. Should be added to a NecromancerEntityRenderer in its constructor, using the NecromancerEntityRenderer.addLayer(layer)
method.