Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon


Home > >

18. June 2014

 

In this part we are going to look at how to add animations to a TileMap in LibGDX.  Along the way we are going to look at using Properties a very important part of using Tile maps, as properties contain your games “data”.  First let’s take a look at setting properties in Tiled.  I am adding a second Tileset to my existing map called “Water” using the following graphic I download from Google Images.  Tiled doesn’t support animations, so we are going to hack support in using properties.

 

WaterTiles

 

You can get more details on working with Tiled here.

 

Properties are set in the Tile, not the Cell in TileEd.  Load the above image as a new Tileset in Tiled named Water.  We are working with just 3 of the water tiles:

Te1

 

Right click on a tile and select Tile Properties...

Te2

Now we want to set a property “Water Frame” and give it the value of 1.

Te3

 

Now repeat for the next two water tiles, with the values 2 and 3 respectively.

 

In addition to Tile properties, you can also set properties at the Layer and Map level.  Properties are just a name value pair of strings when imported into LibGDX.  Let’s take a look at that process now.

 

Save your tiled map and add it to the assets folder of your project.  Also add all the various texture maps used for tiles.  Let’s look at some ( heavily commented ) code that builds on our earlier tile map example:

package com.gamefromscratch;

 

import com.badlogic.gdx.ApplicationAdapter;

import com.badlogic.gdx.Gdx;

import com.badlogic.gdx.Input;

import com.badlogic.gdx.InputProcessor;

import com.badlogic.gdx.graphics.GL20;

import com.badlogic.gdx.graphics.OrthographicCamera;

import com.badlogic.gdx.graphics.Texture;

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

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

import com.badlogic.gdx.maps.tiled.*;

import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;

import java.util.*;

 

public class TiledTest extends ApplicationAdapter implements InputProcessor {

    Texture img;

    TiledMap tiledMap;

    OrthographicCamera camera;

    TiledMapRenderer tiledMapRenderer;

    SpriteBatch sb;

    Texture texture;

    Sprite sprite;

    ArrayList<TiledMapTileLayer.Cell> waterCellsInScene;

    Map<String,TiledMapTile> waterTiles;

    floatelapsedSinceAnimation = 0.0f;

 

    @Override public void create () {

        float w = Gdx.graphics.getWidth();

        float h = Gdx.graphics.getHeight();

 

        camera = new OrthographicCamera();

        camera.setToOrtho(false,w,h);

        // Position the camera over 100pixels and up 400 to capture more interesting part of map

        camera.translate(100,400);

        camera.update();

        //Load our tile map

        tiledMap = new TmxMapLoader().load("MyCrappyMap.tmx");

 

        tiledMapRenderer = new OrthogonalTiledMapRenderer(tiledMap);

        Gdx.input.setInputProcessor(this);

 

        // We created a second set of tiles for Water animations

        // For the record, this is bad for performance, use a single tileset if you can help it

        // Get a reference to the tileset named "Water"

        TiledMapTileSet tileset =  tiledMap.getTileSets().getTileSet("Water");

 

 

        // Now we are going to loop through all of the tiles in the Water tileset

        // and get any TiledMapTile with the property "WaterFrame" set

        // We then store it in a map with the frame as the key and the Tile as the value

        waterTiles = new HashMap<String,TiledMapTile>();

        for(TiledMapTile tile:tileset){

             Object property = tile.getProperties().get("WaterFrame");

            if(property != null)

                waterTiles.put((String)property,tile);

        }

 

        // Now we want to get a reference to every single cell ( Tile instance ) in the map

        // that refers to a water cell.  Loop through the entire world, checking if a cell's tile

        // contains the WaterFrame property.  If it does, add to the waterCellsInScene array

        // Note, this only pays attention to the very first layer of tiles.

        // If you want to support animation across multiple layers you will have to loop through each

        waterCellsInScene = new ArrayList<TiledMapTileLayer.Cell>();

        TiledMapTileLayer layer = (TiledMapTileLayer) tiledMap.getLayers().get(0);

        for(int x = 0; x < layer.getWidth();x++){

            for(int y = 0; y < layer.getHeight();y++){

                TiledMapTileLayer.Cell cell = layer.getCell(x,y);

                Object property = cell.getTile().getProperties().get("WaterFrame");

                if(property != null){

                    waterCellsInScene.add(cell);

                }

            }

        }

    }

 

    @Override public void render () {

        Gdx.gl.glClearColor(1, 0, 0, 1);

        Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);

        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        camera.update();

        tiledMapRenderer.setView(camera);

        tiledMapRenderer.render();

 

        // Wait for half a second to elapse then call updateWaterAnimations

        // This could certainly be handled using an Action if you are using Scene2D

        elapsedSinceAnimation += Gdx.graphics.getDeltaTime();

