I’m learning jMonkey and I decided to try my hand at Nifty Gui. I can’t seem to find the right way to switch from screen to screen. I feel that I’m not too far from a simple working example, but it seems that two things are missing:
1- Why should I need to reload from the xml file each time I want to switch screen, it’s very slow? There’s gotta be something missing.
2- When I debug the code, I see that my screencontrollers are initialized. So the field “Main app” is loaded at that time. But when I get to click on a button that calls a method from the “app” field, the app field is null…
Oh, I forgot to mention. Everything works fine on the first screen. Problems appear when I switch to another screen.
The project is pretty small, so here are the 5 files required to make this work:
Oh, maybe in case the problem wasn’t stated clearly enough, since the app field isn’t loaded properly in screen2 and screen3, any calls made to the app field crashes. (example: buttons makeCubeGreen, makeCubeBlue, switchToScreen3, etc)
Hi guys, I took another step, but I’m not sure I’m headed in the right direction… I use my little POC to make different tests and I get to a point where if I load each screen from XML when I attach the appstate, it works, but if I do not load from xml and simply call a “gotoscreen”, nothings works. I am really lost here. I’m not sure if the problems is how I use Nifty or I use AppStates…
The xml file didn’t change in this test. Se the first post for the XML file.
In the main class, I centralized the niftyDisplay and nifty objects and screencontrollers now refer to the main class fields.
//Allow mouse be visible Much easier to interact with gui
inputManager.setCursorVisible(true);
//Create nifty Gui display, load screen from the xml file and display screen, standard behavior
//only for the main screen, no need to load from xml for other screens
niftyDisplay = new NiftyJmeDisplay(assetManager,
inputManager,
audioRenderer,
guiViewPort);
nifty = niftyDisplay.getNifty();
guiViewPort.addProcessor(niftyDisplay);
/**
Attach the gui class to the application
/
stateManager.attach(niftyScreen1);
}
}
[/java]
The first screen controller initialize method changed to verify if the screen exists in nifty and if it doesn’t it is loaded from the XML file. The first screen works just fine.
[java]/
To change this template, choose Tools | Templates
and open the template in the editor.
*/
package niftypoc;
import com.jme3.app.Application;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.screen.Screen;
import de.lessvoid.nifty.screen.ScreenController;
import java.util.logging.Logger;
/**
*
@author vchevali
*/
public class screen1 extends AbstractAppState implements ScreenController {
//standard field
private Nifty nifty;
//standard field
private Screen screen;
//standard field
private Main app;
/**
Standard code to get logger
/
private static final Logger logger = Logger.getLogger(Main.class.getName());
public void bind(Nifty nifty, Screen screen) {
//standard code to bind controller to nifty gui elements
this.nifty = nifty;
this.screen = screen;
}
public void onStartScreen() {
//nothing
}
public void onEndScreen() {
//nothing
}
/* jME3 AppState methods /
@Override
public void initialize(AppStateManager stateManager, Application app) {
//standard code to bid app state to the application
public void stateAttached(AppStateManager stateManager) {
//Method used to run code when this app state is attached to the application
}
@Override
public void stateDetached(AppStateManager stateManager) {
//Method used to run code when this app state is attached from the application
}
/**
Method called by the btnGoToScreen2
*/
public void btnGoToScreen2Click() {
app.getStateManager().detach(app.niftyScreen1);
app.getStateManager().attach(app.niftyScreen2);
}
/**
Method called by the btnRedCube
/
public void btnRedCubeClick() {
app.MakeCubeRed();
}
}
[/java]
The second screen controller initialize method changed to verify if the screen exists in nifty and if it doesn’t it is loaded from the XML file. It is pretty much identical to the first screen controller… But by the time the application gets there, the XML file is loaded. So rather than reloading from the XML file, a simple Nifty gotoscreen is called. But then, when the second screen is loaded, nothing works.
[java]/
To change this template, choose Tools | Templates
and open the template in the editor.
*/
package niftypoc;
import com.jme3.app.Application;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.screen.Screen;
import de.lessvoid.nifty.screen.ScreenController;
import java.util.logging.Logger;
/**
*
@author vchevali
*/
public class screen2 extends AbstractAppState implements ScreenController {
//standard field
private Nifty nifty;
//standard field
private Screen screen;
//standard field
private Main app;
/**
Standard code to get logger
/
private static final Logger logger = Logger.getLogger(Main.class.getName());
public void bind(Nifty nifty, Screen screen) {
//standard code to bind controller to nifty gui elements
this.nifty = nifty;
this.screen = screen;
}
public void onStartScreen() {
//nothing
}
public void onEndScreen() {
//nothing
}
/* jME3 AppState methods /
@Override
public void initialize(AppStateManager stateManager, Application app) {
//standard code to bid app state to the application
What I don’t understand is this: when the second screen controller is initialized, it receives two parameters: a state manager and the application. These parameters have values. I store the reference to the app in the screen controller app field. But when I click on a button of the second screen, the app field is null. How is that possible? Can someone help me please?
Well, I found 2 solutions. I am unhappy with both of them, but at least it works:
First solution: make one xml file for each screen and load it form xml each time you change screen. That makes switching form screen to screen pretty slow, but since the xml files are pretty small, it’s not decades long. All code controlling screen is separated in different screen controllers: easy to manage, not messy. But not great at runtime since it’s slow.
Second solution: make only one screen controller that manages all screens. Make only one XML file that contains all the screens. It flows really well at runtime (no intteruption to load), but it makes for one ugly screen controller. You have to make sure not to mess up which screen calls which method, etc. When I switch from one nifty screen to another, I simply call “nifty.gotoscreen()” and I don’t attach/detach screen controllers. I only attach the big screen controller when the app is initialized.
here is the code for solution #2. It’s easy to go from the code of my previous posts to solution #1. It’s not worth uploading it here, unless someone asks me for it.
nifty screen controller that manages all nifty screens:
[java]/*
To change this template, choose Tools | Templates
and open the template in the editor.
*/
package niftypoc;
import com.jme3.app.Application;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.screen.Screen;
import de.lessvoid.nifty.screen.ScreenController;
import java.util.logging.Logger;
/**
*
@author vchevali
*/
public class NiftyScreenController extends AbstractAppState implements ScreenController {
//standard field
private Nifty nifty;
//standard field
private Screen screen;
//standard field
private Main app;
/**
Standard code to get logger
/
private static final Logger logger = Logger.getLogger(Main.class.getName());
public void bind(Nifty nifty, Screen screen) {
//standard code to bind controller to nifty gui elements
this.nifty = nifty;
this.screen = screen;
}
public void onStartScreen() {
//nothing
}
public void onEndScreen() {
//nothing
}
/* jME3 AppState methods /
@Override
public void initialize(AppStateManager stateManager, Application app) {
//standard code to bid app state to the application
<control name=“button” id=“btnGoToScreen1” label=“Go to screen 1” width=“100%”>
<interact onClick=“btnGoToScreen1Click()”/>
</control>
</panel>
</panel>
</layer>
</screen>
</nifty>
[/xml]
I think I’m gonna go with solution #2. Runtime experience is more important I believe. And I’ll be careful not to mess my code up. It’s a shame though.
If someone knows how to make this work in a better way, please tell me.
I would suggest a slightly different approach to option 2.
You could have what I call “delegate screen controllers”. What I mean by this is you still have 1 screen controller as you option 2. But isntead of dropping all the code in the same screencontroller, you have sub-controllers.which contain the actual code for the screen in question. I think you should even be able to change out the screen controller at run-time so when you switch to screen 2, you put in the screencontroller for screen2. But I’m not 100% sure about this and not currently in a position to test it for you.
But with sub screen controllers, your main screen controller would just have a bunch of place-holder methods which refer to the sub screen controller for the actual functionality. Makes it a bit cleaner code wise.
Wow, okay, now I’ve got something that really works well. A great many thanks ractoc!
I followed your suggestion. Now, my application contains 5 classes and 1 Interface:
The Main class (of course)
The NiftyScreenController (that manages all nifty stuff)
An interface explaining what a SubScreenController should be.
3 SubScreenControllers that contain the code for each nifty screen (they implement the interface).
Basically, the ScreenController loads from XML (all screens in the same XML file) then it creates and HashMap to contain all SubScreencontrollers with the key being the screen name, of course.) I also have a field containing the currently active SubScreenController. So the ScreenController loads up the start screen and sets the field toward the right SubScreenController. I use reflection to call the proper method of the SubScreenController and I have a method to switch from one screen to another, changing nifty display and the current active SubScreenController.
I don’t know if I followed the most logical approach, but please bear with me as I am new to java.
Here goes…
Main class:
[java]package niftypoc;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.AppState;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.niftygui.NiftyJmeDisplay;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import de.lessvoid.nifty.Nifty;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
test
@author normenhansen
*/
public class Main extends SimpleApplication {
Geometry geom;
/**
One Nifty Screen controller to rule them all
*/
public AppState niftyScreen1 = new NiftyScreenController();
/Nifty shared field/
public NiftyJmeDisplay niftyDisplay;
/standard field/
public Nifty nifty;
/**
Standard code to get logger
*/
private static final Logger logger = Logger.getLogger(Main.class.getName());
public static void main(String[] args) {
Main app = new Main();
app.start();
}
@Override
public void simpleInitApp() {
setupInterface();
Box b = new Box(Vector3f.ZERO, 1, 1, 1);
geom = new Geometry("Box", b);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.Blue);
geom.setMaterial(mat);
rootNode.attachChild(geom);
}
@Override
public void simpleUpdate(float tpf) {
//TODO: add update code
}
@Override
public void simpleRender(RenderManager rm) {
//TODO: add render code
}
public void makeCubeRed() {
logger.log(Level.INFO, "Color was changed to Red");
<control name="button" id="btnGoToScreen1" label="Go to screen 1" width="100%">
<interact onClick="goToScreen(start)"/>
</control>
</panel>
</panel>
</layer>
</screen>
</nifty>
[/xml]
The only downside I see to this method is that I lose the possibility to pass parameters in a function called from a nifty screen (since the parameter is always the name of the method to call). But I can live with that since the runtime is great and the code is neatly spread across useful classes.
On second thought, I pushed the system a step further. Rather than having to hardcode the creation of each SubScreenController, once I found all the screen names in the xml file, I loop through the list of found screen names and instanciate the SubScreenControllers by reflection when I add them to the SubScreenControllers HashMap. As long as the screen names match with the subcontroller class name, it works!
If anyone is interested by the final code, I can post it here.
Just started experimenting with jMonkeyEngine Beta1 & Nifty and got totally confused about why switching screens did not work. I had several nifty-xml-files, one for each screen.
However, reading your post I tried to put them all in one XML-file. That worked, haven’t come a long way with the controllers yet but I guess that you could simplify your approach like this.
Then in your java-code you initialize nifty like this:
[java]nifty.fromXml(
“Interface/nifty.xml”,
“startscreen”,
new com.acme.nifty.Controller1(whatever),
new com.acme.nifty.Controller2(someother));[/java]
Seems that Nifty will match all the controllers you pass into fromXml against the classname in the actual XML and select the right controller. So now you only have to do nifty.gotScreen(“id2”)
So it mostly works as you’d want except that it seems like you have to put everything in one XML-file (or re-initialize nifty with another XML-file as you point out).
If the controllers have default, no-args-constructors you don’t even have to create them and pass them into fromXml, Nifty will do it for you it seems.
No. It works with separated screen xml files as well. I do that all the time, and it works like a boss. It didn’t work for you, because you’ve used the fromXml() method, then it won’t work at all, because it will just read the data from only one xml file. For several xml files, that’s more elegant, you have to use addXml() and it’ll work.