flyCam and AppStates

Hello guys, I’ve some trouble with a First Person Shooter project and after 3 days of ineffective “commits”, I turn to you to find a solution, and a deep understanding of the flyCam system.

Into my project, i’ve made some AppStates to transit between Main Menu, Options menu, principally build with Nifty GUI. And for every elements, I desactivate the flyCam and when I change between states, I systematically clear the mapping of the inputs (because it’s the only way i’ve found, tell me if i’m wrong).

And I’ve found in the TestQ3.java a good example of the way I want to use the FlyBycamera and all. But, I’ve try to reactivate by every way the flyCam and it never run like I want. When I done that :
Into the init function :
[java]
bulletAppState = new BulletAppState();
app.getStateManager().attach(bulletAppState);
bulletAppState.initialize(app.getStateManager(), app);
bulletAppState.setEnabled(true);
// Init flycam
//flyCam = new FlyByCamera(cam);
flyCam.setEnabled(true);
flyCam.setDragToRotate(false);
flyCam.setMoveSpeed(100);
this.cam.setFrustumFar(2000);
cam.updateViewProjection();
// Init Space with Floor and boxes and stuff
this.createPhysicsWorld(bulletAppState.getPhysicsSpace());
// Add a physics character to the world
player = new CharacterControl(new CapsuleCollisionShape(1, 1), .1f);
player.setPhysicsLocation(new Vector3f(0, 1, 0));
player.setJumpSpeed(20);
player.setFallSpeed(30);
player.setGravity(30);
characterNode = new Node(“character node”);
Spatial model = StaticGraphGems.Cube(0, 1, 0, 0.5f, 1.8f, .1f, assetManager, “Caract”, ColorRGBA.Green);
characterNode.addControl(player);
getPhysicsSpace().add(player);
graphNode.attachChild(characterNode);
characterNode.attachChild(model);

    nodeCam = new CameraNode("CamNode", cam);
    nodeCam.setControlDir(ControlDirection.SpatialToCamera);
    nodeCam.lookAt(model.getLocalTranslation(), Vector3f.UNIT_Y);
    characterNode.attachChild(nodeCam);

[/java]
Into the update while :
[java]
Vector3f camDir = cam.getDirection().clone().multLocal(0.6f);
Vector3f camLeft = cam.getLeft().clone().multLocal(0.4f);
walkDirection.set(0,0,0);
if (leftStrafe) {
walkDirection.addLocal(camLeft);
} else
if (rightStrafe) {
walkDirection.addLocal(camLeft.negate());
}
if (forward) {
walkDirection.addLocal(camDir);
} else
if (backward) {
walkDirection.addLocal(camDir.negate());
}
player.setWalkDirection(walkDirection);
cam.setLocation(player.getPhysicsLocation());
[/java]
Into the keyInit :
[java]
inputManager.addMapping(“Strafe Left”, new KeyTrigger(KeyInput.KEY_Q));
inputManager.addMapping(“Strafe Right”, new KeyTrigger(KeyInput.KEY_D));
inputManager.addMapping(“Walk Forward”, new KeyTrigger(KeyInput.KEY_Z));
inputManager.addMapping(“Walk Backward”, new KeyTrigger(KeyInput.KEY_S));
inputManager.addMapping(“Jump”, new KeyTrigger(KeyInput.KEY_SPACE));
inputManager.addMapping(“Rotate Left”, new KeyTrigger(KeyInput.KEY_A));
inputManager.addMapping(“Rotate Right”, new KeyTrigger(KeyInput.KEY_E));
inputManager.addMapping(“BackMenu”, ConfigLoader.mainMenuKey.get(0));
inputManager.addListener(ActionListener, “Strafe Left”);
inputManager.addListener(ActionListener, “Strafe Right”);
inputManager.addListener(ActionListener, “Rotate Left”);
inputManager.addListener(ActionListener, “Rotate Right”);
inputManager.addListener(ActionListener, “Walk Forward”);
inputManager.addListener(ActionListener, “Walk Backward”);
inputManager.addListener(ActionListener, “Jump”);
inputManager.addListener(ActionListener, “BackMenu”);
flyCam.registerWithInput(inputManager);
[/java]
Into the Action Listener :
[java]
if (binding.equals(“Strafe Left”)) {
if (value) {
leftStrafe = true;
} else {
leftStrafe = false;
}
} else if (binding.equals(“Strafe Right”)) {
if (value) {
rightStrafe = true;
} else {
rightStrafe = false;
}
} else if (binding.equals(“Rotate Left”)) {
if (value) {
leftRotate = true;
} else {
leftRotate = false;
}
} else if (binding.equals(“Rotate Right”)) {
if (value) {
rightRotate = true;
} else {
rightRotate = false;
}
} else if (binding.equals(“Walk Forward”)) {
if (value) {
forward = true;
} else {
forward = false;
}
} else if (binding.equals(“Walk Backward”)) {
if (value) {
backward = true;
} else {
backward = false;
}
} else if (binding.equals(“Jump”)) {
player.jump();
} else if (binding.equals(“BackMenu”) && value){
app.getStateManager().getState(TutorialState.class).setEnabled(false);
app.getStateManager().getState(BulletAppState.class).setEnabled(false);
app.getStateManager().getState(MainMenuState.class).setEnabled(true);
}
}
[/java]
I can see the can first person camera and access to the mouse wheel effect on the cam (I’ve never understant what’s effect? A zoom or something?), but I can’t rotate the view with the mouse. I think something has been lost between states but I can’t figure what.

