BetterCharacterControl jittering movement 5 out of 10 game launch

Hey all!

TL;DR: My BetterCharacterControl character jitters and I don’t know why. I humbly ask you guys to cast judgement upon my snippets of code.

I have a problem where my character with a BetterCharacterControl jitters 5 out of 10 application launches. At first I thought it’s my camera that is messed up so I did everything I could read in this forum to mitigate this. Here’s a list of what I did so far:

  • Absolutely made sure that I update the camera in an AppState-s render method as @pspeed suggested in other threads.
  • I tried switching the ThreadingType of BulletAppState between SEQUENTIAL and PARALLEL but it still jitters.
  • I added interpolation to the camera movement so it’s not instantaneously follows the character.

All the above points had the following result:

  • If my camera follows the character, the camera jittering is eliminated like 90%. However the character visibly jitters.
  • If I set my camera stationary and make it continuously look at my character than my character very visibly jitters.

So I came to the conclusion that my BetterCharacterControl jitters.

I tried running the TestBetterCharacter.java test in the JMonkeyEngine repo over and over and over again but it never jitters for me despite this test has the camera update in an update method rather than a render method.

Here is a run-through of my code:

The way I initialize everything:

public class MyApp extends SimpleApplication {
    // [...]
    public void simpleInitApp() {
        getFlyByCamera().setEnabled(false);
        stateManager.attach(new BulletAppState());
        stateManager.attach(new WorldState());
    }
}

I use Artemis ODB.

public class WorldState extends AbstactAppState {
    @Override
    public void initialize(AppStateManager stateManager, Application app) {
        WorldConfiguration worldConfiguration = new WorldConfigurationBuilder()
        // [...]
                // Makes character move on mouse click
                .with(new CharacterMovementSystem())  
                // Update the camera to be x distance away from the character
                .with(new FollowCameraSystem())
                // First time the character is created it adds it to the physicsspace
                .with(new PhysicsSystem()) 
        // [...]
    }

    @Override
    public void update(float tpf) {
        world.setDelta(tpf);
        world.process();
    }
}

This is how I move my character:

@All({Transform.class, CharacterInput.class, TileTrack.class})
public class CharacterMovementSystem extends IteratingSystem {
    // [...]
    @Override
    protected void process(int entityId) {
        // [...]
        // Transform component holds reference to node
        BetterCharacterControl characterControl = transform.node.getControl(BetterCharacterControl.class);
        if (characterControl == null) return;

        boolean isOnGround = characterControl.isOnGround();
//        tmpWalkDirection.set(0f, 0f, 0f);

        if (characterInput.requestWalk) {
            // Apply force toward the direction the user wants it to move
            tmpWalkDirection
                    .set(characterInput.walkDirection)
                    .multLocal(isOnGround ? speed : speed * 0.4f);
            characterControl.setViewDirection(tmpWalkDirection);
        } else {
            // Otherwise decrease movement force until character smoothly stops
            tmpWalkDirection.multLocal(isOnGround ? 0.72f : 0.98f);
        }

        // Don't stick to walls if falling
        characterControl.getRigidBody().setFriction(isOnGround ? 0.5f : 0f);

        characterControl.setWalkDirection(tmpWalkDirection);
    }
}

This system handles camera movement.

@All({Transform.class, FollowCamera.class})
public class FollowCameraSystem extends IteratingSystem {
    // Breaks out of Artemis world update loop and run's system's update in render() method. It gets 
    //  the job done.
    // [...]
    @Override
    protected void initialize() {
        stateManager.attach(new AbstractAppState() {
            @Override
            public void render(RenderManager rm) {
                if(subscription.getEntities().isEmpty())
                    return;

                updateCamera(subscription.getEntities().get(0));
            }
        });
    }

    // This is just a regular update, I don't show the interpolation version here because this should
    //  also work smoothly.
    private void updateCamera(int entityId) {
        // This just holds a reference to the character node
        Transform transform = transformComponentMapper.get(entityId);
        // This holds reference to the camera and some settings related to it
        FollowCamera followCamera = followCameraComponentMapper.get(entityId);

        lateLocation
                .set(transform.node.getWorldTranslation())
                .addLocal(followCamera.distance); // How far should the camera be from the character
        lateLookAtOffset
                .set(transform.node.getWorldTranslation())
                .addLocal(followCamera.lookAtOffset); // How far away should we offset lookAt from character center

        // THIS is the same update as in the TestBetterCharacter.
        camera.setLocation(lateLocation);
        camera.lookAt(lateLookAtOffset, Vector3f.UNIT_Y);
    }

    @Override
    protected void process(int entityId) {} // I don't update here because this would run in update and not render
}

This system handles adding stuff to physics space

