2D Framework Tutorials

Going to start posting tutorials here for using the 2D Framework. Once I’m happy with (or other’s are) them, I’ll move it over to the Wiki.

Seeeeew… to start, here is the SimpleApplication setup for the tutorials: It basically just set’s up the screen and adds an AnimLayer to work with.


import com.jme3.app.SimpleApplication;
import com.jme3.renderer.RenderManager;
import tonegod.gui.core.Screen;
import tonegod.gui.framework.core.AnimLayer;

/**
 * 
 * @author t0neg0d
 */
public class Anim_Tutorial_00 extends SimpleApplication {
	private Screen screen;
	private AnimLayer layer;
	
	public static void main(String[] args) {
		Anim_Tutorial_00 app = new Anim_Tutorial_00();
		app.start();
	}

	@Override
	public void simpleInitApp() {
		initGUIScreen();
		layoutGUI();
	}
	
	private void initGUIScreen() {
		screen = new Screen(this,"tonegod/gui/style/atlasdef/style_map.gui.xml");
		screen.setUseTextureAtlas(true,"tonegod/gui/style/atlasdef/atlas.png");
		guiNode.addControl(screen);

		flyCam.setDragToRotate(true);
		inputManager.setCursorVisible(true);
	}
	
	private void layoutGUI() {
		layer = screen.addAnimLayer();
	}
	
	@Override
	public void simpleUpdate(float tpf) {  }
	
	@Override
	public void simpleRender(RenderManager rm) {  }
}

Now, the first thing we’ll do is create a single AnimElement, set up it’s Texture and TextureRegions and then add a single QuadData (quad) to the mesh.

NOTE: The QuadData selects a random TextureRegion each time the app starts. It is also set to movable, so you can drag it around the screen.

Here is the Texture to use for the examples below…


import com.jme3.app.SimpleApplication;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import com.jme3.renderer.RenderManager;
import tonegod.gui.core.Screen;
import tonegod.gui.framework.core.AnimElement;
import tonegod.gui.framework.core.AnimLayer;
import tonegod.gui.framework.core.QuadData;
import tonegod.gui.framework.core.TextureRegion;

/**
 * 
 * @author t0neg0d
 */
public class Anim_Tutorial_01 extends SimpleApplication {
	private Screen screen;
	private AnimLayer layer;
	
	public static void main(String[] args) {
        Anim_Tutorial_01 app = new Anim_Tutorial_01();
		app.start();
    }

    @Override
    public void simpleInitApp() {
		initGUIScreen();
		layoutGUI();
    }
	
	private void initGUIScreen() {
		screen = new Screen(this,"tonegod/gui/style/atlasdef/style_map.gui.xml");
		screen.setUseTextureAtlas(true,"tonegod/gui/style/atlasdef/atlas.png");
		guiNode.addControl(screen);
		
		flyCam.setDragToRotate(true);
		inputManager.setCursorVisible(true);
	}
	
	AnimElement el;
	QuadData q1;
	
	private void setupTexture(AnimElement el) {
		// Set the texture for the AnimElement
		el.setTexture(screen.createNewTexture("Textures/animtutorial.png"));
		// Loop through and create a TextureRegion for each 32x32 icon in the texture
		for (int y = 0; y < 4; y++) {
			for (int x = 0; x < 4; x++) {
				TextureRegion tr = el.addTextureRegion("x" + x + "y" + y, x*32, y*32, 32, 32);
				// Flip the TextureRegion along the Y axis
				tr.flip(false, true);
			}
		}
	}
	
	private void layoutGUI() {
		layer = screen.addAnimLayer();
		
		el = new AnimElement(screen.getApplication().getAssetManager()) {
			@Override
			public void animElementUpdate(float tpf) {
				// We will use this later for managing some QuadData
				// specific animations
			}
		};
		// Set up the AnimElement's Texture and available TextureRegions
		setupTexture(el);
		
		// Create a single quad as part of the AnimElement
		q1 = el.addQuad(
			"q1", // The key id for the new QuadData
			"x" + FastMath.nextRandomInt(0,3) + "y" + FastMath.nextRandomInt(0,3), // A random icon from our texture regions
			Vector2f.ZERO, // position 0,0 - Will set this later
			Vector2f.ZERO // origin 0,0 - Will set this later
		);
		// Set the dimensions of the QuadData
		q1.setDimensions(50,50);
		// Center the Quad to the middle of the screen
		// NOTE: The AnimElement is still at position 0,0
		q1.setPosition(
			(screen.getWidth()/2)-(q1.getWidth()/2),
			(screen.getHeight()/2)-(q1.getHeight()/2)
		);
		// Make this quad movable via the mouse or touch events
		q1.setIsMovable(true);
		// Initialize the AnimElement
		el.initialize();
		
		// Add the AnimElement to the screen layer
		layer.addAnimElement("el", el);
	}
	
    @Override
    public void simpleUpdate(float tpf) {  }

    @Override
    public void simpleRender(RenderManager rm) {  }
}

Now we can start to take a look at Temporal Actions. The gist of these are they perform a transform over a duration of time using the defined Interpolation (default id linear).

So, let’s add a RotateByAction to the quad inside of the AnimElement.

Everytime the action is restarted, we’ll set the QuadData’s origin to a random Vector within it’s dimensions.


import com.jme3.app.SimpleApplication;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import com.jme3.renderer.RenderManager;
import tonegod.gui.core.Screen;
import tonegod.gui.framework.animation.RotateByAction;
import tonegod.gui.framework.core.AnimElement;
import tonegod.gui.framework.core.AnimLayer;
import tonegod.gui.framework.core.QuadData;
import tonegod.gui.framework.core.TextureRegion;

