How to attach a camera to a geometry/node?

Well, I’m totally new to jMonkey, and am excited to see what I can do with it. What I want to do is lock the camera onto the player’s in-game body, such that when the body moves, the camera stays locked to their eyes’ position. I originally expected to do an “playerNode.attachChild(cam);” but that isn’t allowed and I’m unsure what the correct approach is. Ideas? (And right now, my player model is a block, so keep it simple please :slight_smile: Thanks!

1 Like

this solution should help you. http://hub.jmonkeyengine.org/groups/general-2/forum/topic/camera-that-follow-a-object. Add a chase camera https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:advanced:camera#chase_camera and in an update do something like (solution from the posted thread made by: @anthyon, @normen and @pspeed) [java]public void simpleUpdate(float tpf) {

cam.setLocation(playerNode.localToWorld( new Vector3f( 0, 10 /* units above player /, 10 / units behind player*/ ), null));

cam.lookAt(playerNode.getWorldTranslation(), Vector3f.UNIT_Y);

}[/java]

1 Like

The best way is to use a CameraNode.

create a cameraNode and attach the cam to it, then attach the camNode to the playerNode and voilà.

1 Like

Thanks so much for the help! Nehon, that’s exactly what I was looking for. It’s not working how I expect though. I create a new cameraNode with the default cam, .and attach it to the playerNode along with my player geometry. When I translate the cameraNode, nothing happens, and when I translate the playerNode, only the player geometry moves. Thoughts? Here’s my full class:



edit: removed old code - can see latest at bottom

ok 2 things that may help.

The CameraNode has a CameraControl that moves the camera when the node is moved OR that moves the node when the camera moves depending of the control direction.

You may have to do this :

[java]

cameraNode.getControl().setDirection(ControlDirection.SpatialToCamera);

[/java]

the default is CameraToSpatial.



The other thing is that the flyCam must be disabled or it can interfere with the cameraNode control.

Hmm, tried that, and the camera still isn’t moving when I move the playerNode. I’ve spent some time splitting up the code into classes that make sense to me - hopefully I’m not recreating functionality already existing in jMonkey :slight_smile:

MyApplication is just a copy/paste of SimpleApplication, from which I’ve removed the FlyByCam and just use cam instead. I’m guessing cam is initialized as a regular camera object?

Human extends MobileObject, and right now simply calls the super constructors, so no need to include.



edit: removed old code - can see latest at bottom

jelamb said:
Well, I'm totally new to jMonkey, and am excited to see what I can do with it. What I want to do is lock the camera onto the player's in-game body, such that when the body moves, the camera stays locked to their eyes' position. I originally expected to do an "playerNode.attachChild(cam);" but that isn't allowed and I'm unsure what the correct approach is. Ideas? (And right now, my player model is a block, so keep it simple please :) Thanks!


So, I was able to lock a chase camera onto the back of a cube such that it mirrored its moves. However, this still doesn't allow me to achieve my primary objective of having a camera in FRONT of a cube, mirroring its every move. After about 8hrs on this, I'm entirely at a standstill.
1) Does anyone know if this is even possible using jMonkey?
2) A camera is not a spatial, but a CameraNode is - why does my cameraNode not move the camera it is assigned? If it doesn't, what's the point of a CameraNode?
jelamb said:
1) Does anyone know if this is even possible using jMonkey?

yes It is

jelamb said:
2) A camera is not a spatial, but a CameraNode is - why does my cameraNode not move the camera it is assigned?

It does

jelamb said:
If it doesn't, what's the point of a CameraNode?

This very point.

It was hell to make your test was work, your movement implementation double the position of the cube on every key press....so so cube is in "infinity" in like 2 second press. This does not help...
anyway i managed to make it work by just removing the human.updateVelocity in the update loop and just changed this lines

