-
Notifications
You must be signed in to change notification settings - Fork 0
Getting Started
VoxelFX is published via JitPack. Add the repository and dependency to your build.gradle:
repositories {
maven { url = 'https://jitpack.io' }
}
dependencies {
implementation 'com.github.StainlessStasis:VoxelFX:MOD_VERSION-MINECRAFT_VERSION'
}Replace the placeholders with their respective values. E.g. 1.0.0-26.1.2.
Call VfxAnimationBuilder.create() to get a new builder, then chain methods to set properties of the animation. For example, to create an infinitely looping, spinning block:
VfxAnimation animation = VfxAnimationBuilder.create()
.blockState(Blocks.DIAMOND_BLOCK.defaultBlockState(), builder -> {})
.scale(0.5f, builder -> {}) // constantly half scale - no additional keyframes
.translation(builder -> builder
.addKeyframe(0.5f, 0, 4, 0, EasingType.LINEAR) // move up 4 blocks
.addKeyframe(1f, 0, 0, 0, EasingType.LINEAR)) // move back down
.loopInfinite()
.build(80); // animation length defined in ticksFor more examples, see VfxDemos and NovaBombDemo.
Animations are played from a VfxEntity, which is obtained via VfxEntity.create(level, position). Once you create the entity, you don't need to set its position or do anything else.
Do not add the entity to the level! VFX entities are automatically added to a cache (VfxEntityCache) and ticked/rendered there. Adding the entity to the level can break things or even cause crashes.
To play an animation, simply call playAnimation(animation). You can also use playAnimation(animation, progressOffset) to start it mid-animation, where the offset is from 0-1. There is also playAnimationWithOffset which accepts seconds (float) or ticks (int).
These methods will immediately start the animation, overriding the currently playing one. To queue animations so that they play one after another, use playOrQueueAnimation(animation). Play two animations in sequence like so:
VfxAnimation animation1 = VfxAnimationBuilder.create()
.blockState(Blocks.DIAMOND_BLOCK.defaultBlockState(), builder -> {})
.scale(0.5f, builder -> {})
.translation(builder -> builder
.addKeyframe(0.5f, 0, 4, 0, EasingType.LINEAR)
.addKeyframe(1f, 0, 0, 0, EasingType.LINEAR))
// removed the infinite loop here
.build(80);
VfxAnimation animation2 = VfxAnimationBuilder.create()
.blockState(Blocks.NETHERITE_BLOCK.defaultBlockState(), builder -> {})
.scale(0.5f, builder -> builder
.addKeyframe(0.5f, 1f, EasingType.LINEAR)) // scale to 1x (normal) size half way through
.translation(builder -> builder
.addKeyframe(0.5f, 0, 4, 0, EasingType.LINEAR)
.addKeyframe(1f, 0, 0, 0, EasingType.LINEAR))
.build(80);
VfxEntity vfxEntity = VfxEntity.create(level, pos);
vfxEntity.playOrQueueAnimation(animation1);
vfxEntity.playOrQueueAnimation(animation2);loop(n) plays the animation n + 1 times total, loopInfinite() loops forever.
Combine with onLoop for periodic effects, like a pulsing effect that plays a sound.
Hook into the animation lifecycle with onStart, onEnd, onLoop, and
onKeyframeReached(time, callback). Useful for triggering sounds, particles, or
other logic at specific points:
.onKeyframeReached(0.5f, e -> level.playLocalSound(...))
.onLoop(e -> doSomething())
.onEnd(e -> doSomethingElse())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);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:
VfxAnimation squish = VfxAnimationBuilder.create()
.inheritTranslation()
.inheritBlockState()
.scale(1f, s -> s
.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().
For motion that isn't easily expressed as keyframes - orbiting, wobble, 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.
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);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();-
Culling: VFX entities all have culling disabled by default. You can reenable culling by calling
setAffectedByCullingon the entity, but be warned - it may not be desirable. -
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 todiscard()orstopAnimations()them yourself when done. You can also set the ticks to persist withsetTicksToPersist. -
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.
-
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.