Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon

26. June 2014

 

Since LibGDX 1.1, there has been a massively improved way of creating a LibGDX project.  This document looks at how you get up and started using the setup application.

 

First thing you have to do is download it.  Head on over to the LibGDX download page and download Setup App.

 

Libgdx1

 

Now that you’ve downloaded the file gdx-setup.jar, double click it and say OK to any security questions.  If it doesn’t load when you double click it, you either don’t have Java installed ( you need a JDK to work with LibGDX ) or your file extensions handling for .java files are broken.  Assuming everything worked correctly the setup app should have loaded.

 

Here is an example I’ve configured already:

Libgdx2

 

One important thing is you need to have the Android SDK installed as well.  Google have made the process rather confusing, as their default download is bundled with Eclipse.  If you are intending to use a different IDE, the download you want is under “Download for other Platforms”, which is an extremely stupid name…  anyways, you need to have the Android SDK ( Android Build Tools version 19.1 as of time of writing, keep in mind you have to configure the Android SDK after downloading it ) installed as you have to provide the path to where you installed it.  

 

Otherwise it’s the usual suspects here, your project name, package and class.  Keep in mind they follow Java variable naming conventions, so for example, no spaces and mostly just alphanumeric characters.  LibGDX version allows you to choose which version of LibGDX to use, although oddly enough in every release there has only ever been the option for the most recent release.

 

In the Sub Projects section, you are deciding what targets you want to support.  If you aren’t on a Mac, iOS will not be an option.  

 

Extensions are where you configure which of the LibGDX extensions you want installed/configured.  Bullet is a physics engine, free type is a font engine, tools give you the various command line tools that are bundled with LibGDX ( I believe ), Controllers is gamepad support, Box2d is a 2D physics engine while Box2dlights is… well I have no idea what that is.

 

Now the part that is of critical importance is “Advanced”.  In all honest I think the “Advanced” options are far more important than the extensions.  I would actually instead suggest making the Setup app a 2 step process and make Advanced a mandatory process.  Anyways enough about my opinions, click advanced!

LibGdx3

 

This is where you pick what kind of IDE you use.  For each you intend to use, tick the checkbox and it will generate the appropriate project files.  In this case I am going to be using IntelliJ IDEA, so I will tick next to IDEA.  The Maven mirror box is probably something you will never use so let’s ignore it.  Offline mode is a bit more difficult to decide.  Basically the project you create will automatically go online and figure out what dependencies your project has and make sure your project is always up to date.  If you aren’t going to have an internet connection reliably or yours is ungodly slow, you may consider ticking Offline Mode.  Otherwise you are probably best served leaving it unticked.  Now click Save.

 

Now back in the Setup app, click Generate:

LibGdx4

 

Now the setup app will churn away for a while, downloading required files and configuring your project. If there is something wrong with your configuration, you will be warned.  On my PC using my cell connection the process took about a minute.  If successful it should look like this:

LibGDX5

 

There is still some configuration to be done for running each project type in your IDE.  I have already covered that process in IntelliJ right here.  You only need to read the last half of that link.

 

Programming ,

22. June 2014

 

Title kinda says it all, LibGDX 1.2 was released today.  The update includes:

 

 

I don’t think the breaking changes will impact any of our tutorial series.  Interesting to see the new AI extension, will be nice to see how that develops ( A* next? ).  Improved IntelliJ build times are certainly welcome.  You can get started here.

News ,

4. May 2014

 

You know one of the things that suck about working in Java?

 

Java.

 

Or more precisely the Java runtime.  The need to have the Java runtime pre-installed in order to run your game is a bit of a pain.  Fortunately Badlogicgames, the people behind LibGDX have released Packr.

 

In their own words:

Packages your JAR, assets and a JVM for distribution on Windows (ZIP), Linux (ZIP) and Mac OS X (.app), adding a native executable file to make it appear like the app is a native app. Packr is most suitable for GUI applications.

 

To be honest, that explanation kinda sucks, their description on Reddit is much better.

This has been a pain point for a few of our users, though Minecraft made pretty much anyone on this planet install Java anyways :)

I wrote a little tool called packr which does the following:

  • bundle your jar/assets with an embedded JRE
  • add a native executable for the respective platform to make your app look native
  • minimize the JRE (zipped: 23mb, extracted 40mb)

This should make the user experience a bit better, as there aren't any things the user needs to have installed before trying your game/app. All they need to do is download a ZIP/App Bundle and execute the native executable.

