LibGDX Tutorial 3C: Scene management

11. December 2013

 

So far we’ve look at what Scene2D provides in terms of Actors, Actions as well as handling input, now we will look at some of the scene management functionality it provides.  One of the very powerful capabilities of Scene2D is grouping.  Let’s jump right in with an example.  In this example I used these two graphics:

jet(look, there is a space here!)flame

 

By the way, those are two different images.  One is a jet and the second is the engine exhaust.  Let,s take a look at grouping them together in a scene as a single transformable entity, a pretty common game development task.

 

 

package com.me.mygdxgame;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Group;
import com.badlogic.gdx.scenes.scene2d.Stage;
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;


public class SceneManagementDemo implements ApplicationListener {
    
    private Stage stage;
    private Group group;
    
    @Override
    public void create() {        
        stage = new Stage(Gdx.graphics.getWidth(),Gdx.graphics.getHeight(),true);
        final TextureRegion jetTexture = new TextureRegion(new Texture("data/jet.png"));
        final TextureRegion flameTexture = new TextureRegion(new Texture("data/flame.png"));
        
        final Actor jet = new Actor(){
            public void draw(Batch batch, float alpha){
                batch.draw(jetTexture, getX(), getY(), getOriginX(), getOriginY(), getWidth(), getHeight(), 
                        getScaleX(), getScaleY(), getRotation());
            }
        };
        jet.setBounds(jet.getX(), jet.getY(), jetTexture.getRegionWidth(), jetTexture.getRegionHeight());
    
        final Actor flame = new Actor(){
            public void draw(Batch batch, float alpha){
                batch.draw(flameTexture, getX(), getY(), getOriginX(), getOriginY(), getWidth(), getHeight(),
                        getScaleX(), getScaleY(), getRotation());
            }
        };
        flame.setBounds(0, 0, flameTexture.getRegionWidth(), flameTexture.getRegionHeight());
        flame.setPosition(jet.getWidth()-25, 25);
        
        group = new Group();
        group.addActor(jet);
        group.addActor(flame);
        
        group.addAction(parallel(moveTo(200,0,5),rotateBy(90,5)));
        
        stage.addActor(group);
        
        
    }

    @Override
    public void dispose() {
        stage.dispose();
    }

    @Override
    public void render() {    
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        stage.act(Gdx.graphics.getDeltaTime());
        stage.draw();
    }

    @Override
    public void resize(int width, int height) {
    }

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }
}

 

When you run it, you will see:

SceneManagementCompressed

 

As you can see, once grouped, the attached actors inherit any transformations applied to the group.  The code in this example should be pretty straight forward at this point, not much new here.  First we load our two images as TextureRegions.  We then create an actor for each, in both cases setting it’s boundaries with setBounds() or it wont render correctly.  For each Actor we implement the full batch.draw() function to make sure rotation and scaling are properly rendered.  Finally for the flame texture, we set it’s position relative to the jet Actor.

 

Then we create a new Group object, this is the secret sauce behind Scene2D grouping.  Then instead of adding our two Actors to the Scene, we instead add them to the Group, which is then added to the Scene.  So that we can actually see something happening in this example, we apply a moveTo and rotateBy Action to our group.  We covered Actions in the last tutorial post if you want more details.  One important thing I didn’t show here is, it is possible to translate the individual Actors within the Group.

 

One other aspect of Scene2D is determining if a hit occurred.  Back in Scene2D Tutorial Part 1 we saw the touchDown function, which is called when an Actor within a Scene is touched.  Now we will briefly look at the logic driving this process.  The Stage’s hit() method is called, which in turn calls the hit() method of every Actor within the stage.  hit() passes in un-translated coordinates, making the process easier on the developer.  The default hit() method simply checks the bounding box of the Actor…  the following example instead checks the bounding circle… in case you had a say… circular object! Smile

 

package com.me.mygdxgame;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.Touchable;

import java.util.Random;

public class SceneManagementDemo implements ApplicationListener {
    
    // Create an Actor "Jet" that displays the TextureRegion passed in
    class Jet extends Actor {
        private TextureRegion _texture;
        
        public Jet(TextureRegion texture){
            _texture = texture;
            setBounds(getX(),getY(),_texture.getRegionWidth(), _texture.getRegionHeight());
            
            this.addListener(new InputListener(){
                public boolean touchDown(InputEvent event, float x, float y, int pointer, int buttons){
                    System.out.println("Touched" + getName());
                    setVisible(false);
                    return true;
                }
            });
        }

