Tile-Based Game Map in Java

Overview

A tile-based game map system using 2D arrays to represent game worlds. This implementation supports different tile types, layering, pathfinding, and rendering.

Core Classes

1. Tile Type Enum

public enum TileType {
GRASS(1, true, 1.0f, "Grass"),
WATER(2, false, 0.0f, "Water"),
SAND(3, true, 0.8f, "Sand"),
FOREST(4, true, 1.2f, "Forest"),
MOUNTAIN(5, false, 0.0f, "Mountain"),
ROAD(6, true, 0.5f, "Road"),
WALL(7, false, 0.0f, "Wall"),
DOOR(8, true, 0.7f, "Door");
private final int id;
private final boolean walkable;
private final float movementCost;
private final String name;
TileType(int id, boolean walkable, float movementCost, String name) {
this.id = id;
this.walkable = walkable;
this.movementCost = movementCost;
this.name = name;
}
// Getters
public int getId() { return id; }
public boolean isWalkable() { return walkable; }
public float getMovementCost() { return movementCost; }
public String getName() { return name; }
public static TileType getById(int id) {
for (TileType type : values()) {
if (type.id == id) return type;
}
return GRASS;
}
}

2. Tile Class

public class Tile {
private TileType type;
private int x, y;
private boolean visible;
private boolean explored;
private List<Entity> entities;
public Tile(TileType type, int x, int y) {
this.type = type;
this.x = x;
this.y = y;
this.visible = true;
this.explored = false;
this.entities = new ArrayList<>();
}
public void addEntity(Entity entity) {
entities.add(entity);
}
public void removeEntity(Entity entity) {
entities.remove(entity);
}
public boolean hasEntities() {
return !entities.isEmpty();
}
public List<Entity> getEntities() {
return new ArrayList<>(entities);
}
// Getters and setters
public TileType getType() { return type; }
public void setType(TileType type) { this.type = type; }
public int getX() { return x; }
public int getY() { return y; }
public boolean isVisible() { return visible; }
public void setVisible(boolean visible) { this.visible = visible; }
public boolean isExplored() { return explored; }
public void setExplored(boolean explored) { this.explored = explored; }
public boolean isWalkable() { 
return type.isWalkable() && entities.stream().noneMatch(Entity::isBlocking);
}
@Override
public String toString() {
return String.format("Tile[%s at (%d,%d)]", type.getName(), x, y);
}
}

3. Game Map Class

public class GameMap {
private Tile[][] tiles;
private int width;
private int height;
private String name;
private List<MapLayer> layers;
public GameMap(int width, int height, String name) {
this.width = width;
this.height = height;
this.name = name;
this.tiles = new Tile[width][height];
this.layers = new ArrayList<>();
initializeTiles();
}
private void initializeTiles() {
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
tiles[x][y] = new Tile(TileType.GRASS, x, y);
}
}
}
public void setTile(int x, int y, TileType type) {
if (isInBounds(x, y)) {
tiles[x][y].setType(type);
}
}
public Tile getTile(int x, int y) {
if (isInBounds(x, y)) {
return tiles[x][y];
}
return null;
}
public boolean isInBounds(int x, int y) {
return x >= 0 && x < width && y >= 0 && y < height;
}
public boolean isWalkable(int x, int y) {
Tile tile = getTile(x, y);
return tile != null && tile.isWalkable();
}
public List<Point> findPath(int startX, int startY, int endX, int endY) {
return AStarPathfinder.findPath(this, startX, startY, endX, endY);
}
public void addLayer(MapLayer layer) {
layers.add(layer);
}
public void generateRandomMap() {
Random random = new Random();
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
TileType type = TileType.GRASS;
double rand = random.nextDouble();
if (rand < 0.1) type = TileType.WATER;
else if (rand < 0.2) type = TileType.FOREST;
else if (rand < 0.25) type = TileType.MOUNTAIN;
else if (rand < 0.3) type = TileType.SAND;
setTile(x, y, type);
}
}
generateRoads();
}
private void generateRoads() {
Random random = new Random();
// Create a horizontal road
int roadY = height / 2;
for (int x = 0; x < width; x++) {
setTile(x, roadY, TileType.ROAD);
// Add some vertical connections
if (random.nextDouble() < 0.1) {
int connectionLength = random.nextInt(3) + 2;
for (int y = roadY + 1; y < Math.min(height, roadY + connectionLength); y++) {
setTile(x, y, TileType.ROAD);
}
}
}
}
// Getters
public int getWidth() { return width; }
public int getHeight() { return height; }
public String getName() { return name; }
public List<MapLayer> getLayers() { return new ArrayList<>(layers); }
}

