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:
- Physics update (BulletAppState)
- World update (WorldState)
- Camera update in render() (BreakoutState from FollowCameraSystem)
Question: what do you think what causes the jittering?