Thursday, September 26, 2013

LibGDX Tutorial 11: Using Audio

Objectives
  • Using Table to organize the UI
  • Using Array to hold many items
  • Playing Sound and Music

Using sfxr
sfxr (http://drpetter.se/project_sfxr.html) is an audio tool that can be used to make simple game sounds. Starting this program gives





Using the left hand menu I generated 9 different sounds. I named them according to the menu name. The website describe how they generate the frequencies and envelopes and their basis: sine, square, triangle, sawtooth. After saving the files as .wav (lower right corner), I can put them in the assets folder. I also put in one mp3 file. The Sounds are of short duration (few seconds) and music could be in the several minutes range.

Program
The program uses Table to organize the buttons as you can see from the comments. I also use Array and the button code is moved to an external function. For the audio, I have a separate class. I can only only access its methods as they are public and additionally they are static since it would be useless to use a new operator for the Audio.

MyButtonStage2D.java
package com.tutorials.mybuttonstage2d;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
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.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton.TextButtonStyle;
import com.badlogic.gdx.utils.Array;
import com.tutorials.mybuttonstage2d.Sounds.MySound;

public class MyButtonStage2D implements ApplicationListener {
    private OrthographicCamera camera;
    private SpriteBatch batch;
    private Stage stage; //** stage holds the buttons **//
    private BitmapFont font; //** same as that used in Tut 7 **//
    private TextureAtlas buttonsAtlas; //** image of buttons **//
    private Skin buttonSkin; //** images are used as skins of the button **//
    private Array<TextButton> buttons; //** will contain the 9 buttons **//
    private Table table; //** add table to layout UI **//
    float width, height;
   
    @Override
    public void create() {       
        width = Gdx.graphics.getWidth();
        height = Gdx.graphics.getHeight();
       
        camera = new OrthographicCamera();
        camera.setToOrtho(false, width, height);
       
        batch = new SpriteBatch();
       
        buttonsAtlas = new TextureAtlas("buttons.pack"); //** button atlas image **//
        buttonSkin = new Skin();
        buttonSkin.addRegions(buttonsAtlas); //** skins for on and off **//
        font = new BitmapFont(Gdx.files.internal("CustomFont.fnt"),false); //** font **//
       
        stage = new Stage(width, height, true);        //** window is stage **//
        stage.clear();
        Gdx.input.setInputProcessor(stage); //** stage is responsive **//
       
        table = new Table();
        table.setFillParent(true);
       
        buttons = new Array<TextButton>();
        SetButtons(); //** This method is responsible for creating the buttons and listeners **//
       
        for (int i=0; i<9; i++) {
            table.add(buttons.get(i)).space(50).width(250);
            if ((i==2) || (i==5)) table.row();
        }
       
        MySound.PlayMusic(0.5f); //** Volume passed in **//

        /* Above Loop will do these steps
        able.add(buttons.get(0)).space(50).width(250);
        table.add(buttons.get(1)).space(50).width(250);
        table.add(buttons.get(2)).space(50).width(250).row();
        table.add(buttons.get(3)).space(50).width(250);
        table.add(buttons.get(4)).space(50).width(250);
        table.add(buttons.get(5)).space(50).width(250).row();
        table.add(buttons.get(6)).space(50).width(250);
        table.add(buttons.get(7)).space(50).width(250);
        table.add(buttons.get(8)).space(50).width(250);
        */
       
        stage.addActor(table);
    }
   
    private void SetButtons() {
       
        TextButtonStyle style = new TextButtonStyle(); //** Button properties **//
        style.up = buttonSkin.getDrawable("ButtonOff");
        style.down = buttonSkin.getDrawable("ButtonOn");
        style.font = font;
        TextButton button1 = new TextButton("COIN",style);
        buttons.add(button1);
        TextButton button2 = new TextButton("EXPLOSION", style);
        buttons.add(button2);
        TextButton button3 = new TextButton("HIT",style);
        buttons.add(button3);
        TextButton button4 = new TextButton("JUMP",style);
        buttons.add(button4);
        TextButton button5 = new TextButton("POWERUP",style);
        buttons.add(button5);
        TextButton button6 = new TextButton("RANDOMIZE",style);
        buttons.add(button6);
        TextButton button7 = new TextButton("SELECT",style);
        buttons.add(button7);
        TextButton button8 = new TextButton("SHOOT", style);
        buttons.add(button8);
        TextButton button9 = new TextButton("MUTATE", style);
        buttons.add(button9);
        buttons.get(0).addListener(new InputListener() {
            public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
                    Gdx.app.log("my app", "Coin Pressed");
                    MySound.PlayCoin();
                    return true;
            }
            public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
                    Gdx.app.log("my app", "Coin Released");
            }
        });
        buttons.get(1).addListener(new InputListener() {
            public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
                    Gdx.app.log("my app", "Explosion Pressed");
                    MySound.PlayExplosion();
                    return true;
            }
            public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
                    Gdx.app.log("my app", "Explosion Released");
            }
        });
        buttons.get(2).addListener(new InputListener() {
            public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
                    Gdx.app.log("my app", "Hit Pressed");
                    MySound.PlayHit();
                    return true;
            }
            public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
                    Gdx.app.log("my app", "Hit Released");
            }
        });
       
        buttons.get(3).addListener(new InputListener() {
            public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
                    Gdx.app.log("my app", "Jump Pressed");
                    MySound.PlayJump();
                    return true;
            }
            public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
                    Gdx.app.log("my app", "Jump Released");
            }
        });
        buttons.get(4).addListener(new InputListener() {
            public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
                    Gdx.app.log("my app", "Power Up Pressed");
                    MySound.PlayPowerup();
                    return true;
            }
            public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
                    Gdx.app.log("my app", "Power Up Released");
            }
        });
        buttons.get(5).addListener(new InputListener() {
            public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
                    Gdx.app.log("my app", "Randomize Pressed");
                    MySound.PlayRandomize();
                    return true;
            }
            public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
                    Gdx.app.log("my app", "Randomize Released");
            }
        });
        buttons.get(6).addListener(new InputListener() {
            public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
                    MySound.PlaySelect();
                    Gdx.app.log("my app", "Select Pressed");
                    return true;
            }
            public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
                    Gdx.app.log("my app", "Select Released");
            }
        });
        buttons.get(7).addListener(new InputListener() {
            public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
                    Gdx.app.log("my app", "Shoot Pressed");
                    MySound.PlayShoot();
                    return true;
            }
            public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
                    Gdx.app.log("my app", "Shoot Released");
            }
        });
        buttons.get(8).addListener(new InputListener() {
            public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
                    Gdx.app.log("my app", "Mutate Pressed");
                    MySound.PlayMutate();
                    return true;
            }
            public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
                    Gdx.app.log("my app", "Mutate Released");
            }
        });
    }

    @Override
    public void dispose() {
        batch.dispose();
        buttonSkin.dispose();
        buttonsAtlas.dispose();
        font.dispose();
        stage.dispose();
        MySound.DestroyAudio();
    }

    @Override
    public void render() {       
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
       
        stage.act();
       
        batch.setProjectionMatrix(camera.combined);
        batch.begin();
        stage.draw();
        batch.end();
    }

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

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }
}