4. A* Pathfinding Implementation

public class AStarPathfinder {
public static List<Point> findPath(GameMap map, int startX, int startY, int endX, int endY) {
if (!map.isWalkable(endX, endY)) {
return Collections.emptyList();
}
PriorityQueue<Node> openSet = new PriorityQueue<>();
Map<Point, Node> allNodes = new HashMap<>();
Node startNode = new Node(startX, startY, null, 0, heuristic(startX, startY, endX, endY));
openSet.add(startNode);
allNodes.put(new Point(startX, startY), startNode);
while (!openSet.isEmpty()) {
Node current = openSet.poll();
if (current.x == endX && current.y == endY) {
return reconstructPath(current);
}
for (Point neighbor : getNeighbors(map, current.x, current.y)) {
Node neighborNode = allNodes.getOrDefault(neighbor, 
new Node(neighbor.x, neighbor.y));
float tentativeG = current.gCost + getMovementCost(map, current.x, current.y, neighbor.x, neighbor.y);
if (tentativeG < neighborNode.gCost) {
neighborNode.parent = current;
neighborNode.gCost = tentativeG;
neighborNode.hCost = heuristic(neighbor.x, neighbor.y, endX, endY);
neighborNode.fCost = neighborNode.gCost + neighborNode.hCost;
if (!openSet.contains(neighborNode)) {
openSet.add(neighborNode);
}
allNodes.put(neighbor, neighborNode);
}
}
}
return Collections.emptyList(); // No path found
}
private static List<Point> reconstructPath(Node endNode) {
List<Point> path = new ArrayList<>();
Node current = endNode;
while (current != null) {
path.add(0, new Point(current.x, current.y));
current = current.parent;
}
return path;
}
private static List<Point> getNeighbors(GameMap map, int x, int y) {
List<Point> neighbors = new ArrayList<>();
int[][] directions = {{0,1}, {1,0}, {0,-1}, {-1,0}}; // 4-directional movement
for (int[] dir : directions) {
int newX = x + dir[0];
int newY = y + dir[1];
if (map.isInBounds(newX, newY) && map.isWalkable(newX, newY)) {
neighbors.add(new Point(newX, newY));
}
}
return neighbors;
}
private static float heuristic(int x1, int y1, int x2, int y2) {
return Math.abs(x1 - x2) + Math.abs(y1 - y2); // Manhattan distance
}
private static float getMovementCost(GameMap map, int fromX, int fromY, int toX, int toY) {
Tile tile = map.getTile(toX, toY);
return tile != null ? tile.getType().getMovementCost() : 1.0f;
}
private static class Node implements Comparable<Node> {
int x, y;
Node parent;
float gCost; // Distance from start
float hCost; // Heuristic to end
float fCost; // gCost + hCost
Node(int x, int y) {
this(x, y, null, Float.MAX_VALUE, Float.MAX_VALUE);
}
Node(int x, int y, Node parent, float gCost, float hCost) {
this.x = x;
this.y = y;
this.parent = parent;
this.gCost = gCost;
this.hCost = hCost;
this.fCost = gCost + hCost;
}
@Override
public int compareTo(Node other) {
return Float.compare(this.fCost, other.fCost);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Node node = (Node) obj;
return x == node.x && y == node.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}
}

5. Map Layer System

public abstract class MapLayer {
protected String name;
protected boolean visible;
protected float opacity;
public MapLayer(String name) {
this.name = name;
this.visible = true;
this.opacity = 1.0f;
}
public abstract void render(Graphics2D g, int tileSize, int offsetX, int offsetY);
// Getters and setters
public String getName() { return name; }
public boolean isVisible() { return visible; }
public void setVisible(boolean visible) { this.visible = visible; }
public float getOpacity() { return opacity; }
public void setOpacity(float opacity) { this.opacity = opacity; }
}
public class ObjectLayer extends MapLayer {
private List<MapObject> objects;
public ObjectLayer(String name) {
super(name);
this.objects = new ArrayList<>();
}
public void addObject(MapObject object) {
objects.add(object);
}
public void removeObject(MapObject object) {
objects.remove(object);
}
@Override
public void render(Graphics2D g, int tileSize, int offsetX, int offsetY) {
if (!visible) return;
Composite originalComposite = g.getComposite();
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity));
for (MapObject obj : objects) {
obj.render(g, tileSize, offsetX, offsetY);
}
g.setComposite(originalComposite);
}
public List<MapObject> getObjectsAt(int x, int y, int tileSize) {
return objects.stream()
.filter(obj -> obj.containsPoint(x, y, tileSize))
.collect(Collectors.toList());
}
}
public abstract class MapObject {
protected int x, y;
protected String name;
protected Map<String, String> properties;
public MapObject(int x, int y, String name) {
this.x = x;
this.y = y;
this.name = name;
this.properties = new HashMap<>();
}
public abstract void render(Graphics2D g, int tileSize, int offsetX, int offsetY);
public abstract boolean containsPoint(int pointX, int pointY, int tileSize);
public void setProperty(String key, String value) {
properties.put(key, value);
}
public String getProperty(String key) {
return properties.get(key);
}
// Getters and setters
public int getX() { return x; }
public int getY() { return y; }
public String getName() { return name; }
}
public class CharacterObject extends MapObject {
private Color color;
private String character;
public CharacterObject(int x, int y, String name, String character, Color color) {
super(x, y, name);
this.character = character;
this.color = color;
}
@Override
public void render(Graphics2D g, int tileSize, int offsetX, int offsetY) {
g.setColor(color);
g.setFont(new Font("Arial", Font.BOLD, tileSize / 2));
int screenX = x * tileSize + offsetX;
int screenY = y * tileSize + offsetY;
FontMetrics fm = g.getFontMetrics();
int textWidth = fm.stringWidth(character);
int textHeight = fm.getHeight();
g.drawString(character, 
screenX + (tileSize - textWidth) / 2, 
screenY + (tileSize + textHeight) / 2 - fm.getDescent());
}
@Override
public boolean containsPoint(int pointX, int pointY, int tileSize) {
int screenX = x * tileSize;
int screenY = y * tileSize;
return pointX >= screenX && pointX < screenX + tileSize &&
pointY >= screenY && pointY < screenY + tileSize;
}
}

