Tile Editor

This guide provides a detailed explanation of a Tileset-Map Editor Game. This game allows users to create and edit maps using a tileset. We will break down the entire JavaScript logic, explaining each part thoroughly and providing references to relevant documentation sections.

Introduction

The Tileset-Map Editor Game is a tool that allows users to create, edit, and save maps using a predefined set of tiles. This type of tool is commonly used in game development to design levels and environments.

Setting Up the Project

Before we start coding, ensure you have the following setup:

  • A text editor (like VS Code)
  • Basic knowledge of HTML, CSS, and JavaScript
  • A tileset image to use for creating the map

JavaScript Logic

The JavaScript logic for our Tileset-Map Editor Game is extensive, so we will break it down into sections:

Initialization

First, we need to initialize our canvas and context:

script.js
const canvas = document.querySelector("canvas");
const tilesetContainer = document.querySelector(".tileset-container");
const tilesetSelection = document.querySelector(".tileset-container_selection");
const tilesetImage = document.querySelector("#tileset-source");

const selection = [0, 0]; // Which tile we will paint from the menu
let isMouseDown = false;
let currentLayer = 0;
const layers = [
  {}, // Bottom
  {}, // Middle
  {}, // Top
];

// Load the tileset image
tilesetImage.onload = function () {
  layers = defaultState;
  draw();
  setLayer(0);
};
tilesetImage.src = "https://assets.codepen.io/21542/TileEditorSpritesheet.2x_2.png"; // Default tileset image

const defaultState = [
  // Example of default state map
  // Each entry is structured as "x-y": ["tileset_x", "tileset_y"]
  // Example: "1-1": [3, 4],
];

Canvas Setup

Next, we set up the canvas and draw the initial grid:

script.js
function drawGrid() {
  const ctx = canvas.getContext("2d");
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  layers.forEach((layer) => {
    Object.keys(layer).forEach((key) => {
      const positionX = Number(key.split("-")[0]);
      const positionY = Number(key.split("-")[1]);
      const [tilesheetX, tilesheetY] = layer[key];

      ctx.drawImage(
        tilesetImage,
        tilesheetX * 32,
        tilesheetY * 32,
        32,
        32,
        positionX * 32,
        positionY * 32,
        32,
        32
      );
    });
  });
}

Event Listeners

In this section, we add event listeners to handle user interactions. Event listeners are crucial for making the canvas interactive, allowing users to draw tiles by clicking and dragging.

To learn more about event listeners, you can read the detailed guide.

script.js
canvas.addEventListener("mousedown", () => {
  isMouseDown = true;
});
canvas.addEventListener("mouseup", () => {
  isMouseDown = false;
});
canvas.addEventListener("mouseleave", () => {
  isMouseDown = false;
});
canvas.addEventListener("mousedown", addTile);
canvas.addEventListener("mousemove", (event) => {
  if (isMouseDown) {
    addTile(event);
  }
});

Utility Functions

Utility function to get the coordinates of mouse clicks:

script.js
function getCoords(event) {
  const { x, y } = event.target.getBoundingClientRect();
  const mouseX = event.clientX - x;
  const mouseY = event.clientY - y;
  return [Math.floor(mouseX / 32), Math.floor(mouseY / 32)];
}

Drawing the Grid

Function to draw the grid and map tiles:

script.js
function drawGrid() {
  const ctx = canvas.getContext("2d");
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  layers.forEach((layer) => {
    Object.keys(layer).forEach((key) => {
      const positionX = Number(key.split("-")[0]);
      const positionY = Number(key.split("-")[1]);
      const [tilesheetX, tilesheetY] = layer[key];

      ctx.drawImage(
        tilesetImage,
        tilesheetX * 32,
        tilesheetY * 32,
        32,
        32,
        positionX * 32,
        positionY * 32,
        32,
        32
      );
    });
  });
}

Placing Tiles

Function to place tiles on the grid when the user clicks on the canvas:

script.js
function addTile(event) {
  const clicked = getCoords(event);
  const key = `${clicked[0]}-${clicked[1]}`;

  if (event.shiftKey) {
    delete layers[currentLayer][key];
  } else {
    layers[currentLayer][key] = selection;
  }
  drawGrid();
}

Saving and Loading Maps

Functions to save and load the map data:

script.js
function exportImage() {
  const data = canvas.toDataURL();
  const image = new Image();
  image.src = data;

  const w = window.open("");
  w.document.write(image.outerHTML);
}

function clearCanvas() {
  layers.fill({});
  drawGrid();
}

function setLayer(newLayer) {
  currentLayer = newLayer;
  const oldActiveLayer = document.querySelector(".layer.active");
  if (oldActiveLayer) {
    oldActiveLayer.classList.remove("active");
  }
  document.querySelector(`[tile-layer="${currentLayer}"]`).classList.add("active");
}

Complete Code

Here is the complete code for the game

HTML
CSS
JavaScript
index.html
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tile Map Editor</title>
<link href="style.css" rel="stylesheet" type="text/css" />
</head>

<body>
<div class="card">
    <header>
    <h1>Tile Map Editor</h1>
    <div>
        <button class="button-as-link" onclick="clearCanvas()">Clear Canvas</button>
        <button class="primary-button" onclick="exportImage()">Export Image</button>
    </div>
    </header>
    <div class="card_body">
    <aside>
        <label>Tiles</label>
        <div class="tileset-container">
        <img id="tileset-source" src="https://opengameart.org/sites/default/files/grass_tileset_16x16_preview_0.png" alt="Tileset" />
        <!-- <img id="tileset-source" crossorigin /> -->

        <div class="tileset-container_selection"></div>
        </div>
    </aside>
    <div class="card_right-column">
        <!-- The main canvas -->
        <canvas width="480" height="480"></canvas>
        <p class="instructions">
        <strong>Click</strong> to paint.
        <strong>Shift+Click</strong> to remove.
        </p>
        <!-- UI for layers -->
        <div>
        <label>Editing Layer:</label>
        <ul class="layers">
            <li><button onclick="setLayer(2)" class="layer" tile-layer="2">Top Layer</button></li>
            <li><button onclick="setLayer(1)" class="layer" tile-layer="1">Middle Layer</button></li>
            <li><button onclick="setLayer(0)" class="layer" tile-layer="0">Bottom Layer</button></li>
        </ul>
        </div>
    </div>
    </div>
</div>
<script src="script.js"></script>
</body>

</html>