Sprite engine

Hi all,



this is a presentation for a very simple sprite engine I’ve built.



It supports static, billboard and multi-angle (rotoscoped) sprites all of which can be animated.

also its posibble to lock sprite pitching towards the camera so they keep upright.

it’s also possible to change the animation-fps per sprite.



http://www.youtube.com/watch?v=776xFbSiOuE



For the material it's necessary to use the provided shader which is a customized version of the lighting shader.
I'm aware that the way the SpriteSheets works is not best-practice and maybe this will change in the future but it works great as far as I know.

To generate compatible SpriteSheets it's necessary to use this software : Sprite Sheet Packer.
it generates a spritesheet with all the selected images and a index file with the image-layout which my SpriteSheet class uses to calculate UV-offsets and scales.

The sprite engine uses a naming convention for the sprites, make sure you name them according to this before generating the spritesheet :
{spritename}.png
{spritename}-{frame).png
{spritename}-{orientation}.png
{spritename}-{frame}-{orientation}.png

these names are saved in the txt-file generated by Sprite Sheet Packer and interpreted by the SpriteSheet Class as seperate sprites with the right information.
the first frame is 0 and orientations for rotoscoping can be : left, right, back and front, also it's very easy to adjust the code for support for more orientations;

The source is located at Github and ready to go, with the same scene as the video.
Github location : SpriteEngine

I warn everybody that this is not an example of best practices but it works great.
it runs at about 950 fps on my mobile nvidia gpu.

Any suggestions or questions are welcome also feel free to commit issues at Github.
I just someone besides me has a use for this
8 Likes

Well, good job. I’ll take a look in a near future, when it’s a bit more complete, when I’m going to use sprites to do some animations ^^

I see you are an artist (when looking that running guy) :slight_smile:

@shirkit

Glad you like it. I thought it was about complete, if u miss any options please let me know and I will implement them.



@quad

Hahaha :smiley: yeah everything are just debug / test graphics :wink:

Trying to optimize the rootNode using GeometryBatchFactory makes all sprites static. So if you have a bunch of AnimatedSprites then they won’t follow the camera.

@squizzle said:
Trying to optimize the rootNode using GeometryBatchFactory makes all sprites static. So if you have a bunch of AnimatedSprites then they won't follow the camera.

try using a BatchNode instead of using GeometryBatchFactory

I’m going to update the code in the following weeks.



@MonkeyBrainz pointed out that only the rotosprite class has a separate heading rotation for movement but the rest of the sprite classes don’t have them (you basically override the the sprite orientation towards the camera when using setRotation on them) so I’m gonna fix this.



@Squizzle

One optimization i’m working on is to put all the sprites in a custom mesh and updating the mesh vertexes itself on the fly this will greatly improve fps with big numbers of sprites, and the animations will still be working. I already have working code in but have to port it for the use with sprites instead of voxels.



You can follow the github repository to get updates on changes.

That running guy is the most awesome thing I’ve seen in a while. I want more of this stuff.



This could maybe be used in cloud rendering. Definitely going to check it out. Thumbs up.

Hey, I downloaded the source code to try it out. However, how do I use it in jME?



I tried creating a new game and loading shaders, classes etc… but this came up: java.lang.RuntimeException: Uncompilable source code - Erroneous sym type: (java.lang.String,int,java.lang.String)

at Sprites.RotoSprite.setOrientation(RotoSprite.java:103)

at Sprites.RotoSprite.update(RotoSprite.java:67)

at Sprites.SpriteUpdateControl.update(SpriteUpdateControl.java:56)

at com.jme3.scene.Spatial.runControlUpdate(Spatial.java:540)

at com.jme3.scene.Spatial.updateLogicalState(Spatial.java:658)

at com.jme3.scene.Node.updateLogicalState(Node.java:147)

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

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

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

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

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

@memonick

You have to assign a spritesheet to a texture before adding it to a scene.



the spritesheet has to be generated with the link supplied for Sprite Sheet Packer in the OP,