/**
 * test
 * @author normenhansen
 */
public class Anim_Tutorial_02 extends SimpleApplication {
	private Screen screen;
	private AnimLayer layer;
	
	public static void main(String[] args) {
        Anim_Tutorial_02 app = new Anim_Tutorial_02();
		app.start();
    }

    @Override
    public void simpleInitApp() {
		initGUIScreen();
		layoutGUI();
    }
	
	private void initGUIScreen() {
		screen = new Screen(this,"tonegod/gui/style/atlasdef/style_map.gui.xml");
		screen.setUseTextureAtlas(true,"tonegod/gui/style/atlasdef/atlas.png");
		guiNode.addControl(screen);
		
		flyCam.setDragToRotate(true);
		inputManager.setCursorVisible(true);
	}
	
	AnimElement el;
	QuadData q1;
	RotateByAction ra;
	
	private void setupTexture(AnimElement el) {
		// Set the texture for the AnimElement
		el.setTexture(screen.createNewTexture("Textures/animtutorial.png"));
		// Loop through and create a TextureRegion for each 32x32 icon in the texture
		for (int y = 0; y < 4; y++) {
			for (int x = 0; x < 4; x++) {
				TextureRegion tr = el.addTextureRegion("x" + x + "y" + y, x*32, y*32, 32, 32);
				// Flip the TextureRegion along the Y axis
				tr.flip(false, true);
			}
		}
	}
	
	private void layoutGUI() {
		layer = screen.addAnimLayer();
		ra = new RotateByAction();
		
		el = new AnimElement(screen.getApplication().getAssetManager()) {
			@Override
			public void animElementUpdate(float tpf) {
				if (!q1.actions.contains(ra)) {
					// Set a random origin for q1
					float oX = FastMath.rand.nextFloat() * (q1.getWidth()/2);
					if (FastMath.rand.nextBoolean()) oX = -oX;
					oX += (q1.getWidth()/2);
					float oY = FastMath.rand.nextFloat() * (q1.getHeight()/2);
					if (FastMath.rand.nextBoolean()) oY = -oY;
					oY += (q1.getHeight()/2);
					q1.setOrigin(oX, oY);
					
					// Reset the quad's rotation
					q1.setRotation(0);
					
					// Restart the RotateByAnimation
					ra.restart();
					q1.addAction(ra);
				}
			}
		};
		// Set up the AnimElement's Texture and available TextureRegions
		setupTexture(el);
		
		// Create a single quad as part of the AnimElement
		q1 = el.addQuad(
			"q1", // The key id for the new QuadData
			"x" + FastMath.nextRandomInt(0,3) + "y" + FastMath.nextRandomInt(0,3), // A random icon from our texture regions
			Vector2f.ZERO, // position 0,0 - Will set this later
			Vector2f.ZERO // origin 0,0 - Will set this later
		);
		// Set the dimensions of the QuadData
		q1.setDimensions(50,50);
		// Center the Quad to the middle of the screen
		// NOTE: The AnimElement is still at position 0,0
		q1.setPosition(
			(screen.getWidth()/2)-(q1.getWidth()/2),
			(screen.getHeight()/2)-(q1.getHeight()/2)
		);
		
		// Set the origin to the middle of the QuadData
		q1.setOrigin(q1.getWidth()/2,q1.getHeight()/2);
		
		// Make this quad movable via the mouse or touch events
		q1.setIsMovable(true);
		// Initialize the AnimElement
		el.initialize();
		
		// Add the AnimElement to the screen layer
		layer.addAnimElement("el", el);
		
		// Define and add the RotateByAction
		ra.setDuration(1.5f);
		ra.setAmount(360);
		q1.addAction(ra);
	}
	
    @Override
    public void simpleUpdate(float tpf) {  }

    @Override
    public void simpleRender(RenderManager rm) {  }
}

And… now, we’ll create 20 QuadData’s within the single AnimElement, each using it’s own RotateByAction; each movable and effecting the zOrder of the quad’s within the AnimElement:


import com.jme3.app.SimpleApplication;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import com.jme3.renderer.RenderManager;
import tonegod.gui.core.Screen;
import tonegod.gui.framework.animation.RotateByAction;
import tonegod.gui.framework.core.AnimElement;
import tonegod.gui.framework.core.AnimLayer;
import tonegod.gui.framework.core.QuadData;
import tonegod.gui.framework.core.TextureRegion;

/**
 * test
 * @author normenhansen
 */
public class Anim_Tutorial_03 extends SimpleApplication {
	private Screen screen;
	private AnimLayer layer;
	
	public static void main(String[] args) {
        Anim_Tutorial_03 app = new Anim_Tutorial_03();
		app.start();
    }

    @Override
    public void simpleInitApp() {
		initGUIScreen();
		layoutGUI();
    }
	
	private void initGUIScreen() {
		screen = new Screen(this,"tonegod/gui/style/atlasdef/style_map.gui.xml");
		screen.setUseTextureAtlas(true,"tonegod/gui/style/atlasdef/atlas.png");
		guiNode.addControl(screen);
		
		flyCam.setDragToRotate(true);
		inputManager.setCursorVisible(true);
	}
	
	AnimElement el;
	
	private void setupTexture(AnimElement el) {
		// Set the texture for the AnimElement
		el.setTexture(screen.createNewTexture("Textures/animtutorial.png"));
		// Loop through and create a TextureRegion for each 32x32 icon in the texture
		for (int y = 0; y < 4; y++) {
			for (int x = 0; x < 4; x++) {
				TextureRegion tr = el.addTextureRegion("x" + x + "y" + y, x*32, y*32, 32, 32);
				// Flip the TextureRegion along the Y axis
				tr.flip(false, true);
			}
		}
	}
	