It's very easy to use, either via CLI arguments, a JSON config file, or by invoking it directly from code (it's really a library).

 

There are a couple limitations right now:

    • Icons aren't set yet on any platform, need to do that manually.
    • Windows is 32-bit only, Linux is 64-bit only, Mac OS X is 64-bit only
    • JRE minimization is very conservative, depending on your app, you can carve out stuff from a JRE yourself, disable minimization and pass your custom JRE to packr

 

If this sounds useful to you, you can learn more at the Packr github page.

News ,

1. May 2014

 

In the prior tutorial we got a simple top down tile map working.  Now we want to add some character to it, literally.  Depending on your needs this can be laughably simple or somewhat complex.  Let’s take a look at a basic example.  This is building on our previous code example and if you’ve worked through the prior tutorials in this series, there is nothing new here yet.

 

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.TiledMap;
import com.badlogic.gdx.maps.tiled.TiledMapRenderer;
import com.badlogic.gdx.maps.tiled.TmxMapLoader;
import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;

public class TiledTest extends ApplicationAdapter implements InputProcessor {
    Texture img;
    TiledMap tiledMap;
    OrthographicCamera camera;
    TiledMapRenderer tiledMapRenderer;
    SpriteBatch sb;
    Texture texture;
    Sprite sprite;
    
    @Override public void create () {
        float w = Gdx.graphics.getWidth();
        float h = Gdx.graphics.getHeight();

        camera = new OrthographicCamera();
        camera.setToOrtho(false,w,h);
        camera.update();
        tiledMap = new TmxMapLoader().load("MyCrappyMap.tmx");
        tiledMapRenderer = new OrthogonalTiledMapRenderer(tiledMap);
        Gdx.input.setInputProcessor(this);

        sb = new SpriteBatch();
        texture = new Texture(Gdx.files.internal("pik.png"));
        sprite = new Sprite(texture);
    }

    @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();
        sb.begin();
        sprite.draw(sb);
        sb.end();
    }

    @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;
    }
}

 

 

So what exactly did we do?  Well first off, we added this sprite to the project in the android/asset folder.

pik

It might look somewhat familiar… 

In create() we allocate a sprite batch, load our texture and create a sprite with it.  Finally in render() after the map is drawn, we simply draw our sprite to the batch.  Order is of critical importance.  One other thing I should probably point out… I don’t dispose() of anything, so this code leaks like mad…  Now if you run the code you see:

image

 

So far so good! 

 

First problem we’ve got here is, how do we position our sprite within the world?  In this case I am simply drawing at the origin, but how would we go about positioning the sprite in the tile a user clicks?  Let’s go ahead and see!

 

Add the following to the touchDown handler:

public boolean touchDown(int screenX, int screenY, int pointer, int button) {
    Vector3 clickCoordinates = new Vector3(screenX,screenY,0);
    Vector3 position = camera.unproject(clickCoordinates);
    sprite.setPosition(position.x, position.y);
    return true;
}

 

Now we need to make a small change to our sprite batch.  We need to apply our camera transformations to it, so when we scroll the screen around using the arrow keys, our sprite stays put.  This is easily accomplished in render():

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();
    sb.setProjectionMatrix(camera.combined);
    sb.begin();
    sprite.draw(sb);
    sb.end();
}

 

Now when you run it, the sprite will draw where you click in the world:

image

 

There’s a small possible problem here… what if you wanted your sprite to be BEHIND those rocks, or items in the higher level layers? 

 

There are a couple of ways to do this.  First you could actually create a layer for player sprites.  You could simply create a layer in Tiled that you kept empty and add your sprite into it.  Here instead I am going to do so programmatically.  Let me make something perfectly clear, this is most certainly *NOT* how you would do it, but it does give an interesting bit of insight into how tiles and layers are composed.

