Edit: 2010.04.15 this approach is obsolete as there is a JME2 renderer for nifty available
Please refer to thread: http://www.jmonkeyengine.com/forum/index.php?topic=13658.0
I have been using jme for about 3 month now and I want to thank you all for saving me from countless hours of trial and error.
So i thought i want to give back something to the forum.
About 2 weeks ago i came to the point where i had to think about a GUI.
I looked at GBUI, FenGUI, TWL (l33tlabs) and Nifty-GUI.
The webstart demo of Nifty GUI 'blew me out of the water' :P , so i started to try to integrate it into my jme2 project.
(wanted to have, this gui looks better than the whole rest of my game XD)
I managed to get the nifty gui rendered above the jme scene and mouse and keyboard are mirrored from jme into the nifty framework. Note: works only with lwjgl as nifty is rendering direct to lwjgl.
First steps:
Install nifty 1.1 from source (http://nifty-gui.lessvoid.com/) as described in their wiki.
Get the demo de.lessvoid.nifty.examples.console running.
(all four svn links must be exported: nifty, default-controls, examples and style,
lots of eclipse fiddling with source, lib and native paths,
dont forget to point the lwjgl lib to your jme install)
If you can run the native nifty example from eclipse, the following code should work,
only one little change in the nifty tree is needed:
You should replace /nifty-examples/src/main/resources/background.png with a complete transparent one or
you will not see the monkey through it!
Updated 31.03.2010 to include the GL13.glActiveTexture(GL13.GL_TEXTURE0) call hinted by core-dump
and added a gamestate example:
Updated 13.04.2010 this code and thread is obsolete as there will be a Nifty Renderer for JME2 soon.
The simplegame test main:
package descented.tests;
import java.nio.IntBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.lwjgl.BufferUtils;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GL11;
import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.input.InputSystem;
import com.jme.input.KeyInput;
import com.jme.input.MouseInput;
import com.jme.input.joystick.JoystickInput;
import com.jme.math.Vector3f;
import com.jme.scene.shape.Box;
import com.jme.scene.state.TextureState;
import com.jme.util.TextureManager;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.lwjglslick.render.RenderDeviceLwjgl;
import de.lessvoid.nifty.lwjglslick.sound.SlickSoundDevice;
import de.lessvoid.nifty.sound.SoundSystem;
import de.lessvoid.nifty.tools.TimeProvider;
import descented.input.JmeNiftyInputSystem;
/**
* <code>TestNiftyGUI</code> is an example implementation of Nifty-GUI 1.1 for JME2
*
* @author larynx.ckc@gmx.net
*/
public class TestNiftyGUI extends SimpleGame
{
// the Nifty GUI object
private Nifty nifty = null;
// screen dimension for the nifty lwjgl rendering
private int viewportWidth;
private int viewportHeight;
private static final Logger logger = Logger.getLogger(TestNiftyGUI.class.getName());
/**
* Main entry point for the nifty test,
*
* @param args
*/
public static void main(String[] args)
{
try
{
JoystickInput.setProvider(InputSystem.INPUT_SYSTEM_LWJGL);
}
catch (Exception e)
{
logger.logp(Level.SEVERE, TestNiftyGUI.class.toString(), "main", "Exception", e);
}
TestNiftyGUI app = new TestNiftyGUI();
app.setConfigShowMode(ConfigShowMode.AlwaysShow);
app.start();
}
/**
* builds the scene.
*
* @see com.jme.app.SimpleGame#initGame()
*/
protected void simpleInitGame()
{
display.setTitle("Nifty GUI JME2 SimpleGame Test");
// Allow mouse to leave the game window (very useful on debugging :)
Mouse.setGrabbed(false);
// Set mouse cursor
MouseInput.get().setHardwareCursor(TestNiftyGUI.class.getClassLoader().getResource("jmetest/data/cursor/cursor1.png"));
// Create the monkey box
Box floor = new Box("Floor", new Vector3f(), 100, 1, 100);
floor.setModelBound(new BoundingBox());
floor.updateModelBound();
floor.getLocalTranslation().y = -20;
TextureState ts = display.getRenderer().createTextureState();
// The monkey texture
Texture t0 = TextureManager.loadTexture(
TestNiftyGUI.class.getClassLoader().getResource("jmetest/data/images/Monkey.jpg"),
Texture.MinificationFilter.Trilinear,
Texture.MagnificationFilter.Bilinear);
t0.setWrap(Texture.WrapMode.Repeat);
ts.setTexture(t0);
floor.setRenderState(ts);
floor.scaleTextureCoordinates(0, 5);
rootNode.attachChild(floor);
// Show debug grid
//rootNode.attachChild(new AxisGrid().buildGeometry());
// Get the screen dimension for nifty
viewportWidth = DisplaySystem.getDisplaySystem().getWidth();
viewportHeight = DisplaySystem.getDisplaySystem().getHeight();
// Create a jme <-> nifty input binding
JmeNiftyInputSystem jmeNiftyinput = new JmeNiftyInputSystem();
// Add jme mouse listener
MouseInput.get().addListener(jmeNiftyinput);
// Add jme keyboard listener
KeyInput.get().addListener(jmeNiftyinput);
// Create a nifty
nifty = new Nifty(new RenderDeviceLwjgl(),
new SoundSystem(new SlickSoundDevice()),
jmeNiftyinput,
new TimeProvider());
// This starts the GUI - nearly everything is read from xml
// location nifty-examples/src/main/resources/console/console.xml
nifty.fromXml("console/console.xml", "start");
// Bind the keyboard input to this new nifty
jmeNiftyinput.bind(nifty);
}
@Override
protected void simpleUpdate()
{
}
/**
* Black opengl render magic needed for nifty integration (lots of guessing in here)
*/
@Override
protected void simpleRender()
{
// Push JME attributes onto the stack
GL11.glPushAttrib(GL11.GL_LIGHTING_BIT | GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_ENABLE_BIT | GL11.GL_COLOR_BUFFER_BIT | GL11.GL_CURRENT_BIT | GL11.GL_TEXTURE_BIT | GL11.GL_TRANSFORM_BIT | GL11.GL_VIEWPORT_BIT);
GL11.glMatrixMode(GL11.GL_PROJECTION);
// Put current projection matrix on the stack
GL11.glPushMatrix();
GL11.glLoadIdentity();
GL11.glOrtho(0, viewportWidth, viewportHeight, 0, -9999, 9999);
GL11.glMatrixMode(GL11.GL_MODELVIEW);
// Put current modelview matrix on the stack
GL11.glPushMatrix();
GL11.glLoadIdentity();
// Prepare Rendermode - copied from nifty LwjglInitHelper
GL11.glDisable(GL11.GL_DEPTH_TEST);
GL11.glEnable(GL11.GL_BLEND);
GL11.glDisable(GL11.GL_CULL_FACE);
GL11.glEnable(GL11.GL_ALPHA_TEST);
GL11.glAlphaFunc(GL11.GL_NOTEQUAL, 0);
GL11.glDisable(GL11.GL_LIGHTING);
GL11.glDisable(GL11.GL_TEXTURE_2D);
// Set back to first texture unit so GUI displays properly - from FenGUI example by Core-Dump
// avoids a texture 'spill over' between JME and nifty
GL13.glActiveTexture(GL13.GL_TEXTURE0);
//GL11.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
//GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
//GL11.glEnable(GL11.GL_TEXTURE_2D);
//
// render the GUI
nifty.render(false);
// check gl error at least once per frame
int error = GL11.glGetError();
if (error != GL11.GL_NO_ERROR)
{
String glerrmsg = GLU.gluErrorString(error);
logger.warning("OpenGL Error: (" + error + ") " + glerrmsg);
}
// Get JME modelview and projection matrix from the stack
GL11.glMatrixMode(GL11.GL_MODELVIEW);
GL11.glPopMatrix();
GL11.glMatrixMode(GL11.GL_PROJECTION);
GL11.glPopMatrix();
// Get JME attributes from the stack
GL11.glPopAttrib();
}
}
The Nifty - Jme input binding: (F11, F12 and PAUSE have been binded in here)
package descented.input;
import java.util.ArrayList;
import java.util.List;
import org.lwjgl.input.Keyboard;
import com.jme.input.MouseInputListener;
import com.jme.input.KeyInputListener;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.input.mouse.MouseInputEvent;
import de.lessvoid.nifty.spi.input.InputSystem;
/**
* Gets the mouse events from JME and dispatches them on request to a nifty class
* Is used in the construction of a nifty menu object
* @author larynx.ckc@gmx.net
*
*/
public class JmeNiftyInputSystem implements /*Nifty*/InputSystem, /*JME*/MouseInputListener, /*JME*/KeyInputListener
{
private boolean lastLeftMouseDown = false;
// list of mouse events, gets filled by JME events and gets polled/resetted by Nifty GUI class
private final List<MouseInputEvent> events = new ArrayList<MouseInputEvent>();
// The nifty GUI which this input system is feeding keyboard into (mouse is polled from outside)
private Nifty nifty = null;
/**
* Creates a new inputsystem to serve as binding between JME and Nifty mouse/keyboard
* @param nifty
*/
public JmeNiftyInputSystem()
{
this.nifty = null;
}
public void bind(Nifty nifty)
{
this.nifty = nifty;
}
/**
* Returns a list of mouse events which have happened since the last poll from this function.
* Event list gets emptied after this call.
* Gets called from nifty
*/
public List<MouseInputEvent> getMouseEvents()
{
// create a clone to return
List<MouseInputEvent> resultList = new ArrayList<MouseInputEvent>(events);
// clear master list
events.clear();
return resultList;
}
/**
* JME keyboard input listener, F11/F12 enables debug console, PAUSE makes a screenshot
* @param character
* @param keyCode
* @param pressed
*/
public void onKey( char character, int keyCode, boolean pressed )
{
nifty.keyEvent(keyCode, character, pressed);
if (pressed && keyCode == Keyboard.KEY_PAUSE)
{
DisplaySystem.getDisplaySystem().getRenderer().takeScreenShot( "NiftyScreenShot" );
}
if (pressed && keyCode == Keyboard.KEY_F12)
{
nifty.toggleElementsDebugConsole();
}
else if (pressed && keyCode == Keyboard.KEY_F11)
{
nifty.toggleEffectsDebugConsole();
}
}
/**
* JME MouseInputListener override onButton
* gets called by JME after MouseInput.get().addListener(thisclass);
*/
@Override
public void onButton(int button, boolean pressed, int x, int y)
{
lastLeftMouseDown = pressed;
MouseInputEvent inputEvent = new MouseInputEvent(x, y, lastLeftMouseDown);
events.add(inputEvent);
}
/**
* JME MouseInputListener override onMove
* gets called by JME after MouseInput.get().addListener(thisclass);
*/
@Override
public void onMove(int xDelta, int yDelta, int newX, int newY)
{
MouseInputEvent inputEvent = new MouseInputEvent(newX, newY, lastLeftMouseDown);
events.add(inputEvent);
}
/**
* JME MouseInputListener override onWheel
* gets called by JME after MouseInput.get().addListener(thisclass);
*/
@Override
public void onWheel(int wheelDelta, int x, int y)
{
// TODO: use also the wheel sometime
}
}
The Nifty Game state for a StandardGame (while im at it: thanks a lot to darkfrog for JGN and standardgame)
This gamestate should be added as the last one so the GUI is rendered above all other elements.
package descented.game.states;
import java.util.logging.Logger;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.util.glu.GLU;
import com.jme.input.KeyInput;
import com.jme.input.MouseInput;
import com.jme.system.DisplaySystem;
import com.jmex.game.state.BasicGameState;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.lwjglslick.render.RenderDeviceLwjgl;
import de.lessvoid.nifty.lwjglslick.sound.SlickSoundDevice;
import de.lessvoid.nifty.sound.SoundSystem;
import de.lessvoid.nifty.tools.TimeProvider;
import descented.input.JmeNiftyInputSystem;
/**
* <code>NiftyGameState</code> is an implementation of a gamestate using Nifty-GUI 1.1 for JME2
*
* @author larynx.ckc@gmx.net
*/
public class NiftyGameState extends BasicGameState
{
// the Nifty GUI object
private Nifty nifty = null;
// screen dimension for the nifty lwjgl rendering
private int viewportWidth;
private int viewportHeight;
private static final Logger logger = Logger.getLogger(NiftyGameState.class.getName());
public NiftyGameState ()
{
super("GUI");
// Show debug grid
//rootNode.attachChild(new AxisGrid().buildGeometry());
}
@Override
public void update(float tpf)
{
if (nifty == null)
{
viewportWidth = DisplaySystem.getDisplaySystem().getWidth();
viewportHeight = DisplaySystem.getDisplaySystem().getHeight();
// Create a jme <-> nifty input binding
JmeNiftyInputSystem jmeNiftyinput = new JmeNiftyInputSystem();
// Add jme mouse listener
MouseInput.get().addListener(jmeNiftyinput);
// Add jme keyboard listener
KeyInput.get().addListener(jmeNiftyinput);
// Create a nifty
nifty = new Nifty(new RenderDeviceLwjgl(),
new SoundSystem(new SlickSoundDevice()),
jmeNiftyinput,
new TimeProvider());
// Bind the keyboard input to this new nifty
jmeNiftyinput.bind(nifty);
// This starts the GUI - nearly everything is read from xml
// location nifty-examples/src/main/resources/console/console.xml
nifty.fromXml("console/console.xml", "start");
}
}
@Override
public void cleanup()
{
if (nifty != null)
{
nifty.exit();
}
}
@Override
public void render(float tpf)
{
if (nifty != null)
{
// Push JME attributes onto the stack
GL11.glPushAttrib(GL11.GL_LIGHTING_BIT | GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_ENABLE_BIT | GL11.GL_COLOR_BUFFER_BIT | GL11.GL_CURRENT_BIT | GL11.GL_TEXTURE_BIT | GL11.GL_TRANSFORM_BIT | GL11.GL_VIEWPORT_BIT);
GL11.glMatrixMode(GL11.GL_PROJECTION);
// Put current projection matrix on the stack
GL11.glPushMatrix();
GL11.glLoadIdentity();
GL11.glOrtho(0, viewportWidth, viewportHeight, 0, -9999, 9999);
GL11.glMatrixMode(GL11.GL_MODELVIEW);
// Put current modelview matrix on the stack
GL11.glPushMatrix();
GL11.glLoadIdentity();
// Prepare Rendermode - copied from nifty LwjglInitHelper
GL11.glDisable(GL11.GL_DEPTH_TEST);
GL11.glEnable(GL11.GL_BLEND);
GL11.glDisable(GL11.GL_CULL_FACE);
GL11.glEnable(GL11.GL_ALPHA_TEST);
GL11.glAlphaFunc(GL11.GL_NOTEQUAL, 0);
GL11.glDisable(GL11.GL_LIGHTING);
GL11.glDisable(GL11.GL_TEXTURE_2D);
// Set back to first texture unit so GUI displays properly - from FenGUI example by Core-Dump
// avoids a texture 'spill over' between JME and nifty
GL13.glActiveTexture(GL13.GL_TEXTURE0);
//GL11.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
//GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
//GL11.glEnable(GL11.GL_TEXTURE_2D);
//
// render the GUI
nifty.render(false);
// check gl error at least once per frame
int error = GL11.glGetError();
if (error != GL11.GL_NO_ERROR)
{
String glerrmsg = GLU.gluErrorString(error);
logger.warning("OpenGL Error: (" + error + ") " + glerrmsg);
}
// Get JME modelview and projection matrix from the stack
GL11.glMatrixMode(GL11.GL_MODELVIEW);
GL11.glPopMatrix();
GL11.glMatrixMode(GL11.GL_PROJECTION);
GL11.glPopMatrix();
// Get JME attributes from the stack
GL11.glPopAttrib();
}
}
}