Tile map editor

This short program lets you draw a tile map using a tileset. A tileset is an image consisting of a number of tiles, from which a new image can be drawn. The tiles are drawn is a such a way that if you place several tiles next to each other, they blend together seamlessly - without any gaps between them. This is used in computer games - instead of having a huge bitmap for each level, the level is built from small 'bricks' - tiles. Each brick can be reused multiple times, which greatly reduces the amount of memory needed to store the information (and consequently the loading time).
There is a ton of awesome tilesets on the internet, including many that are royalty free (you can use them in your programs without having to pay for the artwork).
The tile map is the layout of those bricks. It will typically be much larger than the tileset, because many bricks will be reused multiple times.



The top grid is the 'target', where you will draw your map. The bottom is the 'source' - it shows you all the available tiles. You click on the source to select a tile, then click on the grid cells to place the tile. It's that simple!

I'll also show you how easy it is to include your map in your own JavaScript program.

Let's talk about how the editor code works.
The source image and the target grid are simply two areas of one HTML5 Canvas (they are not separate objects per se). The program differentiates between them only based on the Canvas coordinates.
The vast majority of the work is done by two functions that handle the 'mousemove' and 'mouseclick' events.
The doMouseMove function displays the blue rectangle if the user moves the mouse over the source area.
The doMouseClick function selects the source tile (if the source is clicked) or draws the source tile on the map (if the target is clicked).
See that weird label that says 'let tiles=[...]'?
This part is added so that you can easily include the map that your created in your own JavaScript program. Your map becomes an array - each element of the array is the number of tile from your source tileset. You just have to paste this array into the code snippet below to display your tile map in your own program.
In the beginning the array is empty - you only see the commas separating the empty elements. Once you start drawing your map, you will see the values pop up.
If you're not interested in that or want to simplify the program, ignore anything that has to do with sourceTile or string.
Now let's look at the details of the editor code:

[Lines 7-9 and 12-13] describe the tileset image: the tileset filename, the width and the height of the individual tile, as well as the width and height of the entire tileset.
All the Widths and Heights in this tutorial are in pixels.
[10-11] describe your map - how many rows and columns of tiles do you need?
[14-22] declare and calculate some auxiliary variables and set up the Canvas.
[24-33] draw the grid.
[35-37] redraw the source image from the original bitmap. If we don't do this, the mouse will leave a trail of blue boxes on the source.

Now the important part - the mouseclick [39-54]:
[40-43] get the mouse coordinates and determine the grid cell coordinate (which grid cell is the mouse in?)
[45] if the mouse coordinates are in the source area:
[46-50] determine the coordinates of the top-left corner of the source tile (sourceX and sourceY) and the number of the source tile.
[55] if the mouse coordinates are in the target area:
[56] clear the cell (if the tiles are not transparent you can skip this - the new tile will simply overwrite the previous one)
[57] draw the source tile on the map.
[58-61] convert the mouse coordinates into the number of the element of the tiles array.
[63-69] update and display the string that contains the array.
[73-88] The mousemove function checks if the mouse is in the source area and displays a blue box around the current tile.
[90-95] Draw the red box that shows which source tile is currently selected.
This image will help you understand the relationship between the tiles array, the string and the images:



The black numbers are the indexes of the tiles array.
The blue ones are the values of the tiles array. These are the numbers you see in the brackets below the tileset.
The red ones are the tile numbers. They are not stored anywhere, but calculated by the program to be transferred upon mouseclick to the tiles array via the sourceTile variable.
Look at the line of code below the tileset: in this example, the value in the first cell of the map (index 0) is 51 (corresponding to the alien in the tileset).
Somewhere around the middle (index 62) is the value 54 - the round cave ceiling.
Finally, the last cell (index 143) is 50 - the diamond in the tileset.

Here is the full code of the editor:

<html>
<body>
<canvas id='myCanvaswidth='600height='800style='position:absoluteleft:0top:0'></canvas>
<label id='resultstyle='position:absoluteleft:0top:550'>Click on the image at the bottom to select a tilethen click on the grid to draw.</label>
<script>
let image = new Image();
image.src = 'Tiles_32x32.png';
const tileWidth = 32,
  tileHeight = 32;
const mapRows = 8,
  mapColumns = 18;
const sourceWidth = 256,
  sourceHeight = 256;
let tiles = new Array(mapColumns * mapRows);
let mapHeight = mapRows * tileHeight;
let mapWidth = mapColumns * tileWidth;
let sourceXsourceYsourceTile;
let canvas = document.getElementById('myCanvas');
let context = canvas.getContext('2d');
canvas.addEventListener('click', doMouseClick);
canvas.addEventListener('mousemove', doMouseMove);
image.addEventListener('load', redrawSource);
// draw the grid
for (let i = 0i <= mapColumnsi++) {
  context.moveTo(i * tileWidth0);
  context.lineTo(i * tileWidthmapHeight);
}
context.stroke();
for (let i = 0i <= mapRowsi++) {
  context.moveTo(0i * tileHeight);
  context.lineTo(mapWidthi * tileHeight);
}
context.stroke();
 