MySounds.java
package com.tutorials.mybuttonstage2d.Sounds;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.audio.Sound;

public class MySound {
    private static Sound coin = Gdx.audio.newSound(Gdx.files.internal("sounds/coin.wav"));
    private static Sound explosion = Gdx.audio.newSound(Gdx.files.internal("sounds/explosion.wav"));
    private static Sound hit = Gdx.audio.newSound(Gdx.files.internal("sounds/hit.wav"));
    private static Sound jump = Gdx.audio.newSound(Gdx.files.internal("sounds/jump.wav"));
    private static Sound mutate = Gdx.audio.newSound(Gdx.files.internal("sounds/mutate.wav"));
    private static Sound powerup = Gdx.audio.newSound(Gdx.files.internal("sounds/powerup.wav"));
    private static Sound randomize = Gdx.audio.newSound(Gdx.files.internal("sounds/randomize.wav"));
    private static Sound select = Gdx.audio.newSound(Gdx.files.internal("sounds/select.wav"));
    private static Sound shoot = Gdx.audio.newSound(Gdx.files.internal("sounds/shoot.wav"));
    public static Music song = Gdx.audio.newMusic(Gdx.files.internal("sounds/DavidHasselhoff.mp3"));
   
    public static void PlayMusic(float volume){
        song.setLooping(true);
        song.setVolume(volume);
        song.play();
    }
   
    public static void PlayShoot(){
        shoot.play();
    }
   
    public static void PlayMutate(){
        mutate.play();
    }
   
    public static void PlaySelect(){
        select.play();
    }
   
    public static void PlayCoin(){
        coin.play();
    }
   
    public static void PlayExplosion(){
        explosion.play();
    }
   
    public static void PlayHit(){
        hit.play();
    }
   
    public static void PlayJump(){
        jump.play();
    }
   
    public static void PlayPowerup(){
        powerup.play();
    }
   
    public static void PlayRandomize(){
        randomize.play();
    }
   
    public static void DestroyAudio(){
        coin.dispose();
        explosion.dispose();
        hit.dispose();
        jump.dispose();
        mutate.dispose();
        powerup.dispose();
        randomize.dispose();
        select.dispose();
        shoot.dispose();
        song.stop();
        song.dispose();
    }

}