this.translationVelocities.x += translationVelocityChanges.x * timePerFrame;
this.translationVelocities.y += translationVelocityChanges.y * timePerFrame;
this.translationVelocities.z += translationVelocityChanges.z * timePerFrame;

to this

this.translationVelocities.x = translationVelocityChanges.x * timePerFrame;
this.translationVelocities.y = translationVelocityChanges.y * timePerFrame;
this.translationVelocities.z = translationVelocityChanges.z * timePerFrame;


once this is done....the camera follows the cube as expected but you can't notice it as your ground is completely uniform...

jelamb said:
So, I was able to lock a chase camera onto the back of a cube such that it mirrored its moves. However, this still doesn't allow me to achieve my primary objective of having a camera in FRONT of a cube, mirroring its every move. After about 8hrs on this, I'm entirely at a standstill.

Dude relax, have a coffee, and think about it again.
in front or behind is just a question of translation, when you do :

cameraNode.setLocalTranslation(0, 0, -40);
playerBody.setLocalTranslation(0, 0, 5);

your cam is 45 world unit behind your player on the Z axis. do that

cameraNode.setLocalTranslation(0, 0, 50);
playerBody.setLocalTranslation(0, 0, 5);

and it will be 45 unit in front of it.

You should have a look at the ChaseCam control and the related test cases because it does what you want (i think).

First, apologies - I was definitely frustrated, but it came across far stronger than just frustration. I am, in fact, quite relaxed - I have this whole week off on PTO :slight_smile: And thank you SO much for your help.