	private void layoutGUI() {
		layer = screen.addAnimLayer();
		
		el = new AnimElement(screen.getApplication().getAssetManager()) {
			@Override
			public void animElementUpdate(float tpf) {
				for (QuadData qd : quads.values()) {
					if (qd.actions.isEmpty()) {
						// Reset the quad's rotation
						qd.setRotation(0);

						// Add a RotateByAnimation
						RotateByAction ra = new RotateByAction();
						ra.setDuration(1.5f);
						ra.setAmount(360);
						qd.addAction(ra);
					}
				}
			}
		};
		// Set up the AnimElement's Texture and available TextureRegions
		setupTexture(el);
		
		for (int i = 0; i < 20; i++) {
			// Create a single quad as part of the AnimElement
			QuadData qd = el.addQuad(
				"q" + i, // The key id for the new QuadData
				"x" + FastMath.nextRandomInt(0,3) + "y" + FastMath.nextRandomInt(0,3), // A random icon from our texture regions
				Vector2f.ZERO, // position 0,0 - Will set this later
				Vector2f.ZERO // origin 0,0 - Will set this later
			);
			// Set the dimensions of the QuadData
			qd.setDimensions(50,50);
			// Center the Quad to the middle of the screen
			// NOTE: The AnimElement is still at position 0,0
			qd.setPosition(
				((screen.getWidth()-qd.getWidth())*FastMath.nextRandomFloat())+(qd.getWidth()/2),
				((screen.getHeight()-qd.getHeight())*FastMath.nextRandomFloat())+(qd.getHeight()/2)
			);
			
			// Set the origin to the middle of the QuadData
			qd.setOrigin(qd.getWidth()/2,qd.getHeight()/2);

			// Make this quad movable via the mouse or touch events
			qd.setIsMovable(true);
			// Initialize the AnimElement
		}
		
		el.initialize();
		
		// Add the AnimElement to the screen layer
		layer.addAnimElement("el", el);
	}
	
    @Override
    public void simpleUpdate(float tpf) {  }

    @Override
    public void simpleRender(RenderManager rm) {  }
}

That’s it for now… Need to start this same sort of thing for using Layout’s as well.

/wave

4 Likes

To continue on from above, We’ll associate a data structure with each quad that contains a RotateByAction with unique settings that can be reused.

[java]
import com.jme3.app.SimpleApplication;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import com.jme3.renderer.RenderManager;
import tonegod.gui.core.Screen;
import tonegod.gui.framework.animation.RotateByAction;
import tonegod.gui.framework.core.AnimElement;
import tonegod.gui.framework.core.AnimLayer;
import tonegod.gui.framework.core.QuadData;
import tonegod.gui.framework.core.TextureRegion;

/**

  • test

  • @author normenhansen
    */
    public class Anim_Tutorial_04 extends SimpleApplication {
    private Screen screen;
    private AnimLayer layer;

    public static void main(String[] args) {
    Anim_Tutorial_04 app = new Anim_Tutorial_04();
    app.start();
    }

    @Override
    public void simpleInitApp() {
    initGUIScreen();
    layoutGUI();
    }

    private void initGUIScreen() {
    screen = new Screen(this,“tonegod/gui/style/atlasdef/style_map.gui.xml”);
    screen.setUseTextureAtlas(true,“tonegod/gui/style/atlasdef/atlas.png”);
    guiNode.addControl(screen);

     flyCam.setDragToRotate(true);
     inputManager.setCursorVisible(true);
    

    }

    AnimElement el;

    private void setupTexture(AnimElement el) {
    // Set the texture for the AnimElement
    el.setTexture(screen.createNewTexture(“Textures/animtutorial.png”));
    // Loop through and create a TextureRegion for each 32x32 icon in the texture
    for (int y = 0; y < 4; y++) {
    for (int x = 0; x < 4; x++) {
    TextureRegion tr = el.addTextureRegion(“x” + x + “y” + y, x32, y32, 32, 32);
    // Flip the TextureRegion along the Y axis
    tr.flip(false, true);
    }
    }
    }

    private void layoutGUI() {
    layer = screen.addAnimLayer();

     el = new AnimElement(screen.getApplication().getAssetManager()) {
     	@Override
     	public void animElementUpdate(float tpf) {
     		for (QuadData qd : quads.values()) {
     			if (qd.actions.isEmpty()) {
     				QDInfo info = null;
     				if (qd.getDataStruct() == null) {
     					// Create a new Data Struct if one doesn't exist
     					info = new QDInfo(
     						FastMath.rand.nextFloat()+0.5f,
     						FastMath.rand.nextBoolean()
     					);
     					qd.setDataStruct(info);
     				} else {
     					// Otherwise, load the current Data Struct
     					info = qd.getDataStruct();
     				}
     				// Reset the quad's rotation
     				qd.setRotation(0);
    
     				// Add a RotateByAnimation
     				info.ra.restart();
     				qd.addAction(info.ra);
     			}
     		}
     	}
     };
     // Set up the AnimElement's Texture and available TextureRegions
     setupTexture(el);
     
     for (int i = 0; i &lt; 20; i++) {
     	// Create a single quad as part of the AnimElement
     	QuadData qd = el.addQuad(
     		"q" + i, // The key id for the new QuadData
     		"x" + FastMath.nextRandomInt(0,3) + "y" + FastMath.nextRandomInt(0,3), // A random icon from our texture regions
     		Vector2f.ZERO, // position 0,0 - Will set this later
     		Vector2f.ZERO // origin 0,0 - Will set this later
     	);
     	// Set the dimensions of the QuadData
     	qd.setDimensions(50,50);
     	// Center the Quad to the middle of the screen
     	// NOTE: The AnimElement is still at position 0,0
     	qd.setPosition(
     		((screen.getWidth()-qd.getWidth())*FastMath.nextRandomFloat())+(qd.getWidth()/2),
     		((screen.getHeight()-qd.getHeight())*FastMath.nextRandomFloat())+(qd.getHeight()/2)
     	);
     	
     	// Set the origin to the middle of the QuadData
     	qd.setOrigin(qd.getWidth()/2,qd.getHeight()/2);
    
     	// Make this quad movable via the mouse or touch events
     	qd.setIsMovable(true);
     	// Initialize the AnimElement
     }
     
     el.initialize();
     
     // Add the AnimElement to the screen layer
     layer.addAnimElement("el", el);
    

    }

    @Override
    public void simpleUpdate(float tpf) { }

    @Override
    public void simpleRender(RenderManager rm) { }

    public class QDInfo {
    public RotateByAction ra;

     public QDInfo(float rotSpeed, boolean dir) {
     	ra = new RotateByAction();
     	ra.setDuration(rotSpeed);
     	ra.setAmount((dir) ? 360 : -360);
     }
    

    }
    }
    [/java]