The output when the COIN button is selected:



Wednesday, September 25, 2013

LibGDX Tutorial 10: Button and Stage 2D

To use Buttons, you have to use Scene 2D UI.

I have two buttons, only different shades in a folder (say ImageButton)

Like Tutorial 9, I create 9-Patch images of the two buttons, and put them in the same folder:

After running Texture Packer(see Texture Packer Tutorial), and putting the resulting files in same folder these are the new contents
The file buttons.pack is a text file (which can be opened by any text software)
From the split fields, we can see that they are recognized as 9-patch,  and we note the names that we will call in program.

Now we can setup by the projects:

MyButtonStage2D.java

package com.tutorials.mybuttonstage2d;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
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.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton.TextButtonStyle;

public class MyButtonStage2D implements ApplicationListener {
    private OrthographicCamera camera;
    private SpriteBatch batch;
    private Stage stage; //** stage holds the Button **//
    private BitmapFont font; //** same as that used in Tut 7 **//
    private TextureAtlas buttonsAtlas; //** image of buttons **//
    private Skin buttonSkin; //** images are used as skins of the button **//
    private TextButton button; //** the button - the only actor in program **//
   
    @Override
    public void create() {       
        camera = new OrthographicCamera();
        camera.setToOrtho(false, 800, 480); //** w/h ratio = 1.66 **//
       
        batch = new SpriteBatch();
       
        buttonsAtlas = new TextureAtlas("buttons.pack"); //** button atlas image **//
        buttonSkin = new Skin();
        buttonSkin.addRegions(buttonsAtlas); //** skins for on and off **//
        font = new BitmapFont(Gdx.files.internal("CustomFont.fnt"),false); //** font **//
       
        stage = new Stage(800, 480, true);        //** window is stage **//
        stage.clear();
        Gdx.input.setInputProcessor(stage); //** stage is responsive **//
       
        TextButtonStyle style = new TextButtonStyle(); //** Button properties **//
        style.up = buttonSkin.getDrawable("ButtonOff");
        style.down = buttonSkin.getDrawable("ButtonOn");
        style.font = font;
       
        button = new TextButton("PRESS ME", style); //** Button text and style **//
        button.setPosition(100, 100); //** Button location **//
        button.setHeight(300); //** Button Height **//
        button.setWidth(600); //** Button Width **//
        button.addListener(new InputListener() {
            public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
                    Gdx.app.log("my app", "Pressed"); //** Usually used to start Game, etc. **//
                    return true;
            }
           
            public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
                    Gdx.app.log("my app", "Released");
            }
        });
       
        stage.addActor(button);
    }

    @Override
    public void dispose() {
        batch.dispose();
        buttonSkin.dispose();
        buttonsAtlas.dispose();
        font.dispose();
        stage.dispose();
}

    @Override
    public void render() {       
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
       
        stage.act();
       
        batch.setProjectionMatrix(camera.combined);
        batch.begin();
        stage.draw();
        batch.end();
    }

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

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }
}


This is the output when button is pressed



And this the output when the button is released.


This also is the initial state.

Monday, September 23, 2013

LibGDX Tutorial 9: Nine Patch Images

The Android Developers defines 9 Patch at their site http://developer.android.com/reference/android/graphics/NinePatch.html. Basically it is just an image in which the corners do not scale but only the rectangular middle regions. You can see this by looking at the image at the bottom of this page. Nine patch images are important for GUI widgets. Another Androids developers website also describes their tool (Draw 9 Patch). It is the (android dir)/sdk/tools folder.

Starting this tool gives


I have already drawn button1.png. It is just a blue button with slightly rounded corners. You can load any image into this utility and make the black borders where you want the image to scale.


Saving the image from the Draw 9 Patch will save it as .9.png (there are two periods - one before and one after 9).

My directory with the images  (say ImageTest)  contains 2 files - one is normal, other is the 9 patch,

Next I run Texture Packer (see last tutorial). Remember to uncheck the aliases option.

Then you will have four files

The buttons.pack is a text file containing the details of the two images:
From this you can see that button2 is indeed recognized as nine patch. See the split field is added, and the size is 64, 64 (same as button 1) so image will not show the black borders. Thus in the Java code we will refer to it as button2 and not button2.9, etc.

I created a simple Java program to test the nine patch image.

Create the project





Once the projects are imported into Eclipse, we can make changes to the main Java class

MyPatch9Image.java
package com.tutorials.myPatch9Image;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.NinePatch;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureRegion;

