-
Notifications
You must be signed in to change notification settings - Fork 0
Advanced Usage
loop(n) plays the animation n + 1 times total, and loopInfinite() loops forever.
Combine with onLoop for periodic effects, like a pulsing effect that plays a sound.
.loop(2) // plays 3 times total
.loopInfinite() // plays forever, until stoppedHook into the animation lifecycle with onStart, onEnd, onLoop, and
onKeyframeReached(time, callback). Useful for triggering sounds, particles, or
other logic at specific points:
.onStart(e -> doSomething()) // runs only on the start FIRST loop
.onKeyframeReached(0.5f, e -> level.playLocalSound(...)) // runs each loop
.onLoop(e -> doSomething()) // does NOT run on the start of the first loop, or when reaching the end
.onEnd(e -> doSomethingElse()) // runs only on the start of the LAST loop (or when the animation is manually ended)Animations can be paused, resumed, reversed, or sped up/slowed down at any time:
vfxEntity.pauseAnimation();
vfxEntity.resumeAnimation();
vfxEntity.setPlaySpeed(-1f); // reverses the animation
vfxEntity.setPlaySpeed(2f);Calling setPlaySpeed with a negative value reverses direction starting from wherever the
animation currently is, not from the end.
Reversing with a queue: reversing the currently playing animation works fine, but reversing back through a queue is not supported, since inheritance only flows forward. If you need a "play this in reverse" sequence, build the reverse stages explicitly rather than trying to reverse a forward queue.
When animations are queued, the next animation can inherit properties from wherever the
previous one left off, avoiding hardcoded start values and snapping. Just call the
relevant inherit* method instead of declaring a starting value:
// stage 1: slam down and squish
VfxAnimation drop = VfxAnimationBuilder.create()
.blockState(Blocks.ANVIL.defaultBlockState(), builder -> {})
.scale(1f, builder -> {})
.translation(0, 5, 0, builder -> builder
.addKeyframe(1f, 0, 0, 0, EasingType.IN_QUART))
.build(20);
// stage 2: squish on impact, inheriting the block state, and where it landed
VfxAnimation squish = VfxAnimationBuilder.create()
.inheritBlockState()
.inheritTranslation()
.scale(1f, builder -> builder
.addKeyframe(0.3f, 2f, 0.2f, 2f, EasingType.OUT_EXPO)
.addKeyframe(1f, 1f, 1f, 1f, EasingType.OUT_BOUNCE))
.build(30);Available for translation, scale, rotation, overlay color, overlay intensity, block state,
and item stack. You can also use inheritAll() to inherit every property.
For motion that isn't easily expressed as keyframes - like orbiting, wobble, or constant spin -
use onFrame*. These run after the keyframed value is evaluated each frame, so they
combine with whatever the channel is already doing:
.onFrameTranslation((translation, ctx) -> {
float angle = ctx.interpolatedTicks() * 0.1f;
translation.x += (float) Math.cos(angle) * radius;
translation.z += (float) Math.sin(angle) * radius;
})Always scale by interpolatedTicks(), not a raw tick counter. This keeps motion
speed consistent regardless of framerate.
Available for translation, scale, rotation, overlay color, and overlay intensity
(onFrameTranslation, onFrameScale, onFrameRotation, onFrameOverlayColor,
onFrameOverlayIntensity).
Overlays tint a block with a color and intensity, layered on top of the block's normal rendering. Like translation/scale, overlay color and intensity are interpolated channels — they keyframe and blend smoothly:
.overlay(1f, 0f, 0f, 0f, o -> o // start: red, 0 intensity (invisible)
.addColorKeyframe(0.25f, new Vector3f(1f, 0f, 0f))
.addIntensityKeyframe(0.25f, 0.8f, EasingType.OUT_QUAD)
.addColorKeyframe(0.5f, new Vector3f(0f, 1f, 0f))
.addColorKeyframe(0.75f, new Vector3f(0f, 0f, 1f))
.addIntensityKeyframe(1f, 0f, EasingType.IN_QUAD))Intensity controls how opaque the tint is - 0 is invisible, 1 is fully opaque.
Overlays are commonly timed alongside block state keyframes to mask the instant snap
between block types (see Block State & Item Stack Channels)
with what reads as a smooth color transition.
Items are not currently supported by overlays. Overlay tinting only works on the block state channel.
By default, rotation pivots around the center of the block/item, using a pivot of (0.5, 0.5, 0.5). You can change this
with rotationPivot:
.rotationPivot(0, 0, 0) // pivot around a corner instead of the centerThis is useful for effects like a door swinging on a hinge, or anything that should rotate around a point other than its own center.
Bind an entity to follow another entity, optionally with an offset in local or global space:
VfxEntity trail = VfxEntity.createBoundTo(level, projectile, new Vector3f(), /* localSpace */ true);
trail.setOnBoundEntityRemoved(VfxEntity::stopAnimations);localSpace = true rotates the offset along with the bound entity's facing direction.
Useful for things like weapon trails or effects that should stay oriented relative
to the entity they're attached to. false keeps the offset fixed in world space.
setOnBoundEntityRemoved lets you react when the bound entity dies or is removed.
Commonly used to stop or transition the VFX entity's animation at that point.
Since VoxelFX is client-only, you may want to create a purely visual projectile or something, without having access to an entity that was initialized from the server. This can be done by creating an entity, and rather than adding it to the client level, manually tick it. See
VfXDemosorNovaBombDemofor how this is done.
You can manually capture an entity's current rendered state. Useful when building a follow-up animation that needs to branch off from wherever the entity currently is, outside of the normal queue/inheritance flow:
VfxSnapshot snap = entity.captureCurrentSnapshot();This is how effects like shard fly-outs in NovaBombDemo work — capturing the exact
position of an orbiting entity at the moment of detonation, then building a new
animation that starts from that captured position.
The VfxUtils class provides some commonly used math stuff, like forEachPointOnSphere, or options for getting random values. Use these to help reduce boilerplate.
There is also the ClientTaskScheduler which can be used to schedule tasks that run on the client tick event. Useful for more complex effects. NovaBombDemo uses task scheduling a decent bit.
Lastly, the EffectPresets class has (currently only 1) presets that can easily be customized, for common effects like shockwaves. Simply define the effect's config and let it do the work for you.
-
Culling: VFX entities all have culling disabled by default. You can reenable culling by calling
setAffectedByCulling(true)on the entity. It may not be desirable for fast-moving or large-scale effects. If you want culling enabled but with a larger bounding box (rather than fully disabling it), usesetCullingRadius(radius)to inflate the box used for the culling check. -
Persistence: VFX entities despawn automatically once their animation (and queue) finishes. Use
setInfinitePersist(true)for entities you intend to control manually forever (e.g. bound to a projectile, or polled inonTick), and remember to calldiscard()orstopAnimations()on them yourself when done. You can also control how long an entity lingers after its animation completes withsetTicksToPersist(ticks). -
onTickvs per-frame modifiers:entity.setOnTick(...)runs every tick regardless of whether an animation is playing, and is the right place for logic like polling a bound entity's state. Per-frame modifiers (onFrameTranslation, etc.) are scoped to a single animation and only run while that animation is active. -
Overlay limitations: overlays don't currently support items, only block states.
-
Reversing with a queue: see the note under Playback Control above.
-
BlockState / ItemStack channels are discrete, not interpolated: see Block State & Item Stack Channels.