How to make spritesheet for JMonkey

First of all you need a material definition (Spritesheet.j3md)

MaterialDef Spritesheet {
 
    MaterialParameters {
        Texture2D ColorMap
        Float SizeX : 1
        Float SizeY : 1
        Float Position
    }
 
    Technique {
        VertexShader GLSL100:   MatDefs/Spritesheet.vert
        FragmentShader GLSL100: MatDefs/Spritesheet.frag
 
        WorldParameters {
            WorldViewProjectionMatrix        
        }
 
        Defines {          
        }
    }
 
}

Then you need the the fragment shader (Spritesheet.frag)

uniform sampler2D m_ColorMap;
 
varying vec2 texCoord;
 
void main(){  
 
    vec4 color = texture2D(m_ColorMap, texCoord);
  gl_FragColor=color;
}

And then you need the vertex shader (Spritesheet.vert)

uniform mat4 g_WorldViewProjectionMatrix;
uniform float m_SizeX;
uniform float m_SizeY;
uniform float m_Position;
 
attribute vec3 inPosition;
attribute vec2 inTexCoord;
 
varying vec2 texCoord;
 
void main(){
 
    float t = m_Position;
    float tPointerY = 1.0 - ((floor(m_Position / m_SizeX)) / m_SizeY) - 1.0 / m_SizeY;
    float tPointerYOffset = (floor(t / m_SizeX)) / m_SizeY;
    float tPointerX = (t - (tPointerYOffset * m_SizeX * m_SizeY)) / m_SizeX;
    texCoord.x = inTexCoord.x / m_SizeX + tPointerX;
    texCoord.y = inTexCoord.y / m_SizeY + tPointerY;
    gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0);
}

You should put these 3 files on the MatDefs folder.

Then you need the actual spritesheet. With TexturePacker you create a
spritesheet-only file with these parameters.

Finally on the game you need to build a Material with the new material definition:

//create the material with the spritesheet material definition
        Material mat = new Material(assetManager, "MatDefs/Spritesheet.j3md");
//set the spritesheet png built with TexturePacker
        mat.setTexture("ColorMap", assetManager.loadTexture("Models/myspritesheet.png"));
//specify the number of columns and rows
        mat.setFloat("SizeX", 5f);
        mat.setFloat("SizeY", 3f);
//tell the shader to draw the sprite number 1
        mat.setFloat("Position", 1f);
//your sprites most likely contain transparency, so it's probably better to set Alpha otherwise you'll see artefacts
        mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);

Of course, when you want to change sprite from the spritesheet you must do it from Java, by setting:

mat.setFloat("Position", 14f);

The material can be applied to any Geometry. For example we put the sprite as a texture for a cube:

http://wiki.jmonkeyengine.org/doku.php/jme3:beginner:hello_simpleapplication

package jme3test.helloworld;
 
import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.math.ColorRGBA;
 
/** Sample 1 - how to get started with the most simple JME 3 application.
 * Display a blue 3D cube and view from all sides by
 * moving the mouse and pressing the WASD keys. */
public class HelloJME3 extends SimpleApplication {
 
    public static void main(String[] args){
        HelloJME3 app = new HelloJME3();
        app.start(); // start the game
    }
 
    @Override
    public void simpleInitApp() {
        Box b = new Box(1, 1, 1); // create cube shape
        Geometry geom = new Geometry("Box", b);  // create cube geometry from the shape
//create the material with the spritesheet material definition
        Material mat = new Material(assetManager, "MatDefs/Spritesheet.j3md");
//set the spritesheet png built with TexturePacker
        mat.setTexture("ColorMap", assetManager.loadTexture("Models/myspritesheet.png"));
//specify the number of columns and rows
        mat.setFloat("SizeX", 5f);
        mat.setFloat("SizeY", 3f);
//tell the shader to draw the sprite number 1
        mat.setFloat("Position", 1f);
//your sprites most likely contain transparency, so it's probably better to set Alpha otherwise you'll see artefacts
        mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
        geom.setMaterial(mat);                   // set the cube's material
        rootNode.attachChild(geom);              // make the cube appear in the scene
    }
}
3 Likes

I still think spritesheet is the best way of doing 2D games. Later, after I understand the very basic concepts of jME3, i’m planning to work with 2D games and this guide will be very helpful. Thanks!

1 Like

I tested it out and it works pretty nicely! I think there might be some difficulty in trying to convert pixel size to WU (my sprite got distorted on the box. I tried changing the box size to fit the w/h ratio but no dice)

Nyphoon has a really solid sprite loader for jme3 as well: http://nyphoon.com/games/the-sprite-project/ the difference being that it loads sprites like any other texture on a quad.

I don’t know anything about shaders, is your method more efficient than just loading a texture on a box?

Forgot to mention that my shader assumes all sprites to be square and of equal size. :stuck_out_tongue:

I don’t know; however, If you have only one texture you don’t need a spritesheet :smile:

1 Like

Nice. Had some problems getting this to run. first I needed to restart JME to get rid of some identifier expected errors in the .vert file that I had copy/pasted from here. Then I couldn’t figure out why the assetmanager threw up at me, but that was because I had written SpriteSheet instead of Spritesheet. That capital s was really hard to spot…

Now I am on the path however and going to make a 2d game in a 3d engine, because javaFX just kills my old laptop when drawing a large image to a canvas 60 times pr second.

1 Like