public class MyPatch9Image implements ApplicationListener {
    private OrthographicCamera camera;
    private SpriteBatch batch;
    private TextureAtlas buttonsAtlas; //** Holds the entire image **//
    private TextureRegion button1; //** Will Point to button1 (a TextureRegion) **//
    private NinePatch button2; //** Will Point to button2 (a NinePatch) **//
    private BitmapFont font;
       
    @Override
    public void create() {       
       
        camera = new OrthographicCamera();
        camera.setToOrtho(false, 800, 480); //** w/h ratio = 1.66 **//
       
        batch = new SpriteBatch();
        buttonsAtlas = new TextureAtlas("buttons.pack"); //** buttonsAtlas has both buttons **//
        button1 = buttonsAtlas.findRegion("button1"); //** button1 - not 9 patch **//
        button2 = buttonsAtlas.createPatch("button2"); //** button2 - 9 patch **//
        font = new BitmapFont(); //** default font **//
        font.setColor(0, 0, 1, 1); //** blue font **//
        font.setScale(2); //** 2 times size **//
    }

    @Override
    public void dispose() {
        batch.dispose();
        buttonsAtlas.dispose();
        font.dispose();
    }

    @Override
    public void render() {       
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
       
       
        batch.setProjectionMatrix(camera.combined);
        batch.begin();
        batch.draw(button1, 50, 0, 100, 100); //** not a nine patch **//
        font.draw(batch, "Not a nine patch", 180, 80);
        button2.draw(batch, 50, 200, 100, 100); //** is a nine patch **//
        font.draw(batch, "Nine patch", 180, 280);
        batch.end();
    }

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

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }
}


This is the image rendered



Sunday, September 22, 2013

LibGDX Tutorial 8: Using Texture Packer and Atlas Images

The utility TexturePacker can pack images in a selected folder. I have four images in a folder say Images.

I can get TexturePacker from the website http://code.google.com/p/libgdx-texturepacker-gui/downloads/list. It is in a zip file. Extract the main file which is a java jar file. Run the jar file using command

In the GUI program, I select the new pack name say circles as well as the Input and Output directories. They point to images folder


The images folder after runing the program is


There are two new files. One is larger png image (with the four subimages). This is the Atlas image. We also have an atlas text file which has names, positions, and dimensions of the subimages.



Now I can create the project directories


I can modify the program as such. In this I will animate the four subimages in x-direction, each with slightly different velocity.

PackerDemo.java
package com.tutorial.packerdemo;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureRegion;

public class PackerDemo implements ApplicationListener {
    private OrthographicCamera camera;
    private SpriteBatch batch;
    private TextureAtlas circleAtlas; //** Holds the entire image **//
    private TextureRegion redCircle; // ** Points to the red Circle **//
    private TextureRegion blueCircle; // ** Points to the blue Circle **//
    private TextureRegion yellowCircle; // ** Points to the yellow Circle **//
    private TextureRegion greenCircle; // ** Points to the green Circle **//
    private float velRed = 50f; // ** Red Circle velocity **//
    private float velGreen = 55f; //** Green Circle velocity **//
    private float velBlue = 60f; //** Blue Circle velocity **//
    private float velYellow = 65f; //**Red Circle velocity **//
    private float posRed, posGreen, posBlue, posYellow; //** Positions of circles**//
    private float deltaTime; //** Time between frames **//
   
    @Override
    public void create() {      
        camera = new OrthographicCamera();
        camera.setToOrtho(false, 800, 480); //** w/h ratio = 1.66 **//
        batch = new SpriteBatch();
        circleAtlas = new TextureAtlas("circles.pack"); //** Load circles.pack and circles.png **//
        redCircle = circleAtlas.findRegion("redCircle");  //** Load redCircle from circleAtlas **//
        blueCircle = circleAtlas.findRegion("blueCircle"); //** Load blueCircle from circleAtlas **//
        greenCircle = circleAtlas.findRegion("greenCircle"); //** Load greenCircle from circleAtlas **//
        yellowCircle = circleAtlas.findRegion("yellowCircle"); //** Load yellowCircle from circleAtlas **//
    }

    @Override
    public void dispose() {
        batch.dispose();
        circleAtlas.dispose();
    }