And here are the results of the single mesh created using AnimElement:

[video]http://youtu.be/q3AljReagK8[/video]

Next we’ll look at using parent linkage to show how transforms have a cascading effect.

2 Likes

Character Animation Using Cascading Transforms (parent linkage)

Ok… and here is the basics for using parent/child linkage to do skeletal-based character animation:

Here is an overview of how it works:

  1. As seen in one of the first examples, a transform is relative to the QuadData or AnimElements origin.
  2. This includes positioning
  3. All positions and origins are relative the the bottom left corner of QuadData texture region (or the parent) or AnimElement.

For this example, we will use the following image as our character’s atlas:

The next series of images are for reference purposes only… you can grab them if you want, but they will never be used in the example, past showing the placement of origins and positions for the individual QuadData.

** Here is our character atlas with the bounds of each image shown. The images starts inside of each black box from bottom left corner.

** And here is the character atlas showing the image’s origins represented by a green dot. Again the origins are relative to the bottom left corner of each bounding box.

** Here is a reference image showing the positions of each linked child represented by yellow dots. You’ll notice that only two of the image’s actually need position references for their child elements… the body and the appendage (arms/legs). Again, the positions are relative to bottom left corner of the bounding box.

One last reference image… this one shows what our character is going to look like after we create each QuadData and set the origin and position for each:

Now, on with the code:

Instead of instantiating a new AnimElement, we are going to extend AnimElement for organizational purposes.

Character.java
[java]
import com.jme3.math.Vector2f;
import tonegod.gui.core.Screen;
import tonegod.gui.framework.animation.RotateByAction;
import tonegod.gui.framework.core.AnimElement;
import tonegod.gui.framework.core.QuadData;
import tonegod.gui.framework.core.TextureRegion;

/**
*

  • @author t0neg0d
    */
    public class Character extends AnimElement {
    Screen screen;

    public Character(Screen screen) {
    super(screen.getApplication().getAssetManager());

     // Set a reference to the UI library's screen
     this.screen = screen;
     
     // Set the texture for the AnimElement
     setTexture(screen.createNewTexture("Textures/character.png"));
     
     initTextureRegions();
     initQuads();
     
     initialize();
    

    }

    private void initTextureRegions() {
    TextureRegion tr;
    // key, x, y, w, h
    tr = addTextureRegion(“head”, 1, 1, 30, 30);
    tr.flip(false, true);
    tr = addTextureRegion(“body”, 33, 1, 14, 30);
    tr.flip(false, true);
    tr = addTextureRegion(“appendage”, 1, 33, 6, 18);
    tr.flip(false, true);
    tr = addTextureRegion(“handL”, 49, 1, 14, 14);
    tr.flip(false, true);
    tr = addTextureRegion(“handR”, 49, 17, 14, 14);
    tr.flip(false, true);
    tr = addTextureRegion(“footL”, 9, 41, 21, 6);
    tr.flip(false, true);
    tr = addTextureRegion(“footR”, 9, 33, 21, 6);
    tr.flip(false, true);
    }

    private void initQuads() {
    QuadData qd;

     // quad key, TextureRegion key, position, origin
     addQuad("body", "body", Vector2f.ZERO, new Vector2f(0,0));
     
     // quad key, TextureRegion key, position, origin, parent QuadData key
     qd = addQuad("head", "head", new Vector2f(7,28), new Vector2f(15,2), "body");
     // Set the initial rotation and the max amount
     qd.setDataStruct(new QDInfo(-10,20));
     
     qd = addQuad("armL", "appendage", new Vector2f(11,26), new Vector2f(3,16), "body");
     qd.setDataStruct(new QDInfo(45,40));
     qd = addQuad("forearmL", "appendage", new Vector2f(3,2), new Vector2f(3,16), "armL");
     qd.setDataStruct(new QDInfo(45,80));
     qd = addQuad("handL", "handL", new Vector2f(3,2), new Vector2f(4,13), "forearmL");
     qd.setDataStruct(new QDInfo(12,-35));
     
     qd = addQuad("armR", "appendage", new Vector2f(3,26), new Vector2f(3,16), "body");
     qd.setDataStruct(new QDInfo(-85,40));
     qd = addQuad("forearmR", "appendage", new Vector2f(3,2), new Vector2f(3,16), "armR");
     qd.setDataStruct(new QDInfo(-85,-45));
     qd = addQuad("handR", "handR", new Vector2f(3,2), new Vector2f(10,13), "forearmR");
     qd.setDataStruct(new QDInfo(-12,-35));
     
     qd = addQuad("legL", "appendage", new Vector2f(11,3), new Vector2f(3,16), "body");
     qd.setDataStruct(new QDInfo(80,-80));
     qd = addQuad("calfL", "appendage", new Vector2f(3,2), new Vector2f(3,16), "legL");
     qd.setDataStruct(new QDInfo(-80,60));
     qd = addQuad("footL", "footL", new Vector2f(3,2), new Vector2f(3,2), "calfL");
     qd.setDataStruct(new QDInfo(-25,25));
     
     qd = addQuad("legR", "appendage", new Vector2f(3,3), new Vector2f(3,16), "body");
     qd.setDataStruct(new QDInfo(-20,-80));
     qd = addQuad("calfR", "appendage", new Vector2f(3,2), new Vector2f(3,16), "legR");
     qd.setDataStruct(new QDInfo(20,60));
     qd = addQuad("footR", "footR", new Vector2f(3,2), new Vector2f(18,2), "calfR");
     qd.setDataStruct(new QDInfo(0,25));
    

    }

    @Override
    public void animElementUpdate(float tpf) {
    for (QuadData qd : quads.values()) {
    if (qd.getDataStruct() != null) {
    QDInfo info = qd.getDataStruct();
    if (!qd.actions.contains(info.ra)) {
    // Reset the initial rotation
    qd.setRotation(info.initRot);

     			// Restart the RotateByAction
     			info.ra.restart();
     			qd.addAction(info.ra);
     		}
     	}
     }
    

    }

    public class QDInfo {
    public RotateByAction ra = new RotateByAction();
    public float initRot;
    public float maxRot;

     public QDInfo(float initRot, float maxRot) {
     	this.initRot = initRot;
     	this.maxRot = maxRot;
     	
     	ra.setDuration(1f);
     	ra.setAmount(maxRot);
     	ra.setAutoReverse(true);
     }
    

    }
    }
    [/java]