Second, in reaction to your last post, I am confused on two points.

  1. My method of translating using increasing velocities seems to work fine for me - the longer I hold a key, the greater velocity is built. Hold the opposite key and it begins to reverse the velocity. It actually builds rather slowly, and I had to multiply the ftp variable to increase it to distance changes I can see. Perhaps I’m not using the ftp variable correctly, and my program is running at different speeds on different computers?


  2. You agree that the camera should mirror my cube’s movement when I have the CameraNode and cube geometry both children of the playerNode, yet I continue to not see this happen. I’ve been using println to view world locations and the camera just doesn’t move. I’ve also added better visual marking to my “world”. I have been able to do a decent job of mirroring using the following lines in my update method. It’s close, but still not quite right on rotate. I expect I could get it to rotate properly after more quaternion learning.

    [java]camera.setLocation(parentNode.localToWorld(new Vector3f(0, -.3f, 0), new Vector3f(0,0,0)));

    camera.setRotation(geometry.getWorldRotation());[/java]

    Still, I’d rather leave this sort of operation to the engine node system.



    Here’s the latest of my code, since it’s diverged from the above:



    MAIN

    [java]package mygame;



    import mygame.MobileObjects.*;

    import com.jme3.input.KeyInput;

    import com.jme3.input.controls.ActionListener;

    import com.jme3.input.controls.AnalogListener;

    import com.jme3.input.controls.KeyTrigger;

    import com.jme3.material.Material;

    import com.jme3.math.ColorRGBA;

    import com.jme3.math.Vector3f;

    import com.jme3.renderer.Camera;

    import com.jme3.scene.CameraNode;

    import com.jme3.scene.Geometry;

    import com.jme3.scene.Node;

    import com.jme3.scene.shape.Box;





    public class Main extends MyApplication {



    //Class level variables

    protected Geometry playerBody;

    protected Node playerNode;

    CameraNode cameraNode;

    Geometry geom;

    Boolean isRunning = true;

    Spaceship spaceship;

    Human human;



    public static void main(String[] args) {

    Main app = new Main();

    app.start();

    }



    /**
  • Initializes the scene components */

    @Override

    public void simpleInitApp() {

    initGeometry();

    initCamera();

    initKeys();

    }



    @Override

    public void simpleUpdate(float tpf) {



    human.update(tpf);

    }





    /**
  • Set up the custom keybindings for the scene */

    private void initKeys() {

    // You can map one or several inputs to one named action

    inputManager.addMapping("P", new KeyTrigger(KeyInput.KEY_P));

    inputManager.addMapping("Q", new KeyTrigger(KeyInput.KEY_Q));

    inputManager.addMapping("E", new KeyTrigger(KeyInput.KEY_E));

    inputManager.addMapping("A", new KeyTrigger(KeyInput.KEY_A));

    inputManager.addMapping("D", new KeyTrigger(KeyInput.KEY_D));

    inputManager.addMapping("W", new KeyTrigger(KeyInput.KEY_W));

    inputManager.addMapping("S", new KeyTrigger(KeyInput.KEY_S));

    inputManager.addMapping("Space", new KeyTrigger(KeyInput.KEY_SPACE));



    // Add the names to the action listener.

    inputManager.addListener(actionListener, new String[]{"P"});

    inputManager.addListener(analogListener, new String[]{"Q", "E", "W", "S", "A", "D", "Space"});



    }

    private ActionListener actionListener = new ActionListener() {



    public void onAction(String name, boolean keyPressed, float tpf) {

    if (name.equals("Pause") && !keyPressed) {

    isRunning = !isRunning;

    }

    }

    };

    private AnalogListener analogListener = new AnalogListener() {



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





    if (isRunning) {

    if (name.equals("Rotate")) {

    human.updateVelocity(null, new Vector3f(0,1,0), ftp);

    }

    if (name.equals("E")) {

    human.updateVelocity(new Vector3f(-value, 0, 0), null, ftp);

    }

    if (name.equals("Q")) {

    human.updateVelocity(new Vector3f(value, 0, 0), null, ftp);

    }

    if (name.equals("W")) {

    human.updateVelocity(new Vector3f(0, 0, value), null, ftp);

    }

    if (name.equals("S")) {

    human.updateVelocity(new Vector3f(0, 0, -value), null, ftp);

    }

    if (name.equals("A")) {

    human.updateVelocity(null, new Vector3f(0, value, 0), ftp);

    }

    if (name.equals("D")) {

    human.updateVelocity(null, new Vector3f(0, -value, 0), ftp);

    }

    } else {

    System.out.println("Press P to unpause.");

    }

    }

    };



    /**
  • Set up the camera for the scene */

    private void initCamera(){



    CameraNode camNode = new CameraNode("CameraNode", cam);

    playerNode.attachChild(camNode);

    }



    /**
  • Sets up the geometry for the scene /

    private void initGeometry() {



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

    matRed.setColor(“Color”, ColorRGBA.Red);

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

    matBlue.setColor(“Color”, ColorRGBA.Blue);

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

    matGreen.setColor(“Color”, ColorRGBA.Green);

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

    matOrange.setColor(“Color”, ColorRGBA.Orange);



    //Create a blue “player body”

    Box playerBox = new Box(Vector3f.ZERO, 1, 1, 1);

    playerBody = new Geometry(“Player”, playerBox);

    playerBody.setMaterial(matBlue);

    //attach the player geometry to it’s own node, and attach that to the root node

    playerNode = new Node(“playerNode”);

    rootNode.attachChild(playerNode);

    playerNode.attachChild(playerBody);

    human = new Human(playerBody, playerNode, cam);

    //playerBody.setLocalTranslation(0, 0, 0);



    //Create green “ground”

    Box ground = new Box(Vector3f.ZERO, 100, 0, 100);

    Geometry groundGeom = new Geometry(“Box2”, ground);

    groundGeom.setMaterial(matGreen);

    rootNode.attachChild(groundGeom);

    groundGeom.setLocalTranslation(0, -10, 0);



    //Create orange “ground”

    Box ground2 = new Box(Vector3f.ZERO, 400, 0, 400);

    Geometry ground2Geom = new Geometry(“Box2”, ground2);

    ground2Geom.setMaterial(matOrange);

    rootNode.attachChild(ground2Geom);

    ground2Geom.setLocalTranslation(0, -15, 0);



    //create locator boxes

    Box box2 = new Box(Vector3f.ZERO, 1, 1, 1);

    Geometry geom2 = new Geometry(“Box”, box2);

    geom2.setMaterial(matRed);

    rootNode.attachChild(geom2);

    geom2.setLocalTranslation(20, 0, -20);



    //create locator boxes

    Box box3 = new Box(Vector3f.ZERO, 1, 1, 1);

    Geometry geom3 = new Geometry(“Box”, box3);

    geom3.setMaterial(matRed);

    rootNode.attachChild(geom3);

    geom3.setLocalTranslation(20, 0, 20);



    //create locator boxes

    Box box4 = new Box(Vector3f.ZERO, 1, 1, 1);

    Geometry geom4 = new Geometry(“Box”, box4);

    geom4.setMaterial(matRed);

    rootNode.attachChild(geom4);

    geom4.setLocalTranslation(-20, 0, -20);



    //create locator boxes

    Box box5 = new Box(Vector3f.ZERO, 1, 1, 1);

    Geometry geom5 = new Geometry(“Box”, box5);

    geom5.setMaterial(matRed);

    rootNode.attachChild(geom5);

    geom5.setLocalTranslation(-20, 0, 20);

    }

    }

    [/java]



    HUMAN

    [java]/

  • To change this template, choose Tools | Templates
  • and open the template in the editor.

    */

    package mygame.MobileObjects;



    import com.jme3.math.Vector3f;

    import com.jme3.renderer.Camera;

    import com.jme3.scene.Geometry;

    import com.jme3.scene.Node;



    /**

    *
  • @author Jonathan Lamb

    /

    public class Human extends MobileObject{



    Camera camera;



    public Human() {

    super();

    }

    public Human(Geometry geometry, Node parentNode) {

    super(geometry, parentNode);

    }

    public Human(Geometry geometry, Node parentNode, Camera camera) {

    super(geometry, parentNode);

    this.camera = camera;

    }



    @Override

    public void update(float timePerFrame){

    System.out.println("CAM: "+camera.getLocation());

    System.out.println("GEO: "+geometry.getWorldTranslation());



    //camera.setLocation(parentNode.localToWorld(new Vector3f(0, 10f, 0), new Vector3f(0,0,0)));

    //camera.setRotation(geometry.getWorldRotation());



    //can’t use lookAt or the camera will turn around towards my cube

    //camera.lookAt(parentNode.getWorldTranslation(), Vector3f.UNIT_Y);



    super.update(timePerFrame);

    }

    }

    [/java]



    MOBILEOBJECT

    [java]/

  • To change this template, choose Tools | Templates
  • and open the template in the editor.

    */

    package mygame.MobileObjects;



    import com.jme3.math.Vector3f;

    import com.jme3.scene.Geometry;

    import com.jme3.scene.Node;

    /**

    *
  • @author Jonathan Lamb

    /

    public class MobileObject {



    public Geometry geometry;

    public Node parentNode;

    private Vector3f translationVelocities;

    private Vector3f rotationVelocities;



    protected MobileObject(){}



    public MobileObject(Geometry geometry, Node parentNode) {

    this.geometry = geometry;

    this.parentNode = parentNode;

    this.translationVelocities = new Vector3f();

    this.rotationVelocities = new Vector3f();

    this.parentNode.attachChild(geometry);



    System.out.println("MobileObject.updateLocalTranslation(): parentNode: "+parentNode.getName());

    }







    //update location with any new changes

    public void update(float timePerFrame){

    updateVelocity(null, null, timePerFrame);

    }

    public void updateVelocity(Vector3f translationVelocityChanges, Vector3f rotationVelocityChanges, float timePerFrame) {

    timePerFrame = timePerFrame
    10;

    if (rotationVelocityChanges != null){

    this.rotationVelocities.x += rotationVelocityChanges.x * timePerFrame;

    this.rotationVelocities.y += rotationVelocityChanges.y * timePerFrame;

    this.rotationVelocities.z += rotationVelocityChanges.z * timePerFrame;

    }

    updateLocalRotation();



    if (translationVelocityChanges != null){

    this.translationVelocities.x += translationVelocityChanges.x * timePerFrame;

    this.translationVelocities.y += translationVelocityChanges.y * timePerFrame;

    this.translationVelocities.z += translationVelocityChanges.z * timePerFrame;

    }

    updateLocalTranslation();

    }

    /

    public void updateNoVelocity(float timePerFrame){

    updateVelocity(null, null, timePerFrame);

    }
    /

    public void updateNoVelocity(Vector3f translationChanges, Vector3f rotationChanges, float timePerFrame){



    if (rotationChanges != null){

    float rotationTPF = timePerFrame10000;

    this.rotationVelocities.x = rotationChanges.x * rotationTPF;

    this.rotationVelocities.y = rotationChanges.y * rotationTPF;

    this.rotationVelocities.z = rotationChanges.z * rotationTPF;

    }

    updateLocalRotation();



    if (translationChanges != null){

    float translationTPF = timePerFrame
    10000;

    parentNode.getLocalTranslation().x += translationChanges.x * translationTPF;

    parentNode.getLocalTranslation().y += translationChanges.y * translationTPF;

    parentNode.getLocalTranslation().z += translationChanges.z * translationTPF;

    }

    updateLocalTranslation();

    }



    //update location based on current location

    private void updateLocalTranslation(){

    System.out.println("MobileObject.updateLocalTranslation(): translationVelocities: "+translationVelocities);





    Vector3f localTranslation = parentNode.getLocalTranslation();

    parentNode.setLocalTranslation(localTranslation.x + translationVelocities.x, localTranslation.y + translationVelocities.y, localTranslation.z + translationVelocities.z);

    }



    //update rotation based on current rotation

    private void updateLocalRotation() {

    geometry.rotate(rotationVelocities.x, rotationVelocities.y, rotationVelocities.z);

    }

    }

    [/java]



    MYAPPLICATION (just in case)

    [java]/*
  • Copyright © 2009-2010 jMonkeyEngine
  • All rights reserved.

    *
  • Redistribution and use in source and binary forms, with or without
  • modification, are permitted provided that the following conditions are
  • met:

    *
    • Redistributions of source code must retain the above copyright
  • notice, this list of conditions and the following disclaimer.

    *
    • Redistributions in binary form must reproduce the above copyright
  • notice, this list of conditions and the following disclaimer in the
  • documentation and/or other materials provided with the distribution.

    *
    • Neither the name of ‘jMonkeyEngine’ nor the names of its contributors
  • may be used to endorse or promote products derived from this software
  • without specific prior written permission.

    *
  • THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  • "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  • TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  • PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  • CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  • EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  • PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  • PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  • LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  • NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  • SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

    */

    package mygame;



    import com.jme3.app.Application;

    import com.jme3.app.StatsView;

    import com.jme3.font.BitmapFont;

    import com.jme3.font.BitmapText;

    import com.jme3.input.KeyInput;

    import com.jme3.input.controls.ActionListener;

    import com.jme3.input.controls.KeyTrigger;

    import com.jme3.math.Quaternion;

    import com.jme3.math.Vector3f;

    import com.jme3.renderer.Camera;

    import com.jme3.renderer.RenderManager;

    import com.jme3.renderer.queue.RenderQueue.Bucket;

    import com.jme3.scene.Node;

    import com.jme3.scene.Spatial.CullHint;

    import com.jme3.system.AppSettings;

    import com.jme3.system.JmeContext.Type;

    import com.jme3.system.JmeSystem;

    import com.jme3.util.BufferUtils;



    /**
  • <code>SimpleApplication</code> extends the {@link com.jme3.app.Application}
  • class to provide default functionality like a first-person camera,
  • and an accessible root node that is updated and rendered regularly.
  • Additionally, <code>SimpleApplication</code> will display a statistics view
  • using the {@link com.jme3.app.StatsView} class. It will display
  • the current frames-per-second value on-screen in addition to the statistics.
  • Several keys have special functionality in <code>SimpleApplication</code>:<br/>

    *
  • <table>
  • <tr><td>Esc</td><td>- Close the application</td></tr>
  • <tr><td>C</td><td>- Display the camera position and rotation in the console.</td></tr>
  • <tr><td>M</td><td>- Display memory usage in the console.</td></tr>
  • </table>

    */

    public abstract class MyApplication extends Application {



    protected Node rootNode = new Node("Root Node");

    protected Node guiNode = new Node("Gui Node");

    protected float secondCounter = 0.0f;

    protected BitmapText fpsText;

    protected BitmapFont guiFont;

    protected StatsView statsView;

    protected Camera camera = cam;

    protected boolean showSettings = true;

    private AppActionListener actionListener = new AppActionListener();



    private class AppActionListener implements ActionListener {



    public void onAction(String name, boolean value, float tpf) {

    if (!value) {

    return;

    }



    if (name.equals("SIMPLEAPP_Exit")) {

    stop();

    } else if (name.equals("SIMPLEAPP_CameraPos")) {

    if (cam != null) {

    Vector3f loc = cam.getLocation();

    Quaternion rot = cam.getRotation();

    System.out.println("Camera Position: ("
  • loc.x + ", " + loc.y + ", " + loc.z + ")");

    System.out.println("Camera Rotation: " + rot);

    System.out.println("Camera Direction: " + cam.getDirection());

    }

    } else if (name.equals("SIMPLEAPP_Memory")) {

    BufferUtils.printCurrentDirectMemory(null);

    }

    }

    }



    public MyApplication() {

    super();

    }



    @Override

    public void start() {

    // set some default settings in-case

    // settings dialog is not shown

    boolean loadSettings = false;

    if (settings == null) {

    setSettings(new AppSettings(true));

    loadSettings = true;

    }



    // show settings dialog

    if (showSettings) {

    if (!JmeSystem.showSettingsDialog(settings, loadSettings)) {

    return;

    }

    }

    //re-setting settings they can have been merged from the registry.

    setSettings(settings);

    super.start();

    }



    /**
  • Retrieves flyCam
  • @return flyCam Camera object

    *

    */

    public Camera getCamera() {

    return camera;

    }



    /**
  • Retrieves guiNode
  • @return guiNode Node object

    *

    */

    public Node getGuiNode() {

    return guiNode;

    }



    /**
  • Retrieves rootNode
  • @return rootNode Node object

    *

    */

    public Node getRootNode() {

    return rootNode;

    }



    public boolean isShowSettings() {

    return showSettings;

    }



    /**
  • Toggles settings window to display at start-up
  • @param showSettings Sets true/false

    *

    */

    public void setShowSettings(boolean showSettings) {

    this.showSettings = showSettings;

    }



    /**
  • Attaches FPS statistics to guiNode and displays it on the screen.

    *

    */

    public void loadFPSText() {

    guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");

    fpsText = new BitmapText(guiFont, false);

    fpsText.setLocalTranslation(0, fpsText.getLineHeight(), 0);

    fpsText.setText("Frames per second");

    guiNode.attachChild(fpsText);

    }



    /**
  • Attaches Statistics View to guiNode and displays it on the screen
  • above FPS statistics line.

    *

    */

    public void loadStatsView() {

    statsView = new StatsView("Statistics View", assetManager, renderer.getStatistics());

    // move it up so it appears above fps text

    statsView.setLocalTranslation(0, fpsText.getLineHeight(), 0);

    guiNode.attachChild(statsView);

    }



    @Override

    public void initialize() {

    super.initialize();



    guiNode.setQueueBucket(Bucket.Gui);

    guiNode.setCullHint(CullHint.Never);

    loadFPSText();

    loadStatsView();

    viewPort.attachScene(rootNode);

    guiViewPort.attachScene(guiNode);



    if (inputManager != null) {

    //camera = new Camera(cam);

    //camera.setMoveSpeed(1f);

    //camera.registerWithInput(inputManager);











    if (context.getType() == Type.Display) {

    inputManager.addMapping("SIMPLEAPP_Exit", new KeyTrigger(KeyInput.KEY_ESCAPE));

    }



    inputManager.addMapping("SIMPLEAPP_CameraPos", new KeyTrigger(KeyInput.KEY_C));

    inputManager.addMapping("SIMPLEAPP_Memory", new KeyTrigger(KeyInput.KEY_M));

    inputManager.addListener(actionListener, "SIMPLEAPP_Exit",

    "SIMPLEAPP_CameraPos", "SIMPLEAPP_Memory");

    }



    // call user code

    simpleInitApp();

    }



    @Override

    public void update() {

    super.update(); // makes sure to execute AppTasks

    if (speed == 0 || paused) {

    return;

    }



    float tpf = timer.getTimePerFrame() * speed;



    secondCounter += timer.getTimePerFrame();

    int fps = (int) timer.getFrameRate();

    if (secondCounter >= 1.0f) {

    fpsText.setText("Frames per second: " + fps);

    secondCounter = 0.0f;

    }



    // update states

    stateManager.update(tpf);



    // simple update and root node

    simpleUpdate(tpf);

    rootNode.updateLogicalState(tpf);

    guiNode.updateLogicalState(tpf);

    rootNode.updateGeometricState();

    guiNode.updateGeometricState();



    // render states

    stateManager.render(renderManager);

    renderManager.render(tpf);

    simpleRender(renderManager);

    stateManager.postRender();

    }



    public abstract void simpleInitApp();



    public void simpleUpdate(float tpf) {

    }



    public void simpleRender(RenderManager rm) {

    }

    }[/java]