        if(elapsedSinceAnimation > 0.5f){

            updateWaterAnimations();

            elapsedSinceAnimation = 0.0f;

        }

    }

 

    // This is the function called every half a second to update the animated water tiles

    // Loop through all of the cells containing water.  Find the current frame and increment it

    // then update the cell's tile accordingly

    // NOTE!  This code depends on WaterFrame values being sequential in Tiled

    private void updateWaterAnimations(){

        for(TiledMapTileLayer.Cell cell : waterCellsInScene){

            String property = (String) cell.getTile().getProperties().get("WaterFrame");

            Integer currentAnimationFrame = Integer.parseInt(property);

 

            currentAnimationFrame++;

            if(currentAnimationFrame > waterTiles.size())

                currentAnimationFrame = 1;

 

            TiledMapTile newTile = waterTiles.get(currentAnimationFrame.toString());

            cell.setTile(newTile);

        }

    }

 

    @Override public boolean keyDown(int keycode) {

        return false;

    }

 

    @Override public boolean keyUp(int keycode) {

        if(keycode == Input.Keys.LEFT)

            camera.translate(-32,0);

        if(keycode == Input.Keys.RIGHT)

            camera.translate(32,0);

        if(keycode == Input.Keys.UP)

            camera.translate(0,-32);

        if(keycode == Input.Keys.DOWN)

            camera.translate(0,32);

        if(keycode == Input.Keys.NUM_1)

            tiledMap.getLayers().get(0).setVisible(!tiledMap.getLayers().get(0).isVisible());

        if(keycode == Input.Keys.NUM_2)

            tiledMap.getLayers().get(1).setVisible(!tiledMap.getLayers().get(1).isVisible());

        return false;

    }

 

    @Override public boolean keyTyped(char character) {

 

        return false;

    }

 

    @Override public boolean touchDown(int screenX, int screenY, int pointer, int button) {

        return false;

    }

 

    @Override public boolean touchUp(int screenX, int screenY, int pointer, int button) {

        return false;

    }

 

    @Override public boolean touchDragged(int screenX, int screenY, int pointer) {

        return false;

    }

 

    @Override public boolean mouseMoved(int screenX, int screenY) {

        return false;

    }

 

    @Override public boolean scrolled(int amount) {

        return false;

    }

}

Basically what we do is load our map, then we loop through the “Water” tile set and grab a reference to any tile marked as a Waterframe.  We then perform the same action, looping through all of the cells in our map ( on the first layer! ) and if the cells tile has a reference to a tile that has the Waterframe property defined.  Then every half a second we update each cell to the next available frame of animation, or loop back to the first frame if none are available.

 

Now if you run this code, voila!  Animated water:

Te4

 

In this case we manually updated tiles in the map.  LibGDX does however present another option.  They have recently added an Animated tile class.  That said, this functionality is very new so warning ,there be dragons.  In fact, I didn’t find a single implementation online, so this may in fact be the first!

 

Here is a code example using AnimatedTiledMapTile to achieve the same effect:

 

package com.gamefromscratch;

 

import com.badlogic.gdx.ApplicationAdapter;

import com.badlogic.gdx.Gdx;

import com.badlogic.gdx.graphics.GL20;

import com.badlogic.gdx.graphics.OrthographicCamera;

import com.badlogic.gdx.graphics.Texture;

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

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

import com.badlogic.gdx.maps.tiled.*;

import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;

import com.badlogic.gdx.maps.tiled.tiles.AnimatedTiledMapTile;

import com.badlogic.gdx.maps.tiled.tiles.StaticTiledMapTile;

import com.badlogic.gdx.utils.Array;

 

public class TiledTest extends ApplicationAdapter{

    Texture img;

    TiledMap tiledMap;

    OrthographicCamera camera;

    TiledMapRenderer tiledMapRenderer;

    SpriteBatch sb;

    Texture texture;

    Sprite sprite;

    Array<AnimatedTiledMapTile> waterTilesInScene;

    Array<StaticTiledMapTile> waterTiles;

    floatelapsedSinceAnimation = 0.0f;

 

    @Override public void create () {

        float w = Gdx.graphics.getWidth();

        float h = Gdx.graphics.getHeight();

 

        camera = new OrthographicCamera();

        camera.setToOrtho(false,w,h);

        camera.translate(100,400);

        camera.update();

        tiledMap = new TmxMapLoader().load("MyCrappyMap.tmx");

 

        tiledMapRenderer = new OrthogonalTiledMapRenderer(tiledMap);

 

        TiledMapTileSet tileset =  tiledMap.getTileSets().getTileSet("Water");

 

        waterTiles = new Array<StaticTiledMapTile>();

        for(TiledMapTile tile:tileset){

            Object property = tile.getProperties().get("WaterFrame");

            if(property != null) {

                waterTiles.add(new StaticTiledMapTile(tile.getTextureRegion()));

            }

        }

 

        waterTilesInScene = new Array<AnimatedTiledMapTile>();

 

        TiledMapTileLayer layer = (TiledMapTileLayer) tiledMap.getLayers().get(0);

        for(int x = 0; x < layer.getWidth();x++){

            for(int y = 0; y < layer.getHeight();y++){

                TiledMapTileLayer.Cell cell = layer.getCell(x,y);

                Object property = cell.getTile().getProperties().get("WaterFrame");

                if(property != null){

                    cell.setTile(new AnimatedTiledMapTile(0.5f,waterTiles));

                }

            }

        }

 

    }

 

    @Override public void render () {

        Gdx.gl.glClearColor(1, 0, 0, 1);

        Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);

        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        camera.update();

        tiledMapRenderer.setView(camera);

        tiledMapRenderer.render();

    }

}

 

Ultimately the logic is very similar.  Here however we actually replace the tile type of for each water instance we find in our map with a AnimatedTiledMapTile.  It is passed an interval to update ( 0.5 second again ) as well as an array of tiles to use as part of the animation.  The logic is basically identical, you just have slightly less control and no longer have to handle the updating on your own!

blog comments powered by Disqus

Month List

Popular Comments