Main.java
[java]
import com.jme3.app.SimpleApplication;
import com.jme3.renderer.RenderManager;
import tonegod.gui.core.Screen;
import tonegod.gui.framework.core.AnimLayer;

/**

  • test

  • @author normenhansen
    */
    public class Anim_Tutorial_05 extends SimpleApplication {
    private Screen screen;
    private AnimLayer layer;

    public static void main(String[] args) {
    Anim_Tutorial_05 app = new Anim_Tutorial_05();
    app.start();
    }

    @Override
    public void simpleInitApp() {
    initGUIScreen();
    layoutGUI();
    }

    private void initGUIScreen() {
    screen = new Screen(this,“tonegod/gui/style/atlasdef/style_map.gui.xml”);
    screen.setUseTextureAtlas(true,“tonegod/gui/style/atlasdef/atlas.png”);
    guiNode.addControl(screen);

     flyCam.setDragToRotate(true);
     inputManager.setCursorVisible(true);
    

    }

    Character character;

    private void layoutGUI() {
    layer = screen.addAnimLayer();

     // Create a new Character and add it to the screen layer
     character = new Character(screen);
     layer.addAnimElement("character", character);
     
     // Center the AnimElement
     character.setPosition(screen.getWidth()/2, screen.getHeight()/2);
    

    }

    @Override
    public void simpleUpdate(float tpf) { }

    @Override
    public void simpleRender(RenderManager rm) { }
    }
    [/java]

And here are the results:

[video]http://youtu.be/5Y_5QFh8YEc[/video]

2 Likes

With that smile, that character has to be up to no good. :slight_smile:

1 Like

Here is another example using the same technique as the last example:

Planets.java
[java]
import com.jme3.math.Vector2f;
import tonegod.gui.core.Screen;
import tonegod.gui.framework.animation.RotateByAction;
import tonegod.gui.framework.animation.ScaleByAction;
import tonegod.gui.framework.core.AnimElement;
import tonegod.gui.framework.core.QuadData;
import tonegod.gui.framework.core.TextureRegion;

