JGameGrid
 
 

The source code of all examples is included in the JGameGrid distribution.

Ex09: Tile Maps

Tile maps are essential for most "platform games" in which a player sprite runs and jumps form platform to platform, avoiding to be caught by bad guys. For platform games the entire game scenario is normally many screens wide. As the player actor runs over the screen, the background image must scroll, to give the impression to move through a changing world. It is not a very good idea to create the entire world in one huge image file for two reasons: first the memory of many machines wouldn't be capable of loading the image, second it would be very difficult to implement collisions between parts of the image and the player actor.

Tile-based maps (or tile maps for short) break down the entire game background into a grid. Each cell contains either a small tile image or nothing. Each cell is a rectangle and has the same pixel size, but may contain a different image. The following tile map is 6 x 5 square cells wide and all cells contains the same image. Like in a game grid, the cells are located using indices in horizontal (x) and vertical (x) direction starting from 0. The origin of the entire map is at the upper left corner. To draw the tile map into the game grid window you use the setPosition(Point p) method that takes the pixel position (x, y) of the tile map origin relative to the upper left corner of the game grid window. (x, y) may be positioned outside the window and the tile map is clipped accordingly.

  GG11
     A tile map is like a Lego puzzle

For performance reasons the tiles in a tile map are not considered to be objects in the sense of OOP. They are addressed by their location indices relative to the grid map. For example to allocate a image to a tile, you use setImage(String imagePath, int x, int y) or setImage(String imagePath, Location location) resp.

The class GGTileMap does not provide a public constructor. To create a tile map attributed to a GameGrid instance you call createTileMap() and pass the number of tiles in each direction and the size of the tiles. A reference to the GGTileMap instance is returned that can be used to invoke methods of the class GGTileMap.

For platform games it is very important to consider interactions between actors and tiles, e.g. when an actor hits a wall or jumps on a table. In JGameGrid collisions between actors and tiles are implemented exactly the same way as collisions between actors. By default the collision area of a tile is the bounding rectangle, but you may change it to a circle, line or point at any position relative to the tile. Collisions are reported as events by a GGTileCollisionListener that declares the single method collide(Actor actor, Location location). The location parameter may be used to identify the colliding tile within the tile map. To enable collision detection between an actor myActor and a tile at location (i, k) relative to the tile map, you call myActor.addCollisionTile(new Location(i, k)).

In the following demonstration we create a tile map and use Lego bricks to display a JGameGrid advertising text (sorry, you may change it easily to whatever you like). The letters are composed by the Lego bricks. To have some more fun, we let a couple of balls travel across the letters. When hit by an brick, the balls will bounce. To implement this scenario using the Java standard API only would cost you hundred of hours, even if you are an advanced Java programmer. With JGameGrid it's still not trivial, but it reduces your effort by an order of magnitude.

We do not want to worry about the balls quitting the game grid window, so we extend the Ball class from CapturedActor from Ex05 (see Lesson 4).

public class Ball extends CapturedActor
{

  public Ball()
  {
    super(false"sprites/ball.gif", 2);
  }

  public void act()
  {
    move(10);
  }
}

It is somewhat laborious to design the letter shapes with a simple Lego brick. Because programmers are generally lazy, they try to avoid mountain climbing by inventing sophisticated algorithms and data structures. Instead of the back-breaking work to consider the 49 x 5 = 245 tiles individually, we better define a class TypeSetter to represent the letters by a descriptive string and apply a simple algorithm to transform it to tile visibility. (We only show it for the letters 'a' and 'd'.)

public class Typesetter
{
  public static String a =
    "xxxx" +
    "x  x" +
    "xxxx" +
    "x  x" +
    "x  x";
  public static String d =
    "xxx " +
    "x  x" +
    "x  x" +
    "x  x" +
    "xxx ";

  ...
  ...
}

To display the letter tiles we select the appropriate bricks with showBrick() where we also add every brick as collision partner for every ball. The Ball actors have two sprite images, one for the red and one for the green ball. We create 4 balls using a Ball array and make ball[2] and ball[3] displaying the green image by setting their visibility to sprite id 1.

The application calls implements a GGTileActionListener to get notification each time a ball hits a tile. We are happy to be exempt from tracking all the moving objects ourselves! Whenever a collision occurs the callback method collide() is invoked by the system, and from the parameter values we can retrieve the actor and tile involved in the collision process. It's then up to you to decide what to do. In our example we just bounce the ball in the vertical direction (only when the ball is above or below the tile). To avoid multiple bouncing in a short sequence, we disable the collision detection for 5 cycles by returning 5 from the collide() method.

The global act() method just moves the tile map slowly back and forth to give the impression of a moving text. Now it's time to see the result.

import ch.aplu.jgamegrid.*;
import java.awt.*;

public class Ex09 extends GameGrid implements GGTileCollisionListener
{
  private final int nbHorzTiles = 49;
  private final int nbVertTiles = 5;
  private final int tileSize = 20;
  private GGTileMap tm =
    createTileMap(nbHorzTiles, nbVertTiles, tileSize, tileSize);
  private final int xMapStart = 20;
  private final int yMapStart = 40;
  private final int xMapEnd = -380;
  private int xMap = xMapStart;
  private int dx = -1;
  private Ball[] balls = new Ball[4];

  public Ex09()
  {
    super(620, 180, 1, false);
    setBgColor(java.awt.Color.blue);
    setSimulationPeriod(50);
    tm.setPosition(new Point(xMapStart, yMapStart));

    for (int n = 0; n < 4; n++)
    {
      balls[n] = new Ball();
      addActor(balls[n], getRandomLocation(), getRandomDirection());
      balls[n].setCollisionCircle(new Point(0, 0), 8);
      balls[n].addTileCollisionListener(this);
    }
    balls[2].show(1);
    balls[3].show(1);

    createText();
    show();
    doRun();
  }

  public int collide(Actor actor, Location location)
  {
    int d = 10;
    Point ballCenter = toPoint(actor.getLocation());
    Point brickCenter = toPoint(location);
    if (ballCenter.y < brickCenter.y - d ||
      ballCenter.y > brickCenter.y + d)
      actor.setDirection(360 - actor.getDirection());
    return 5;
  }

  public void act()
  {
    xMap += dx;
    if (xMap == xMapStart || xMap == xMapEnd)
      dx = -dx;
    tm.setPosition(new Point(xMap, yMapStart));
  }

  private void createText()
  {
    Typesetter ts = new Typesetter();
    letter(0, ts.j);
    letter(5, ts.g);
    letter(11, ts.a);
    letter(17, ts.m);
    letter(24, ts.e);
    letter(30, ts.g);
    letter(36, ts.r);
    letter(42, ts.i);
    letter(45, ts.d);
  }

  private void letter(int x, String s)
  {
    int w = s.length() / 5;
    for (int k = 0; k < 5; k++)
      for (int i = 0; i < w; i++)
        if (s.charAt(k * w + i) == 'x')
          showBrick(x + i, k);
  }

  public void showBrick(int i, int k)
  {
    tm.setImage("sprites/brick.gif", i, k);
    for (int n = 0; n < 4; n++)
      balls[n].addCollisionTile(new Location(i, k));
  }

  public static void main(String[] args)
  {
    new Ex09();
  }
}

 

Execute the program locally using WebStart.

jgamegrid19