How to switch states

I have succeed in creating and initializing a state. But, Now I don’t know how to change or/add more state, so one state can monitor a certain condition to check, if other state should be initialized.



A guideline would help.



I am using the following 2 classes,

[java]/*

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

    */

    package RFP.core;



    import RFP.states.StartMenuState;

    import com.jme3.app.Application;

    import com.jme3.bullet.BulletAppState;



    /**

    *
  • @author irfan

    /

    public class RFPGame extends Application {



    BulletAppState bulletAppState = null;

    StartMenuState startMenuState = null;



    public RFPGame() {

    super();

    }



    @Override

    public void start() {

    super.start();

    }



    @Override

    public void initialize() {

    super.initialize();



    bulletAppState = new BulletAppState();

    startMenuState = new StartMenuState(this);



    getStateManager().attach(startMenuState);

    }



    @Override

    public void update() {

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



    if (speed == 0 || paused) {

    return;

    }



    float tpf = timer.getTimePerFrame() * speed;



    stateManager.update(tpf);



    // simpleUpdate(tpf);



    stateManager.render(renderManager);

    renderManager.render(tpf, context.isRenderable());

    stateManager.postRender();

    }



    public static void main(String… args){

    new RFPGame().start();

    }

    }



    [/java]



    State class :



    [java]/

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

    */

    package RFP.states;



    import RFP.core.RFPGame;

    import com.jme3.app.Application;

    import com.jme3.app.state.AbstractAppState;

    import com.jme3.app.state.AppStateManager;

    import com.jme3.input.KeyInput;

    import com.jme3.input.controls.ActionListener;

    import com.jme3.input.controls.KeyTrigger;

    import com.jme3.scene.Node;



    /**

    *
  • @author irfan

    */

    public class StartMenuState extends AbstractAppState implements ActionListener{

    private RFPGame RFPGameObject;

    private Node rootNode = new Node("StartMenu Root Node");

    private Node guiNode = new Node("StartMenu GUI Node");



    public StartMenuState(RFPGame RFPGameObject) {

    this.RFPGameObject = RFPGameObject;

    }



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

    if (name.equals("Lefts") && !isPressed) {

    System.out.println("StartMenu Start working!");

    }

    }



    @Override

    public void stateAttached(AppStateManager stateManager) {

    }



    @Override

    public void stateDetached(AppStateManager stateManager) {

    RFPGameObject.getViewPort().detachScene(guiNode);

    RFPGameObject.getViewPort().detachScene(rootNode);

    }



    @Override

    public void initialize(AppStateManager stateManager, Application app) {

    super.initialize(stateManager, app);



    // test method

    setupKey();

    RFPGameObject.getViewPort().attachScene(guiNode);

    RFPGameObject.getViewPort().attachScene(rootNode);

    }

    @Override

    public void update(float tpf) {

    super.update(tpf);

    // super :: public void update(float tpf){}



    rootNode.updateLogicalState(tpf);

    guiNode.updateLogicalState(tpf);

    rootNode.updateGeometricState();

    guiNode.updateGeometricState();

    }



    private void setupKey(){

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

    RFPGameObject.getInputManager().addListener(this, "W");

    // getInputManager() is inside Application Class

    }



    }



    [/java]

You can add a second state in the same manner you added the first:



[java]getStateManager().attach(someOtherState);[/java]



In terms of monitoring conditions (presumably monitoring a condition in one state to know what to do in another), that’s where the callback/listener design pattern comes into play.

Yeah, as sbook says, if you are attaching a new state that’s all you need to do. If you want to detach the existing state also then just detach it.



And you could save yourself quite a bit of code just be extending SimpleApplication (ie: the real ApplicationBaseClass) instead of exsting Application (ie: ApplicationClassNoOneShouldActuallyExtend) and then you don’t have to call the state manager stuff yourself. And you can just use the existing rootNode and guidNode rather than replacing them… and avoid having to manually manage those also.



…but that’s a separate issue.

sbook said:
In terms of monitoring conditions (presumably monitoring a condition in one state to know what to do in another), that's where the callback/listener design pattern comes into play.


@sbook that's exactly what I am asking for. Could you please enlighten me :)

A callback pattern may not be required in this case. It is a way of decoupling code that shouldn’t be coupled.



In this case, my guess is that you want to start your game when something in the menu is clicked, ie: moving to a new state. In that case, when they key is pressed or whatever just attach the new state and detach the StartMenuState.

@pspeed’s answer is really a better one. It depends on what you’re trying to accomplish. I didn’t have a specific use case in mind :slight_smile:



An example of callbacks, however, would be something like ActionListeners in Swing.

1 Like

@pspeed say, InGame and StartMenu, 2 states.



When StartMenu needed to be removed, I would specify the condition in the AbstractAppState::update() and at the moment, the old state would be detached and new state would get attached. Is that ok?



[java] public void update(float tpf) {

super.update(tpf);



if(condition == true) {

stateManager.attach(newState);

stateManager.detach(oldState);

}

rootNode.updateLogicalState(tpf);

guiNode.updateLogicalState(tpf);

rootNode.updateGeometricState();

guiNode.updateGeometricState();

}[/java]



Is that what you are saying? or is it complete?

Yep, that’s what I’m saying. More in the next post…