/**
*

  • @author t0neg0d
    */
    public class Planets extends AnimElement {
    Screen screen;

    public Planets(Screen screen) {
    super(screen.getApplication().getAssetManager());

     // Set a reference to the UI library's screen
     this.screen = screen;
     
     // Set the texture for the AnimElement
     setTexture(screen.createNewTexture("Textures/planets.png"));
     
     initTextureRegions();
     initQuads();
     
     initialize();
    

    }

    private void initTextureRegions() {
    TextureRegion tr;
    // key, x, y, w, h
    tr = addTextureRegion(“sun”, 1, 49, 46, 46);
    tr.flip(false, true);
    tr = addTextureRegion(“moon1”, 97, 1, 30, 30);
    tr.flip(false, true);
    tr = addTextureRegion(“moon2”, 97, 33, 30, 30);
    tr.flip(false, true);
    tr = addTextureRegion(“mars”, 1, 1, 46, 46);
    tr.flip(false, true);
    tr = addTextureRegion(“dune”, 49, 1, 46, 46);
    tr.flip(false, true);
    tr = addTextureRegion(“neptune”, 49, 49, 46, 46);
    tr.flip(false, true);
    }

    private void initQuads() {
    QuadData qd;

     // quad key, TextureRegion key, position, origin
     qd = addQuad("sun1", "sun", Vector2f.ZERO, new Vector2f(23,23));
     QDInfo info = new QDInfo();
     info.setRotation(0,360,11.6f);
     qd.setDataStruct(info);
     
     qd = addQuad("sun2", "sun", Vector2f.ZERO, new Vector2f(23,23));
     qd.setColorA(0.5f);
     info = new QDInfo();
     info.setRotation(0,-360,12.5f);
     info.setScale(1,0.24f,1.5f);
     qd.setDataStruct(info);
     
     qd = addQuad("neptune", "neptune", Vector2f.ZERO, new Vector2f(133,23), "sun");
     info = new QDInfo();
     info.setRotation(0,-360,12.5f);
     qd.setDataStruct(info);
     
     qd = addQuad("moon", "moon1", new Vector2f(23,23), new Vector2f(65,15), "neptune");
     info = new QDInfo();
     info.setRotation(0,360,2.5f);
     qd.setDataStruct(info);
     
     qd = addQuad("moon2", "moon2", new Vector2f(23,23), new Vector2f(80,15), "neptune");
     qd.setDimensions(15,15);
     info = new QDInfo();
     info.setRotation(0,360,2f);
     qd.setDataStruct(info);
     
     qd = addQuad("mars", "mars", Vector2f.ZERO, new Vector2f(263,23), "sun");
     info = new QDInfo();
     info.setRotation(0,-360,8.5f);
     qd.setDataStruct(info);
     
     qd = addQuad("moon3", "moon2", new Vector2f(23,23), new Vector2f(55,15), "mars");
     qd.setDimensions(22,22);
     info = new QDInfo();
     info.setRotation(0,-360,3f);
     qd.setDataStruct(info);
    

    }

    @Override
    public void animElementUpdate(float tpf) {
    for (QuadData qd : quads.values()) {
    if (qd.getDataStruct() != null) {
    QDInfo info = qd.getDataStruct();
    if (info.useRotation) {
    if (!qd.actions.contains(info.ra)) {
    // Reset the initial rotation
    qd.setRotation(info.initRot);

     				// Restart the RotateByAction
     				info.ra.restart();
     				qd.addAction(info.ra);
     			}
     		}
     		if (info.useScale) {
     			if (!qd.actions.contains(info.sa)) {
     				// Reset the initial scale
     				qd.setScale(info.initScale,info.initScale);
    
     				// Restart the RotateByAction
     				info.sa.restart();
     				qd.addAction(info.sa);
     			}
     		}
     	}
     }
    

    }

    public class QDInfo {
    public RotateByAction ra = new RotateByAction();
    public float initRot = 0;
    public float maxRot = 360;
    public boolean useRotation = false;

     public float maxScale = 0.2f;
     public float initScale = 1;;
     public boolean useScale = false;
     
     public ScaleByAction sa = new ScaleByAction();
     
     public QDInfo() {  }
     
     public void setRotation(float initRot, float maxRot, float duration) {
     	this.initRot = initRot;
     	this.maxRot = maxRot;
     	
     	ra.setDuration(duration);
     	ra.setAmount(maxRot);
     	
     	useRotation = true;
     }
     
     public void setScale(float initScale, float maxScale, float duration) {
     	this.initScale = initScale;
     	this.maxScale = maxScale;
     	
     	sa.setDuration(duration);
     	sa.setAmount(maxScale);
     	sa.setAutoReverse(true);
     	
     	useScale = true;
     }
    

    }
    }
    [/java]

Main.java
[java]
import com.jme3.app.SimpleApplication;
import com.jme3.renderer.RenderManager;
import tonegod.gui.core.Screen;
import tonegod.gui.framework.core.AnimLayer;

/**

  • test

  • @author normenhansen
    */
    public class Anim_Tutorial_06 extends SimpleApplication {
    private Screen screen;
    private AnimLayer layer;

    public static void main(String[] args) {
    Anim_Tutorial_06 app = new Anim_Tutorial_06();
    app.start();
    }

    @Override
    public void simpleInitApp() {
    initGUIScreen();
    layoutGUI();
    }

    private void initGUIScreen() {
    screen = new Screen(this,“tonegod/gui/style/atlasdef/style_map.gui.xml”);
    screen.setUseTextureAtlas(true,“tonegod/gui/style/atlasdef/atlas.png”);
    guiNode.addControl(screen);

     flyCam.setDragToRotate(true);
     inputManager.setCursorVisible(true);
    

    }

    Planets planets;

    private void layoutGUI() {
    layer = screen.addAnimLayer();

     planets = new Planets(screen);
     
     layer.addAnimElement("planets", planets);
     
     planets.setPosition(screen.getWidth()/2, screen.getHeight()/2);
    

    }

    @Override
    public void simpleUpdate(float tpf) { }

    @Override
    public void simpleRender(RenderManager rm) { }
    }
    [/java]

And the output:

[video]http://youtu.be/JLOrtPgWvIE[/video]

1 Like

And here are a few hints how to minimized the code above…

In these examples, I’ve handled restarting the actions manually… however, you really do not need to do this at all.

  1. TemporalAction.setAutoRestart(true); does this for you.
  2. You can get the run count (# of times completed) from the TemporalAction at any point
  3. You can flush the action from the queue at the next completed cycle by setting setAutoRestart(false); at any point.

So a for-instance (from the Planet example above):

In QDInfo:
[java]
public void setRotation(float initRot, float maxRot, float duration) {
this.initRot = initRot;
this.maxRot = maxRot;

        ra.setDuration(duration);
        ra.setAmount(maxRot);

        ra.setAutoRestart(true); // &lt;&lt;&lt; THIS LINE

        useRotation = true;
    }

    public void setScale(float initScale, float maxScale, float duration) {
        this.initScale = initScale;
        this.maxScale = maxScale;

        sa.setDuration(duration);
        sa.setAmount(maxScale);
        sa.setAutoReverse(true);

        sa.setAutoRestart(true); // &lt;&lt;&lt; THIS LINE

        useScale = true;
    }

[/java]

And then modify the update method, like so:

[java]
@Override
public void animElementUpdate(float tpf) { }
[/java]

Now, when initializing each of the quads, you can simply add the temporal action after creation and it will run forever. For instance:

[java]
qd = addQuad(“sun1″, “sun”, Vector2f.ZERO, new Vector2f(23,23));
QDInfo info = new QDInfo();
info.setRotation(0,360,11.6f);
qd.setDataStruct(info);
qd.addAction(info.ra); // <<< this line
[/java]

If there are other actions you have defined that autorestart… add those too.

1 Like

Pretty cool, I like the animated character example.

1 Like

Using Pool & PoolObjectFactory To Extend AnimElement’s Functionality

One of the interesting things you can do with Pool & PoolObjectFactory is to segment QuadData or groups of QuadData within an AnimElement. They’ll appear to function as their own object, but all is still inside of a single mesh.

To demonstrate this, we’ll use the Character example above, however, this time, we’ll be creating each of the characters within a Pool contained by the AnimElement.

Each Pool Object will create another character, however the Pool itself only needs to track the top most parent QuadData–in this case the character’s body.

Each body will store a data structure containing a reference to each of it’s associated parts, incase we need them in the future for defining poses and animations specific to each character.

Each QuadData part will still store it’s own data structure containing relevant Temporal Action info.

So, let’s take a look at the PoolObjectFactory first:

CharacterFactory.java
[java]
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import tonegod.gui.framework.animation.RotateByAction;
import tonegod.gui.framework.core.AnimElement;
import tonegod.gui.framework.core.QuadData;
import tonegod.gui.framework.core.util.PoolObjectFactory;

/**
*

  • @author t0neg0d
    */
    public class CharacterFactory implements PoolObjectFactory {
    AnimElement el;
    int index = 0;

    public CharacterFactory(AnimElement el) {
    this.el = el;
    }

    public QuadData newPoolObject() {
    QuadData body = el.addQuad(“body”+index, “body”, Vector2f.ZERO, new Vector2f(0,0));
    body.setIsMovable(true);
    body.setRotation(FastMath.nextRandomFloat()*360);

     CharacterInfo info = new CharacterInfo();
     body.setDataStruct(info);
     
     info.head = el.addQuad("head"+index, "head", new Vector2f(7,28), new Vector2f(15,2), "body"+index);
     info.head.setDataStruct(new QDInfo(-10,20));
     info.head.setColor(ColorRGBA.randomColor());
     
     QDInfo qdInfo = info.head.getDataStruct();
     info.head.setRotation(qdInfo.initRot);
     info.head.addAction(qdInfo.ra);
     
     info.armL = el.addQuad("armL"+index, "appendage", new Vector2f(11,26), new Vector2f(3,16), "body"+index);
     info.armL.setDataStruct(new QDInfo(45,40));
     qdInfo = info.armL.getDataStruct();
     info.armL.setRotation(qdInfo.initRot);
     info.armL.addAction(qdInfo.ra);
     info.forearmL = el.addQuad("forearmL"+index, "appendage", new Vector2f(3,2), new Vector2f(3,16), "armL"+index);
     info.forearmL.setDataStruct(new QDInfo(45,80));
     qdInfo = info.forearmL.getDataStruct();
     info.forearmL.setRotation(qdInfo.initRot);
     info.forearmL.addAction(qdInfo.ra);
     info.handL = el.addQuad("handL"+index, "handL", new Vector2f(3,2), new Vector2f(4,13), "forearmL"+index);
     info.handL.setDataStruct(new QDInfo(12,-35));
     qdInfo = info.handL.getDataStruct();
     info.handL.setRotation(qdInfo.initRot);
     info.handL.addAction(qdInfo.ra);
     
     info.armR = el.addQuad("armR"+index, "appendage", new Vector2f(3,26), new Vector2f(3,16), "body"+index);
     info.armR.setDataStruct(new QDInfo(-85,40));
     qdInfo = info.armR.getDataStruct();
     info.armR.setRotation(qdInfo.initRot);
     info.armR.addAction(qdInfo.ra);
     info.forearmR = el.addQuad("forearmR"+index, "appendage", new Vector2f(3,2), new Vector2f(3,16), "armR"+index);
     info.forearmR.setDataStruct(new QDInfo(-85,-45));
     qdInfo = info.forearmR.getDataStruct();
     info.forearmR.setRotation(qdInfo.initRot);
     info.forearmR.addAction(qdInfo.ra);
     info.handR = el.addQuad("handR"+index, "handR", new Vector2f(3,2), new Vector2f(10,13), "forearmR"+index);
     info.handR.setDataStruct(new QDInfo(-12,-35));
     qdInfo = info.handR.getDataStruct();
     info.handR.setRotation(qdInfo.initRot);
     info.handR.addAction(qdInfo.ra);
     
     info.legL = el.addQuad("legL"+index, "appendage", new Vector2f(11,3), new Vector2f(3,16), "body"+index);
     info.legL.setDataStruct(new QDInfo(80,-80));
     qdInfo = info.legL.getDataStruct();
     info.legL.setRotation(qdInfo.initRot);
     info.legL.addAction(qdInfo.ra);
     info.calfL = el.addQuad("calfL"+index, "appendage", new Vector2f(3,2), new Vector2f(3,16), "legL"+index);
     info.calfL.setDataStruct(new QDInfo(-80,60));
     qdInfo = info.calfL.getDataStruct();
     info.calfL.setRotation(qdInfo.initRot);
     info.calfL.addAction(qdInfo.ra);
     info.footL = el.addQuad("footL"+index, "footL", new Vector2f(3,2), new Vector2f(3,2), "calfL"+index);
     info.footL.setDataStruct(new QDInfo(-25,25));
     qdInfo = info.footL.getDataStruct();
     info.footL.setRotation(qdInfo.initRot);
     info.footL.addAction(qdInfo.ra);
     
     info.legR = el.addQuad("legR"+index, "appendage", new Vector2f(3,3), new Vector2f(3,16), "body"+index);
     info.legR.setDataStruct(new QDInfo(-20,-80));
     qdInfo = info.legR.getDataStruct();
     info.legR.setRotation(qdInfo.initRot);
     info.legR.addAction(qdInfo.ra);
     info.calfR = el.addQuad("calfR"+index, "appendage", new Vector2f(3,2), new Vector2f(3,16), "legR"+index);
     info.calfR.setDataStruct(new QDInfo(20,60));
     qdInfo = info.calfR.getDataStruct();
     info.calfR.setRotation(qdInfo.initRot);
     info.calfR.addAction(qdInfo.ra);
     info.footR = el.addQuad("footR"+index, "footR", new Vector2f(3,2), new Vector2f(18,2), "calfR"+index);
     info.footR.setDataStruct(new QDInfo(0,25));
     qdInfo = info.footR.getDataStruct();
     info.footR.setRotation(qdInfo.initRot);
     info.footR.addAction(qdInfo.ra);
     
     index++;
     
     return body;
    

    }

    public class CharacterInfo {
    public QuadData head,
    armL, forearmL, handL,
    armR, forearmR, handR,
    legL, calfL, footL,
    legR, calfR, footR;
    }

    public class QDInfo {
    public RotateByAction ra = new RotateByAction();
    public float initRot;
    public float maxRot;

     public QDInfo(float initRot, float maxRot) {
     	this.initRot = initRot;
     	this.maxRot = maxRot;
     	
     	ra.setDuration(1f);
     	ra.setAmount(maxRot);
     	ra.setAutoReverse(true);
     	ra.setAutoRestart(true);
     }
    

    }
    }
    [/java]

And now the new Character AnimElement–Characters.java
[java]
import tonegod.gui.core.Screen;
import tonegod.gui.framework.core.AnimElement;
import tonegod.gui.framework.core.QuadData;
import tonegod.gui.framework.core.TextureRegion;
import tonegod.gui.framework.core.util.Pool;

/**
*

  • @author t0neg0d
    */
    public class Characters extends AnimElement {
    Screen screen;
    Pool<QuadData> characters;
    CharacterFactory characterFactory;

    public Characters(Screen screen) {
    super(screen.getApplication().getAssetManager());
    this.screen = screen;

     setTexture(screen.createNewTexture("Textures/character.png"));
     
     initTextureRegions();
     
     characterFactory = new CharacterFactory(this);
     characters = new Pool(characterFactory, 10);
     
     initialize();
    

    }

    private void initTextureRegions() {
    TextureRegion tr;
    // key, x, y, w, h
    tr = addTextureRegion(“head”, 1, 1, 30, 30);
    tr.flip(false, true);
    tr = addTextureRegion(“body”, 33, 1, 14, 30);
    tr.flip(false, true);
    tr = addTextureRegion(“appendage”, 1, 33, 6, 18);
    tr.flip(false, true);
    tr = addTextureRegion(“handL”, 49, 1, 14, 14);
    tr.flip(false, true);
    tr = addTextureRegion(“handR”, 49, 17, 14, 14);
    tr.flip(false, true);
    tr = addTextureRegion(“footL”, 9, 41, 21, 6);
    tr.flip(false, true);
    tr = addTextureRegion(“footR”, 9, 33, 21, 6);
    tr.flip(false, true);
    }

    public QuadData getNextAvailableCharacter() {
    return characters.getNextAvailable();
    }

    public void freeCharacter(QuadData character) {
    characters.freePoolObject(character);
    }

    @Override
    public void animElementUpdate(float tpf) { }

}
[/java]

And next, Main.java --here we loop through the available pool objects and set a random position for each. This could have been handled in multiple places, but here is as good as any.

[java]
import com.jme3.app.SimpleApplication;
import com.jme3.math.FastMath;
import com.jme3.renderer.RenderManager;
import tonegod.gui.core.Screen;
import tonegod.gui.framework.core.AnimLayer;
import tonegod.gui.framework.core.QuadData;

/**

  • test

  • @author normenhansen
    */
    public class Anim_Tutorial_07 extends SimpleApplication {
    private Screen screen;
    private AnimLayer layer;

    public static void main(String[] args) {
    Anim_Tutorial_07 app = new Anim_Tutorial_07();
    app.start();
    }

    @Override
    public void simpleInitApp() {
    initGUIScreen();
    layoutGUI();
    }

    private void initGUIScreen() {
    screen = new Screen(this,“tonegod/gui/style/atlasdef/style_map.gui.xml”);
    screen.setUseTextureAtlas(true,“tonegod/gui/style/atlasdef/atlas.png”);
    guiNode.addControl(screen);

     flyCam.setDragToRotate(true);
     inputManager.setCursorVisible(true);
    

    }

    Characters characters;

    private void layoutGUI() {
    layer = screen.addAnimLayer();

     characters = new Characters(screen);
     
     for (int i = 0; i &lt; 10; i++) {
     	QuadData qd = characters.getNextAvailableCharacter();
     	qd.setPosition(
     		FastMath.nextRandomFloat()*(screen.getWidth()/2)+(screen.getWidth()/4),
     		FastMath.nextRandomFloat()*(screen.getHeight()/2)+(screen.getHeight()/4)
     	);
     }
     
     layer.addAnimElement("characters", characters);
    

    }

    @Override
    public void simpleUpdate(float tpf) { }

    @Override
    public void simpleRender(RenderManager rm) { }
    }
    [/java]

And finally, the results of this example:

NOTE: This is still a single mesh.

[video]http://youtu.be/VZSmclOa5C4[/video]

1 Like