AppState with Nifty boilerplate

Hello. Longtime no see, Jmonkeys, huh! :smile:

So I’ve been starting to create a new game. And for this game this time around I wanted to properly use AppStates together with Nifty.

The best solution in the tutorials section is this: http://wiki.jmonkeyengine.org/doku.php/jme3:advanced:nifty_gui_scenarios

But it only explains it conceptually. And yes we all know the concept it is pretty simple. So after a bit of searching I found this: http://wiki.jmonkeyengine.org/doku.php/jme3:advanced:appstatesdemo but as the header of the page says, it is outdated.

So what is this post about? I’m posting what I think is an updated version of the AppState + Nifty boilerplate as per the last link I just provided on this post.

Hopefully the next programmer who sees this post will just copy and paste my example below and include it in his/her game. Also, maybe someone (including me) will update the out-dated example in the tutorial section.

Note: you have to create your own Start.xml, GameRunning.xml, Settings.xml files in order for the example below to run. These files can be empty files.

public class Main extends SimpleApplication {
        
        private Trigger pause_trigger = new KeyTrigger(KeyInput.KEY_BACK);
        private Trigger save_trigger = new KeyTrigger(KeyInput.KEY_RETURN);
        private boolean isRunning = false; // starts at startscreen
        private GameRunningState gameRunningState;
        private StartScreenState startScreenState;
        private SettingsScreenState settingsScreenState;
        
        
        public static void main(String[] args) {
            AppSettings settings = new AppSettings(true);
            settings.setTitle("My Game");
            settings.setResolution(1280, 720);
            settings.setUseJoysticks(true);
            
            
            Main app = new Main();
            app.setSettings(settings);
            app.setShowSettings(false);
            app.setDisplayStatView(false);
            app.setDisplayFps(false);
            app.start();
        }
    
        @Override
        public void simpleInitApp() {
    
            inputManager.clearMappings();
            inputManager.setCursorVisible(true);
            getFlyByCamera().setEnabled(false);
            
            startScreenState = new StartScreenState(this);
            gameRunningState = new GameRunningState(this);
            settingsScreenState = new SettingsScreenState(this);
            
            stateManager.attach(startScreenState);
            
            NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager, inputManager, audioRenderer, viewPort);
            Nifty nifty = niftyDisplay.getNifty();
            //nifty.setDebugOptionPanelColors(true);
            nifty.addXml("Interface/Start.xml");
            nifty.addXml("Interface/GameRunning.xml");
            nifty.addXml("Interface/Settings.xml");
            nifty.gotoScreen("start");
            guiViewPort.addProcessor(niftyDisplay);
    
            gameRunningState    = new GameRunningState(this);
            startScreenState    = new StartScreenState(this);
            settingsScreenState = new SettingsScreenState(this);
    
            stateManager.attach(startScreenState);
    
