Static-acating SimpleApplication

From the perspective of someone trying to write a game it makes sense to expose the bits and pieces of the Jme3 engine (the assetManager, rootNode, guiNode, etc) that are needed for easy accesses in the same way System.out.println is easily accessible. I say so because then I can concentrate on structuring the game according to either its storyline, or its scenegraph, and no longer have to organize and run the lines of spagetti necessary allow classes that end up over yonder to access the bits and pieces they need. From the perspective of Jme3 developers, however, it might be best were I lined up against a wall and shot… I don’t know, but that is what I am asking… have I missed some information about the game engine that would see the following code lead nowhere, or to insurmountable troubles?



There are two classes. Jme is taken from SimpleApplication. It performs the same initialization as SimpleApplication. It is booted from within by main(). Once initialized it calls the init method of the second class called Game.

package mygame;



import com.jme3.app.Application;

import com.jme3.asset.AssetManager;

import com.jme3.input.InputManager;

import com.jme3.font.BitmapFont;

import com.jme3.font.BitmapText;

import com.jme3.input.KeyInput;

import com.jme3.input.controls.ActionListener;

import com.jme3.input.controls.KeyTrigger;

import com.jme3.renderer.queue.RenderQueue.Bucket;

import com.jme3.scene.Node;

import com.jme3.scene.Spatial.CullHint;

import com.jme3.system.AppSettings;

import com.jme3.system.JmeContext.Type;