also If you want to use rotosprites you have use the naming confention :

{sprite-name}-{orientation}.png



for example : hero-front.png or hero-left.png



if you want an animated sprite the naming for frame 0 and 1 for the hero from the front are hero-0-front.png and hero-1-front.png

When you pack them with sprite sheet packer it generates a lookup file with the names and the uv coordinates for the sprites.



Look at my main Class for how to assign and load spritesheets and look in the textures/debug directory in the assets directory for a sample of a Spritesheet

I already looked over them. I just tried to compile the Main class. I didn’t change anything, and the spritesheets are already assigned, no?



rotoDebug = new SpriteSheet(assetManager, “/Textures/Debug/roto.png”, “/Textures/Debug/roto.txt”);

@Memonick

And you also set the Sprite name after you set the spritesheet?



line 108 in Main.java : roto1.setSprite(“walk”);



If that also doesn’t work then I’m at a loss, maybe if you can post your code somewhere I can debug it.

Nothing :confused:



This is what I have in Main:

[java]package mygame;



import SpriteSheets.SpriteSheet;

import Sprites.AnimatedSprite;

import Sprites.AnimatedStaticSprite;

import Sprites.BillboardSprite;

import Sprites.RotoSprite;

import Sprites.SpriteUpdateControl;

import Sprites.StaticSprite;

import com.jme3.app.SimpleApplication;

import com.jme3.light.AmbientLight;

import com.jme3.light.PointLight;

import com.jme3.material.Material;

import com.jme3.material.RenderState.FaceCullMode;

import com.jme3.math.ColorRGBA;

import com.jme3.math.FastMath;

import com.jme3.math.Quaternion;

import com.jme3.math.Vector3f;

import com.jme3.renderer.RenderManager;

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

import com.jme3.scene.Geometry;



