Build a Tile-Based Level Editor in Unity for Beginners
So, you think building level editors is some arcane art reserved for AAA studios? Think again! I’m here to tell you that even a complete beginner can whip up a functional, tile-based level editor in Unity. We’re diving deep into the nitty-gritty, skipping the hand-waving, and building something real. Forget about just reading tutorials; we’re making a tool you can actually use.
Setting Up Your Unity Scene - The Foundation
The most-downloaded assets on Wayline this month — sorted by what real developers actually use.
First, create a new Unity project. Choose the 2D template for simplicity. This will provide the optimal settings for a tile-based game.
Next, import the “Tile Palette” window by going to Window > 2D > Tile Palette. This will be our core for managing tile assets. Create a new Tile Palette and import your desired tile sprites. Think of these as your building blocks. Drag and drop your sprites into the Tile Palette to populate it.
It’s CRUCIAL that you set the “Pixels Per Unit” setting correctly on your tile sprites. If you’re using 16x16 tiles, set Pixels Per Unit to 16. Inconsistent Pixels Per Unit values are the #1 cause of blurry or misaligned tiles. Trust me, I’ve seen the horror.
Implementing Object Placement with Mouse Clicks
Now, the magic. We’re writing a script to handle tile placement on mouse clicks.
Create a new C# script called TilePlacer. Attach it to your main camera.
using UnityEngine;
using UnityEngine.Tilemaps;
public class TilePlacer : MonoBehaviour
{
public Tilemap tilemap;
public TileBase selectedTile;
void Update()
{
if (Input.GetMouseButtonDown(0)) // Left mouse button
{
Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector3Int gridPos = tilemap.WorldToCell(mousePos);
tilemap.SetTile(gridPos, selectedTile);
}
if (Input.GetMouseButtonDown(1)) // Right mouse button
{
Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector3Int gridPos = tilemap.WorldToCell(mousePos);
tilemap.SetTile(gridPos, null); // Erase tile
}
}
}
Drag your Tilemap into the tilemap field on the TilePlacer script in the Inspector. Also, you need to create a tile asset to select, in your project window, right click -> Create -> Tile, and then you can select it as the default selected tile.
Pitfall Alert: Forgetting to assign the Tilemap to the script is a common beginner mistake. Double-check! It will save you hours of debugging.
This script handles tile placement on left click and tile removal on right click.
Adding a Simple UI for Object Selection
We need a way to choose which tile to place. Let’s create a basic UI.
Add a Canvas to your scene (GameObject > UI > Canvas). Set the Render Mode to “Screen Space - Overlay” for simplicity.
Create a Panel inside the Canvas. This will hold our tile selection buttons.
For each tile you want to use, create a Button (GameObject > UI > Button) inside the Panel.
Create a new C# script called TileSelector.
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Tilemaps;
public class TileSelector : MonoBehaviour
{
public TilePlacer tilePlacer;
public TileBase tile;
public Button button;
void Start()
{
button.onClick.AddListener(SetSelectedTile);
}
public void SetSelectedTile()
{
tilePlacer.selectedTile = tile;
}
}
Attach this script to each of your buttons. Drag the TilePlacer component (from your Camera) into the tilePlacer field, and the Tile Asset (the tile itself, not the sprite) into the tile field. Finally, drag the button component itself into the button field. This will connect each button to the correct tile.
Now, when you click a button, it will update the selectedTile variable in your TilePlacer script, allowing you to place that tile in the scene.
Common Mistake: Accidentally dragging the sprite into the tile field instead of the Tile Asset. This will cause errors.
Basic Save/Load System Using JSON
Saving and loading is essential. We’ll use JSON for simplicity.
Create a new C# script called LevelManager.
using UnityEngine;
using UnityEngine.Tilemaps;
using System.IO;
[System.Serializable]
public class TileData
{
public int x;
public int y;
public string tileName;
}
[System.Serializable]
public class LevelData
{
public TileData[] tiles;
}
public class LevelManager : MonoBehaviour
{
public Tilemap tilemap;
public string saveFileName = "level.json";
public TileBase[] availableTiles;
public void SaveLevel()
{
string filePath = Path.Combine(Application.dataPath, saveFileName);
BoundsInt bounds = tilemap.cellBounds;
int tileCount = 0;
for (int x = bounds.min.x; x < bounds.max.x; x++) {
for (int y = bounds.min.y; y < bounds.max.y; y++) {
TileBase tile = tilemap.GetTile(new Vector3Int(x, y, 0));
if(tile != null){
tileCount++;
}
}
}
LevelData levelData = new LevelData();
levelData.tiles = new TileData[tileCount];
int i = 0;
for (int x = bounds.min.x; x < bounds.max.x; x++)
{
for (int y = bounds.min.y; y < bounds.max.y; y++)
{
TileBase tile = tilemap.GetTile(new Vector3Int(x, y, 0));
if (tile != null)
{
TileData tileData = new TileData();
tileData.x = x;
tileData.y = y;
tileData.tileName = tile.name;
levelData.tiles[i] = tileData;
i++;
}
}
}
string json = JsonUtility.ToJson(levelData);
File.WriteAllText(filePath, json);
Debug.Log("Level saved to " + filePath);
}
public void LoadLevel()
{
string filePath = Path.Combine(Application.dataPath, saveFileName);
if (File.Exists(filePath))
{
string json = File.ReadAllText(filePath);
LevelData levelData = JsonUtility.FromJson<LevelData>(json);
tilemap.ClearAllTiles();
foreach (TileData tileData in levelData.tiles)
{
Vector3Int gridPos = new Vector3Int(tileData.x, tileData.y, 0);
foreach(TileBase availableTile in availableTiles){
if(availableTile.name == tileData.tileName){
tilemap.SetTile(gridPos, availableTile);
break;
}
}
}
Debug.Log("Level loaded from " + filePath);
}
else
{
Debug.LogError("Save file not found: " + filePath);
}
}
}
Attach this script to an empty GameObject in your scene. Drag your Tilemap into the tilemap field. And all available tiles into the availableTiles array, to make loading possible.
Add two buttons to your UI: one for “Save” and one for "Load". Create two new functions to call these methods:
public void CallSaveLevel(){
GetComponent<LevelManager>().SaveLevel();
}
public void CallLoadLevel(){
GetComponent<LevelManager>().LoadLevel();
}
Create onClick listeners for the save and load buttons and select these functions in the LevelManager object.
This script serializes the tile positions and tile names to a JSON file.
Important Consideration: This is a very basic save/load system. It doesn’t handle complex data or error conditions robustly. For a real game, you’d need a more sophisticated solution.
Pitfalls to avoid:
- Make sure all the tiles in your availableTiles array, match the tiles used on the level, or it won’t load them.
- The LevelManager component must be on the same object of the call save/load button functions.
That’s it! You now have a rudimentary, but functional, tile-based level editor in Unity. Go forth and create! Remember, this is just the beginning. Add more features, refine the UI, and make it your own. The only limit is your imagination.