@All({Transform.class})
public class PhysicsSystem extends IteratingSystem {
    @Override
    protected void inserted(int entityId) {
        // [...]
        Node node = transformMapper.get(entityId).node;
        tmp.set(node.getLocalTranslation());
        physicsSystem.getPhysicsSpace().addAll(node);
        // Adding to physics space resets transform to 0, 0, 0 so we need to do this
        BetterCharacterControl characterControl = node.getControl(BetterCharacterControl.class);
        if(characterControl != null) {
            characterControl.warp(tmp);
        }
    }
}

This all ends up in this update loop:

  1. Physics update (BulletAppState)
  2. World update (WorldState)
  3. Camera update in render() (BreakoutState from FollowCameraSystem)

Question: what do you think what causes the jittering?

More information:

  • The floor the character moves on consist of tiles made from a RigidBodyControl with BoxCollisionShape with 0 mass making it static. As the character moves more tiles are added in front of it and removed behind it including adding removing from physicsspace.
  • Here is how my character node looks like, the BetterCharacterControl is not directly on the model but on the parent of multiple models that move together (face, body, particles).
    • Character Root Node (has BetterCharacterControl)
      • Geometry (This is body model) (has AnimComposer)
      • Geometry (This is face model) (has AnimComposer)
      • ParticleEmitter

Can you better characterize the jitter? Is it up/down while standing still like the rigid body is constantly resolving with the floor? Or is it only while moving like the camera is out of sync with the rigid body somehow?

Edit: and when you have the camera directly synced to the object is the “jitter” that the character is moving relative to the camera or that the character+camera are jittering with the world?

It only happens while moving. It shakes as it moves. When I launch the application it will either jitter all the time or won’t jitter at all until the application finishes. There is no in between meaning that if the app starts without jittering it will not start jittering later on. Also if the app starts with jittering it will not stop jittering later on.

I’m using JME 3.6.1-stable and Minie 7.7.0 btw.

Pictures worth a 1000 words. I’ve attached 3 videos. They all run on the same code. They all are recorded just after launching the application. There is one difference compared to my first post: I wrote a FollowCameraControl that similarly to CameraControl updates the cam location but in the render() method. The jittering behaviour comes out randomly when you launch the app. Sometimes I launch it 10 times and no jittering. Sometimes it jitters at every launch.

This is how it looks when it works and it is smooth:

This is how it looks when it jitters a little bit (bulletAppState debug enabled here):

This is how it looks when it jitters hard:

From my original post there is one change. I’ve read previous posts about jittering and I’ve created a FollowCameraControl that runs update in the render() method of the control. It jitters the same way as before. That’s why I believe it’s the physics/BetterCharacterControl because anything I do the character still jitters.

public class FollowCameraControl extends AbstractControl {

    private Camera camera;
    private Vector3f distance = new Vector3f(8f, 10f, 8f);
    private Vector3f lookAtOffset = new Vector3f(0f, 0f, 0f);

    public FollowCameraControl(Camera camera) {
        this.camera = camera;
    }

    @Override
    protected void controlUpdate(float tpf) {
    }

    @Override
    protected void controlRender(RenderManager rm, ViewPort vp) {
        TempVars tempVars = TempVars.get();
        tempVars.vect1
                .set(spatial.getWorldTranslation())
                .addLocal(distance);
        camera.setLocation(tempVars.vect1);

        tempVars.vect1
                .set(spatial.getWorldTranslation())
                .addLocal(lookAtOffset);

        camera.lookAt(tempVars.vect1, Vector3f.UNIT_Y);
        tempVars.release();
    }

    public void setDistance(Vector3f distance) {
        this.distance.set(distance);
    }

    public void setLookAtOffset(Vector3f lookAtOffset) {
        this.lookAtOffset.set(lookAtOffset);
    }
}
1 Like

You may need to put some debug logging in at this point.

Like a log in simpleUpdate() that logs at the start of the frame.

Then override the appropriate setLocalXXX methods on your character Node to log the position.

Then log the position when you set the camera.

Usually jitter is caused by these things being out of order… but maybe not.

If it IS caused by one being run before another you can start to work backwards and figure out why sometimes when your app starts they go in one order and sometimes another.

1 Like

I’ll do some debugging in the following days and gonna report back. This is not my first project this jittering plagues.

Does it matter where I attach the BetterCharacterControl? Right now I have a root node for my character and I put the control on it. Then I also attach some other spatials like the Geometry so the control is on the geometry’s parent.

I think it may depend on what the camera is following… but really it shouldn’t matter.

JMonkeyEngine updates controls in a deterministic order, based on where they’re located in the scene graph. If the scene graph is constructed differently, the update order will change. I suspect that’s the root of this issue. Look at where the BetterCharacter and FollowCamera controls are added to the graph and in what order.

The FollowCameraControl updates camera in the controlRender() method. Doesn’t this mean that the order they are getting added is irrelevant because BetterCharacterControl runs on the update phase in the graph and the FollowCameraControl runs in the render phase?

1 Like

First thing as pspeed suggested. Log the actual frametime. I had issues with windows that resulted in similar jitteri g. Random frame time spikes, which leads to spiky deltas

That seems correct.