        // Implement the full form of draw() so we can handle rotation and scaling.
        public void draw(Batch batch, float alpha){
            batch.draw(_texture, getX(), getY(), getOriginX(), getOriginY(), getWidth(), getHeight(),
                    getScaleX(), getScaleY(), getRotation());
        }
        
        // This hit() instead of checking against a bounding box, checks a bounding circle.
        public Actor hit(float x, float y, boolean touchable){
            // If this Actor is hidden or untouchable, it cant be hit
            if(!this.isVisible() || this.getTouchable() == Touchable.disabled)
                return null;
            
            // Get centerpoint of bounding circle, also known as the center of the rect
            float centerX = getWidth()/2;
            float centerY = getHeight()/2;
            
            // Square roots are bad m'kay. In "real" code, simply square both sides for much speedy fastness
            // This however is the proper, unoptimized and easiest to grok equation for a hit within a circle
            // You could of course use LibGDX's Circle class instead.
            
            // Calculate radius of circle
            float radius = (float) Math.sqrt(centerX * centerX +
                    centerY * centerY);

            // And distance of point from the center of the circle
            float distance = (float) Math.sqrt(((centerX - x) * (centerX - x)) 
                    + ((centerY - y) * (centerY - y)));
            
            // If the distance is less than the circle radius, it's a hit
            if(distance <= radius) return this;
            
            // Otherwise, it isnt
            return null;
        }
    }
    
    private Jet[] jets;
    private Stage stage;
    
    @Override
    public void create() {        
        stage = new Stage(Gdx.graphics.getWidth(),Gdx.graphics.getHeight(),true);
        final TextureRegion jetTexture = new TextureRegion(new Texture("data/jet.png"));
        
        jets = new Jet[10];
        
        // Create/seed our random number for positioning jets randomly
        Random random = new Random();
        
        // Create 10 Jet objects at random on screen locations
        for(int i = 0; i < 10; i++){
            jets[i] = new Jet(jetTexture);
            
            //Assign the position of the jet to a random value within the screen boundaries
            jets[i].setPosition(random.nextInt(Gdx.graphics.getWidth() - (int)jets[i].getWidth())
                    , random.nextInt(Gdx.graphics.getHeight() - (int)jets[i].getHeight()));
            
            // Set the name of the Jet to it's index within the loop
            jets[i].setName(Integer.toString(i));
            
            // Add them to the stage
            stage.addActor(jets[i]);
        }
        
        Gdx.input.setInputProcessor(stage);
    }

    @Override
    public void dispose() {
        stage.dispose();
    }

    @Override
    public void render() {    
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        stage.act(Gdx.graphics.getDeltaTime());
        stage.draw();
    }

    @Override
    public void resize(int width, int height) {
    }

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }
}

 

 

When you run this app, 10 randomly located jet images will be drawn on screen. 

 

SceneManagement2Compressed

 

As you click each, it will disappear.  One important thing to realize is hit() is evaluated in reverse order objects are created.  So, the last object you add to the stage will be the first one hit() if more than one object occupy the same space.  The determining function whether a Jet has been touched or not is hit().  hit() works by creating a bounding circle to fit the image within, then checking to see if the mouse pointer is within the bounds of that circle.  When a hit occurs, we return the object hit, otherwise we return null.

 

The nice thing about this system is the user doesn’t have to worry about any translations applied to it’s parent or other translations that have occurred.  It’s also important to realize this is a pretty derived example.  If you removed the overriden hit() method, the default implementation would actually work better.  You do NOT need to provide a hit() method in your derived Actor classes unless the default doesn’t fit your needs.  This example is merely to show how the Scene2D hit detection works, and how to implement a custom detector.  Should you wish to say, implement pixel-perfect detection, you could do it this way.  I commented this example a bit more than I regularly do, to explain the bits I’ve glossed over.

Programming , ,







blog comments powered by Disqus
Creating a spritesheet using Daz Studio and The GIMP
Home > Art

Creating a spritesheet using Daz Studio and The GIMP

13. February 2012

 

 

I have been playing around a bit with Daz 3D Studio since it was recently made freely available. At first I struggled to find an actual use for the program, then I realized how exceptionally easy it made creating animated sprites. The following tutorial will walk through creating the following walk cycle using Daz Studio:



The above image is actually a web animation generated from this spritesheet that we will create. All told, the process will take about 5-10 minutes, most of it will be you waiting for your computer!  If the above image isn’t animating, that means your browser ( most likely Internet Explorer ) doesn’t support the keyframes CSS attribute.  Trust me, it works. Smile

 

 

You are going to need a couple things to follow along this tutorial, all of which are (currently) freely available.

 

You will need:

 

 

 

Install all of these products.  Now we fire up Daz Studio.

 

We are going to use the default human, feel free to drag and drop and design your guy however you want.  That said, do not move the person from the default screen location.

 

Once your guy or gal is dressed/decorated however you want, its time to add some animations.  On the left hand panel, select Content Library, Walks then start-(N) and drag it down to the beginning of the timeline.

 

Like so:

 

imageimage

 

If done correctly, if you press play your character will now have a walk cycle.  You can drag down as many animations as you would like to capture, just add them one after another in the timeline.  In this example we are just going to do the single walk cycle animation.

 

Now comes the key part, you don’t actually want your character to be moving like it does currently, you want him to remain stationary.  First lets frame things into the left.  Click the view selector box to rotate to the left view.

 

image

This is the guy you are looking for, click the red section labeled left.  Now ideally your window should look something like this:

 

image

 

 

Now we need to strip out the movement part from the animation.  To do so, first we need to convert to Studio keyframes.  This is done by right clicking in the blank gray area above the timeline and selecting Bake to Studio Keyframes, like so:

 

image

 

You will get the following message:

 

image

 

Simply click Yes.  Now we can edit out your animation.  What we want to do is remove movement along the Z-axis.  In order to do this, select Parameters along the right hand panel, then you want to select the Hip ( the root of all animations ).  You can do this by either clicking it within the scene Window, or selecting it from this drop down:

 

image

 

 

Now that you have the Hip selected, in the Parameters panel ALT+Left click the zTranslate panel:

image

 

This process should reset it’s value to 0.  Now if you press play on the timeline, your animation should now be stationary.  Now its time to render our images out.  To do so in the menu select Render->Render Settings…  like so:

 

image

 

The following window will appear:

 

image

 

If not already done, make sure at the bottom right it is set to “Show Advanced Settings”.  Now drag the quality/speed slider down to 3 ( or it will take forever, for little visible gain ).  Now you want to scroll the options down a bit.  First we want to set our image render resolution.  I personally went with 128x96, but you can use whatever you want.

 

image

 

 

Now scroll the options down a bit more and select the Render To: drop down.  You want to select Image Series like this:

image

 

 

Now we want to select where to render it to.  Leave Start and End Frame at the defaults ( the entire animation ), file in a name and leave it as PNG so we get transparencies.  Switch the location from Library to Folder and pick a directory you want it to save your renderings to, like so:

image

 

Now click the green “Render” button.  You will get a warning like the following:

image

 

Simply click OK.

 

Now we wait… there is absolutely no indication it’s actually doing anything, but Daz Studio is now rendering your sprites.  The only real indicator it’s doing anything is the spinning “busy” mouse icon.  Let it do it’s thing, it took approximately 4 minutes on my PC.

 

Once it is completed, in Explorer navigate to the directory you told it to render to.  If all went well, your directory should be populated with 51 PNG images.  Here’s mine:

 

image

 

 

Now that we have our sprites, we need to make them into a sprite sheet.  If you haven’t already, install The GIMP and the sprite sheet plugin I linked earlier.  Now load up The Gimp.

 

In GIMP select File->Open As Layers…

image

 

Navigate to the folder you saved your images to, then CTRL+A to select them all ( or CTRL + Click to select them one by one ).  When finished press Open:

image

 

If everything worked correctly, your layers list should look like this:

 

image

 

Now select the Filters Menu->Sprite-Sheet->Create From Layers…  If this menu option doesn’t show up, you haven’t installed the spritesheet plugin correctly.

 

image

 

Gimp will now merge all of the layers together into a single sprite sheet in a new window like so:

 

image

 

 

Simply save this file and you are done.  My end results are this.  You may want to do some editing, like making your spritesheet square instead of one wide and short image, but this can be accomplished in a few minutes of copy and paste.  All told, one remarkably fast way to generate a walk cycle sprite animation.  Rendering other angles or different animations is simply a matter of repeating the process from a different angle or dragging and dropping different animation sets.

 

Of course, you can also create your own animations quite simply in Daz.  You can also import your own meshes and props, although I haven’t really experienced this part yet, so I do not know how painful the process is. 

Art , ,







blog comments powered by Disqus