The com.jme3.app.state.AppState class is a customizable jME3 interface that allows you to control the global game logic – the overall game mechanics. (To control the behaviour of a Spatial, see Custom Controls instead. Controls and AppStates can be used together.)
There are situations during your game development where you think:
You can! This is what AppStates are there for. An AppState class is subset of (or an extension to) your application. Every AppState class has access to all fields in your main application (AssetManager, ViewPort, StateManager, InputManager, RootNode, GuiNode, etc) and hooks into the main update loop. An AppState can contain:
Each AppState lets you define what happens in the following situations:
update() loop.
To implement game logic:
app object.stateManager.attach(myAppState);).When you add several AppStates to one Application and activate them, their initialize() methods and update() loops are executed in the order in which the AppStates were added to the AppStateManager.
JME3 comes with a BulletAppState that implements Physical behaviour (using the jBullet library). You, for example, could write an Artificial Intelligence AppState to control all your enemy units. Existing examples in the code base include:
The AppState interface lets you initialize sets of objects, and hook a set of continously executing code into the main loop.
| AppState Method | Usage |
|---|---|
| initialize(asm,app) | When this AppState is added to the game, the RenderThread initializes the AppState and then calls this method. You can modify the scene graph from here (e.g. attach nodes). To get access to the main app, call: super.initialize(stateManager, app); this.app = (SimpleApplication) app; |
| cleanup() | This methid is executed after you remove the AppState from the game. Here you implement clean-up code for when this state is detached. You can modify the scene graph from here (e.g. detach nodes). |
| update(float tpf) | Here you implement the behaviour that you want to hook into the simpleUpdate() loop while this state is attached to the game. You can modify the scene graph from here. |
| isInitialized() | Your implementations of this interface should return the correct respective boolean value. (See AbstractAppState) |
| setActive(true) setActive(false) | Temporarily enables or disables an AppState. (See AbstractAppState) |
| isActive() | Test whether AppState is enabled or disabled. Your implementation should consider the boolean. (See AbstractAppState) |
| stateAttached(asm) stateDetached(asm) | The AppState knows when it is attached to, or detached from, the AppStateManager, and triggers these two methods. Don't modify the scene graph from here! (Typically not used.) |
| render(RenderManager rm) | Renders the state, plus your optional customizations. (Typically not used.) |
| postRender() | Called after all rendering commands are flushed, including your optional customizations. (Typically not used.) |
The AbstractAppState class already implements some common methods (isInitialized(), setActive(), isActive()) and makes creation of custom AppStates a bit easier. We recommend you extend AbstractAppState and override the remaining AppState methods: initialize(), setEnabled(), cleanUp().
Definition:
public class MyAppState extends AbstractAppState { private SimpleApplication app; private Node x = new Node("x"); // some custom class fields... public Node getX(){ return x; } // some custom methods... @Override public void initialize(AppStateManager stateManager, Application app) { super.initialize(stateManager, app); this.app = (SimpleApplication)app; // cast to a more specific class // init stuff that is independent of whether state is PAUSED or RUNNING this.app.getRootNode().attachChild(getX()); // modify scene graph... this.app.doSomething(); // call custom methods... } @Override public void cleanup() { super.cleanup(); // unregister all my listeners, detach all my nodes, etc... this.app.getRootNode().detachChild(getX()); // modify scene graph... this.app.doSomethingElse(); // call custom methods... } @Override public void setEnabled(boolean enabled) { // Pause and unpause super.setEnabled(enabled); if(enabled){ // init stuff that is in use while this state is RUNNING this.app.getRootNode().attachChild(getX()); // modify scene graph... this.app.doSomethingElse(); // call custom methods... } else { // take away everything not needed while this state is PAUSED ... } } @Override public void update(float tpf) { if(isEnabled()){ // do the following while game is RUNNING this.app.getRootNode().getChild("blah").scale(tpf); // modify scene graph... x.setUserData(...); // call some methods... } else { // do the following while game is PAUSED, e.g. play an idle animation. ... } } }
You define what an AppState does when Paused or Unpaused, in the setEnabled() and update() methods. Call myState.setEnabled(false) on all states that you want to pause. Call myState.setEnabled(true) on all states that you want to unpause.
The com.jme3.app.state.AppStateManager holds the list of AppStates for an application. AppStateManager ensures that activate AppStates can modify the scene graph, and that the update() loops of active AppStates is executed. There is one AppStateManager per application. You typically attach several AppStates to one AppStateManager, but the same state can only be attached once.
| AppStateManager Method | Usage |
|---|---|
| hasState(myState) | Is AppState object 'myState' attached? |
| getState(MyAppState.class) | Returns the first attached state that is an instance of a subclass of MyAppState.class. |
The AppStateManager's render(), postRender(), cleanUp() methods are internal, ignore them, users never call them directly.
You can only access other AppStates (read from and write to them) from certain places: From a Control's update() method, from an AppState's update() method, and from the SimpleApplication's simpleUpdate() loop. Don't mess with the AppState from other places, because from other methods you have no control over the order of modifications; the game can go out of sync because you can't know when (during which half-finished step of another state change) your modification will be performed.
You can use custom accessors to get data from AppStates, to set data in AppStates, or to trigger methods in AppStates.
this.app.getStateManager().getState(MyAppState.class).doSomeCustomStuffInThisState();
To access class fields of the application the way you are used to, initialize them to local variables:
private SimpleApplication app; private Node rootNode; private AssetManager assetManager; private AppStateManager stateManager; private InputManager inputManager; private ViewPort viewPort; private BulletAppState bullet; 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.bullet = this.stateManager.getState(BulletAppState.class); }