6. Map Renderer

public class MapRenderer {
private GameMap map;
private int tileSize;
private int viewportWidth;
private int viewportHeight;
private int offsetX;
private int offsetY;
public MapRenderer(GameMap map, int tileSize, int viewportWidth, int viewportHeight) {
this.map = map;
this.tileSize = tileSize;
this.viewportWidth = viewportWidth;
this.viewportHeight = viewportHeight;
this.offsetX = 0;
this.offsetY = 0;
}
public void render(Graphics2D g) {
renderMapTiles(g);
renderLayers(g);
renderGrid(g);
}
private void renderMapTiles(Graphics2D g) {
int startX = Math.max(0, offsetX / tileSize);
int startY = Math.max(0, offsetY / tileSize);
int endX = Math.min(map.getWidth(), startX + viewportWidth / tileSize + 1);
int endY = Math.min(map.getHeight(), startY + viewportHeight / tileSize + 1);
for (int x = startX; x < endX; x++) {
for (int y = startY; y < endY; y++) {
Tile tile = map.getTile(x, y);
if (tile != null) {
renderTile(g, tile, x, y);
}
}
}
}
private void renderTile(Graphics2D g, Tile tile, int x, int y) {
int screenX = x * tileSize - offsetX;
int screenY = y * tileSize - offsetY;
// Draw tile background based on type
Color tileColor = getTileColor(tile.getType());
g.setColor(tileColor);
g.fillRect(screenX, screenY, tileSize, tileSize);
// Draw tile border
g.setColor(Color.BLACK);
g.drawRect(screenX, screenY, tileSize, tileSize);
// Draw entities on tile
if (tile.hasEntities()) {
renderEntities(g, tile, screenX, screenY);
}
// Fog of war effect
if (!tile.isVisible()) {
g.setColor(new Color(0, 0, 0, 150));
g.fillRect(screenX, screenY, tileSize, tileSize);
} else if (!tile.isExplored()) {
g.setColor(new Color(0, 0, 0, 100));
g.fillRect(screenX, screenY, tileSize, tileSize);
}
}
private Color getTileColor(TileType type) {
switch (type) {
case GRASS: return new Color(34, 139, 34);
case WATER: return new Color(0, 105, 148);
case SAND: return new Color(238, 214, 175);
case FOREST: return new Color(0, 100, 0);
case MOUNTAIN: return new Color(139, 137, 137);
case ROAD: return new Color(210, 180, 140);
case WALL: return new Color(100, 100, 100);
case DOOR: return new Color(139, 69, 19);
default: return Color.GRAY;
}
}
private void renderEntities(Graphics2D g, Tile tile, int screenX, int screenY) {
g.setColor(Color.RED);
g.fillOval(screenX + tileSize/4, screenY + tileSize/4, tileSize/2, tileSize/2);
}
private void renderLayers(Graphics2D g) {
for (MapLayer layer : map.getLayers()) {
layer.render(g, tileSize, -offsetX, -offsetY);
}
}
private void renderGrid(Graphics2D g) {
g.setColor(new Color(0, 0, 0, 50));
for (int x = 0; x <= viewportWidth; x += tileSize) {
g.drawLine(x - (offsetX % tileSize), 0, x - (offsetX % tileSize), viewportHeight);
}
for (int y = 0; y <= viewportHeight; y += tileSize) {
g.drawLine(0, y - (offsetY % tileSize), viewportWidth, y - (offsetY % tileSize));
}
}
public void centerOn(int x, int y) {
offsetX = Math.max(0, x * tileSize - viewportWidth / 2);
offsetY = Math.max(0, y * tileSize - viewportHeight / 2);
// Clamp to map boundaries
offsetX = Math.min(offsetX, map.getWidth() * tileSize - viewportWidth);
offsetY = Math.min(offsetY, map.getHeight() * tileSize - viewportHeight);
}
public Point screenToWorld(int screenX, int screenY) {
int worldX = (screenX + offsetX) / tileSize;
int worldY = (screenY + offsetY) / tileSize;
return new Point(worldX, worldY);
}
// Getters and setters
public void setOffset(int x, int y) {
this.offsetX = Math.max(0, Math.min(x, map.getWidth() * tileSize - viewportWidth));
this.offsetY = Math.max(0, Math.min(y, map.getHeight() * tileSize - viewportHeight));
}
}