Anyone can help me? To turn on the flyByCamera or recreate a complete first person camera?

1 Like

Fly cam works and is defined by default. You do not need to add mappings or anything to make it work unless you are replacing it with your own code. Just extend SimpleApplication and you will have a fly cam on and running without doing anything.

You have two problems :

  1. your camera is embedded in a CameraNode set to spatialToCamera => this prevents the flycam from operating
  2. you should not fiddle with key mapping as far as flycam is concerned. Why not only enable/disable the flycam app-state ?
@pspeed said: Fly cam works and is defined by default. You do not need to add mappings or anything to make it work unless you are replacing it with your own code. Just extend SimpleApplication and you will have a fly cam on and running without doing anything.

I cannot extends an AbstractAppState to a SimpleApplication. And also, obviously my game extends SimpleApplication, or I wouldnt have spoken about a flyCam.
So, my flyCam exist, but it wasnt respond like it must do. That’s the problem.

@yang71 said: You have two problems : 1) your camera is embedded in a CameraNode set to spatialToCamera => this prevents the flycam from operating

I’ve invented nothing, there is the code of TestQ3.java on the JME3Test project :

[java]
package jme3test.bullet;

import com.jme3.app.SimpleApplication;
import com.jme3.asset.plugins.HttpZipLocator;
import com.jme3.asset.plugins.ZipLocator;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.shapes.SphereCollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.objects.PhysicsCharacter;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.MaterialList;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.plugins.ogre.OgreMeshKey;
import java.io.File;

public class TestQ3 extends SimpleApplication implements ActionListener {

private BulletAppState bulletAppState;
private Node gameLevel;
private PhysicsCharacter player;
private Vector3f walkDirection = new Vector3f();
private static boolean useHttp = false;
private boolean left=false,right=false,up=false,down=false;

public static void main(String[] args) {
    File file = new File("quake3level.zip");
    if (!file.exists()) {
        useHttp = true;
    }
    TestQ3 app = new TestQ3();
    app.start();
}

public void simpleInitApp() {
    bulletAppState = new BulletAppState();
    stateManager.attach(bulletAppState);
    flyCam.setMoveSpeed(100);
    setupKeys();

    this.cam.setFrustumFar(2000);

    DirectionalLight dl = new DirectionalLight();
    dl.setColor(ColorRGBA.White.clone().multLocal(2));
    dl.setDirection(new Vector3f(-1, -1, -1).normalize());
    rootNode.addLight(dl);

    AmbientLight am = new AmbientLight();
    am.setColor(ColorRGBA.White.mult(2));
    rootNode.addLight(am);

    // load the level from zip or http zip
    if (useHttp) {
        assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/quake3level.zip", HttpZipLocator.class);
    } else {
        assetManager.registerLocator("quake3level.zip", ZipLocator.class);
    }

    // create the geometry and attach it
    MaterialList matList = (MaterialList) assetManager.loadAsset("Scene.material");
    OgreMeshKey key = new OgreMeshKey("main.meshxml", matList);
    gameLevel = (Node) assetManager.loadAsset(key);
    gameLevel.setLocalScale(0.1f);

    // add a physics control, it will generate a MeshCollisionShape based on the gameLevel
    gameLevel.addControl(new RigidBodyControl(0));

    player = new PhysicsCharacter(new SphereCollisionShape(5), .01f);
    player.setJumpSpeed(20);
    player.setFallSpeed(30);
    player.setGravity(30);

    player.setPhysicsLocation(new Vector3f(60, 10, -60));

    rootNode.attachChild(gameLevel);

    getPhysicsSpace().addAll(gameLevel);
    getPhysicsSpace().add(player);
}

private PhysicsSpace getPhysicsSpace(){
    return bulletAppState.getPhysicsSpace();
}

@Override
public void simpleUpdate(float tpf) {
    Vector3f camDir = cam.getDirection().clone().multLocal(0.6f);
    Vector3f camLeft = cam.getLeft().clone().multLocal(0.4f);
    walkDirection.set(0,0,0);
    if(left)
        walkDirection.addLocal(camLeft);
    if(right)
        walkDirection.addLocal(camLeft.negate());
    if(up)
        walkDirection.addLocal(camDir);
    if(down)
        walkDirection.addLocal(camDir.negate());
    player.setWalkDirection(walkDirection);
    cam.setLocation(player.getPhysicsLocation());
}

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.addMapping("Space", new KeyTrigger(KeyInput.KEY_SPACE));
    inputManager.addListener(this,"Lefts");
    inputManager.addListener(this,"Rights");
    inputManager.addListener(this,"Ups");
    inputManager.addListener(this,"Downs");
    inputManager.addListener(this,"Space");
}

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

    if (binding.equals("Lefts")) {
        if(value)
            left=true;
        else
            left=false;
    } else if (binding.equals("Rights")) {
        if(value)
            right=true;
        else
            right=false;
    } else if (binding.equals("Ups")) {
        if(value)
            up=true;
        else
            up=false;
    } else if (binding.equals("Downs")) {
        if(value)
            down=true;
        else
            down=false;
    } else if (binding.equals("Space")) {
        player.jump();
    }
}

}
[/java]