function redrawSource() {
  context.drawImage(image00sourceWidthsourceHeight0mapHeightsourceWidthsourceHeight);
}
 
function doMouseClick(e) {
  let x = e.clientX;
  let y = e.clientY;
  let gridX = Math.floor(x / tileWidth) * tileWidth;
  let gridY = Math.floor(y / tileHeight) * tileHeight;
 
  if (y > mapHeight && y < (mapHeight + sourceHeight) && x < sourceWidth) { // source
    let tileX = Math.floor(x / tileWidth);
    let tileY = Math.floor((y - mapHeight) / tileHeight);
    sourceTile = tileY * (sourceWidth / tileWidth) + tileX;
    sourceX = gridX;
    sourceY = gridY - mapHeight;
    redrawSource();
    drawBox();
  }
 
  if (y < mapHeight && x < mapWidth) { // target
    context.clearRect(gridXgridYtileWidthtileHeight);
    context.drawImage(imagesourceXsourceYtileWidthtileHeightgridXgridYtileWidthtileHeight);
    let tileX = Math.floor(x / tileWidth);
    let tileY = Math.floor(y / tileHeight);
    let targetTile = tileY * mapColumns + tileX;
    tiles[targetTile] = sourceTile;
    // update the string    
    let string = 'let tiles = [';
    for (let i = 0i < mapColumns * mapRowsi++) {
      if (tiles[i] != undefinedstring = string + tiles[i];
      string = string + ',';
    }
    string = string + '];';
    document.getElementById('result').innerHTML = string;
  }
}
 
function doMouseMove(e) {
  let x = e.clientX;
  let y = e.clientY;
 
  if (y > mapHeight && y < (mapHeight + sourceHeight) && x < sourceWidth) { // source
    let gridX = Math.floor(x / tileWidth) * tileWidth;
    let gridY = Math.floor(y / tileHeight) * tileHeight;
    context.clearRect(0mapHeightsourceWidthsourceHeight);
    redrawSource();
    context.beginPath();
    context.strokeStyle = 'blue';
    context.rect(gridXgridYtileWidthtileHeight);
    context.stroke();
    drawBox();
  }
}
 
function drawBox() {
  context.beginPath();
  context.strokeStyle = 'red';
  context.rect(sourceXsourceY + mapHeighttileWidthtileHeight);
  context.stroke();
}
</script>
</body></html>


Now let's see how easy it is to include your tile map in your own program.

<html>
<body>
<canvas id='myCanvaswidth="600height="800style="position:absoluteleft:0top:0"></canvas>
 
<script>
let image = new Image();
image.src = "Tiles_32x32.png";
const tileWidth = 32,
  tileHeight = 32;
const mapHeight = 8,
  mapColumns = 12;
let tiles = [,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,75,,,,,,,,,,,,,,,,,,,,,,,,53,60,60,60,55,,,,,,,,,,,,,,,,,,,,,,,,,,,,];
 
image.addEventListener('load', draw);
let canvas = document.getElementById("myCanvas");
let context = canvas.getContext("2d");
image.addEventListener('load', draw);
function draw() {
  for(let i=0;i<mapColumns*mapHeight;i++) {
    let tile = tiles[i];
    let sourceX = (tile % mapColumns) * tileWidth;
    let sourceY = Math.floor (tile/mapColumns)* tileHeight;
    let targetX = (i % mapColumns) * tileWidth;
    let targetY = Math.floor (i/mapColumns)* tileHeight;
    context.drawImage(imagesourceXsourceYtileWidthtileHeighttargetXtargetYtileWidthtileHeight);
  }
}
</script>
</body></html>


This small code snippet displays your map. You just need to replace line [12] with the string that you generated in the editor.

If you want to use a different tileset, you may also need to update lines [7-11]. The same numbers should be entered in the editor and the display program.

The tileset above (Public Domain by Eris) was a side view - great for platformers. The one below (Public Domain by Matiaan) is top view - great for top-down scrolling shooters or RPGs.



The bottom two rows are the transparency tiles, which we are not using. You can hide them by changing the sourceHeight to 6*32 (the height of the tileset without the transparency tiles).

Here is another version of the editor where you can 'paint' multiple tiles by dragging the mouse and delete them using the right mouse button.

Good luck!



Check out these programming tutorials:

JavaScript:

Optical illusion (18 lines)

Oldschool fire effect (20 lines)

Fireworks (60 lines)

Animated fractal (32 lines)

Physics engine for beginners

Starfield (21 lines)

Yin Yang with a twist (4 circles and 20 lines)

Interactive animated sprites

Your first program in JavaScript: you need 5 minutes and a notepad


Fractals in Excel

Python in Blender 3d:

Domino effect (10 lines)


Wrecking ball effect (14 lines)

3d fractal in Blender Python