Creating a 'top view' ChaseCamera

I’m experimenting for a simple game here. What I want to do is this: show a floor with some blocks (maze) on it. The player sees the maze from above (e.g. looking down the Y axis) and the camera follows the movements across the x,z plane with a ChaseCamera.



Here’s what I have now:







The floor is shown in blue, the player in red.



I have a few questions:


  • How can I get the camera in the right position? It already follows the player, but across the x,y-plane.
  • The floor appears to be shaking - can this be caused by the AnalogListener I use?



    My code:



    [java]package nl.ariejan.plexor;



    import com.jme3.app.SimpleApplication;

    import com.jme3.bullet.BulletAppState;

    import com.jme3.bullet.collision.shapes.BoxCollisionShape;

    import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;

    import com.jme3.bullet.nodes.PhysicsCharacterNode;

    import com.jme3.bullet.nodes.PhysicsNode;

    import com.jme3.input.KeyInput;

    import com.jme3.input.controls.AnalogListener;

    import com.jme3.input.controls.KeyTrigger;

    import com.jme3.light.DirectionalLight;

    import com.jme3.material.Material;

    import com.jme3.math.ColorRGBA;

    import com.jme3.math.Vector3f;

    import com.jme3.renderer.RenderManager;

    import com.jme3.scene.CameraNode;

    import com.jme3.scene.Geometry;

    import com.jme3.scene.Node;

    import com.jme3.scene.control.CameraControl.ControlDirection;

    import com.jme3.scene.shape.Box;

    import com.jme3.system.AppSettings;



    /**
  • test
  • @author Ariejan de Vroom <ariejan@ariejan.nl>

    /

    public class Main extends SimpleApplication {



    protected boolean isRunning = false;



    private BulletAppState bulletAppState;



    // 7x7 box

    private int boxSize = 10;

    private PhysicsNode map;



    protected PhysicsCharacterNode player;

    protected CameraNode playerCam;



    public static void main(String[] args) {

    // Default settings

    AppSettings settings = new AppSettings(true);

    settings.setFullscreen(false);

    settings.setResolution(1280, 720);

    settings.setTitle(“Plexor”);



    Main app = new Main();



    // Set settings, don’t show settings popup

    app.setSettings(settings);

    app.setShowSettings(false);



    app.start();

    }



    @Override

    public void simpleInitApp() {



    bulletAppState = new BulletAppState();

    stateManager.attach(bulletAppState);



    float middle = boxSize / 2.0f;



    // Create map

    Node mapNode = new Node();



    Box floor = new Box(Vector3f.ZERO, 10f, 1f, 10f);

    Geometry floorGeo = new Geometry(“Floor”, floor);

    floorGeo.updateModelBound();



    Material floorMat = new Material(assetManager, “Common/MatDefs/Misc/WireColor.j3md”);

    floorMat.setColor(“m_Color”, ColorRGBA.Blue);

    floorGeo.setMaterial(floorMat);

    mapNode.attachChild(floorGeo);



    // Border boxes

    // for (float z = 0; z < boxSize; z += 1.0f) {

    // for (float x = 0; x < boxSize; x += 1.0f) {

    // if ((z == 0 || z == boxSize-1) || (x == 0 || x == boxSize-1)) {

    // // Create a 1x1x1 Box

    // Box b = new Box(new Vector3f(x, 1.0f, z), 0.5f, 0.5f, 0.5f);

    // Geometry geo = new Geometry(“Box”, b);

    // geo.updateModelBound();

    //

    // // Material mat = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

    // Material mat = new Material(assetManager, “Common/MatDefs/Misc/SimpleTextured.j3md”);

    // // mat.setColor(“m_Color”, ColorRGBA.Blue);

    // mat.setTexture(“m_ColorMap”, assetManager.loadTexture(“Interface/Logo/Monkey.png”));

    // geo.setMaterial(mat);

    // mapNode.attachChild(geo);

    // }

    // }

    // }



    // CompoundCollisionShape mapShape =

    // CollisionShapeFactory.createBoxCompoundShape(mapNode);



    BoxCollisionShape mapShape = new BoxCollisionShape(new Vector3f(10f, 1f, 10f));

    map = new PhysicsNode(mapNode, mapShape, 0);



    // Add light!

    // DirectionalLight light = new DirectionalLight();

    // light.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());

    // light.setColor(ColorRGBA.White.clone());

    // rootNode.addLight(light);



    // Add the player

    Box b = new Box(new Vector3f(0, 0, 0), 0.5f, 0.5f, 0.5f);

    Geometry geo = new Geometry(“Player”, b);

    geo.updateModelBound();



    Material mat = new Material(assetManager, “Common/MatDefs/Misc/WireColor.j3md”);

    mat.setColor(“m_Color”, ColorRGBA.Red);

    geo.setMaterial(mat);



    player = new PhysicsCharacterNode(new CapsuleCollisionShape(1.5f, 0.6f, 1), .05f);

    player.setLocalTranslation(0, 5, 0);

    player.attachChild(geo);

    player.setJumpSpeed(20);

    player.setFallSpeed(30);

    player.setGravity(30);



    // Set camera

    flyCam.setEnabled(false);



    playerCam = new CameraNode(cam);

    playerCam.setControlDir(ControlDirection.SpatialToCamera);

    playerCam.setLocalTranslation(0, 10, -50);



    player.attachChild(playerCam);



    rootNode.attachChild(map);

    rootNode.attachChild(player);

    bulletAppState.getPhysicsSpace().add(map);

    bulletAppState.getPhysicsSpace().add(player);



    setupKeys();



    isRunning = true;

    }



    private void setupKeys() {

    inputManager.addMapping(“Lefts”, new KeyTrigger(KeyInput.KEY_A));

    inputManager.addMapping(“Rights”, new KeyTrigger(KeyInput.KEY_D));

    inputManager.addMapping(“Ups”, new KeyTrigger(KeyInput.KEY_W));

    inputManager.addMapping(“Downs”, new KeyTrigger(KeyInput.KEY_S));



    inputManager.addListener(analogListener, new String[]{ “Lefts”, “Rights”, “Ups”, “Downs” });

    }



    private AnalogListener analogListener = new AnalogListener() {

    public void onAnalog(String name, float value, float tpf) {

    if (isRunning) {

    if (name.equals(“Rights”)) {

    Vector3f v = player.getLocalTranslation();

    player.setLocalTranslation(v.x - value
    4speed, v.y, v.z);

    }

    if (name.equals(“Lefts”)) {

    Vector3f v = player.getLocalTranslation();

    player.setLocalTranslation(v.x + value
    4speed, v.y, v.z);

    }

    if (name.equals(“Ups”)) {

    Vector3f v = player.getLocalTranslation();

    player.setLocalTranslation(v.x, v.y + value
    4speed, v.z);

    }

    if (name.equals(“Downs”)) {

    Vector3f v = player.getLocalTranslation();

    player.setLocalTranslation(v.x, v.y - value
    4*speed, v.z);

    }

    }

    }

    };



    @Override

    public void simpleUpdate(float tpf) {



    }



    @Override

    public void simpleRender(RenderManager rm) {

    //TODO: add render code

    }

    }

    [/java]

Its probably the character thats shaking, not the ground. Its a known issue with the character and you can workaround it by adding rootNode.updateGeometricState() at the beginning of your simpleUpdate() method.

normen said:
Its probably the character thats shaking, not the ground. Its a known issue with the character and you can workaround it by adding rootNode.updateGeometricState() at the beginning of your simpleUpdate() method.


That seems to help. However, I notice that my player sometimes 'sinks' into the floor.

Anyway, any clue on the top-down camer view? I'm not sure how to do the rotation stuff.

I would have to look through the methods of ChaseCam myself atm. But you should learn about “the rotation stuff” asap when you want to be successful in jME: https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:math#quaternion

for the cam there is a setDefaultVerticalRotation(float angle) method in the ChaseCam.

set it to FastMath.HALF_PI and it should behave like you want.

nehon said:
for the cam there is a setDefaultVerticalRotation(float angle) method in the ChaseCam.
set it to FastMath.HALF_PI and it should behave like you want.


That works beatifully! Thanks!

Does anyone have a clue: the player 'block' sometimes gets stuck in the floor. I have added `player.jump()`, but sometimes it just won't perform the jump. Any clues on that? I've already toyed with the CapsuleCollisionShape sizes, but that didn't help much.

I probably have to use 'setWalkDirection' instead of 'setLocalTranslation' to get walking over another PhysicsNode working properly.

Exactly!

But beware setWalkDirection is continuous, so once you set it the player will move for ever, to stop it, just use setWalkDirection(Vector3f.ZERO);

nehon said:
Exactly!
But beware setWalkDirection is continuous, so once you set it the player will move for ever, to stop it, just use setWalkDirection(Vector3f.ZERO);


I already found out about that :) Strangely enough the AnalogListener does not send a 0 value when a button is released. Not sure that's be design or not.

don’t use the analog listener, use the action listener.

On press, set walk direction, on release set the walk direction to 0.