    @Override
    public void render() {      
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
      
        deltaTime = Gdx.graphics.getDeltaTime();
      
        posRed += velRed * deltaTime; //** Increase red position by its vel * time **//
        if (posRed>750) posRed = 0f; //** If near end of screen, send to beginning **//
      
        posBlue += velBlue * deltaTime; //** Increase blue position by its vel * time **//
        if (posBlue>750) posBlue = 0f; //** If near end of screen, send to beginning **//
      
        posGreen =posGreen + velGreen * deltaTime; //** Increase green position by its vel * time **//
        if (posGreen>750) posGreen = 0f; //** If near end of screen, send to beginning **//
      
        posYellow =posYellow + velYellow * deltaTime; //** Increase yellow position by its vel * time **//
        if (posYellow>750) posYellow = 0f; //** If near end of screen, send to beginning **//
      
        batch.setProjectionMatrix(camera.combined);
        batch.begin();
        batch.draw(redCircle, posRed, 100); //** draw Red circle **//
        batch.draw(greenCircle, posGreen, 200); //** draw Green circle **//
        batch.draw(blueCircle, posBlue, 300); //** draw Blue circle **//
        batch.draw(yellowCircle, posYellow, 400); //** draw Yellow circle **//
        batch.end();
    }

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

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }
}


This is the output at one time




Saturday, September 21, 2013

LibGDX Tutorial 7: TextureRegions and Custom Fonts

Objectives
  • What are TextureRegions
  • How to use the BMFont program
  • How to use the custom fonts created by BMFont in libGDX

What are TextureRegions
An image might have many subimages. This is an image with four subimages.

This is shown in GIMP window:

One way to use such images in a ligGDX application is to load into Texture, and then create TextureRegions and load a region from texture like
boxes = new Texture(Gdx.files.internal("boxes.png")); //** loading boxes **//
box1 = new TextureRegion(boxes,0,0,180,320);
box2 = new TextureRegion(boxes,200,0,480-200,320);
box3 = new TextureRegion(boxes,0,330,127,511-330);
box4 = new TextureRegion(boxes,180,325,511-180,511-320);


I calculated the locations and widths, and heights using the GIMP graphics program, and then using them in the four TextureRegion constructors. As we will see there are better methods rather than writing the coordinates ourselves.

The BMFont program
This program can be obtained at http://www.angelcode.com/products/bmfont/. This is a Windows program. The libGDX website has also another program Hiero (which is a JAR archive) and is explained at http://code.google.com/p/libgdx/wiki/Hiero.

Opening the BMFont program after installation gives


The Options menu gives the various options

We use Font Settings option to select our font, Export Options to select how to export, Visualize to see what we are exporting and finally we can save the file using Save bitmap font as ...

In the Font Setting we select a font

The font selected in the example is Comic Sans MS, with a size of 48 pixels. After selecting OK, next we go to Export Options.

I selected a size of 512 by 512. Select bit depth of 32 bits. I used the Preset Black text with alpha. Finally select PNG for image format in Textures. Leave the Text option for Font descriptor.

After selecting OK,I select these 65 characters (highlightened). Select by clicking on character.

Next I use the Visualize option


This shows the size is too big. I settled on 256 by 256 and I entered that in the Font Settings. Next I visualized


This is now OK. Thus the Option for saving can be selected. If the saved name is CustomFont we will get these two files:

  

I changed the image file name to CustomFont.png (as I only have one image) and opened the text file and changed the name of the file (in line 3):

Now these two files can be moved to the asset folder in the Eclipse project for the project, that is after creating the project files.

Setup and using the font
Create the project files.



Import the projects into Eclipse. After placing the file we can use the font.
fontTexture = new Texture(Gdx.files.internal("CustomFont.png"));
font=BitmapFont(Gdx.files.internal("CustomFont.fnt"), new TextureRegion(fontTexture), false);

MyCustomFont.java
package com.tutorials.mycustomfont;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;

public class MyCustomFont implements ApplicationListener {
    private OrthographicCamera camera;
    private SpriteBatch batch;
    private Texture fontTexture;
    private Texture boxes; //** all 4 boxes **//
    private TextureRegion box1; //** box 1 **//
    private TextureRegion box2; //** box 2 **//
    private TextureRegion box3; //** box 3 **//
    private TextureRegion box4; //** box 4 **//
    private BitmapFont font; //** font **//
    private int pos1, pos2, pos3, pos4; //** positions boxes are displayed **//
   
    @Override
    public void create() {      
        camera = new OrthographicCamera();
        camera.setToOrtho(false, 800, 800);
      
        batch = new SpriteBatch();
      
        fontTexture = new Texture(Gdx.files.internal("CustomFont.png")); //** loading font **//
        //fontTexture.setFilter(TextureFilter.Linear, TextureFilter.Linear); //** setting its scaling behavior **//
      
        boxes = new Texture(Gdx.files.internal("boxes.png")); //** loading boxes **//
        box1 = new TextureRegion(boxes,0,0,180,320);
        box2 = new TextureRegion(boxes,200,0,480-200,320);
        box3 = new TextureRegion(boxes,0,330,127,511-330);
        box4 = new TextureRegion(boxes,180,325,511-180,511-320);
        // ** Calculate the positions once so program will not have to recalculate each time in render **//
        pos1 = 50; // ** first box will be displayed at x = 0
        pos2 = pos1 + box1.getRegionWidth()+10; //** 2nd box x-position **//
        pos3 = 50; //pos2 + box2.getRegionWidth()+10; //** 3rd box x-position **//
        pos4 = pos3 + box3.getRegionWidth()+10; //** 4th box x-position **//
      
        font = new BitmapFont(Gdx.files.internal("CustomFont.fnt"), new TextureRegion(fontTexture), false);
        font.setColor(0,0,1,1); //** Blue text **//
    }

