Prettier Maps


Code for this tutorial is on this git branch:

git clone -b better_maps https://dglencross@bitbucket.org/dglencross/zombiegametutorial.git

Have a look here for help setting it up


This is a screenshot from Dinowar, our Android game written with Libgdx. The random map generation is very similar to what we have implemented in this Zombie tutorial.

Compare that to what we have done so far in this tutorial series:

Until now, we haven’t put a lot of effort into making the maps look better. In this tutorial, we will make some changes so that our maps look more like this:

50% water

Notice how the edges are much cleaner, they are have curved corners and nicer edges. There is also a clear, coloured border line.

20% water

Let’s take a look at how we have achieved this. Firstly, we have created a bunch of new images (which you can use in your own projects). These are under the assets_general/tiles folder.

These images might not look obviously related to the screenshots above – but we will see how they form to create the rounded edges that we want.

Terrain transitions

At the moment, the borders of the land are just solid, blocky divisions and look very rough. To make our map look good, we need some way of making the transitions between these different types of tile look more natural.

The solution we have used is taken from this tutorial:

https://www.gamedev.net/resources/_/technical/game-programming/tilemap-based-game-techniques-handling-terrai-r934

I strongly recommend you go read this tutorial – it is not long, and I do not see the point of reproducing the content here.

But, to summarise as quickly as possible – for each tile, we work out if it touches a tile of a different type (in our case, where land and water touch) and work out what bits of the tile we should draw. But seriously, go read that tutorial.

When water meets land, the water ‘overlaps’ the ground to allow for a smoother looking transition.

Our implementation

We calculate the the transition information for each tile when we create the map. We loop through all the tiles and work out which transition images we want to apply to each one, then store that information in the Tile object. Each tile has been assigned a number of sprites which sit on top of one another to achieve the full effect. Then each time we draw, we draw every sprite for each individual tile.

Check out the changes in the following classes:

Tile.java, TileSprite.java, Pair.java and MapTools.java

Tile.java

Firstly, we load in all the sprites that we are going to use in setSprites().

Now, when a Tile is created, we create our corners and edges that will be drawn on each update.

public Tile (Pair pair) {
   makeCorners(pair);
   makeEdges(pair);
}

I’m not going to reproduce all the code here – check out the project and look for yourself. But the makeCorners() and makeEdges() methods set up what the tile is going to look like based on its neighbours (i.e. its surrounding tiles).

TileSprite.java

Now, when we draw our tiles (and TileSprites), we draw the ‘solid’ and ‘line’ sections’ of each tile. The ‘line’ parts are the borders – you can see what I mean with the following screenshot – this has had the lines removed:

Compare that to a previous screenshot. Personally I think it looks much better with the lines – but if you don’t want them, delete the following in TileSprite.java:

if (linePart!=null) {
   linePart.setBounds(pair.x, pair.y, 1, 1);
   linePart.setColor(lineColor);
   linePart.draw(batch);
}

Then delete code around adding the lines – but I don’t think you’ll want to do this.

Conclusion

I haven’t gone into a huge amount of detail about the code in this tutorial, but it is not that complicated – the important thing is to understand the concept, and your best way to do that is to read the tutorial I linked to above (and here) on GameDev.net. The code provided is based on the ideas from the GameDev tutorial.

It transforms a blocky map into a much nicer, smoother looking map. And it is possible for more than just land and water – if you are creating a more detailed map, you can fiddle with the order of precedence of your map details to get them to smoothly transition into one another.



If you like these tutorials, or want to see a game that was written in much the same way, please download Dinowar from the Google Play Store. It costs nothing and is ad-free.

Moving a character around the map 3 – Input Processing


Code for this tutorial is on this git branch:

git clone -b First_signs_of_movement https://dglencross@bitbucket.org/dglencross/zombiegametutorial.git

Have a look here for help setting it up


Next we turn to the PlayerInputSystem, our class which implements Libgdx’s InputProcessor.

When we touch the screen, we want the following to happen:

  • Detect the co-ordinates of the screen that we touched/clicked
  • Convert these to map co-ordinates
  • Work out which tile we have touched based on these map co-ordinates
  • Implement some basic logic to stop you touching off-map

ZombieGamePlayerOnScreen

