Let users add text to an object

Hello all,

I’m struggling with setting up a textbox that allows a user to give comments about an object.

I want the next functionality to be possible:

  • click on any object in the 3D world
    - show / pop-up a textbox
  • let user enter any text
  • save the text together with a reference to the object clicked.

I can’t find any beginner’s documentation about this action.

Could you please help me? Any tips are more than welcome!

Maybe theres no specific tutorial for this (duh) but can’t you just combine the knowledge from the mouse picking and Nifty tutorials?

Thanks for your reply. I’ve been trying to combine both (using keys as input handlers), but don’t get it working. I get a null pointer exception when I try to switch to another screen.

Here’s my code:

Main:
[java]
package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.Trigger;
import com.jme3.niftygui.NiftyJmeDisplay;
import com.jme3.renderer.RenderManager;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.screen.Screen;
import de.lessvoid.nifty.screen.ScreenController;

public class Main extends SimpleApplication
implements ScreenController {

private Trigger feedback_trigger = new KeyTrigger(KeyInput.KEY_BACK);
private Trigger running_trigger = new KeyTrigger(KeyInput.KEY_RETURN);
private boolean isRunning = false; // starts at startscreen
private StartScreenState startScreenState;
private RunningScreenState runningScreenState;
private FeedbackScreenState feedbackScreenState;
public static SimpleApplication app;
private Nifty nifty;
private Screen screen;

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

@Override
public void simpleInitApp() {

setDisplayFps(false);
setDisplayStatView(false);
        
inputManager.addMapping("Feedback enter close", feedback_trigger);
inputManager.addListener(actionListener, new String[]{"Feedback enter close"});
inputManager.addMapping("Run evaluation", running_trigger);
inputManager.addListener(actionListener, new String[]{"Run evaluation"});       

startScreenState    = new StartScreenState(this);      
runningScreenState  = new RunningScreenState(this);
feedbackScreenState = new FeedbackScreenState(this);
    
NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay( assetManager, 
                                                    inputManager, 
                                                    audioRenderer, 
                                                    guiViewPort);

// Create a new NiftyGUI object /
nifty = niftyDisplay.getNifty();

// register nifty screencontrollers
nifty.registerScreenController(startScreenState, 
                               runningScreenState, 
                               feedbackScreenState);

// Read your XML and initialize your custom ScreenController 
nifty.addXml("Interface/fields.xml");
nifty.gotoScreen("start");

// attach the Nifty display to the gui view port as a processor
guiViewPort.addProcessor(niftyDisplay);

// disable the fly cam
flyCam.setDragToRotate(true);

}

//Nifty GUI ScreenControl methods
public void bind(Nifty nifty, Screen screen) {
this.nifty = nifty;
this.screen = screen;

}

private ActionListener actionListener = new ActionListener() {
public void onAction(String name, boolean isPressed, float tpf) {

  if (name.equals("Run evaluation") && !isPressed) {
    if (isRunning) {
        stateManager.detach(runningScreenState);
        stateManager.attach(startScreenState);     
    } else {
        stateManager.detach(startScreenState);
        stateManager.attach(runningScreenState);
    }
    
    // turn running on/off
    isRunning = !isRunning;
    
  } else if 
          
          (name.equals("Feedback enter close") && !isPressed && !isRunning) {
        if (!isRunning && stateManager.hasState(startScreenState)) {
            stateManager.detach(startScreenState);
            stateManager.attach(feedbackScreenState);
          } else if (!isRunning && stateManager.hasState(feedbackScreenState)) {
            stateManager.detach(feedbackScreenState);
            stateManager.attach(startScreenState);
        }
    }
  };

};

@Override
public void simpleUpdate(float tpf) {  
}

@Override
public void simpleRender(RenderManager rm) { 
}

public void onStartScreen() {       
}

public void onEndScreen() {
}

}

[/java]

StartScreenState (and similar for the other AppStates):
[java]
package mygame;

import com.jme3.app.Application;
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.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.input.InputManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Node;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.screen.Screen;
import de.lessvoid.nifty.screen.ScreenController;

public class StartScreenState extends AbstractAppState
implements ScreenController {

private ViewPort viewPort;
private Node rootNode;
private Node guiNode;
private AssetManager assetManager;
private InputManager inputManager;
private Node localRootNode = new Node(“Start Screen RootNode”);
private Node localGuiNode = new Node(“Start Screen GuiNode”);
private Nifty nifty;
private Screen screen;
private SimpleApplication app;

public StartScreenState(SimpleApplication app){
this.rootNode = app.getRootNode();
this.viewPort = app.getViewPort();
this.guiNode = app.getGuiNode();
this.assetManager = app.getAssetManager();

}

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

public void onStartScreen() { }

public void onEndScreen(){ }

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

// Read your XML and initialize your custom ScreenController 
nifty.gotoScreen("start");

BitmapFont guiFont = assetManager.loadFont(
        "Interface/Fonts/Default.fnt");
BitmapText displaytext = new BitmapText(guiFont);
displaytext.setSize(guiFont.getCharSet().getRenderedSize());
displaytext.move( 10, displaytext.getLineHeight() + 20,  0);
displaytext.setText("Start screen.");
localGuiNode.attachChild(displaytext);

}

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

}