(but, somehow I don’t like the idea of continuously testing condition in the update loop, its so primitive)

To complicate things a little further, let me describe at a high level what I do in Mythruna.



Three states:

-MainMenuState (also a nifty ScreenController)

-LoadingState

-GameState



MainMenuState is the main screen controller for my main menus. When the user has drilled in to the appropriate level and clicks “Start Game” in the single player menu (multiplayer has some more states I won’t get into here), then I instantiate the GameState and the LoadingState. I give the LoadingState a reference to GameState. I attach both of these states and detach the MainMenuState.



LoadingState watched the loading progress of GameState. When it gets to 100% it detaches itself and now GameState is the only state. One cool side effect of this approach is that certain game features are ready right away even while the loading screen is up. For example, players can chat with other players while loading. For the things like that I want to avoid exposing that early then I just don’t turn them on until GameState reaches 100%… since GameState essentially knows this its not that big of a deal. Though technically, those features could be additional AppStates attached by the loading state.

iamcreasy said:
(but, somehow I don't like the idea of continuously testing condition in the update loop, its so primitive)


But that's what everything else is doing. InputManager is polled on update, for example.

You haven't told us what the condition is so we can only speculate. However, in a system where you are already polling 60+ times a second, an event/listener system can be overkill.

But tell us how the condition is set and maybe there is another idea.
1 Like
And you could save yourself quite a bit of code just be extending SimpleApplication (ie: the real ApplicationBaseClass) instead of exsting Application (ie: ApplicationClassNoOneShouldActuallyExtend) and then you don’t have to call the state manager stuff yourself. And you can just use the existing rootNode and guidNode rather than replacing them… and avoid having to manually manage those also.


Well, if I is the main class is gonna be something like that?

[java]package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;

/**
* test
* @author normenhansen
*/
public class RFPGame extends SimpleApplication {
StartMenuState startMenuState = null;

public static void main(String[] args) {
RFPGame app = new RFPGame();
app.start();
}

@Override
public void simpleInitApp() {
startMenuState = new StartMenuState(this);

getStateManager().attach(startMenuState);
}

@Override
public void simpleUpdate(float tpf) {
//TODO: add update code
}

@Override
public void simpleRender(RenderManager rm) {
//TODO: add render code
}
}
[/java]

and the state class is gonna be,

[java]/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package mygame;

/**
*
* @author irfan
*/
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/

import com.jme3.app.Application;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.scene.Node;

/**
*
* @author irfan
*/
public class StartMenuState extends AbstractAppState implements ActionListener{
private RFPGame RFPGameObject;
private Node rootNode = new Node("StartMenu Root Node");
private Node guiNode = new Node("StartMenu GUI Node");

public StartMenuState(RFPGame RFPGameObject) {
this.RFPGameObject = RFPGameObject;
}

public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals("W") && !isPressed) {
System.out.println("StartMenu Start working!");
}
}

@Override
public void stateAttached(AppStateManager stateManager) {
}

@Override
public void stateDetached(AppStateManager stateManager) {
RFPGameObject.getViewPort().detachScene(guiNode);
RFPGameObject.getViewPort().detachScene(rootNode);
}

@Override
public void initialize(AppStateManager stateManager, Application app) {
super.initialize(stateManager, app);

// test method
setupKey();
RFPGameObject.getViewPort().attachScene(guiNode);
RFPGameObject.getViewPort().attachScene(rootNode);
}
@Override
public void update(float tpf) {
super.update(tpf);
}

private void setupKey(){
RFPGameObject.getInputManager().addMapping("W", new KeyTrigger(KeyInput.KEY_W));
RFPGameObject.getInputManager().addListener(this, "W");
// getInputManager() is inside Application Class
}

}

[/java]

But, I see only 4 lines I was able to reduce,

[java] rootNode.updateLogicalState(tpf);
guiNode.updateLogicalState(tpf);
rootNode.updateGeometricState();
guiNode.updateGeometricState();[/java] & I dont have to create 2 new nodes every state.

You said something related to stateManager. But, I still find I have to attach and detach assets to root & gui node. What exactly I'm missing.

If your states are managing themselves properly then you can use the rootNode and guiNodes already provided and don’t need to create your own.



re: StateManager, in your version that extends Application directly, you had to call:

stateManager.update(tpf);

stateManager.render(renderManager);

stateManager.postRender();



Get those wrong and the state management comes crashing down around your knees… and there’s just no good reason not to just extend SimpleApplication and let it do the work for you.

re: StateManager, in your version that extends Application directly, you had to call:
stateManager.update(tpf);
stateManager.render(renderManager);
stateManager.postRender();


Sorry for sounding lame, but how can I get those wrong? I plan to handle everything in states. Those lines should remain as they are. What kind of modification of these lines can crash the game?

You likely just cut and pasted them from SimpleApplication so you probably won’t get them wrong but why bother with it anyway. There is absolutely no good reason to extend Application directly when SimpleApplication is handling all of that for you. And it’s a half dozen less things to worry about.



Code just hanging around is just asking to get messed with. Maybe you are messing around and invert these lines:

stateManager.render(renderManager);

renderManager.render(tpf, context.isRenderable());



…and weird stuff starts happening. Maybe JME decides it prefers some of these things be called in a different order. There is more than 0 risk and there are 0 reasons to do it manually. Seems like an easy win to me.