Most of this logic is built-in to Libgdx. When you touch/click the screen, the method touchDown() is called. Let’s take a look at our implementation:

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

   if (!screen.gameController.isRunning()) {
      return false;
   }

   currentTouch.x = screenX;
   currentTouch.y = screenY;
   
   convertScreenToWorldCoordinates(currentTouch);

   int x = (int) currentTouch.x;
   int y = (int) currentTouch.y;

   Pair selectedTile = screen.gameController.getWorld().getMap()[x][y];
   /*
    *
    * Pretty much all commands are done here...
    *
    */

   getPlayer().setDestination(selectedTile);

   return false;

}

First up, we check if the game is running. If not, we do nothing. What does this mean? Basically we just want to make sure input is not processed before we’re ready for it. We try to access elements of our map – what if we haven’t created it yet? This is just a precaution for that.

Next, we create a vector and pass it off to be converted:

convertScreenToWorldCoordinates(currentTouch);

Let’s take a look at that method:

private Vector3 convertScreenToWorldCoordinates(Vector3 touch) {
   ZombieGame.getViewport().unproject(touch);
   
   if (touch.x < 0 ){
      touch.x = 0;
   } else if (touch.x > screen.gameController.getWorld().getWidth()) {
      touch.x = screen.gameController.getWorld().getWidth();
   }

   if (touch.y < 0) {
      touch.y = 0;
   } else if (touch.y > screen.gameController.getWorld().getHeight()) {
      touch.y = screen.gameController.getWorld().getHeight();
   }

   return touch;
}

The core part of this is just the unproject() method call. This is built into Libgdx and according to the documentation: “Transforms the specified screen coordinate to world coordinates.”

Next, we make sure the co-ordinates not off-map. You may have noticed that you cannot make the player character walk off the map. In fact, you can never set its destination to be off-map. This is where we make sure of that.

Now we return to touchDown():

int x = (int) currentTouch.x;
int y = (int) currentTouch.y;

Pair selectedTile = screen.gameController.getWorld().getMap()[x][y];

We convert to ints so that we can directly access the tiles of the map, and we ask for the tile (Pair) corresponding to those co-ordinates.


Aside: you might want to round these floats first before casting them to ints. I didn’t bother – but keep in mind that casting from float to int isn’t quite the same as rounding – it doesn’t round to the nearest number, it just discards everything after the decimal point. If your game requires this precision, use Math.round() first.


Now we’ve got a Pair to work with. In Dinowar, we next have a bunch of logic depending on which Pair you’ve clicked. Did you select your a tile belonging to your own base? Did you select a tile belonging to an opponent’s base? Did you touch water? Depending on all these, different logic is applied.

Move dinosaurs around based on which tile was touched
Move dinosaurs around based on which tile was touched

For us now, all we want to do is tell the player where to go:

/*
 *
 * Pretty much all commands are done here...
 *
 */

getPlayer().setDestination(selectedTile);

In later tutorials, we’ll flesh out the logic here. What if you touch a zombie? Maybe we should fire a bullet in that situation, rather than walk towards it.

Now you’ve got an idea of how we’re selecting where to move around the map. Next up, we’ll take a look at the camera and viewport, to make sure that we always track the player’s movement.



If you like these tutorials, or want to see a game that was written in much the same way, please download Dinowar from the Google Play Store. It costs nothing and is ad-free.

Generating random maps


Code for this tutorial is on this git branch:

git clone -b Base_project https://dglencross@bitbucket.org/dglencross/zombiegametutorial.git

Have a look here for help setting it up


This game isn’t going to have fixed ‘levels’ – each time you play a new random map will be created. This is exactly how our released game Dinowar works. We even let the user regenerate the maps until they find one they are interested in playing – the maps generate in a fraction of a second.

If you are interested to see how quickly it generates on a mobile device, download the game and have a play (if you are on Android).

Every time the player clicks the ‘refresh’ button on the right hand side, the following happens:

  • New map is generated
  • Bases are allocated (notice the lighter purple border lines)
  • Paths are generated between each base
  • The player and AI players are created and each allocated a base, depending on difficulty level.

For the Zombie game we are creating in this tutorial series, we will be looking at the first of those – creating a random map.

Representing a map in code

Our maps are rectangles, with a width and a height, and is basically broken up into lots of little cells. This might start to sound familiar…