I just followed the path taken by this code. Or tried to follow, because my own doesent work anyway. Even if i delete the “SpatialtoCamera”, it still doesnt work.
The massive difference between the two codes is test3Q.Java is made on the main SimpleApplication, while I’m doin on an AppState behind a nifty menu where I desactivate the freakin damn flyCam.

@yang71 said: 2) you should not fiddle with key mapping as far as flycam is concerned. Why not only enable/disable the flycam app-state ?

I’ve never seen the flyByCam like an AppState. Anyway, i’ve try to implement this :

[java]
// Init Bullets
bulletAppState = new BulletAppState();
app.getStateManager().attach(bulletAppState);
bulletAppState.initialize(app.getStateManager(), app);
bulletAppState.setEnabled(true);
// Init flycam
//flyCam = new FlyByCamera(cam);
flyCamAppState = new FlyCamAppState();
app.getStateManager().attach(flyCamAppState);
flyCamAppState.initialize(app.getStateManager(), app);
flyCamAppState.setEnabled(true);
// Try to add this, make no differences : flyCam = flyCamAppState.getCamera();

    // Init Space with Floor and boxes and stuff
    this.createPhysicsWorld(bulletAppState.getPhysicsSpace());
    // Add a physics character to the world
    player = new CharacterControl(new CapsuleCollisionShape(1, 1), .1f);
    player.setPhysicsLocation(new Vector3f(0, 1, 0));
    player.setJumpSpeed(20);
    player.setFallSpeed(30);
    player.setGravity(30);
    characterNode = new Node("character node");
    Spatial model = StaticGraphGems.Cube(0, 1, 0, 0.5f, 1.8f, .1f, assetManager, "Caract", ColorRGBA.Green);
    characterNode.addControl(player);
    getPhysicsSpace().add(player);
    graphNode.attachChild(characterNode);
    characterNode.attachChild(model); 

    nodeCam = new CameraNode("CamNode", cam);
    nodeCam.lookAt(model.getLocalTranslation(), Vector3f.UNIT_Y);
    characterNode.attachChild(nodeCam);

[/java]

And deleting this line on my initKeys() :

[java]
flyCam.registerWithInput(inputManager);
[/java]

And it did it like the other, I can move right, left, forward and backward, i can jump too, i dont see my mouse, I can’t dragToRotate, I can zoom with the mouse wheel, but i cant move the camera to rotate, to see and go in another direction. I’m very desperate now, because it must be simple to make this kind of stuff but it’s not!
I think it’s because appStates ruins everything.

If anyone have an idea, or want a deeper part of the code, plz come to my rescue! I really really REALLY need help…

  1. Please explain me where you have seen a cameraNode in the testQ3 you pasted ?
    I’ve not tested it, but I think that if you really need it, it should be set to CameraToSpatial instead. (attaching some object to the camera maybe ?)

  2. The FlyCam appState is already launched by SimpleApplication. You MUSTNOT install a second one !
    Instead, just try and enable/disable the standard one. Or first detach it…

Explication for 1) : The cameraNode you set up copies the transform of the node into the camera. Thus, the flycam has no effect since it modifies the camera directly.

To change the default app states over-ride the constructor and call super() with the list of app states you want to see…

@yang71 said: 1) Please explain me where you have seen a cameraNode in the testQ3 you pasted ? I've not tested it, but I think that if you really need it, it should be set to CameraToSpatial instead. (attaching some object to the camera maybe ?)
  1. The FlyCam appState is already launched by SimpleApplication. You MUSTNOT install a second one !
    Instead, just try and enable/disable the standard one. Or first detach it…

Explication for 1) : The cameraNode you set up copies the transform of the node into the camera. Thus, the flycam has no effect since it modifies the camera directly.

Yeah, well, after all your helps guys, Hail for you all, nothing running correctly. My camera was definitly lock.

So i’ve tried another thing : I’ve bypass my “Main menu” and was going directly to my appState FPS and SURPRISE, everything run perfectly. So, I publish here my MainAppState here, and if someone find why my FlyByCam is stucked after pass on it, feel free to explain me x), now i’m goin to continue to work on my Game FPS State, and hope to find a solution.

And I post my “TutorialState” (it’s the FPS Stage), I hope it helping you to understand more.
I repeat, the “TutorialState” with the FPS run perfectly if I dont enable the main menu state, or don’t pass on it.
And the “Main Menu State” run correctly alone.

MainMenuState.java

