Tutorial [Java]: Creating a Snake Game [Part 03] - The World

Note: I have completely rewritten and improved this game. Most of my traffic comes from these posts, so I've not deleted them. Please use the new version which can be found here.

The next step in creating the Snake Game is to create the world. Without the world, there would be no where for the game to take place, which wouldn't be fun at all. In the case of the Snake Game, the world will be an array of tiles, each with a type, which we can make use of when writing the game's logic.

Our game world will be stored in a new class named GameBoard. This class will contain an array that determines what type each tile is. While we could easily just push this into the Engine class, I prefer to separate things into different classes as often as possible.

The first thing we're going to do with this class is create the constant variables associated with it.

/*
 * The number of pixels each tile is along the x and y axis.
 */
public static final int TILE_SIZE = 25;

/*
 * The number of tiles the map is along the x and y axis.
 */
public static final int MAP_SIZE = 20;

These variables are used when rendering the board, and can be changed at any time without causing problems with the rendering. To reflect this change, we're going to need to change the window creation code in the Engine class quickly. Change the line for setting the dimension of the canvas to:

canvas.setPreferredSize(new Dimension(GameBoard.MAP_SIZE * GameBoard.TILE_SIZE,
GameBoard.MAP_SIZE * GameBoard.TILE_SIZE));

Now, if we decide to change the variables at all, the window will adjust it's size to reflect these changes.

Next, we will create an way of determining tile types. We could just create new constants for each tile type, but there are a few problems with that, which I won't bother discussing here. Instead, we're going to create a new enum that contains the different valid tile types.

public static enum TileType {
 /*
  * Snake Tiles will kill us if we run into them.
  */
 SNAKE(Color.GREEN),
 
 /*
  * Fruit Tiles will give us points when we run into them.
  */
 FRUIT(Color.RED),

 /*
  * Empty Tiles do nothing when we run into them.
  */
 EMPTY(null);

 /*
  * The color of the tile.
  */
 private Color tileColor;

 /*
  * TileType constructor
  * @param color The color of this tile.
  */
 private TileType(Color color) {
  this.tileColor = color;
 }

 /*
  * @return The color of this tile.
  */
 public Color getColor() {
  return tileColor;
 }
}

Now, we can use this to not only give each tile type a unique identifier, but we can also store information such as the color to render the tile in as well. This allows us to be more flexible than if we used an integer value.

Now that we have the basic information setup, we still need to create the tile array itself, and need to allow the user to get and set the types of each tile as needed. This is quite simple, so I'll just post the code.

/**
 * Stores an array that tracks the type of tile.
 */
private TileType[] tiles;

/**
 * Creates a new instance of the GameBoard class, and initialises
 * all of the tiles to EMPTY.
 */
public GameBoard() {
 tiles = new TileType[MAP_SIZE * MAP_SIZE];
 resetBoard();
}

/**
 * Reset all of the tiles to EMPTY.
 */
public void resetBoard() {
 for(int i = 0; i < tiles.length; i++) {
  tiles[i] = TileType.EMPTY;
 }
}

/**
 * Sets the tile at the given coordinates.
 * @param x The x coordinate of the tile.
 * @param y The y coordinate of the tile.
 * @param type The type of tile.
 */
public void setTile(int x, int y, TileType type) {
 tiles[y * MAP_SIZE + x] = type;
}

/**
 * Gets the tile at the given coordinates.
 * @param x The x coordinate of the tile.
 * @param y The y coordinate of the tile.
 * @return The type of tile.
 */
public TileType getTile(int x, int y) {
 return tiles[y * MAP_SIZE + x];
}

The final step is to create a method that will render the tiles when called. Now we could draw every tile in order, but that would be very inefficient. If we think about it for a minute, we can easily optimize the code to render much faster (even though the game is not very CPU intensive to begin with, it's still good practice to optimize code when possible). Because our screen is cleared to black at the beginning of every frame, all tiles will be black, which means we don't need to worry about rendering EMPTY tiles. This leaves us with FRUIT and SNAKE tiles. We could just set the color before drawing each tile, but there's a better way yet.

Since we will only be drawing one FRUIT tile per frame, and dozens of SNAKE tiles per frame, we can just set the color to the SNAKE color before looping through the tiles, and only change it when we hit the FRUIT tile. Since this is a bit hard to explain in words, here's the source code.

/**
 * Draws the game board.
 * @param g The graphics object to draw to.
 */
public void draw(Graphics2D g) {
 
 /*
  * Set the color of the tile to the snake color.
  */
 g.setColor(TileType.SNAKE.getColor());
 
 /*
  * Loop through all of the tiles.
  */
 for(int i = 0; i < MAP_SIZE * MAP_SIZE; i++) {
  
  /*
   * Calculate the x and y coordinates of the tile.
   */
  int x = i % MAP_SIZE;
  int y = i / MAP_SIZE;
  
  /*
   * If the tile is empty, so there is no need to render it.
   */
  if(tiles[i].equals(TileType.EMPTY)) {
   continue;
  }
  
  /*
   * If the tile is fruit, we set the color to red before rendering it.
   */
  if(tiles[i].equals(TileType.FRUIT)) {
   g.setColor(TileType.FRUIT.getColor());
   g.fillOval(x * TILE_SIZE + 4, y * TILE_SIZE + 4, TILE_SIZE - 8, TILE_SIZE - 8);
   g.setColor(TileType.SNAKE.getColor());
  } else {
   g.fillRect(x * TILE_SIZE + 1, y * TILE_SIZE + 1, TILE_SIZE - 2, TILE_SIZE - 2);
  }
 }
}

As you can see, the color will only be set a total of three times per frame, rather than once per tile. While this isn't really a necessary optimization (with the game running at 2 frames per second and all). Anyway, the last thing we need to do is add this into the Engine class.

The last thing we need to do is implement this into the Engine class. To do this, just create an instance of GameBoard in the Engine constructor, and add a call to the draw method into the Engine's render method.

And with that, we're done with the game board class. If you want, you can randomize the board at the beginning of the game to see how it looks, if you're too lazy, here's a picture.


Randomized board, showing snake tiles and fruit tiles.

Posted in , , , , , . Bookmark the permalink. RSS feed for this post.

4 Responses to Tutorial [Java]: Creating a Snake Game [Part 03] - The World

  1. line FRUIT(Color.RED) should be ended with "," not ";" in enum TileType

    ReplyDelete
    Replies
    1. Fixed it, thanks for pointing that out. Also, I recommend using the Snake Remake project (link at top of post) rather than this one. There are improvements, and a lot of the mistakes and problems in this project are fixed in the other project.

      Delete
    2. No problem. I opened the remake and figure out two lines #248 and #249 in SnakeGame.java file. My compiler gives me an error message and I have to change empty angle brakets to the explicit names of parameters, Point and Direction. Now complier is happy, game works. Thanks again.

      Delete
    3. You're welcome! Feel free to post any questions if you have them.

      Delete

Search

Swedish Greys - a WordPress theme from Nordic Themepark. Converted by LiteThemes.com.