            inputManager.addMapping("Game Pause Unpause", pause_trigger);
            inputManager.addListener(actionListener, new String[]{"Game Pause Unpause"});
            inputManager.addMapping("Toggle Settings", save_trigger);
            inputManager.addListener(actionListener, new String[]{"Toggle Settings"});
    
        }
    
        @Override
        public void simpleUpdate(float tpf) {
    
        }
        
        private ActionListener actionListener = new ActionListener() {
            public void onAction(String name, boolean isPressed, float tpf) {
            System.out.println("key" + name);
            if (name.equals("Game Pause Unpause") && !isPressed) {
              if (isRunning) {
                stateManager.detach(gameRunningState);
                stateManager.attach(startScreenState);
                System.out.println("switching to startscreen...");
    
              } else {
                stateManager.detach(startScreenState);
                stateManager.attach(gameRunningState);
                System.out.println("switching to game...");
              }
              isRunning = !isRunning;
            } else if (name.equals("Toggle Settings") && !isPressed && !isRunning) {
              if (!isRunning && stateManager.hasState(startScreenState)) {
                stateManager.detach(startScreenState);
                stateManager.attach(settingsScreenState);
                System.out.println("switching to settings...");
              } else if (!isRunning && stateManager.hasState(settingsScreenState)) {
                stateManager.detach(settingsScreenState);
                stateManager.attach(startScreenState);
                System.out.println("switching to startscreen...");
              }
            }
          }
        };
    
        @Override
        public void simpleRender(RenderManager rm) {
            //TODO: add render code
            /* (optional) Make advanced modifications to frameBuffer and scene graph. */
        }
        
    }
    
    public class StartScreenState extends AbstractAppState implements ScreenController {
     
      private ViewPort viewPort;
      private Node rootNode;
      private Node guiNode;
      private AssetManager assetManager;
      private Node localRootNode = new Node("Start Screen RootNode");
      private Node localGuiNode = new Node("Start Screen GuiNode");
      private final ColorRGBA backgroundColor = ColorRGBA.Yellow;      
     
    public StartScreenState(SimpleApplication app){
        this.rootNode     = app.getRootNode();
        this.viewPort     = app.getViewPort();
        this.guiNode      = app.getGuiNode();
        this.assetManager = app.getAssetManager();  
      }
     
      @Override
      public void initialize(AppStateManager stateManager, Application app) {
        super.initialize(stateManager, app);
     
     
        rootNode.attachChild(localRootNode);
        guiNode.attachChild(localGuiNode);
        viewPort.setBackgroundColor(backgroundColor);
     
        /** init the screen */    
      }
     
      @Override
      public void update(float tpf) {
        /** the action happens here */
      } 
      
      @Override
      public void cleanup() {
        rootNode.detachChild(localRootNode);
        guiNode.detachChild(localGuiNode);
     
        super.cleanup();
      }
    
        public void bind(Nifty nifty, Screen screen) {
    
        }
    
        public void onStartScreen() {
    
        }
    
        public void onEndScreen() {
    
        }
    }
    
    public class GameRunningState extends AbstractAppState implements ScreenController {
     
        private ViewPort viewPort;
        private Node rootNode;
        private Node guiNode;
        private AssetManager assetManager;
        private Node localRootNode = new Node("Game Screen RootNode");
        private Node localGuiNode = new Node("Game Screen GuiNode");
        private final ColorRGBA backgroundColor = ColorRGBA.Green;    
     
        public GameRunningState(SimpleApplication app){
            this.rootNode     = app.getRootNode();
            this.viewPort     = app.getViewPort();
            this.guiNode      = app.getGuiNode();
            this.assetManager = app.getAssetManager();  
        }
     
      @Override
      public void initialize(AppStateManager stateManager, Application app) {
        super.initialize(stateManager, app);
     
     
        rootNode.attachChild(localRootNode);
        guiNode.attachChild(localGuiNode);
        viewPort.setBackgroundColor(backgroundColor);
     
        /** init the screen */    
      }
     
      @Override
      public void update(float tpf) {
        /** the action happens here */
      } 
      
      @Override
      public void cleanup() {
        rootNode.detachChild(localRootNode);
        guiNode.detachChild(localGuiNode);
     
        super.cleanup();
      }
    
        public void bind(Nifty nifty, Screen screen) {
    
        }
    
        public void onStartScreen() {
    
        }
    
        public void onEndScreen() {
    
        }
    }
    
    public class SettingsScreenState extends AbstractAppState implements ScreenController {
     
        private ViewPort viewPort;
        private Node rootNode;
        private Node guiNode;
        private AssetManager assetManager;
        private Node localRootNode = new Node("Settings Screen RootNode");
        private Node localGuiNode = new Node("Settings Screen GuiNode");
        private final ColorRGBA backgroundColor = ColorRGBA.DarkGray;    
     
        public SettingsScreenState(SimpleApplication app){
        this.rootNode     = app.getRootNode();
        this.viewPort     = app.getViewPort();
        this.guiNode      = app.getGuiNode();
        this.assetManager = app.getAssetManager();  
        }
     
      @Override
      public void initialize(AppStateManager stateManager, Application app) {
        super.initialize(stateManager, app);
     
     
        rootNode.attachChild(localRootNode);
        guiNode.attachChild(localGuiNode);
        viewPort.setBackgroundColor(backgroundColor);
     
        /** init the screen */    
      }
     
      @Override
      public void update(float tpf) {
        /** the action happens here */
      } 
      
      @Override
      public void cleanup() {
        rootNode.detachChild(localRootNode);
        guiNode.detachChild(localGuiNode);
     
        super.cleanup();
      }
    
        public void bind(Nifty nifty, Screen screen) {
    
        }
    
        public void onStartScreen() {
    
        }
    
        public void onEndScreen() {
    
        }
    }