2dArray

This is a 2-dimensional array, and the highlighted field can be referenced with map[5][8].

Now imagine if you filled this array with 1s and 0s. 0s can represent water, and 1s can represent land. Or you could fill it with any numbers you want – 0s can represent water, 1s represent coastlines, 10s represent mountainous regions, 2-9 represents somewhere in between the two extremes. Then when you draw your map, whenever you see a 10 you draw a mountain colour and so on.

In fact, early versions of Dinowar did exactly that:

early_map

Very different from the first screenshot in this post! But the core mechanics behind it are the same.


An aside: why did we choose to go with more simple maps, rather than ones with gradients? In the end, the biggest factor was speed of path-finding. Our path-finding uses Jump Point Search, an adaptation of A* search algorithms, which is faster but loses the ability to take into account different gradations of node – each node is either passable or it is not, unlike a graded map where it might be ‘expensive’ to cross a mountain.


So, all you need is a 2-D array of ints – this is a simple structure to work with. Have a look in World. We have written our own wrapper class for an int, so that we can add additional functionality to it – the ability to tell a node to draw itself, the ability to ask if it is land or water. This might seem a little pointless at the moment, but further down the line we will find it easier to work with than ints. But we have still got the 2-D array declared in World:

protected Pair[][] map

Now all you need to do is decide which Pairs should be water, and which should be land.

Noise

What you want is an algorithm which you can pass some desired width/height parameters and get back an array filled with noise. As mentioned above, we want this to be fast. There are a few different choices of algorithm that we researched, but settled on Simplex Noise. Simplex Noise is computationally cheap compared to other algorithms. For more information read the wiki:

https://en.wikipedia.org/wiki/Simplex_noise

In the best spirit of open-source software development, we are going to use code someone else wrote so that we don’t have to get too bogged down in the detail. This code comes from Richard Tingle on Stack Overflow, who in turn used code in the public domain from Stefan Gustavson.

http://stackoverflow.com/questions/18279456/any-simplex-noise-tutorials-or-resources

I’m not going to repeat his lesson here – if you are interested in the technical detail of how it works, read the answer on Stack Overflow.

Simplex Noise works well for maps because it has a continuous gradient throughout, so you can get ‘blocks’ of land.

We also have to provide this algorithm with some frequencies and amplitudes (again, read Stack Overflow for details) so that it has some random ‘seeds’ to work from. This logic is in WorldGenerator, take a look at the generateMap method.

You’ve essentially got hold of an array of noise, of the correct size for your map. Each time you call it, you pass in some differently generated freqs and amps, and get back a 2-D array full of floats.

The final stage to get your actual map data is to convert these floats into ints of 0 or 1, representing land/water. In our case, every Pair (or node) represents either water or land. We have set a threshold (still in WorldGenerator) – anything below this threshold is water, anything above it (or equal to it) is land. Try fiddling with this threshold if you want to increase/decrease the amount of water.


Aside: if you do want to have proper detail on your maps (with mountains etc), check out this excellent tutorial here: http://javagamexyz.blogspot.co.uk/2013/03/terrain-generation.html

The idea is the same, you just have various thresholds for different levels of land instead of only one.


There you have it! An 2-D array which looks like a not-too-terrible representation of a 2-D map.

All that’s left is to draw it.

Rendering the map

Open up the GameScreenRenderer and take a look at the only thing it is drawing. It makes use of a SpriteBatch (which we’ll come to in a tutorial more focused on graphics).

We loop over the Pairs in the map, and tell each one to draw itself. Each Pair holds a reference to its own Sprite, which in this case is just a white square which we will set the colour of to either blue or green depending on whether it is water or land. This might seem like overkill for drawing coloured squares – but when you want to do more complicated stuff (in Dinowar, some of the tiles are borders and have to draw different images), this is very handy.

And there you have it!

Screen Shot 2016-07-17 at 15.17.31
A map generated with Simplex Noise

Update!

I’ve written a new tutorial, where you can make your maps look a lot nicer. Check it out here: http://tutorials.boondog.xyz/2017/02/19/prettier-maps/



If you like these tutorials, or want to see a game that was written in much the same way, please download Dinowar from the Google Play Store. It costs nothing and is ad-free.