public void create () {
    float w = Gdx.graphics.getWidth();
    float h = Gdx.graphics.getHeight();

    camera = new OrthographicCamera();
    camera.setToOrtho(false,w,h);
    camera.update();
    tiledMap = new TmxMapLoader().load("MyCrappyMap.tmx");
    tiledMapRenderer = new OrthogonalTiledMapRenderer(tiledMap);
    Gdx.input.setInputProcessor(this);

    sb = new SpriteBatch();
    texture = new Texture(Gdx.files.internal("pik.png"));
    sprite = new Sprite(texture);

    
    // Get the width and height of our maps
    // Then halve it, as our sprites are 64x64 not 32x32 that our map is made of
    int mapWidth = tiledMap.getProperties().get("width",Integer.class)/2;
    int mapHeight = tiledMap.getProperties().get("height",Integer.class)/2;

    // Create a new map layer
    TiledMapTileLayer tileLayer = new TiledMapTileLayer(mapWidth,mapHeight,64,64);
    
    // Create a cell(tile) to add to the layer
    TiledMapTileLayer.Cell cell = new TiledMapTileLayer.Cell();
    
    // The sprite/tilesheet behind our new layer is a single image (our sprite)
    // Create a TextureRegion that is the entire size of our texture
    TextureRegion textureRegion = new TextureRegion(texture,64,64);
    
    // Now set the graphic for our cell to our newly created region
    cell.setTile(new StaticTiledMapTile(textureRegion));
    
    // Now set the cell at position 4,10 ( 8,20 in map coordinates ).  This is the position of a tree
    // Relative to 0,0 in our map which is the bottom left corner
    tileLayer.setCell(4,10,cell);

    // Ok, I admit, this part is a gross hack. 
    // Get the current top most layer from the map and store it
    MapLayer tempLayer = tiledMap.getLayers().get(tiledMap.getLayers().getCount()-1);
    // Now remove it
    tiledMap.getLayers().remove(tiledMap.getLayers().getCount()-1);
    // Now add our newly created layer
    tiledMap.getLayers().add(tileLayer);
    // Now add it back, now our new layer is not the top most one.
    tiledMap.getLayers().add(tempLayer);
}

 

And look, we are behind a tree!

image

 

 

The comments explain it all really.  We basically are programmatically creating a new Tile Layer within the map and positioning it below the top most layer.  You would never do it this way for a sprite for several reasons.  First you will only be able to move in cell by cell chunks ( aka, 64 pixels at a time ).  In certain maps that makes sense, like old school Ultima 4 type role playing games.  Next, to move you actually will have to remove the tile from it’s current location and add it to the new one.  Finally, it’s probably pretty slow to boot.  However if you want to dynamically populate a layer, say with power ups or things you create programmatically instead of using the level editor, this is how you can do it.

 

What about when dealing with the players sprite and moving it only a few pixels at a time?  Well that’s slightly more complicated.

 

One option you have, especially if you want to use the Sprite class, is to extend the map rendering class, in this case OrthogonalTiledMapRenderer then override the render method and render your sprite(s).  Here is a simple example:

package com.gamefromscratch;

import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.maps.MapLayer;
import com.badlogic.gdx.maps.MapObject;
import com.badlogic.gdx.maps.tiled.TiledMap;
import com.badlogic.gdx.maps.tiled.TiledMapTileLayer;
import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;

import java.util.ArrayList;
import java.util.List;

public class OrthogonalTiledMapRendererWithSprites extends OrthogonalTiledMapRenderer {
    private Sprite sprite;
    private List<Sprite> sprites;
    private int drawSpritesAfterLayer = 1;

    public OrthogonalTiledMapRendererWithSprites(TiledMap map) {
        super(map);
        sprites = new ArrayList<Sprite>();
    }

    public void addSprite(Sprite sprite){
        sprites.add(sprite);
    }

    @Override
    public void render() {
        beginRender();
        int currentLayer = 0;
        for (MapLayer layer : map.getLayers()) {
            if (layer.isVisible()) {
                if (layer instanceof TiledMapTileLayer) {
                    renderTileLayer((TiledMapTileLayer)layer);
                    currentLayer++;
                    if(currentLayer == drawSpritesAfterLayer){
                        for(Sprite sprite : sprites)
                            sprite.draw(this.getSpriteBatch());
                    }
                } else {
                    for (MapObject object : layer.getObjects()) {
                        renderObject(object);
                    }
                }
            }
        }
        endRender();
    }
}

 

Now we simply use it in place of the other TileMapRenderer.  Not now that we no longer need a SpriteBatch, since we are using the map renderer’s.  Here’s the updated code:

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.TiledMap;
import com.badlogic.gdx.maps.tiled.TmxMapLoader;
import com.badlogic.gdx.math.Vector3;

public class TiledTest extends ApplicationAdapter implements InputProcessor {
    Texture img;
    TiledMap tiledMap;
    OrthographicCamera camera;
    OrthogonalTiledMapRendererWithSprites tiledMapRenderer;
    Texture texture;
    Sprite sprite;
    