Cool but why do you pass the app in the constructor when you get it in initialize?

@normen Could you quickly fix what I’m missing. I just put some barely functional code just to get it out of my system really. I’m still learning which is the correct way of doing things here.

Improved code as per @normen suggestion. Let me know if there’s something that can be improved.

public class Main extends SimpleApplication {
    
    private Trigger pause_trigger = new KeyTrigger(KeyInput.KEY_BACK);
    private Trigger save_trigger = new KeyTrigger(KeyInput.KEY_RETURN);
    private boolean isRunning = false; // starts at startscreen
    private GameRunningState gameRunningState;
    private StartScreenState startScreenState;
    private SettingsScreenState settingsScreenState;
    
    
    public static void main(String[] args) {
        AppSettings settings = new AppSettings(true);
        settings.setTitle("My Game");
        settings.setResolution(1280, 720);
        settings.setUseJoysticks(true);
        
        
        Main app = new Main();
        app.setSettings(settings);
        app.setShowSettings(false);
        app.setDisplayStatView(false);
        app.setDisplayFps(false);
        app.start();
    }

    @Override
    public void simpleInitApp() {

        inputManager.clearMappings();
        inputManager.setCursorVisible(true);
        getFlyByCamera().setEnabled(false);
                
        NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager, inputManager, audioRenderer, viewPort);
        Nifty nifty = niftyDisplay.getNifty();
        //nifty.setDebugOptionPanelColors(true);
        nifty.addXml("Interface/Start.xml");
        nifty.addXml("Interface/GameRunning.xml");
        nifty.addXml("Interface/Settings.xml");
        nifty.gotoScreen("start");
        guiViewPort.addProcessor(niftyDisplay);

        gameRunningState    = (GameRunningState) nifty.getScreen("hud").getScreenController();
        startScreenState    = (StartScreenState) nifty.getScreen("start").getScreenController();
        settingsScreenState = (SettingsScreenState) nifty.getScreen("pause").getScreenController();

        stateManager.attach(startScreenState);

        inputManager.addMapping("Game Pause Unpause", pause_trigger);
        inputManager.addListener(actionListener, new String[]{"Game Pause Unpause"});
        inputManager.addMapping("Toggle Settings", save_trigger);
        inputManager.addListener(actionListener, new String[]{"Toggle Settings"});

    }

    @Override
    public void simpleUpdate(float tpf) {

    }
    
    private ActionListener actionListener = new ActionListener() {
        public void onAction(String name, boolean isPressed, float tpf) {
        System.out.println("key" + name);
        if (name.equals("Game Pause Unpause") && !isPressed) {
          if (isRunning) {
            stateManager.detach(gameRunningState);
            stateManager.attach(startScreenState);
            System.out.println("switching to startscreen...");

          } else {
            stateManager.detach(startScreenState);
            stateManager.attach(gameRunningState);
            System.out.println("switching to game...");
          }
          isRunning = !isRunning;
        } else if (name.equals("Toggle Settings") && !isPressed && !isRunning) {
          if (!isRunning && stateManager.hasState(startScreenState)) {
            stateManager.detach(startScreenState);
            stateManager.attach(settingsScreenState);
            System.out.println("switching to settings...");
          } else if (!isRunning && stateManager.hasState(settingsScreenState)) {
            stateManager.detach(settingsScreenState);
            stateManager.attach(startScreenState);
            System.out.println("switching to startscreen...");
          }
        }
      }
    };

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
        /* (optional) Make advanced modifications to frameBuffer and scene graph. */
    }
    
}

public class StartScreenState extends AbstractAppState implements ScreenController {
 
  
    private SimpleApplication app;
    private Node              rootNode;
    private AssetManager      assetManager;
    private AppStateManager   stateManager;
    private InputManager      inputManager;
    private ViewPort          viewPort;
    private BulletAppState    physics;
    
    private final ColorRGBA backgroundColor = ColorRGBA.Yellow;   
  
    private Nifty nifty;
 
    @Override
    public void initialize(AppStateManager stateManager, Application app) {
        super.initialize(stateManager, app);
        
        this.app = (SimpleApplication) app; // can cast Application to something more specific
        this.rootNode     = this.app.getRootNode();
        this.assetManager = this.app.getAssetManager();
        this.stateManager = this.app.getStateManager();
        this.inputManager = this.app.getInputManager();
        this.viewPort     = this.app.getViewPort();
        this.physics      = this.stateManager.getState(BulletAppState.class);

        this.viewPort.setBackgroundColor(backgroundColor);

      /** init the screen */    
    }
 
    @Override
    public void update(float tpf) {
      /** the action happens here */
    } 

    @Override
    public void cleanup() {
      super.cleanup();
    }

    public void bind(Nifty nifty, Screen screen) {
        this.nifty = nifty;
    }

    public void onStartScreen() {

    }

    public void onEndScreen() {

    }

}

public class SettingsScreenState extends AbstractAppState implements ScreenController {
 
    private SimpleApplication app;
    private Node              rootNode;
    private AssetManager      assetManager;
    private AppStateManager   stateManager;
    private InputManager      inputManager;
    private ViewPort          viewPort;
    private BulletAppState    physics;
    
    private final ColorRGBA backgroundColor = ColorRGBA.DarkGray;   
  
    private Nifty nifty;
 
    @Override
    public void initialize(AppStateManager stateManager, Application app) {
        super.initialize(stateManager, app);
        
        this.app = (SimpleApplication) app; // can cast Application to something more specific
        this.rootNode     = this.app.getRootNode();
        this.assetManager = this.app.getAssetManager();
        this.stateManager = this.app.getStateManager();
        this.inputManager = this.app.getInputManager();
        this.viewPort     = this.app.getViewPort();
        this.physics      = this.stateManager.getState(BulletAppState.class);

        this.viewPort.setBackgroundColor(backgroundColor);

      /** init the screen */    
    }
 
  @Override
  public void update(float tpf) {
    /** the action happens here */
  } 
  
  @Override
  public void cleanup() {
    super.cleanup();
  }

    public void bind(Nifty nifty, Screen screen) {
        this.nifty = nifty;
    }

    public void onStartScreen() {

    }

    public void onEndScreen() {

    }

}

public class GameRunningState extends AbstractAppState implements ScreenController {
 
    private SimpleApplication app;
    private Node              rootNode;
    private AssetManager      assetManager;
    private AppStateManager   stateManager;
    private InputManager      inputManager;
    private ViewPort          viewPort;
    private BulletAppState    physics;
    
    private final ColorRGBA backgroundColor = ColorRGBA.Green;   
  
    private Nifty nifty;
 
    @Override
    public void initialize(AppStateManager stateManager, Application app) {
        super.initialize(stateManager, app);
        
        this.app = (SimpleApplication) app; // can cast Application to something more specific
        this.rootNode     = this.app.getRootNode();
        this.assetManager = this.app.getAssetManager();
        this.stateManager = this.app.getStateManager();
        this.inputManager = this.app.getInputManager();
        this.viewPort     = this.app.getViewPort();
        this.physics      = this.stateManager.getState(BulletAppState.class);

        this.viewPort.setBackgroundColor(backgroundColor);

      /** init the screen */    
    }
 
  @Override
  public void update(float tpf) {
    /** the action happens here */
  } 
  
  @Override
  public void cleanup() {
    super.cleanup();
  }

    public void bind(Nifty nifty, Screen screen) {
        this.nifty = nifty;
    }

    public void onStartScreen() {

    }

    public void onEndScreen() {

    }

}