7. Complete Example with Swing GUI

public class TileMapGame extends JPanel {
private GameMap map;
private MapRenderer renderer;
private int tileSize = 32;
private Point selectedTile = null;
private Point playerPosition = new Point(5, 5);
public TileMapGame() {
initializeMap();
setupUI();
setupInput();
}
private void initializeMap() {
map = new GameMap(50, 50, "Fantasy World");
map.generateRandomMap();
// Add player character
ObjectLayer objectLayer = new ObjectLayer("Objects");
objectLayer.addObject(new CharacterObject(playerPosition.x, playerPosition.y, 
"Player", "P", Color.BLUE));
map.addLayer(objectLayer);
renderer = new MapRenderer(map, tileSize, 800, 600);
renderer.centerOn(playerPosition.x, playerPosition.y);
}
private void setupUI() {
setPreferredSize(new Dimension(800, 600));
setBackground(Color.BLACK);
}
private void setupInput() {
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
Point worldPos = renderer.screenToWorld(e.getX(), e.getY());
selectedTile = worldPos;
if (SwingUtilities.isLeftMouseButton(e)) {
// Move player to selected tile
if (map.isWalkable(worldPos.x, worldPos.y)) {
movePlayerTo(worldPos.x, worldPos.y);
}
} else if (SwingUtilities.isRightMouseButton(e)) {
// Change tile type
map.setTile(worldPos.x, worldPos.y, TileType.WATER);
}
repaint();
}
});
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
handleKeyPress(e);
}
});
setFocusable(true);
}
private void movePlayerTo(int x, int y) {
List<Point> path = map.findPath(playerPosition.x, playerPosition.y, x, y);
if (!path.isEmpty()) {
// Simple movement - just jump to destination
// In a real game, you'd animate through the path
playerPosition.setLocation(x, y);
renderer.centerOn(x, y);
// Update fog of war
updateVisibility();
}
}
private void updateVisibility() {
// Simple visibility - reveal tiles around player
int visionRange = 5;
for (int dx = -visionRange; dx <= visionRange; dx++) {
for (int dy = -visionRange; dy <= visionRange; dy++) {
int x = playerPosition.x + dx;
int y = playerPosition.y + dy;
if (map.isInBounds(x, y)) {
Tile tile = map.getTile(x, y);
tile.setExplored(true);
tile.setVisible(true);
}
}
}
}
private void handleKeyPress(KeyEvent e) {
int newX = playerPosition.x;
int newY = playerPosition.y;
switch (e.getKeyCode()) {
case KeyEvent.VK_W: case KeyEvent.VK_UP:    newY--; break;
case KeyEvent.VK_S: case KeyEvent.VK_DOWN:  newY++; break;
case KeyEvent.VK_A: case KeyEvent.VK_LEFT:  newX--; break;
case KeyEvent.VK_D: case KeyEvent.VK_RIGHT: newX++; break;
}
if (map.isWalkable(newX, newY)) {
playerPosition.setLocation(newX, newY);
renderer.centerOn(newX, newY);
updateVisibility();
repaint();
}
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
// Enable anti-aliasing
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
renderer.render(g2d);
// Draw selected tile highlight
if (selectedTile != null) {
int screenX = selectedTile.x * tileSize - renderer.offsetX;
int screenY = selectedTile.y * tileSize - renderer.offsetY;
g2d.setColor(new Color(255, 255, 0, 100));
g2d.fillRect(screenX, screenY, tileSize, tileSize);
g2d.setColor(Color.YELLOW);
g2d.drawRect(screenX, screenY, tileSize, tileSize);
}
// Draw UI information
drawUI(g2d);
}
private void drawUI(Graphics2D g2d) {
g2d.setColor(Color.WHITE);
g2d.drawString(String.format("Player: (%d, %d)", playerPosition.x, playerPosition.y), 10, 20);
g2d.drawString("WASD/Arrows: Move | Click: Move/Select | Right-click: Edit", 10, 40);
if (selectedTile != null) {
Tile tile = map.getTile(selectedTile.x, selectedTile.y);
if (tile != null) {
g2d.drawString(String.format("Selected: %s (%d, %d) - Walkable: %s", 
tile.getType().getName(), selectedTile.x, selectedTile.y, tile.isWalkable()), 10, 60);
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Tile-Based Game Map");
TileMapGame game = new TileMapGame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(game);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
game.requestFocusInWindow();
});
}
}

Features Included

  1. Tile System: Different terrain types with properties
  2. Pathfinding: A* algorithm for movement
  3. Layered Rendering: Support for multiple map layers
  4. Fog of War: Visibility and exploration system
  5. Object System: Game objects on the map
  6. Interactive GUI: Mouse and keyboard controls
  7. Map Generation: Procedural map creation

Extensions

You can extend this system with:

  • Different map generators (caves, dungeons, cities)
  • Saving/loading maps
  • Multiple map levels
  • Combat system
  • Inventory and items
  • NPC AI and behavior
  • Multiplayer support

This tile-based map system provides a solid foundation for various types of 2D games including RPGs, strategy games, and roguelikes.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper