2d games

Does anyone have a basic example of how to set up an orthographic camera for a 2D game?



I want to make something which is essentially an old-school 2D arcade game, using JME to do the rendering and animation.



Hopefully I am not the first person ever to have needed this?

jME3 supports orthogonal rendering, just set the queue bucket of your objects to “Gui”.

See TestOrtho for an example

Right, I’ve got the TestOrtho example. Then, I’m trying to attach a box to guiNode, but I get a black screen. I can see the box if I attach it to rootNode…



This is my modified version of TestOrtho:



[java]

package mygame;



import com.jme3.app.SimpleApplication;

import com.jme3.bullet.BulletAppState;

import com.jme3.bullet.control.RigidBodyControl;

import com.jme3.math.Vector2f;

import com.jme3.math.Vector3f;

import com.jme3.scene.Geometry;

import com.jme3.scene.shape.Box;

import com.jme3.ui.Picture;

import com.jme3.material.Material;



public class TestOrtho extends SimpleApplication {



private BulletAppState bulletAppState;

private RigidBodyControl brick_phy;

private static final Box box;

Material wall_mat;



/** dimensions used for bricks and wall /

private static final float brickLength = 0.48f;

private static final float brickWidth = 0.24f;

private static final float brickHeight = 0.12f;



static{

/
* Initialize the brick geometry /

box = new Box(Vector3f.ZERO, brickLength, brickHeight, brickWidth);

}



public static void main(String[] args) {

TestOrtho app = new TestOrtho();

app.start();

}



public void simpleInitApp() {

wall_mat = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);



bulletAppState = new BulletAppState();

stateManager.attach(bulletAppState);



cam.setLocation(new Vector3f(0, 6f, 6f));

cam.lookAt(Vector3f.ZERO, new Vector3f(0, 1, 0));

cam.setFrustumFar(15);



/
* Create a brick geometry and attach to scene graph. */

Geometry brick_geo = new Geometry(“brick”, box);

brick_geo.setMaterial(wall_mat);

//rootNode.attachChild(brick_geo); //result: white box falling into the darkness

guiNode.attachChild(brick_geo); //result: black screen



brick_geo.setLocalTranslation(new Vector3f(0f, 0f, 0f));

}

}

[/java]



I thought I’d have a square, when attaching the box to guiNode . Any ideas of what am I doing wrong?

That puts your box in the lower left corner and I think it will be super-duper tiny… like sub-pixel tiny if in the guiNode.

I thought you were making a 2D game, boxes are 3D objects.

For the Gui bucket, the coordinates are in pixels, which is probably not what you want if you have a 3D scene.

See TestParallelProjection for isometric 3D scene rendering.

Well, I’m still learning how jME works… I thought I’d to create 3D objects using an static camera for creating a 2D scene, once jME was made for 3D games.



Thanks for the TestParallelProjection tip, I’ll have a look on it, Momoko_Fan…



Now I’ve created two 2D objects (Pictures) that collide with a thin line. And they are rolled some degrees. Strangely, the two pictures do not collide with each other. Strangely, the thin line is a 3D object which seem to be 2D when attached to guiNode…



[java]

package mygame;



import com.jme3.app.SimpleApplication;

import com.jme3.asset.TextureKey;

import com.jme3.bullet.BulletAppState;

import com.jme3.bullet.collision.shapes.MeshCollisionShape;

import com.jme3.bullet.control.RigidBodyControl;

import com.jme3.bullet.nodes.PhysicsNode;

import com.jme3.math.Vector3f;

import com.jme3.scene.Geometry;

import com.jme3.scene.shape.Box;

import com.jme3.ui.Picture;

import com.jme3.material.Material;

import com.jme3.math.FastMath;

import com.jme3.math.Quaternion;

import com.jme3.texture.Texture;



public class TwoPicturesWithPhysics_2D extends SimpleApplication {



private RigidBodyControl p_phy;

private RigidBodyControl p2_phy;

private BulletAppState bulletAppState;



public static void main(String[] args) {

TwoPicturesWithPhysics_2D app = new TwoPicturesWithPhysics_2D();

app.start();

}



public void simpleInitApp() {



bulletAppState = new BulletAppState();

stateManager.attach(bulletAppState);



cam.setLocation(new Vector3f(0, 6f, 6f));

cam.lookAt(Vector3f.ZERO, new Vector3f(0, 1, 0));

cam.setFrustumFar(15);



Picture p = new Picture(“Picture”);

p.move(0, 0, -1); // make it appear behind stats view

p.setPosition(400, 500);

p.setWidth(263);

p.setHeight(290);

p.setImage(assetManager, “Interface/image.png”, false);



Picture p2 = new Picture(“Picture”);

p2.move(0, 0, -1); // make it appear behind stats view

p2.setPosition(100, 800);

p2.setWidth(263);

p2.setHeight(290);

p2.setImage(assetManager, “Interface/image.png”, false);



// This quaternion stores a 10 degree rolling rotation

Quaternion roll = new Quaternion();

roll.fromAngleAxis(FastMath.PI*-10/180, new Vector3f(0, 0, 1));

//The rotation is applied: The objects roll by 10 degrees.

p.setLocalRotation(roll);

p2.setLocalRotation(roll);



// attach picture to orthoNode

guiNode.attachChild§;

guiNode.attachChild(p2);



//Make picture physical with a mass > 0.0f.

p_phy = new RigidBodyControl(0.5f);

p2_phy = new RigidBodyControl(10.5f);



// Add physical picture to physics space.

p.addControl(p_phy);

p2.addControl(p2_phy);



p.setLocalTranslation(new Vector3f(0f, 0f, 0f));

p2.setLocalTranslation(new Vector3f(0f, 0f, 0f));



bulletAppState.getPhysicsSpace().add(p_phy);

bulletAppState.getPhysicsSpace().add(p2_phy);



setupFloor();



//guiNode.attachChild§;

}

public void setupFloor() {

Material mat = new Material(assetManager, “Common/MatDefs/Misc/SimpleTextured.j3md”);

TextureKey key = new TextureKey(“Interface/Logo/Monkey.jpg”, true);

key.setGenerateMips(true);

Texture tex = assetManager.loadTexture(key);

tex.setMinFilter(Texture.MinFilter.Trilinear);

mat.setTexture(“m_ColorMap”, tex);



//magically this box appears in 2D, as a thin line

//when attached to guiNode, below…

Box floor = new Box(Vector3f.ZERO, 200, 1f, 200);

Geometry floorGeom = new Geometry(“Floor”, floor);

floorGeom.setMaterial(mat);



PhysicsNode tb = new PhysicsNode(floorGeom, new MeshCollisionShape(floorGeom.getMesh()), 0);



Quaternion roll = new Quaternion();

roll.fromAngleAxis(FastMath.PI*-10/180, new Vector3f(0, 0, 1));



//The rotation is applied: The object rolls by 10 degrees.

tb.setLocalRotation(roll);

//rootNode.attachChild(tb); //3D

guiNode.attachChild(tb); //2D



tb.setLocalTranslation(new Vector3f(400f, 100f, 0f)); //2D

//tb.setLocalTranslation(new Vector3f(0f, -3, 0f)); //3D



bulletAppState.getPhysicsSpace().add(tb);

}

}



[/java]

Remember, guiNode uses an orthographic projection. This means that even 3D objects will be rendered without any perception of depth. Something that is 500 units away will look the same, including size, as something that is 5 units away. This can cause what you might otherwise think is odd behavior, including some 3D things looking 2D.



For example, if you have a very long rectangular prism, 2x2x200, stretching way far away back into the distance, but it’s in an orthographic projection, the far end of it will look just as big as the close end, so if it’s viewed at only a slight angle, it won’t look very long at all. And if it’s directly on 2 axes and is only off the origin on 1 axis, could even look like a 2D rectangle.

Great. Getting acquainted with Quad now… I’m able to set physics properties to a Quad object the same way I do in a Picture object. But my Quad objects are flying from the bottom to the upper side of the screen (?). Weird… When I get some results, I’ll post here…

I can’t understand why the two objects (a square and a rectangle, both Quads) do not collide… Any idea?

Also, the gravity is quite slow in this implementation… It’s like a moon’s yellow squared rock. 8)

[java]

/*

  • The little yellow square falls but do not collide with the red rectangle.
  • It passes behind the red one in the 2D scene

    /

    import com.jme3.app.SimpleApplication;

    import com.jme3.bullet.BulletAppState;

    import com.jme3.bullet.collision.shapes.PlaneCollisionShape;

    import com.jme3.bullet.control.RigidBodyControl;

    import com.jme3.math.Vector3f;

    import com.jme3.scene.Geometry;

    import com.jme3.material.Material;

    import com.jme3.math.ColorRGBA;

    import com.jme3.math.FastMath;

    import com.jme3.math.Quaternion;

    import com.jme3.scene.shape.Quad;





    public class TwoQuadsAndNoCollision_2D extends SimpleApplication {

    private RigidBodyControl geo_red_phy;

    private RigidBodyControl geo_yellow_phy;

    private BulletAppState bulletAppState;

    public static void main(String[] args) {

    TwoQuadsAndNoCollision_2D app = new TwoQuadsAndNoCollision_2D();

    app.start();

    }

    public void simpleInitApp() {

    bulletAppState = new BulletAppState();

    stateManager.attach(bulletAppState);

    cam.setLocation(new Vector3f(0, 6f, 6f));

    cam.lookAt(Vector3f.ZERO, new Vector3f(0, 1, 0));

    cam.setFrustumFar(15);

    Quad q1 = new Quad(10, 10);

    Geometry geo_yellow = new Geometry("quadrado1", q1);

    Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/SolidColor.j3md");

    mat1.setColor("Color", ColorRGBA.Yellow);

    geo_yellow.setMaterial(mat1);

    Quad q = new Quad(20, 500);

    Geometry geo_red = new Geometry("quadrado", q);

    Material mat = new Material(assetManager, "Common/MatDefs/Misc/SolidColor.j3md");

    mat.setColor("Color", ColorRGBA.Red);

    geo_red.setMaterial(mat);

    // This quaternion stores a 70 degree rolling rotation

    Quaternion roll70 = new Quaternion();

    roll70.fromAngleAxis(FastMath.PI
    70/180, new Vector3f(0, 0, 1));

    //The rotation is applied: The objects roll by 70 degrees.

    geo_red.setLocalRotation(roll70);

    geo_red.setLocalTranslation(500f, 150f, 0f);

    geo_yellow.setLocalTranslation(300f, 260f, 0f);

    geo_red_phy = new RigidBodyControl(new PlaneCollisionShape(), 4.0f);

    geo_yellow_phy = new RigidBodyControl(10.5f);

    // Add physical Quad geometries to physics space.

    geo_red.addControl(geo_red_phy);

    geo_yellow.addControl(geo_yellow_phy);

    bulletAppState.getPhysicsSpace().add(geo_yellow);//gravity works, yellow square falls

    // bulletAppState.getPhysicsSpace().add(geo_red); //I dont want the red one to fall

    guiNode.attachChild(geo_yellow);//2D

    guiNode.attachChild(geo_red);//2D

    }

    }

    [/java]

There is no collision because the red object is not in the physics space.

If you don’t want it to fall set its mass to 0 instead of 4.

1 Like
nehon said:
There is no collision because the red object is not in the physics space.
If you don't want it to fall set its mass to 0 instead of 4.

It really makes sense, nehon! But it worked partially. The little yellow square now collide with a inside part of the red rectangle and it goes sliding through the red rectangle when eventually it "stops holding" and falls into the never-ending darkness. This image shows what happens: the yellow square is sliding down the red rectangle, just like it was being held somehow...

I thought it would be something related to the collision shape that is not implemented in the code I've posted before... But if I add it, the yellow square stops falling...
[java]
//if using PlaneCollisionShape or SimplexCollisionShape in the yellow one, it doesnt fall
geo_yellow_phy = new RigidBodyControl(new SimplexCollisionShape(), 10.5f);
geo_red_phy = new RigidBodyControl(new SimplexCollisionShape(), 10.5f);
/****/
//The yellow square falls, but there's a partial collision
geo_red_phy = new RigidBodyControl(0.0f);
geo_yellow_phy = new RigidBodyControl(10.5f);
[/java]
Am I missing something else?

If you use a RigidBodyControl on a quad, and that you didn’t made a collision shape yourself, it creates a hullCollisionShape for it.

Maybe the HullCollisionShape is a bit lost with only 4 vertices…dunno.



What you can do is to activate the display of the collision shape like this



bulletAppState.getPhysicsSpace().enableDebug(assetManager);



So you’ll be able to see what’s going on.

nehon said:
If you use a RigidBodyControl on a quad, and that you didn't made a collision shape yourself, it creates a hullCollisionShape for it.
Maybe the HullCollisionShape is a bit lost with only 4 vertices....dunno.

What you can do is to activate the display of the collision shape like this
`
bulletAppState.getPhysicsSpace().enableDebug(assetManager);
`
So you'll be able to see what's going on.


nehon, I can't see anything different between the execution with and without the enableDebug() code. Should the geometries change somehow?

Testing the TestPhysicsCar (a little bit modified…) I’m able to see what is the collision debug stuff. Interesting.



But in my 2D test it doesn’t show anything…

mhhh ok… could you try to use a BoxCollisionShape?

Nope. It didn’t work. Same behaviour of Simplex and Plane shapes (the yellow square don’t fall). Well, I had already tried all types of shapes, none of them worked out. (But the 3D collision examples work pretty well…)



I think I’ll contact the Mad Skills Motocross team… maybe they want to share some jMonkey3 2D coding techniques. :slight_smile:



I’ll post here any findings.

Ah, I forgot: enabling debug when using BoxCollisionShape, while instantiating RigidBodyControl, generates a java.lang.NullPointerException:



java.lang.NullPointerException

at com.jme3.scene.Geometry.setMesh(Geometry.java:135)

at com.jme3.bullet.util.DebugShapeFactory.createDebugShape(DebugShapeFactory.java:117)

at com.jme3.bullet.util.DebugShapeFactory.getDebugShape(DebugShapeFactory.java:106)

at com.jme3.bullet.collision.PhysicsCollisionObject.getDebugShape(PhysicsCollisionObject.java:225)

at com.jme3.bullet.objects.PhysicsRigidBody.getDebugShape(PhysicsRigidBody.java:620)

at com.jme3.bullet.collision.PhysicsCollisionObject.attachDebugShape(PhysicsCollisionObject.java:212)

at com.jme3.bullet.collision.PhysicsCollisionObject.attachDebugShape(PhysicsCollisionObject.java:181)

at com.jme3.bullet.control.RigidBodyControl.render(RigidBodyControl.java:231)

at com.jme3.scene.Spatial.runControlRender(Spatial.java:523)

at com.jme3.renderer.RenderManager.renderScene(RenderManager.java:516)

at com.jme3.renderer.RenderManager.renderScene(RenderManager.java:522)

at com.jme3.renderer.RenderManager.renderViewPort(RenderManager.java:712)

at com.jme3.renderer.RenderManager.render(RenderManager.java:745)

at com.jme3.app.SimpleApplication.update(SimpleApplication.java:249)

at com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:158)

at com.jme3.system.lwjgl.LwjglDisplay.runLoop(LwjglDisplay.java:203)

at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:221)

at java.lang.Thread.run(Thread.java:662)

Well, a friend of mine tried it with a circle and the physics worked pretty well (code below). The problem (a partial random-behavior collision) happens when I use a square to fall and collide with another square.

Anyway, I asked the Mad Skills Motocross (MSM) developers and Tobias said MSM is made with jME1 (!) and he advises I should use a proper 2D library/engine instead of jME3 for 2D developing. http://turborilla.com/forums/index.php?topic=173.0

He also suggested Flashpunk engine (Flash), Slick 2D (Java) and Pulpcore (Java).

Below, the test code that uses a circle where collision works:

[java]

package mygame;

import com.jme3.app.SimpleApplication;

import com.jme3.bullet.BulletAppState;

import com.jme3.bullet.control.RigidBodyControl;

import com.jme3.font.BitmapText;

import com.jme3.material.Material;

import com.jme3.math.ColorRGBA;

import com.jme3.math.Quaternion;

import com.jme3.math.Vector3f;

import com.jme3.scene.Geometry;

import com.jme3.scene.Spatial;

import com.jme3.scene.shape.Quad;

import com.jme3.scene.shape.Sphere;

/**

*

  • @author wagners

    */

    public class Main_wagners extends SimpleApplication {

    private BulletAppState bas = null;

    public static void main(String[] args) {

    Main_wagners app = new Main_wagners();

    app.start();

    }

    @Override

    public void simpleInitApp() {

    //Physics setup

    bas = new BulletAppState();

    stateManager.attach(bas);

    bas.getPhysicsSpace().setGravity(new Vector3f(0f, -908f, 0f));

    //using root node guiNode for flat (i.e., 2D) scene

    //"cleaning up" guiNode

    guiNode.detachAllChildren();

    //composing 2D scene

    //a sample text

    guiNode.attachChild(makeTextOrtho("testando essa coisa…", 3));

    //the circle (2D-looking sphere)

    Geometry sphere = makeSphereOrtho("esfera01", 100, 400);

    attachPhysics(sphere, 2f); //that falls from above…

    guiNode.attachChild(sphere);

    //the ground

    Geometry ground = makeQuadOrtho("quad01", settings.getWidth(), 50, 0, 0);

    attachPhysics(ground, 0); //that is, of course, fixed at the scene bottom

    guiNode.attachChild(ground);

    //the ramp over the ground

    Geometry r = makeQuadOrtho("quad02", 10, (float)(settings.getHeight()/2), 190,50);

    r.rotate(new Quaternion(new float[]{0,0,45f}));

    attachPhysics(r, 0);

    guiNode.attachChild®;

    }

    /**
  • Enrich 2D objects with Physics properties
  • @param object the geometry object
  • @param mass the object mass

    *

    */

    private void attachPhysics(Spatial object, float mass) {

    //CollisionShape cs = CollisionShapeFactory.createMeshShape(object);

    RigidBodyControl rbc = new RigidBodyControl(mass);

    object.addControl(rbc);

    System.out.println("local translation: " + object.getLocalTranslation().toString());

    rbc.setPhysicsLocation(object.getLocalTranslation());

    bas.getPhysicsSpace().add(rbc);

    }

    /**
  • 2D "sphere" (circle) object factoring method. <b>Note</b>: circle with fixed radius.
  • @param x circle center at the x-axis
  • @param y circle center at the y-axis
  • @return circle Geometry object

    */

    private Geometry makeSphereOrtho(String name, float x, float y) {

    Sphere rock = new Sphere(32, 32, 20f);

    Geometry rockGeo = new Geometry(name, rock);

    Material m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

    m.setColor("Color", ColorRGBA.White);

    rockGeo.setMaterial(m);

    rockGeo.setLocalTranslation(x, y, 0);

    return rockGeo;

    }

    /**
  • flat text factoring method
  • @param x quad center at the x-axis
  • @return BitmapText object

    */

    private BitmapText makeTextOrtho(String text, float x) {

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

    BitmapText theText = new BitmapText(guiFont, false);

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

    theText.setText(text);

    theText.setLocalTranslation(x, theText.getLineHeight(), 0);

    return theText;

    }

    /**
  • 2D quad object factoring method
  • @param w quad width
  • @param h quad height
  • @param x quad position at the x-axis
  • @param y quad position at the y-axis
  • @return quad Geometry object

    /

    private Geometry makeQuadOrtho(String name, float w, float h, float x, float y) {

    Quad q = new Quad(w, h);

    Geometry quadGeo = new Geometry(name, q);

    Material m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

    m.setColor("Color", ColorRGBA.Yellow);

    quadGeo.setMaterial(m);

    quadGeo.setLocalTranslation(x, y, 0);

    return quadGeo;

    }

    /
    * 3D object factoring methods**/

    /**
  • Sphere factoring method

    */

    private Geometry makeSphere(String name, float x, float y, float z) {

    Sphere rock = new Sphere(32, 32, 2f);

    Geometry rockGeo = new Geometry(name, rock);

    Material mat_lit = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

    mat_lit.setColor("Color", ColorRGBA.White);

    rockGeo.setMaterial(mat_lit);

    rockGeo.setLocalTranslation(x, y, z);

    return rockGeo;

    }

    }

    [/java]