[java]
/*

  • To change this template, choose Tools | Templates
  • and open the template in the editor.
    /
    package net.nakou.girdermadness.states;
    import com.jme3.app.Application;
    import com.jme3.app.FlyCamAppState;
    import com.jme3.app.SimpleApplication;
    import com.jme3.app.state.AbstractAppState;
    import com.jme3.app.state.AppStateManager;
    import com.jme3.asset.AssetManager;
    import com.jme3.audio.AudioRenderer;
    import com.jme3.font.BitmapFont;
    import com.jme3.font.BitmapText;
    import com.jme3.input.FlyByCamera;
    import com.jme3.input.InputManager;
    import com.jme3.input.KeyInput;
    import com.jme3.input.controls.ActionListener;
    import com.jme3.input.controls.KeyTrigger;
    import com.jme3.light.AmbientLight;
    import com.jme3.light.DirectionalLight;
    import com.jme3.math.ColorRGBA;
    import com.jme3.math.Matrix3f;
    import com.jme3.math.Vector3f;
    import com.jme3.niftygui.NiftyJmeDisplay;
    import com.jme3.renderer.Camera;
    import com.jme3.renderer.ViewPort;
    import com.jme3.scene.CameraNode;
    import com.jme3.scene.Geometry;
    import com.jme3.scene.Node;
    import com.jme3.scene.control.CameraControl;
    import com.jme3.util.SkyFactory;
    import de.lessvoid.nifty.Nifty;
    import de.lessvoid.nifty.builder.LayerBuilder;
    import de.lessvoid.nifty.builder.PanelBuilder;
    import de.lessvoid.nifty.builder.ScreenBuilder;
    import de.lessvoid.nifty.controls.ListBox;
    import de.lessvoid.nifty.controls.button.builder.ButtonBuilder;
    import de.lessvoid.nifty.controls.label.builder.LabelBuilder;
    import de.lessvoid.nifty.controls.listbox.builder.ListBoxBuilder;
    import de.lessvoid.nifty.screen.DefaultScreenController;
    import de.lessvoid.nifty.screen.Screen;
    import de.lessvoid.nifty.screen.ScreenController;
    import java.util.ArrayList;
    import net.nakou.girdermadness.main.
    ;
    import net.nakou.girdermadness.niftycontroller.MainMenuController;
    import net.nakou.girdermadness.states.*;

/**

  • It’s an example of the use of state, Nakou’s Style, with Javadoc inside.

  • @author Nakou
    */
    public class MainMenuState extends AbstractAppState{

    private GirderMain app;
    private BitmapFont guiFont;
    private Node rootNode;
    private Node guiNode;
    private FlyByCamera flyCam;
    private Camera cam;
    private AssetManager assetManager;
    private CameraNode nodeCam;
    private InputManager inputManager;
    private Node graphNode;
    private Node guiLocalNode;
    private Nifty nifty;
    private Screen screen;
    private NiftyJmeDisplay nitfyDisplay;
    private AudioRenderer audioRenderer;
    private ViewPort guiViewPort;
    private ViewPort viewPort;
    private MainMenuController controller;
    private String MenuButtonSize = “10%”;
    private String MenuButtonSpace = “5%”;
    private boolean isInit;

    /*

    • Initialization of State
      */

    public void initialize(AppStateManager stateManager, GirderMain app){
    if(!this.initialized){
    super.initialize(stateManager, (SimpleApplication)app);
    this.app = app;
    guiViewPort = app.getGuiViewPort();
    viewPort = app.getViewPort();
    audioRenderer = app.getAudioRenderer();
    rootNode = this.app.getRootNode();
    guiNode = this.app.getGuiNode();
    flyCam = this.app.getFlyByCamera();
    cam = this.app.getCamera();
    assetManager = this.app.getAssetManager();
    inputManager = this.app.getInputManager();
    nitfyDisplay = this.app.getNiftyDisplay();
    nifty = this.app.getNifty();
    graphNode = new Node(“graphNode”);
    guiLocalNode = new Node(“guiLocalNode”);
    }
    }

    /*
    *

    • STATE CLASSICAL CODE

    */

    /**

    • Call when the states is enabled.
      */
      public void stateInit(){
      Magic.showInfos(1, app, guiLocalNode);
      initKey();
      init();
      setUpGraphics();
      setUpUI();
      isInit = true;
      }

    /**

    • Special Initialization from you personalized class.
      */
      public void init(){
      app.getStateManager().getState(FlyCamAppState.class).setEnabled(false);

    }

    /**

    • Set up all your graphics parts, it’s here to do.
      /
      public void setUpGraphics(){
      // Just a jazzy cube
      Geometry c = StaticGraphGems.Cube(0, 0, 0, 2, 2, 2, assetManager, “bro” , ColorRGBA.Gray);
      nodeCam = new CameraNode();
      nodeCam.setName(“MainNodeCam”);
      AmbientLight ambient = new AmbientLight();
      ambient.setColor(ColorRGBA.White);
      /
      * A white, directional light source */
      DirectionalLight sun = new DirectionalLight();
      sun.setDirection((new Vector3f(-0.5f, -0.5f, -0.5f)).normalizeLocal());
      sun.setColor(ColorRGBA.White);
      rootNode.addLight(sun);
      Node theCube = new Node(“theCube”);
      theCube.attachChild(c);
      graphNode.attachChild(theCube);
      setBottomCamera(theCube);
      graphNode.attachChild(SkyFactory.createSky(assetManager, “Textures/Terrains/Skies/SkyGameII.dds”, false));
      rootNode.attachChild(graphNode);
      rootNode.attachChild(nodeCam);
      }

    /**

    • Set up all your HUD or UI interface, it’s here to do.
      */
      public void setUpUI(){
      setUpNitfy();
      fillNitfy();
      guiNode.attachChild(guiLocalNode);
      isInit = true;
      }

    /**

    • Fill your interface/UI/HUD nifty.
      */
      public void fillNitfy() {
      nifty.loadStyleFile(“nifty-default-styles.xml”);
      nifty.loadControlFile(“nifty-default-controls.xml”);
      controller = new MainMenuController(this.app, this);
      //nifty.fromXml(“Interface/Nifty/YourCruiserMenuController.xml”, “start”, controller);
      nifty.addScreen(“MainMenu”, new ScreenBuilder(“MainMenuScreen”){{
      controller(controller); // Screen properties
      layer(new LayerBuilder(“MainLayer”) {{
      childLayoutVertical();
      panel(new PanelBuilder(“Menu”){{
      childLayoutVertical();
      paddingTop(“50%”);
      //valignCenter();
      alignCenter();
      control(new ButtonBuilder(“goToCreateMode_B”, “Creation Mode”){{
      height(MenuButtonSize);
      width(“75%”);
      alignCenter();
      valignCenter();
      interactOnClick(“goToCreateMode()”);
      }});
      control(new LabelBuilder(“”,“”){{
      height(MenuButtonSpace);
      width(“75%”);
      alignCenter();
      valignCenter();
      }});
      control(new ButtonBuilder(“goToMultiplayer_B”, “Multiplayer”){{
      height(MenuButtonSize);
      width(“75%”);
      alignCenter();
      valignCenter();
      interactOnClick(“goToMultiplayer()”);
      }});
      control(new LabelBuilder(“”,“”){{
      height(MenuButtonSpace);
      width(“75%”);
      alignCenter();
      valignCenter();
      }});
      control(new ButtonBuilder(“goToProof_B”, “Proof of Concept (Tutorial)”){{
      height(MenuButtonSize);
      width(“75%”);
      alignCenter();
      valignCenter();
      interactOnClick(“goToProof()”);
      }});
      control(new LabelBuilder(“”,“”){{
      height(MenuButtonSpace);
      width(“75%”);
      alignCenter();
      valignCenter();
      }});
      control(new ButtonBuilder(“goToOption_B”, “Options”){{
      height(MenuButtonSize);
      width(“75%”);
      alignCenter();
      valignCenter();
      interactOnClick(“goToOption()”);
      }});
      control(new LabelBuilder(“”,“”){{
      height(MenuButtonSpace);
      width(“75%”);
      alignCenter();
      valignCenter();
      }});
      control(new ButtonBuilder(“quit_B”, “Quit”){{
      height(MenuButtonSize);
      width(“75%”);
      alignCenter();
      valignCenter();
      interactOnClick(“quit()”);
      }});
      control(new LabelBuilder(“”,“”){{
      height(MenuButtonSpace);
      width(“75%”);
      alignCenter();
      valignCenter();
      }});
      control(new ButtonBuilder(“Credits_B”, “Credits”){{
      height(MenuButtonSize);
      width(“75%”);
      alignCenter();
      valignCenter();
      interactOnClick(“goToCredits()”);
      }});
      }});
      }});
      }}.build(nifty));
      screen = nifty.getScreen(“MainMenu”);
      nifty.gotoScreen(“MainMenu”);

    }

    /**

    • Set Up Nitfy parameters.
      */
      public void setUpNitfy(){
      nitfyDisplay = new NiftyJmeDisplay(
      assetManager, inputManager, audioRenderer, guiViewPort);
      nifty = nitfyDisplay.getNifty();
      guiViewPort.addProcessor(nitfyDisplay);
      }

    /**

    • Enable or disable the state in the stateManager.DONT FORGET TO CLEARMAPPING!!!.
    • @param enabled if true, call init(), esle call deleteStatesChanges().
      */
      @Override
      public void setEnabled(boolean enabled){
      super.setEnabled(enabled);
      if(enabled){
      stateInit();
      } else {
      if(isInit){
      deleteStatesChanges();
      if(nifty != null)
      if(nifty.getCurrentScreen().getScreenId().equals(“MainMenuScreen”))
      nifty.exit();
      }
      isInit = false;
      }
      }

    /**

    • Here, you have to write all the things you have change, dans detach the childs. Basicly, you can juste detach all childs of rootNode, guiNode, camNode and inputManager.
      */
      public void deleteStatesChanges(){
      graphNode.detachAllChildren();
      guiLocalNode.detachAllChildren();
      guiNode.detachChildNamed(“guiLocalNode”);
      rootNode.detachChildNamed(“graphNode”);
      rootNode.detachChild(nodeCam);
      inputManager.clearMappings();
      }

    /**

    • Just a standardized deleteStatesChanges() for jMonkeyEngine.
      */
      @Override
      public void cleanup(){
      deleteStatesChanges();
      }

    /**

    • This is what is call on the master update while in the main app, and it’s executed only if the state is enabled.
    • @param tpf is the time of a frame, see update loop on the main app.
      */
      @Override
      public void update(float tpf){
      Geometry c = (Geometry)((Node)graphNode.getChild(“theCube”)).getChild(“bro”);
      c.rotate((float)0.001 , (float)0.001, 0);
      }

    /*
    *

    • STATES SPECIFIC CODE
    • Here, you have to place all the method specific of your state.
      */

    /*

    • SupMetods invoke from MainMenuController.
      */
      public void quit(){
      app.stop();
      }

    public void goToCredits() {
    app.getStateManager().getState(MainMenuState.class).setEnabled(false);
    app.getStateManager().getState(CreditsState.class).setEnabled(true);
    }

    public void goToProof() {
    app.getStateManager().getState(MainMenuState.class).setEnabled(false);
    app.getStateManager().getState(TutorialState.class).setEnabled(true);
    }

    public void setBottomCamera(Node target){
    //create the camera Node
    CameraNode camNode = new CameraNode(“CameraBottomNode”, cam);
    //This mode means that camera copies the movements of the target:
    //camNode.setControlDir(CameraControl.ControlDirection.SpatialToCamera);
    //Attach the camNode to the target:
    target.attachChild(camNode);
    //Move camNode, e.g. behind and above the target:
    camNode.setLocalTranslation(new Vector3f(20, 0, 0));
    camNode.setLocalScale(15, 15, 15);
    //Rotate the camNode to look at the target:
    camNode.setEnabled(true);
    camNode.lookAt(target.getLocalTranslation(), Vector3f.UNIT_Y);
    nodeCam = camNode;
    }

    /*
    *

    • INPUTS AND LISTENERS

    */

    /**

    • Call on the init(), initialize all the key from your keyboard/mouse/joystick/penis you want.
      */
      private void initKey(){
      inputManager.addMapping(“DebugProof”, new KeyTrigger(KeyInput.KEY_F12));
      inputManager.addListener(ActionListener, “DebugProof”);
      }

    private ActionListener ActionListener = new ActionListener(){
    public void onAction(String name, boolean isPressed, float tpf) {
    if(name.equals(“DebugProof”) && isPressed){
    app.getStateManager().getState(MainMenuState.class).setEnabled(false);
    app.getStateManager().getState(TutorialState.class).setEnabled(true);
    }
    }
    };
    }
    [/java]

and there is the “TutorialState” :
[java]
public class TutorialState extends AbstractAppState{

private GirderMain app;
private BitmapFont guiFont;
private Node rootNode;
private Node guiNode;
private FlyByCamera flyCam;
private Camera cam;
private AssetManager assetManager;
private CameraNode nodeCam;
private InputManager inputManager;
private Node graphNode;
private Node guiLocalNode;
private Nifty nifty;
private Screen screen;
private NiftyJmeDisplay nitfyDisplay;
private AudioRenderer audioRenderer;
private ViewPort guiViewPort;
private ViewPort viewPort;
private boolean isInit;

/*
 * Bullet Style! 
 */

private Spatial sceneModel;
private BulletAppState bulletAppState;
private FlyCamAppState flyCamAppState;
private RigidBodyControl land;
private CharacterControl player;
private Node characterNode;
private Vector3f walkDirection = new Vector3f(0,0,0);
private Vector3f viewDirection = new Vector3f(0,0,0);
boolean leftStrafe = false, rightStrafe = false, forward = false, backward = false, 
        leftRotate = false, rightRotate = false;

/*
 * Initialization of State
 */

public void initialize(AppStateManager stateManager, GirderMain app){
    if(!this.initialized){
        super.initialize(stateManager, (SimpleApplication)app);
        this.app = app;
        guiViewPort = app.getGuiViewPort();
        viewPort = app.getViewPort();
        audioRenderer = app.getAudioRenderer();
        rootNode = this.app.getRootNode();
        guiNode = this.app.getGuiNode();
        flyCam = this.app.getFlyByCamera();
        cam = this.app.getCamera();
        assetManager = this.app.getAssetManager();  
        inputManager = this.app.getInputManager();
        graphNode = new Node("graphNode");
        guiLocalNode = new Node("guiLocalNode");
        nitfyDisplay = this.app.getNiftyDisplay();
        nifty = this.app.getNifty();
    }
}

/*
 * 
 * STATE CLASSICAL CODE
 * 
 */

/**
 * Call when the states is enabled.
 */
public void stateInit(){
    Magic.showInfos(2, app, guiLocalNode);
    init();
    initKey();
    setUpGraphics();
    setUpUI();
    isInit = true;
}

/**
 * Special Initialization.
 */
public void init(){
    // Init Bullets
    bulletAppState = new BulletAppState();
    app.getStateManager().attach(bulletAppState);
    bulletAppState.initialize(app.getStateManager(), app);
    bulletAppState.setEnabled(true);
    app.getStateManager().getState(FlyCamAppState.class).setEnabled(true);

    // Init Space with Floor and boxes and stuff
    this.createPhysicsWorld(bulletAppState.getPhysicsSpace());
    // Add a physics character to the world
    player = new CharacterControl(new CapsuleCollisionShape(1, 1), .1f);
    player.setPhysicsLocation(new Vector3f(0, 1, 0));
    player.setJumpSpeed(20);
    player.setFallSpeed(30);
    player.setGravity(30);
    characterNode = new Node("character node");
    Spatial model = StaticGraphGems.Cube(0, 1, 0, 0.5f, 1.8f, .1f, assetManager, "Caract", ColorRGBA.Green);
    characterNode.addControl(player);
    getPhysicsSpace().add(player);
    graphNode.attachChild(characterNode);
    characterNode.attachChild(model); 
    Magic.show("FlyCam active? : "+flyCam.isEnabled()+" flyCam is DragToRotate? : "+flyCam.isDragToRotate());
}

/**
 * Set up all your graphics parts, it's here to do.
 */
public void setUpGraphics(){
    rootNode.attachChild(graphNode);
}

/**
 * Set up all your HUD or UI interface, it's here to do.
 */
public void setUpUI(){
    setUpNitfy();
    fillNitfy();
    initCrossHairs();
    guiNode.attachChild(guiLocalNode);
}

/**
 * Fill your interface/UI/HUD nifty.
 */
public void fillNitfy() {
}

/**
 * Set Up Nitfy parameters.
 */
public void setUpNitfy(){
    /*nitfyDisplay = new NiftyJmeDisplay(
    assetManager, inputManager, audioRenderer, guiViewPort);
    nifty = nitfyDisplay.getNifty();
    guiViewPort.addProcessor(nitfyDisplay);*/
    //inputManager.clearMappings();
    //nifty.exit();
}

/**
 * Enable or disable the state in the stateManager.DONT FORGET TO CLEARMAPPING!!!!!!!.
 * @param enabled if true, call init(), esle call deleteStatesChanges().
 */
@Override
public void setEnabled(boolean enabled){
    super.setEnabled(enabled);
    if(enabled){
        stateInit();
    } else {
        if(isInit)
            deleteStatesChanges();
        isInit = false;
    }
}

/**
 * Here, you have to write all the things you have change, dans detach the childs. Basicly, you can juste detach all childs of rootNode, guiNode, camNode and inputManager.
 */
public void deleteStatesChanges(){
    graphNode.detachAllChildren();
    guiLocalNode.detachAllChildren();
    guiNode.detachChildNamed("guiLocalNode");
    rootNode.detachChildNamed("graphNode");
    //guiViewPort.clearScenes();
    inputManager.clearMappings();
}

/**
 * Just a standardized deleteStatesChanges() for jMonkeyEngine. 
 */
@Override
public void cleanup(){
    deleteStatesChanges();
}

/**
 * This is what is call on the master update while in the main app, and it's executed only if the state is enabled.
 * @param tpf is the time of a frame, see update loop on the main app.
 */
@Override
public void update(float tpf){
    if(isInit)
    {
        //Magic.show("Floor position" + graphNode.getChild("Floor").getLocalTranslation());
        //Magic.show("Player fall Speed : "+player.getFallSpeed());
        Vector3f camDir = cam.getDirection().clone().multLocal(0.6f);
        Vector3f camLeft = cam.getLeft().clone().multLocal(0.4f);
        walkDirection.set(0,0,0);
        if (leftStrafe) {
            walkDirection.addLocal(camLeft);
        } else
        if (rightStrafe) {
            walkDirection.addLocal(camLeft.negate());
        }
        if (forward) {
            walkDirection.addLocal(camDir);
        } else
        if (backward) {
            walkDirection.addLocal(camDir.negate());
        }
        player.setWalkDirection(walkDirection);
        cam.setLocation(player.getPhysicsLocation());
        Magic.show("Pos Cam : "+cam.toString()+" ; Fly Cam : "+flyCam.toString()+" ;");
    }
}

/*
 * 
 * STATES SPECIFIC CODE
 * Here, you have to place all the method specific of your state.
 */

/**
 * Here, we init Nifty for the first time! yay!
 */

public void initCrossHairs() {
    guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
    BitmapText ch = new BitmapText(guiFont, false);
    ch.setSize(guiFont.getCharSet().getRenderedSize());
    ch.setName("Crosshair");
    ch.setText("[-]"); // crosshairs
    ch.setLocalTranslation( // center
            app.getContext().getSettings().getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,
            app.getContext().getSettings().getHeight() / 2 + ch.getLineHeight() / 2, 0);
    guiLocalNode.attachChild(ch);
}    

/**
 * creates a simple physics test world with a floor, an obstacle and some test boxes
 * @param rootNode
 * @param assetManager
 * @param space
 */
public void createPhysicsWorld(PhysicsSpace space) {
    // Light and Sky
    AmbientLight light = new AmbientLight();
    light.setColor(ColorRGBA.LightGray);
    graphNode.addLight(light);
    graphNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Terrains/Skies/SkyGameII.dds", false));
    
    // Floor
    Geometry floorGeometry = StaticGraphGems.Cube( 0, -5, 0,140, 0.25f, 140, assetManager, "Floor", ColorRGBA.White);
    floorGeometry.getMaterial().setTexture("ColorMap", assetManager.loadTexture("Textures/Terrains/Land/Dalles.png"));
    Material standardMat = floorGeometry.getMaterial();
    land = new RigidBodyControl(0);
    floorGeometry.addControl(new RigidBodyControl(0));
    graphNode.attachChild(floorGeometry);
    space.add(floorGeometry);

    //movable boxes
    for (int i = 0; i < 12; i++) {
        Box box = new Box(0.25f, 0.25f, 0.25f);
        Geometry boxGeometry = new Geometry("Box", box);
        boxGeometry.setMaterial(standardMat);
        boxGeometry.setLocalTranslation(i, 5, -3);
        //RigidBodyControl automatically uses box collision shapes when attached to single geometry with box mesh
        boxGeometry.addControl(new RigidBodyControl(2));
        graphNode.attachChild(boxGeometry);
        space.add(boxGeometry);
    }

    //immovable sphere with mesh collision shape
    Sphere sphere = new Sphere(8, 8, 1);
    Geometry sphereGeometry = new Geometry("Sphere", sphere);
    Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    mat.setColor("Color", ColorRGBA.Orange);
    sphereGeometry.setMaterial(mat);
    sphereGeometry.setLocalTranslation(4, -4, 2);
    sphereGeometry.addControl(new RigidBodyControl(new MeshCollisionShape(sphere), 0));
    graphNode.attachChild(sphereGeometry);
    space.add(sphereGeometry);

}    

public void setThirdPersonCamera(Node target){
    nodeCam = StaticCam.ThirdCam(cam, target);
}

private PhysicsSpace getPhysicsSpace() {
  return bulletAppState.getPhysicsSpace();
}
/*
 * 
 * INPUTS AND LISTENERS
 * 
 */

/**
 * Call on the init(), initialize all the key from your keyboard/mouse/joystick/penis you want.
 */
private void initKey(){
    inputManager.addMapping("Strafe Left", new KeyTrigger(KeyInput.KEY_Q));
    inputManager.addMapping("Strafe Right", new KeyTrigger(KeyInput.KEY_D));
    inputManager.addMapping("Walk Forward", new KeyTrigger(KeyInput.KEY_Z));
    inputManager.addMapping("Walk Backward", new KeyTrigger(KeyInput.KEY_S));
    inputManager.addMapping("Jump", new KeyTrigger(KeyInput.KEY_SPACE));
    inputManager.addMapping("Rotate Left", new KeyTrigger(KeyInput.KEY_A));
    inputManager.addMapping("Rotate Right", new KeyTrigger(KeyInput.KEY_E));
    inputManager.addMapping("BackMenu", ConfigLoader.mainMenuKey.get(0));
    inputManager.addListener(ActionListener, "Strafe Left");
    inputManager.addListener(ActionListener, "Strafe Right");
    inputManager.addListener(ActionListener, "Rotate Left");
    inputManager.addListener(ActionListener, "Rotate Right");
    inputManager.addListener(ActionListener, "Walk Forward");
    inputManager.addListener(ActionListener, "Walk Backward");
    inputManager.addListener(ActionListener, "Jump");
    inputManager.addListener(ActionListener, "BackMenu");
    //flyCam.registerWithInput(inputManager);
}

private ActionListener ActionListener = new ActionListener(){
    public void onAction(String binding, boolean value, float tpf) {
        if (binding.equals("Strafe Left")) {
            if (value) {
                leftStrafe = true;
            } else {
                leftStrafe = false;
            }
        } else if (binding.equals("Strafe Right")) {
            if (value) {
                rightStrafe = true;
            } else {
                rightStrafe = false;
            }
        } else if (binding.equals("Rotate Left")) {
            if (value) {
                leftRotate = true;
            } else {
                leftRotate = false;
            }
        } else if (binding.equals("Rotate Right")) {
            if (value) {
                rightRotate = true;
            } else {
                rightRotate = false;
            }
        } else if (binding.equals("Walk Forward")) {
            if (value) {
                forward = true;
            } else {
                forward = false;
            }
        } else if (binding.equals("Walk Backward")) {
            if (value) {
                backward = true;
            } else {
                backward = false;
            }
        } else if (binding.equals("Jump")) {
            player.jump();
        } else if (binding.equals("BackMenu") && value){
            app.getStateManager().getState(TutorialState.class).setEnabled(false);
            app.getStateManager().getState(BulletAppState.class).setEnabled(false);
            app.getStateManager().getState(FlyCamAppState.class).setEnabled(false);
            app.getStateManager().getState(MainMenuState.class).setEnabled(true);
        }
    }
};

}
[/java]

NB : Magic.show is a System.out.println.