/**

  • The purpose of the class is to kickstart the Jme3 engine and expose
  • bits and pieces such as assetManager, rootNode, etc as static fields
  • that can be easily accesed throughout the game’s classes. The code is
  • based on SimpleApplication…SimpleBulletApplication bits and pieces could be added
  • @author ste3e

    */

    public class Jme extends Application {

    private Game game=new Game();//your game starts in here

    //static fields accessible via Jme.fieldName

    public static Node rootNode = new Node(“Root Node”);

    public static Node guiNode = new Node(“Gui Node”);

    //will be initialized with super’s managers

    public static AssetManager assetManager;

    public static InputManager inputManager;

    //universally available debug text

    public static BitmapText sysout;

    protected float secondCounter = 0.0f;



    protected BitmapFont guiFont;



    //enable esc to quit

    private AppActionListener actionListener = new AppActionListener();



    private class AppActionListener implements ActionListener {

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

    if (!value)

    return;



    if (name.equals(“SIMPLEAPP_Exit”)){

    stop();

    }

    }

    }



    public Jme(){

    super();

    this.start();

    }



    //Main

    public static void main(String[] args){

    Jme jme=new Jme();

    }



    @Override

    public void start(){

    //hand the settings you want to super

    settings=new AppSettings(true);

    settings.put(“Width”, 1024);

    settings.put(“Height”, 768);

    settings.put(“Title”, “My Game”);

    settings.put(“VSync”, true);

    settings.put(“Samples”, 4);

    super.setSettings(settings);



    super.start();

    }



    @Override

    public void initialize(){

    super.initialize();



    guiNode.setQueueBucket(Bucket.Gui);

    guiNode.setCullHint(CullHint.Never);

    viewPort.attachScene(rootNode);

    guiViewPort.attachScene(guiNode);



    //initialize managers

    Jme.assetManager=super.getAssetManager();

    Jme.inputManager=super.getInputManager();



    if (Jme.inputManager != null){

    if (context.getType() == Type.Display)

    Jme.inputManager.addMapping(“SIMPLEAPP_Exit”, new KeyTrigger(KeyInput.KEY_ESCAPE));



    Jme.inputManager.addListener(actionListener, “SIMPLEAPP_Exit”);

    }

    guiFont = assetManager.loadFont(“Interface/Fonts/Default.fnt”);

    sysout = new BitmapText(guiFont, false);

    sysout.setSize(guiFont.getCharSet().getRenderedSize());

    sysout.setLocalTranslation(0, sysout.getLineHeight(), 0);

    sysout.setText(“Frames per second”);

    guiNode.attachChild(sysout);

    Jme.debug(“This”);//won’t show as debug is called later in game



    //Jme3 is all initialized so we can start the game

    game.init();

    }



    @Override

    public void update() {

    if (speed == 0 || paused)

    return;



    super.update();

    float tpf = timer.getTimePerFrame() * speed;



    secondCounter += timer.getTimePerFrame();

    int fps = (int) this.timer.getFrameRate();

    if (secondCounter >= 1.0f){

    //sysout.setText("Frames per second: "+secondCounter);

    secondCounter = 0.0f;

    }



    // update states

    stateManager.update(tpf);



    // simple update and root node

    rootNode.updateLogicalState(tpf);

    guiNode.updateLogicalState(tpf);

    rootNode.updateGeometricState();

    guiNode.updateGeometricState();



    // render states

    stateManager.render(renderManager);

    renderManager.render(tpf);

    //simpleRender(renderManager);

    }



    public static void debug(String str){

    sysout.setText(str);

    }

    }





    Game illustrates how the static fields are accessed.





    package mygame;



    import com.jme3.light.DirectionalLight;

    import com.jme3.math.Vector3f;

    import com.jme3.scene.Node;

    import com.jme3.input.KeyInput;

    import com.jme3.input.controls.KeyTrigger;

    import com.jme3.input.controls.ActionListener;

    import com.jme3.animation.AnimChannel;

    import com.jme3.animation.AnimControl;

    import com.jme3.animation.AnimEventListener;

    import com.jme3.animation.LoopMode;



    /**
  • This is where the game begins. Note there are no references to a Jme
  • object other than the static calls to Jme.fieldName

    *@author ste3e

    */

    public class Game implements AnimEventListener{

    private Node root, placeholder, player;

    private AnimChannel channel;

    private AnimControl control;

    private boolean play=false;//play or stop the animation



    public Game(){}



    //init is called from Jme after the Jme initializes the game engine.

    public void init() {

    //the node “root” reorients the world space to coincide with Blender’s

    reorient();



    placeholder=new Node(“placeholder”);

    root.attachChild(placeholder);

    //use static access to the assetManager

    player=(Node)Jme.assetManager.loadModel(“Models/ogre/boxMesh.mesh.j3o”);

    placeholder.attachChild(player);



    initKeys();



    //setup animation and set to Start animation as named in Ogre exporter

    control=player.getControl(AnimControl.class);

    control.addListener(this);

    channel=control.createChannel();

    channel.setAnim(“Start”);



    DirectionalLight sun=new DirectionalLight();

    sun.setDirection(new Vector3f(-0.1f,-0.7f,-1.0f));

    //static access to the rootNode

    Jme.rootNode.addLight(sun);



    //static access to debug method

    Jme.debug(“Fred is nigh”);

    }



    private void initKeys(){

    //static access to the inputManager

    Jme.inputManager.addMapping(“ccw”, new KeyTrigger(KeyInput.KEY_LEFT));

    Jme.inputManager.addMapping(“cw”, new KeyTrigger(KeyInput.KEY_RIGHT));

    Jme.inputManager.addMapping(“play”, new KeyTrigger(KeyInput.KEY_P));



    Jme.inputManager.addListener(actionListener, “ccw”);

    Jme.inputManager.addListener(actionListener, “cw”);

    Jme.inputManager.addListener(actionListener, “play”);

    }



    private ActionListener actionListener=new ActionListener(){

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

    if(name.equals(“ccw”)){

    placeholder.rotate(0.0f,0.0f,-0.05f);

    }

    if(name.equals(“cw”)){

    placeholder.rotate(0.0f,0.0f,0.05f);

    }

    if(name.equals(“play”) && !keyPressed){

    if(!play){

    channel.setAnim(“Action”, 0.5f);

    channel.setLoopMode(LoopMode.Loop);

    play=true;

    }else{

    channel.setAnim(“Start”, 0.5f);

    play=false;

    }

    }

    }

    };



    //need to implement listener

    public void onAnimCycleDone(AnimControl control, AnimChannel channel, String name){}



    public void onAnimChange(AnimControl control, AnimChannel channel, String name){}



    private void reorient(){

    root=new Node(“root”);

    root.rotate((float)Math.toRadians(90.0f),(float)Math.toRadians(270.0f),0.0f);

    root.setLocalTranslation(new Vector3f(0.0f,-2.5f,0.0f));

    Jme.rootNode.attachChild(root);



    }

    }

No, this is the exact reason why jME2 was very limited when it comes to creating multiple applications, texture loaders etc. How you handle the needed objects is completely up to you, you can create static accessors no problem, but the engine will not depend on or implement static classes like these.



Cheers,

Normen



Edit: A simple way to implement a static accessor in your SimpleApplication:

[snippet id=“12”]

If you want to make those objects singletons/static, that is up to you.

Personally I found it a lot easier to develop a game when most things are like that.

In jME2 you couldn’t choose if things are singletons or not, in jME3 you can.

One very dreadful thing in jME2 that forced me to drop singletons in jME3:

DisplaySystem.getDisplaySystem().getRenderer().createTextureState()

Those lines were everywhere in jME2 apps …

Given Momoko Fan’s last I suspect I am better off using the class I have drawn up and only wrapping those fields that can harmlessly be made static static, rather than using the simpler wrap provided by Normen… but then I haven’t looked into createTextureState yet.

You dont need createTextureState() at all in jme3. The simple wrapper will work fine if you just want to access the application and its renderer etc. The problem Momoko refers to is that when the code he mentioned is used in the engine it cannot be capsuled into objects anymore as they all would reference global objects. Just having these static methods would lure extension developers into using them and then we are back to jme2 times :confused:

Cheers,

Normen