Making a LibGDX Roguelike Survival Game Part 7 – Adding Trees, Entity Sorting #gamedev

** Noticed a typo in my entity package, this has been renamed 
evoscape/core/src/uk/co/carelesslans/entity/Entity.java **

[ Full source code for this tutorial ]

EvoScape is starting to look a little like a game but we yet to have any items to interact with, if we are going to have crafting etc then we need resources, to get started we will add some trees for wood. Before we look at how we handle collisions we need to populate the island with some trees.

Screen Shot 2017-08-06 at 14.24.27

The tree images needs to be saved to assets/entities/tree.png

Media.java
Add new a new class variable for the Tree texture, load it in the load_assets() method and ensure it is disposed in the dispose method:

public static Texture tree;
...
tree = new Texture("entities/tree.png");
...
tree.dispose();

Enums.java
Our EntityType enum needs a new type of “TREE” added for the Tree class, at the moment we do not need to use the type but it will come in useful when we want to save our game etc:

public enum EntityType {
 HERO,
 TREE
}

Tree.java
The Tree class extends Entity, it initialises entity its public construcor then sets the type, size, position, texture and creates a new Box2D body so it has a collision box the hero cannot walk through.

The hitbox is half the width and height, here you can see the createBody method accepts another 2 parameters, these are xOffset and yOffset. These new values allow us to have better positioned hitboxes. We want the Tree hitbox to be just the size of the trunk area:

public class Tree extends Entity{
    public Tree(Vector3 pos, Box2DWorld box2d){
        super();
        type = EntityType.TREE;
        width = 8;
        height = 8;
        this.pos = pos;
        texture = Media.tree;
        body = Box2DHelper.createBody(box2d.world, width/2, height/2, width/4, 0, pos, BodyDef.BodyType.StaticBody);
    }
}
tree_hitbox.png

The Tree hitbox.

Box2DHelper.java
The updated class adds xOffset onto the X value and yOffset onto the Y value of the entities position in the createBody method:

public static Body createBody(World world, float width, float height, float xOffset, float yOffset, Vector3 pos, BodyDef.BodyType type) {
       Body body;
       BodyDef bodyDef = new BodyDef();
       // Updated postion code.
       bodyDef.position.set( (pos.x + width/2) + xOffset, (pos.y + height/2) + yOffset);
       bodyDef.angle = 0;
       bodyDef.fixedRotation = true;
       bodyDef.type = type;
       body = world.createBody(bodyDef);

       FixtureDef fixtureDef = new FixtureDef();
       PolygonShape boxShape = new PolygonShape();
       boxShape.setAsBox(width / 2, height / 2);

       fixtureDef.shape = boxShape;
       fixtureDef.restitution = 0.4f;

       body.createFixture(fixtureDef);
       boxShape.dispose();

       return body;
   }

Island.java

Before Trees can be added to the Island we need an ArrayList to store them in, the array will hold a type of Entity, any class that extends Entity can be added to this list:

// Class variable
public ArrayList<Entity> entities = new ArrayList<Entity>();

Next we create a new method that will loop through all of our tiles and randomly add trees, we will look if the tile is grass before adding one also we will roll a random number and allow there to be a 10% chance of a tree being placed:

private void addEntities(Box2DWorld box2D) {
    // Loop all tiles and add random trees
    for(ArrayList<Tile>; row : chunk.tiles){
        for(Tile tile : row){
            if (tile.isGrass()){
                if(MathUtils.random(100) > 90){
                    entities.add(new Tree(tile.pos, box2D));
                }
            }
        }
    }
}
...
// As previously shown the Tree public constuctor which is called to create
// a new tree
public Tree(Vector3 pos, Box2DWorld box2d){

Calling this method and populating Trees will work but running the game they will not be drawn, we add to our render loop after the tiles have been drawn, looping and drawing the entities. The draw method within the Entity class is called unless the Tree class overrides this method:

// Draw all entities
for(Entity e: island.entities){
    e.draw(batch);
}

Running this code will show our randomly sized map and now a number of trees, this looks great until you behind a tree and find you are rendered on top of it. To overcome this we need to add the hero to the array of entities and sort it before rendering.

Screen Shot 2017-08-05 at 23.57.32

No “Z Sorting”

Entity.java
To sort the array of Entity we implement Comparable and compare the class to itself. With that in place we add a compareTo method that will compare the Y values:

// implement comparable
public class Entity implements Comparable<Entity> {
...

// New method
public int compareTo(Entity e) {
    float tempY =  e.pos.y;
    float compareY = pos.y;

    return (tempY < compareY ) ? -1: (tempY > compareY) ? 1:0 ;
}

gameclass.java
After the Hero instance is created add it to the list of Island entities so that it can be sorted and drawn in order with the array.

// Island
island = new Island(box2D); 

// Hero
hero = new Hero(island.centreTile.pos, box2D);
island.entities.add(hero); // Add hero to entity array
...

// Before drawing the entities sort the array
Collections.sort(island.entities);

Running the game we can now move behind trees:

zSort.gif

The hero is now drawn in the correct order (based on its Y value)

Adding a reset option that re-generates the Island

Control.java
Add a new boolean to the class called reset, this will be set to true on pressing ‘R’:

// New class variable
public boolean reset;

// public boolean keyUp(int keycode) {
// new key
case Keys.R:
    reset = true;
    break;

Box2DWorld.java
To reset everything we will need to clear down all of the bodies we have added to the world, a new method clearAllBodies handles this:

// New method to remove Box2D bodies from the World
public void clearAllBodies() {
    Array<Body> bodies = new Array<Body>();
    world.getBodies(bodies);
    for(Body b: bodies){
        world.destroyBody(b);
        }
    }
 }

gameclass.java
Within our game logic (Render loop) we check this reset variable and when its true call reset on hero and island, re-add hero to entities and set reset to false:

// GAME LOGIC
if(control.reset){
    island.reset(box2D);
    hero.reset(box2D,island.getCentrePosition());
    island.entities.add(hero);
    control.reset = false;
}

Island.java
Create a new reset method and call this from the public constructor, this saves re-writing the same calls twice:

public Island(Box2DWorld box2D){
    reset(box2D);
}

public void reset(Box2DWorld box2D) {
   entities.clear();
   box2D.clearAllBodies();
   setupTiles();
   codeTiles();
   generateHitboxes(box2D);
   addEntities(box2D);
}

Hero.java
Add reset method and update the public constructor, when we reset the hero we re-position it to the centre and re-create its hitbox:

public Hero(Vector3 pos, Box2DWorld box2d){
    type = EntityType.HERO;
    width = 8;
    height = 8;
    texture = Media.hero;
    speed = 30;
    reset(box2d, pos);
}

public void reset(Box2DWorld box2d, Vector3 pos) {
    this.pos.set(pos);
    body = Box2DHelper.createBody(box2d.world, width/2, height/2, width/4, 0, pos, BodyType.DynamicBody);
}

Now when we run the game and press ‘R’ the hero is placed back to the centre of the map and we generate a new island with different number of trees.

random_island

Next we will implement collision listeners so we can act on being in contact with a tree, exciting stuff!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s