    @Override
    public void create () {
        float w = Gdx.graphics.getWidth();
        float h = Gdx.graphics.getHeight();

        camera = new OrthographicCamera();
        camera.setToOrtho(false,w,h);
        camera.update();

        texture = new Texture(Gdx.files.internal("pik.png"));
        sprite = new Sprite(texture);

        tiledMap = new TmxMapLoader().load("MyCrappyMap.tmx");
        tiledMapRenderer = new OrthogonalTiledMapRendererWithSprites(tiledMap);
        tiledMapRenderer.addSprite(sprite);
        Gdx.input.setInputProcessor(this);
    }

    @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();
    }

    @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) {
        Vector3 clickCoordinates = new Vector3(screenX,screenY,0);
        Vector3 position = camera.unproject(clickCoordinates);
        sprite.setPosition(position.x, position.y);
        return true;
    }

    @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;
    }
}

 

Now when you run it, where you click the player sprite will be positioned correctly and behind objects:

image

.

Under this system however, you just treat your sprite normally.  Update it’s position and it’s updated in the game world.

 

There is one other possible way to support this functionality, that is a bit of a hybrid of both approaches.  Like the earlier example, we add a new layer in to the map, in this case we are going to do it in Tiled instead of programmatically.

 

In Tiled, create a new object layer and position it like so:

image

 

Now we are going to access it using the MapObject class, specifically the TextureMapObject ( a somewhat confusingly named class… ).  Once again this requires a custom TileRenderer implementation, this time overriding the renderObject class, like so:

package com.gamefromscratch;

import com.badlogic.gdx.maps.MapObject;
import com.badlogic.gdx.maps.objects.TextureMapObject;
import com.badlogic.gdx.maps.tiled.TiledMap;
import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;

public class OrthogonalTiledMapRendererWithSprites extends OrthogonalTiledMapRenderer {

    public OrthogonalTiledMapRendererWithSprites(TiledMap map) {
        super(map);
    }

    @Override
    public void renderObject(MapObject object) {
        if(object instanceof TextureMapObject) {
            TextureMapObject textureObj = (TextureMapObject) object;
                spriteBatch.draw(textureObj.getTextureRegion(), textureObj.getX(), textureObj.getY());
        }
    }
}

 

renderObject() is called to render the various well… MapObjects in the tilemap.  The default implementation is empty, so we need to provide one.  Here we simply check to see if our object is a TextureMapObject and if it is we draw it using the spriteBatch, much like before.

 

A side effect is we no longer use the Sprite class for positioning.  Instead we create a TextureRegion again and control position via the TextureMapObject setX/setY methods.  Let’s take a look at the result on our main application:

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.graphics.g2d.TextureRegion;
import com.badlogic.gdx.maps.MapLayer;
import com.badlogic.gdx.maps.objects.TextureMapObject;
import com.badlogic.gdx.maps.tiled.TiledMap;
import com.badlogic.gdx.maps.tiled.TiledMapRenderer;
import com.badlogic.gdx.maps.tiled.TmxMapLoader;

import com.badlogic.gdx.math.Vector3;

public class TiledTest extends ApplicationAdapter implements InputProcessor {
    Texture img;
    TiledMap tiledMap;
    OrthographicCamera camera;
    TiledMapRenderer tiledMapRenderer;
    SpriteBatch sb;
    Texture texture;
    Sprite sprite;
    MapLayer objectLayer;

    TextureRegion textureRegion;

    @Override
    public void create () {
        float w = Gdx.graphics.getWidth();
        float h = Gdx.graphics.getHeight();

        camera = new OrthographicCamera();
        camera.setToOrtho(false,w,h);
        camera.update();
        tiledMap = new TmxMapLoader().load("MyCrappyMap.tmx");
        tiledMapRenderer = new OrthogonalTiledMapRendererWithSprites(tiledMap);
        Gdx.input.setInputProcessor(this);
        texture = new Texture(Gdx.files.internal("pik.png"));

        objectLayer = tiledMap.getLayers().get("objects");
        textureRegion = new TextureRegion(texture,64,64);

        TextureMapObject tmo = new TextureMapObject(textureRegion);
        tmo.setX(0);
        tmo.setY(0);
        objectLayer.getObjects().add(tmo);
    }

    @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();
    }

    @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) {
        Vector3 clickCoordinates = new Vector3(screenX,screenY,0);
        Vector3 position = camera.unproject(clickCoordinates);
        TextureMapObject character = (TextureMapObject)tiledMap.getLayers().get("objects").getObjects().get(0);
        character.setX((float)position.x);
        character.setY((float)position.y);
        return true;
    }

    @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;
    }
}

 

It’s mostly the same, except of course the different renderer, the fact we no longer use a Sprite object and in touchDown() we locate the TextureMapObject in the “objects” layer and update its positions.

 

 

Which method you use is up to you, your personal preference and the requirements of your game.  Myself, I find the second to be probably the most clean.  Of course if your sprite doesn’t need to care about tile depth at all, you can ignore all of this and just use SpriteBatch over top and save yourself many headaches!

Programming , , ,

16. April 2014

 

If you’ve never used Tiled read this first!

 

In this tutorial part we are going to look at loading orthogonal maps in LibGDX.  Orthogonal basically means “at a right angle to” which is a fancy way of saying the camera is looking straight at the scene.  Another much less fancy word for this style of game is “top down”.  Common examples include almost every single game from the 16Bit generation of consoles such as Zelda or MegaMan.

 

One of the first problems you are going to encounter is how do you create your maps?  One very common solution is the Tiled Map Editor which fortunately I just completed a tutorial on!  This tutorial assumes you know how to generate a TMX file, so if you haven’t be sure to go through the linked tutorial.  Fortunately LibGDX makes it very easy to work with TMX files.

 

First things first we need to copy the TMX and any/all tilemap image files used to your assets folder, like so:

image

You may notice unlike earlier tutorials I am currently using IntelliJ.  With the recent switch to Gradle it is much easier to get up and running in IntelliJ and I massively prefer it to Eclipse.  That said, everything I say is equally valid in Eclipse or IntelliJ and when there are differences, I will point them out.  If you want to get started with IntelliJ read here.

 

Alright, back on topic…

 

Now that you have the map and tilesets in your project, let’s jump right in with the code:

 

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.maps.tiled.TiledMap;
import com.badlogic.gdx.maps.tiled.TiledMapRenderer;
import com.badlogic.gdx.maps.tiled.TmxMapLoader;
import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;

public class TiledTest extends ApplicationAdapter implements InputProcessor {
    Texture img;
    TiledMap tiledMap;
    OrthographicCamera camera;
    TiledMapRenderer tiledMapRenderer;
    
    @Override
    public void create () {
        float w = Gdx.graphics.getWidth();
        float h = Gdx.graphics.getHeight();

        camera = new OrthographicCamera();
        camera.setToOrtho(false,w,h);
        camera.update();
        tiledMap = new TmxMapLoader().load("MyCrappyMap.tmx");
        tiledMapRenderer = new OrthogonalTiledMapRenderer(tiledMap);
        Gdx.input.setInputProcessor(this);
    }

    @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();
    }

    @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;
    }
}

 

When you run the code you should see your map.  Pressing the arrow keys will scroll around the map ( and show bright red when you’ve moved beyond the extents of your map ) .  Pressing 0 or 1 will toggle the visibility of each of the two layers in your map.  ( See the tutorial on Tiled if that makes no sense ).

 

image

 

The impressive thing here is how little code was required to accomplish so much.  In a nutshell it was basically just this:

 

camera = new OrthographicCamera();
camera.setToOrtho(false,w,h);
camera.update();
tiledMap = new TmxMapLoader().load("MyCrappyMap.tmx");
tiledMapRenderer = new OrthogonalTiledMapRenderer(tiledMap);

 

We create an OrthographicCamera, set it to the dimensions of the screen and update() it.  Next we load our map using TmxMapLoader.load() and create a OrthogonalTiledMapRenderer passing in our tiled map.

 

In our render method:

camera.update();
tiledMapRenderer.setView(camera);
tiledMapRenderer.render();

Basically update the camera ( in case we moved it using arrow keys ), pass it in to the TiledMapRenderer with setView() and finally render() the map.  That’s it.

As you can see in our key handler:

 

@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;
}

 

Navigating around the map is simply a matter of moving around the camera.  We move in 32pixel chunks because those are the size of our tiles.  So basically we move the camera left/right/up/down by one tile each time an arrow key is pressed.  In the event the 0 or 1 key are pressed we toggle the visibility of that particular layer.  As you can see, you can access the TileMap layers using the getLayers().get() function.

 

In the next part we will look at adding some more complex functionality.

 

On to part two.

Programming , , ,

Month List

Popular Comments