ok what do you want to achieve exactly is it a first person camera handler or a third person?

because if it’s the latter ChaseCamera is definitely what you need.

I want a first person camera. However, for my game architecture it makes more sense to me to have the character model drive the camera, rather than the other way around. (Space combat game, where you can both pilot a ship, and get up and walk around it. Seems easier to move the camera through the node tree. Just translate/rotate character or spaceship node and the camera follows as it should.) The implementations of first person in jMonkey I’ve found all seem to be running the other direction - control the camera and the character model follows.

Bump - still can’t solve this :frowning:

Here is a very simple example demonstrating how to achieve what you want with a CameraNode.

Please look into it, I bet you’ll find you way out of this.

http://code.google.com/p/jmonkeyengine/source/browse/trunk/engine/src/test/jme3test/input/TestCameraNode.java?spec=svn7970&r=7970

Thank you Nehon! I added your code sample to the docs!

making the camera follow a character

Thanks, I’ll look into both of those tonight after work. I do have a question right now though from my brief glance through. I don’t entirely understand this line: flyCam.setEnabled(false); Is the flyCam always in existence? Perhaps this is the piece I was missing - I think was under the impression that if I didn’t create it, it wouldn’t be there since cam was just a camera. Any enlightening you can shower me with? :slight_smile:

The flyCam is created in the SimpleApplication class, so yes, if you extend it you have a flyCam and it’s enabled.

The flyCam in SimpleApplication is actually an object implementing the AnalogListener/ActionListener interface controlling the camera, it is not a camera. You can switch its default navigation inputs (WASD etc) off to implement your own controls – e.g. to control a third-person character, whom you want the camera to follow.

CameraNode is a node. ChaseCam also implements AnalogListener/ActionListener, and Control, because it can control not only a camera, but also the player rotation.