Topic category: Help with Minecraft modding (Java Edition)
I’m working on a Manta Ray entity in MCreator using GeckoLib animations, and my goal is to have a looping (flip) animation that ends at −360°, then transitions seamlessly into a swim animation starting at 0°. However, every method I’ve tried—like quickly interpolating the angle, inserting a brief keyframe at 0°, or using a micro “bridge” animation—still causes a visible “flash” https://imgur.com/a/5ucjUb9 or "jump" when the rotation resets. I want a perfectly smooth motion from the flip’s final rotation to the swim’s initial rotation. If anyone has solved this in MCreator/GeckoLib, or found a better trick for handling the −360° →0° gap without a snap, I’d appreciate some advice !
P.S.- I cannot set swim to start at -360 because I would have the same issue but in reverse.
Here's the custom LoopingAnimationGoal :
class LoopingAnimationGoal extends Goal {
private final MantaRayEntity entity;
private final int cooldownTime;
private int animationTimer;
private int cooldownTimer;
// New boolean to prevent double calls
private boolean isLoopingActive = false;
public LoopingAnimationGoal(MantaRayEntity entity, int cooldownTime) {
this.entity = entity;
this.cooldownTime = cooldownTime;
this.animationTimer = 0;
this.cooldownTimer = 0;
this.setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK));
}
@Override
public boolean canUse() {
System.out.println("[DEBUG] LoopingGoal canUse => cooldownTimer=" + cooldownTimer);
if (cooldownTimer > 0) {
cooldownTimer--;
return false;
}
BlockPos entityPos = entity.blockPosition();
boolean canUse = entity.isWaterAbove(entityPos, 4);
System.out.println("[DEBUG] LoopingGoal canUse => WATER "
+ (canUse ? "DETECTED" : "NOT DETECTED")
+ " at " + entityPos + ", returning " + canUse);
return canUse;
}
@Override
public void start() {
entity.setAnimation("looping");
animationTimer = 63;
isLoopingActive = true;
System.out.println("[DEBUG] Looping animation STARTED. Timer="
+ animationTimer + ", gameTime=" + entity.level().getGameTime());
}
@Override
public boolean canContinueToUse() {
System.out.println("[DEBUG] LoopingGoal canContinueToUse => animationTimer=" + animationTimer);
return animationTimer > 0;
}
@Override
public void tick() {
animationTimer--;
System.out.println("[DEBUG] LoopingGoal TICK => animationTimer=" + animationTimer);
// We stop ONLY if we are still looping
if (animationTimer <= 0 && isLoopingActive) {
System.out.println("[DEBUG] condition => animationTimer <= 0 && isLoopingActive");
stop();
}
}
@Override
public void stop() {
// Check if already stopped
if (!isLoopingActive) {
System.out.println("[DEBUG] stop() called again, but isLoopingActive = false. Doing nothing.");
return;
}
System.out.println("[DEBUG] Looping STOP at tick=" + entity.level().getGameTime()
+ ", last known rotation=" + entity.getXRot() + "/" + entity.getYRot()
+ ", animationTimer=" + animationTimer);
// Immediately switch to "swim"
entity.setAnimation("swim");
// Reset cooldown
cooldownTimer = cooldownTime;
// Disable looping to prevent a second stop
isLoopingActive = false;
System.out.println("[DEBUG] Looping STOP => setAnimation('swim'), cooldownTimer="
+ cooldownTimer);
}
}