/**

  • test
  • @author normenhansen

    */

    public class Main extends SimpleApplication {

    private SpriteSheet staticDebug;

    private SpriteSheet rotoDebug;

    private Material spriteMat;

    private SpriteUpdateControl spriteControl;

    private RotoSprite roto1 = new RotoSprite(2,4,2);

    private Geometry roto2;



    public static void main(String[] args) {

    Main app = new Main();

    app.start();

    }



    @Override

    public void simpleInitApp() {

    getViewPort().setBackgroundColor(new ColorRGBA(0.9f, 1, 1, 1));

    getFlyByCamera().setMoveSpeed(50);

    staticDebug = new SpriteSheet(assetManager, “/Textures/Debug/static.png”, “/Textures/Debug/static.txt”);

    rotoDebug = new SpriteSheet(assetManager, “/Textures/Debug/roto.png”, “/Textures/Debug/roto.txt”);

    spriteControl = new SpriteUpdateControl(rootNode, cam);



    AmbientLight ambient = new AmbientLight();

    ambient.setColor(ColorRGBA.White);



    PointLight p = new PointLight();

    p.setColor(new ColorRGBA(0.9f, 0.8f, 0.8f, 1.0f));



    BillboardSprite static1 = new BillboardSprite();

    StaticSprite static2 = new StaticSprite();

    AnimatedStaticSprite static3 = new AnimatedStaticSprite(3);

    BillboardSprite static4 = new BillboardSprite(2, 4);

    AnimatedSprite static5 = new AnimatedSprite(2, 4, 3);



    roto1.setSpriteSheet(rotoDebug);

    static1.setSpriteSheet(staticDebug);

    static2.setSpriteSheet(staticDebug);

    static3.setSpriteSheet(staticDebug);

    static4.setSpriteSheet(staticDebug);

    static5.setSpriteSheet(staticDebug);



    static1.setMaterial(new Material(assetManager, “MatDefs/UVOffsetLighting.j3md”));

    static2.setMaterial(new Material(assetManager, “MatDefs/UVOffsetLighting.j3md”));

    static3.setMaterial(new Material(assetManager, “MatDefs/UVOffsetLighting.j3md”));

    static4.setMaterial(new Material(assetManager, “MatDefs/UVOffsetLighting.j3md”));

    static5.setMaterial(new Material(assetManager, “MatDefs/UVOffsetLighting.j3md”));

    roto1.setMaterial(new Material(assetManager, “MatDefs/UVOffsetLighting.j3md”));



    static1.setSprite(“static1”);

    static2.setSprite(“static1”);

    static3.setSprite(“static3”);

    static4.setSprite(“static4”);

    static5.setSprite(“static2”);



    roto1.setSprite(“walk”);

    roto1.setFps(2);

    roto1.setPlaying(true);

    roto1.setPitchLock(true);



    static2.getMaterial().getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off);



    static3.setPlaying(true);

    static3.setFps(3);



    static5.setPlaying(true);

    static5.setLoop(true);

    static5.setFps(1);



    rootNode.addLight(ambient);

    rootNode.addLight§;



    rootNode.attachChild(static1);

    rootNode.attachChild(static2);

    rootNode.attachChild(static3);

    rootNode.attachChild(static4);

    rootNode.attachChild(static5);



    rootNode.attachChild(roto1);

    rootNode.addControl(spriteControl);



    static1.setLocalTranslation(-10, 0, -4);

    static2.setLocalTranslation(-5, 2, 4);

    static3.setLocalTranslation(5, -2, -4);

    static4.setLocalTranslation(0, 0, -10);

    static5.setLocalTranslation(0, 0, 10);

    roto1.setSprite(“walk”);



    static1.setPitchLock(true);



    rootNode.setQueueBucket(Bucket.Transparent);

    }



    @Override

    public void simpleUpdate(float tpf) {

    Quaternion rot = roto1.getLocalRotation();

    rot = rot.mult(new Quaternion().fromAngleAxis(tpf * FastMath.PI / 8, new Vector3f(0,1,0)));



    Vector3f dir = rot.getRotationColumn(2);

    dir.normalizeLocal();

    dir = dir.mult(4f * tpf);



    roto1.setLocalRotation(rot);

    roto1.setLocalTranslation(roto1.getLocalTranslation().add(dir));

    }



    @Override

    public void simpleRender(RenderManager rm) {

    //TODO: add render code

    }

    }

    [/java]



    All I did was create a new project, and copy-pasted the assets and source packages into the new project.



    This is how it looks like:



Can you tell me what I should do to use the source code? Or maybe upload the jME Project to see what I am doing wrong?

Ah, the guy is lit, that’s why he changes colors. Main.java runs nice for me, but I couldn’t open it as a jme project so i just created one from source and added the assets to the root dir.



Gonna put a terrain in and then use sprites for everything that moves.

@Memonick



At first sight there’s nothing wrong with the code, but as requested here is my complete project, also in the textures directory are all the separate images. where I made the SpriteSheet out of.



SpriteEngine,zip @ 2shared



I hope this helps



@androlo

I can’t wait to see how that is gonna work out, some androlo magic on my sprites xD, I’m doing something similar and planning to use your forester plugin also.

Also you are right about using this for a weather system, I like the idea of rendering clouds in 3d and then using them as rotoscoping sprites, also maybe you can use it for the imposter-code for the trees in the forester plugin, do you only show the side of the tree you are facing or are the imposters multiple faces placed in a cross. I have to admit I haven’t tried it yet, so I really don’t know.

The impostors are one sided. The trees are rendered from 8 directions and a texture is made. It has 8 different colormaps and 8 different normalmaps in the same texture (kind of like an atlas of 4x4 images). The shader is responsible for selecting image and blending (make transitions smooth).

It worked well. However, when I move, the game slows down considerably. Is it to do with my Graphics Card?



EDIT: Big sorry. I updated jME, and it works fine now. Sorry for all the hassle I created in the past few days.

Glad to hear you got it working!

Big congrats. Seeing it working, it shows the amount of work you put in.