    @Override
    public void dispose() {
        batch.dispose();
        fontTexture.dispose();
        boxes.dispose();
        font.dispose();
    }

    @Override
    public void render() {      
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
      
        batch.setProjectionMatrix(camera.combined);
        batch.begin();
        font.draw(batch, "HELLO WORLD!", 50, 700);
        font.draw(batch, "BELOW ARE FOUR BOXES:", 100, 650);
        batch.draw(box1, pos1, 0);
        batch.draw(box2, pos2, 0);
        batch.draw(box3, pos3, 350);
        batch.draw(box4, pos4, 350);
        batch.end();
    }

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

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }
}


This is the program output:









Friday, September 20, 2013

LibGDX Tutorial 6: Tween Callback

This is a continuation of Tutorial 5. The different tweens do not animate at the same rate. To animate them at the same rate, the variable startTime will have to be updated at the end of each tween. This requires a callback. In the program it is called myCallBack. The event type END, which is fired when one tween events. Now instead of repeat, there is repeatYoyo for symmetrical motion, forward and back. It is easy to see the END is fired by looking at the screen, seeing when a tween ends, and looking at the log messages.

Just like yesterday, there are two programs in two different packages. Only the main one is changed with the callback. The function variable myCallBack is attached to sprite1 as the other sprites are always in sync with it. There is no need to have say myCallBack1, myCallBack2, myCallBack3 attached to the different sprites. That would be unnecessary.

MyTweenGame.java
package com.tutorialse.mytweengame;

import aurelienribon.tweenengine.BaseTween;
import aurelienribon.tweenengine.Tween;
import aurelienribon.tweenengine.TweenCallback;
import aurelienribon.tweenengine.TweenEquations;
import aurelienribon.tweenengine.TweenManager;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.utils.TimeUtils;
import com.tutorials.spritetween.SpriteTween;

public class MyTweenGame implements ApplicationListener {
    private OrthographicCamera camera;
    private SpriteBatch batch;
    private Texture texture;
    private Texture CircTxt, ElasticTxt, QuadTxt; //** text **//
    private Sprite sprite1, sprite2, sprite3;
    private TweenManager manager1, manager2, manager3;
    private long startTime;
    private long delta;
    private float w,h;
    private int TweenCount;
      
    @Override
    public void create() {      
        w = Gdx.graphics.getWidth();
        h = Gdx.graphics.getHeight();
      
        camera = new OrthographicCamera();
        camera.setToOrtho(false, 640, 480);
        batch = new SpriteBatch();
      
        texture = new Texture("circle.png");
        texture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
      
        CircTxt = new Texture("CircTxt.png");
        CircTxt.setFilter(TextureFilter.Linear, TextureFilter.Linear);
      
        ElasticTxt = new Texture("ElasticTxt.png");
        ElasticTxt.setFilter(TextureFilter.Linear, TextureFilter.Linear);
      
        QuadTxt = new Texture("QuadTxt.png");
        QuadTxt.setFilter(TextureFilter.Linear, TextureFilter.Linear);
      
        sprite1 = new Sprite(texture);
        sprite1.setPosition(50,0.25f*h);
      
        sprite2 = new Sprite(sprite1);
        sprite2.setY(0.5f*h);
      
        sprite3 = new Sprite(sprite1);
        sprite3.setY(0.75f*h);
      
        TweenCallback myCallBack = new TweenCallback(){ //** myCallBack object runs time reset **//
            @Override
            public void onEvent(int type, BaseTween<?> source) {
                startTime = TimeUtils.millis();
                TweenCount++; //** Another tween ended **//
                Gdx.app.log("my app", "ending a tween..." + TweenCount);
            }
        };
        Tween.registerAccessor(Sprite.class, new SpriteTween());
        manager1 = new TweenManager();
        Tween.to(sprite1,SpriteTween.POSITION_X,200f) //** tween POSITION_X for a duration **//
            .target(w-100) // ** final POSITION_X **//
            .ease(TweenEquations.easeInOutQuad) //** easing equation **//
            .repeatYoyo(10, 10f) //** 5 times forward, 5 times backward **//
            .setCallback(myCallBack) //** use myTweenCallback created above **//
            .setCallbackTriggers(TweenCallback.END) //** Event called 11 times after each tween **//
            .start(manager1); //** start it
        manager2 = new TweenManager();
        Tween.to(sprite2,SpriteTween.POSITION_X,200f) //** tween POSITION_X for a duration **//
            .target(w-100) // ** final POSITION_X **//
            .ease(TweenEquations.easeInOutCirc) //** easing equation **//
            .repeatYoyo(10,10f) //** 5 times forward, 5 times backward **//
            .start(manager2);
        manager3 = new TweenManager();
        Tween.to(sprite3,SpriteTween.POSITION_X,200f) // ** tween POSITION_X for a duration **/
            .target(w-100) // ** final POSITION_X **//
            .ease(TweenEquations.easeInOutElastic) //** easing equation **//
            .repeatYoyo(10,10f) //** 5 times forward, 5 times backward **//
            .start(manager3);
        startTime = TimeUtils.millis();
    }
   
