GeckoLib Animations: Smooth Transition from -360° to 0°?

Started by hamza_bhh on

Topic category: Help with Minecraft modding (Java Edition)

Joined Jan 2025
Points:

User statistics:

  • Modifications:
  • Forum topics:
  • Wiki pages:
  • MCreator plugins:
  • Comments:
GeckoLib Animations: Smooth Transition from -360° to 0°?

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);
    }
}