/** jME3 AppState methods */

@Override
public void update(float tpf) {
/** jME update loop! */

}

@Override
public void stateAttached(AppStateManager stateManager) {
rootNode.attachChild(localRootNode);
guiNode.attachChild(localGuiNode);
}

@Override
public void stateDetached(AppStateManager stateManager) {
rootNode.detachChild(localRootNode);
guiNode.detachChild(localGuiNode);
}

}
[/java]

What is going wrong?

The most important parts of the NullPointerException were left out of your post. All I can tell you for sure is that something was null.

By the way… never override stateAttached() and stateDetached()… these are never the methods you want. They are mostly there for diagnostic purposes and should never actually be used unless you are a super-app-state-expert. I think you really want initialize() and cleanup().

Thanks for the quick reply. To be more clear, this is my error:

SEVERE: Uncaught exception thrown in Thread[LWJGL Renderer Thread,5,main]
java.lang.NullPointerException
at mygame.RunningScreenState.initialize(RunningScreenState.java:76)
at com.jme3.app.state.AppStateManager.initializePending(AppStateManager.java:219)
at com.jme3.app.state.AppStateManager.update(AppStateManager.java:249)
at com.jme3.app.SimpleApplication.update(SimpleApplication.java:238)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:151)
at com.jme3.system.lwjgl.LwjglDisplay.runLoop(LwjglDisplay.java:185)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:228)
at java.lang.Thread.run(Thread.java:724)

The first line of the error, mygame.RunningScreenState.initialize(RunningScreenState.java:76), refers to the line: nifty.gotoScreen(“hud”), but I don’t know why this is wrong. The other code is similar to the code of StartScreenState above.

The XML file is working fine (I believe):

<?xml version=“1.0” encoding=“UTF-8”?>
<nifty xmlns=“http://nifty-gui.sourceforge.net/nifty-1.3.xsd
xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance
xsi:schemaLocation=“http://nifty-gui.sourceforge.net/nifty-1.3.xsd http://nifty-gui.sourceforge.net/nifty-1.3.xsd”>

<useStyles filename=“nifty-default-styles.xml” />
<useControls filename=“nifty-default-controls.xml” />

<screen id=“start” controller=“mygame.StartScreenState”>
<layer id=“background” backgroundColor="#000f">
<!-- … -->
</layer>
<layer id=“foreground” backgroundColor="#0000" childLayout=“vertical”>

  &lt;panel id="panel_top" height="20%" width="100%" align="center" childLayout="vertical"
         backgroundColor="#f008"&gt; 
      &lt;text text="Title" font="Interface/Fonts/Default.fnt" width="100%" height="100%" wrap="true"/&gt; 
  &lt;/panel&gt;
  &lt;panel id="panel_mid" height="50%" width="100%" align="center" childLayout="vertical"
         backgroundColor="#0f08"&gt;            
  &lt;/panel&gt;
  &lt;panel id="panel_bottom" height="30%" width="100%" align="center" childLayout="horizontal"
         backgroundColor="#00f8"&gt;  
    &lt;panel id="panel_bottom_left"  height="100%" width="50%" align="center" childLayout="vertical"
         backgroundColor="#f058"&gt;
      &lt;control name="button" label="Start" id="StartButton" align="center" valign="center" 
                visibleToMouse="true" &gt; 

      &lt;/control&gt;
    &lt;/panel&gt; 
    &lt;panel id="panel_bottom_right" height="100%" width="50%" align="center" childLayout="vertical"
         backgroundColor="#f082"&gt;
      &lt;control name="button" label="Quit" id="QuitButton" align="center" valign="center" 
               visibleToMouse="true" &gt; 

      &lt;/control&gt;  
    &lt;/panel&gt;               
  &lt;/panel&gt;
&lt;/layer&gt;

</screen>

<screen id=“hud” controller=“mygame.RunningScreenState”>
<layer id=“background” >
<!-- … -->
</layer>
<layer id=“foreground” childLayout=“vertical”>

  &lt;panel id="panel_top" height="10%" width="100%" align="center" childLayout="vertical"
         backgroundColor="#f008"&gt; 
      &lt;text text="HUD Screen" font="Interface/Fonts/Default.fnt" width="100%" height="100%" wrap="true"/&gt; 
  &lt;/panel&gt;
  &lt;panel id="panel_bottom" height="90%" width="100%" align="center" childLayout="vertical"&gt;
  &lt;/panel&gt;
&lt;/layer&gt;

</screen>

Well, when app state initialize() is called your state won’t have been bound to nifty yet I think. Some printlns or debugging will show you the order these things are happening.

I got it working now. For those with the same problem, this is what I did:

  • I attached all AppStates to the stateManager (main problem in the code above);
  • I used enabling / disabling for switching to another screen (instead of attaching / detaching);
  • To start correctly, I only enabled the start AppState (disabled the others) and at the same time let Nifty go to the Nifty start screen;
  • In the ActionListener, I let jME respond to got to another Nifty screen and another AppState simultaneously.