    @Override
    public void dispose() {
        batch.dispose();
        texture.dispose();
    }

    @Override
    public void render() {      
        Gdx.gl.glClearColor(0, 1, 1, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        delta = (TimeUtils.millis()-startTime)/1000; // **get time delta **//
        manager1.update(delta); //** update sprite1 **//
        manager2.update(delta); //** update sprite2 **//
        manager3.update(delta); //** update sprite3 **//
        batch.setProjectionMatrix(camera.combined);
        batch.begin();
        batch.draw(ElasticTxt, 150, 0.75f*h+50);
        batch.draw(CircTxt, 150, 0.5f*h+50);
        batch.draw(QuadTxt, 150, 0.25f*h+50);
        sprite1.draw(batch);
        sprite2.draw(batch);
        sprite3.draw(batch);
        batch.end();
    }

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

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }
}
 


 This is the output in the middle of the animations. The log window shows the approximate position in time. (You can click on any image to make it bigger).



Thursday, September 19, 2013

LibGDX Tutorial 5: Tween Engine

The tween engine allows for animation of an object like position, color, rotation, etc.
Create the projects using gdx-setup.ui.jar


Notice that in the Library Selection, the Universal Tween Engine is being used (check box darkened), and it is green (as it was downloaded using last button).

Program
I will have a simple circle sprite:

I will animate it across the window in the x-direction using three of the many different Tween Equations. I will animate it across 11 times.

Now I will have two different packages. The first package if for the two tweening functions - setValues and getValues. Only the first index of the array is used for setting only 1 value. The second package is the standard libgdx file.

SpriteTween.java
package com.tutorials.spritetween;

import com.badlogic.gdx.graphics.g2d.Sprite;

import aurelienribon.tweenengine.TweenAccessor;

public class SpriteTween implements TweenAccessor<Sprite>{ //** Tweening a Sprite **//
    public static final int POSITION_X = 1; //** there will one int declaration per object **//

    @Override
    public int getValues(Sprite target, int tweenType, float[] returnValues) {
        switch(tweenType) {
            case POSITION_X: returnValues[0] = target.getX(); return 1; // ** one case for each object - returned one as only 1 value is being changed **//
            default: assert false; return -1;
        }
    }

    @Override
    public void setValues(Sprite target, int tweenType, float[] newValues) {
        switch (tweenType) {
            case POSITION_X: target.setX(newValues[0]); break;
            default: assert false; break;
        }
    }
}


For the main java file 3 managers are defined as 3 sprite objects are being controlled. In the render I will have to update each of the managers.

MyTweenGame.java
package com.tutorialse.mytweengame;

import aurelienribon.tweenengine.Tween;
import aurelienribon.tweenengine.TweenEquations;
import aurelienribon.tweenengine.TweenManager;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.utils.TimeUtils;
import com.tutorials.spritetween.SpriteTween;

public class MyTweenGame implements ApplicationListener {
    private OrthographicCamera camera;
    private SpriteBatch batch;
    private Texture texture;
    private Texture CircTxt, ElasticTxt, QuadTxt; //** text **//
    private Sprite sprite1, sprite2, sprite3;
    private TweenManager manager1, manager2, manager3;
    private long startTime;
    private long delta;
    private float w,h;
   
    @Override
    public void create() {       
        w = Gdx.graphics.getWidth();
        h = Gdx.graphics.getHeight();
       
        camera = new OrthographicCamera();
        camera.setToOrtho(false, 640, 480);
        batch = new SpriteBatch();
       
        texture = new Texture("circle.png");
        texture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
       
        CircTxt = new Texture("CircTxt.png");
        CircTxt.setFilter(TextureFilter.Linear, TextureFilter.Linear);
       
        ElasticTxt = new Texture("ElasticTxt.png");
        ElasticTxt.setFilter(TextureFilter.Linear, TextureFilter.Linear);
       
        QuadTxt = new Texture("QuadTxt.png");
        QuadTxt.setFilter(TextureFilter.Linear, TextureFilter.Linear);
       
        sprite1 = new Sprite(texture);
        sprite1.setPosition(50,0.25f*h);
       
        sprite2 = new Sprite(sprite1); //** sprite2 identical to sprite1 **//
        sprite2.setY(0.5f*h);   //** except for the y position **//
       
        sprite3 = new Sprite(sprite1); //** sprite3 identical to sprite1 **//
        sprite3.setY(0.75f*h);  //** except for the y position **//
       
        Tween.registerAccessor(Sprite.class, new SpriteTween());
        manager1 = new TweenManager();
        Tween.to(sprite1,SpriteTween.POSITION_X,1000f) //** tween POSITION_X for a duration **//
            .target(w-100) // ** final POSITION_X **//
            .ease(TweenEquations.easeInOutQuad) //** easing equation **//
            .repeat(10,1000f) //** ten more times **//
            .start(manager1); //** start it
        manager2 = new TweenManager();
        Tween.to(sprite2,SpriteTween.POSITION_X,1000f) //** tween POSITION_X for a duration **//
            .target(w-100) // ** final POSITION_X **//
            .ease(TweenEquations.easeInOutCirc) //** easing equation **//
            .repeat(10,1000f) //** ten more times **//
            .start(manager2);
        manager3 = new TweenManager();
        Tween.to(sprite3,SpriteTween.POSITION_X,1000f) // ** tween POSITION_X for a duration **/
            .target(w-100) // ** final POSITION_X **//
            .ease(TweenEquations.easeInOutElastic) //** easing equation **//
            .repeat(10,1000f) //** ten more times **//
            .start(manager3);
        startTime = TimeUtils.millis();
    }
   
   
    @Override
    public void dispose() {
        batch.dispose();
        texture.dispose();
        //font.dispose();
    }

    @Override
    public void render() {       
        Gdx.gl.glClearColor(0, 1, 1, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        delta = (TimeUtils.millis()-startTime)/1000; // **get time delta **//
        manager1.update(delta); //** update sprite1 **//
        manager2.update(delta); //** update sprite2 **//
        manager3.update(delta); //** update sprite3 **//
        batch.setProjectionMatrix(camera.combined);
        batch.begin();
        batch.draw(ElasticTxt, 150, 0.75f*h+50);
        batch.draw(CircTxt, 150, 0.5f*h+50);
        batch.draw(QuadTxt, 150, 0.25f*h+50);
        sprite1.draw(batch);
        sprite2.draw(batch);
        sprite3.draw(batch);
        batch.end();
    }

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

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }
}


This is the output at a particular time:



Wednesday, September 18, 2013

LibGDX Tutorial 4: Creating a Sprite

This is the image used:

Create projects using gdx-setup-ui.jar:

Objectives
  • Set Texture into Sprite
  • Set Sprite Size
  • Set Sprite Offset

Program
 Below I display the same sprite 12 times, and in each render there is a slightly different offset. I set the GL2 flag to true in Main.java so the images do not have to be power of 2.
cfg.useGL20 = true; //** Code in Main.java of the desktop project **//

MyCircleSprite.java
package com.tutorials.circlesprite;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;

public class MyCircleSprite implements ApplicationListener {
    private OrthographicCamera camera;
    private SpriteBatch batch;
    private Texture texture;
    private Sprite sprite;
    private int rotation;
   
    @Override
    public void create() {       
        float w = Gdx.graphics.getWidth();
        float h = Gdx.graphics.getHeight();
        camera = new OrthographicCamera(1, h/w);
        batch = new SpriteBatch();
        texture = new Texture("circleSprite.png");
        texture.setFilter(TextureFilter.Linear, TextureFilter.Linear); //** for scaling **//
        sprite = new Sprite(texture);
        sprite.setSize(0.1f, 0.3f); //** sprite size in screen coordinates **//
        sprite.setOrigin(0.05f,0); //** relative origin for rotation **//
    }

    @Override
    public void dispose() {
        batch.dispose();
        texture.dispose();
    }

    @Override
    public void render() {
        rotation=(rotation + 1) % 360; //** angle keeps on increasing **//
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        batch.setProjectionMatrix(camera.combined);
        batch.begin();
        for (int i=0; i<12; i++) {
            sprite.setRotation(i*30+rotation);
            sprite.draw(batch);
        }
        batch.end();
        Gdx.app.log("my Circle App", "Frame Rate: " +1/Gdx.graphics.getDeltaTime());
    }

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

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }
}


This is the output at a specific time:

: