Editor tool for painting tilemaps in the editor. It manages a TileMap instance, shows a sidebar for layers and tileset selection, tracks mouse hover and paint strokes, and uses a TileBrush to draw or erase tiles on the TileMap.
using Editor;
using Sandbox;
using System;
using Saandy.Tilemapper;
using Editor.TerrainEditor;
namespace Saandy.Editor.Tilemapper;
[EditorTool]
[Title( "Tilemap Painter" )]
[Icon( "grid_3x3" )]
public class TilemapEditor : EditorTool
{
private TileMap _tilemap;
private Vector3 _hoverWorldPosition;
private Vector2Int _hoverCell;
private bool _hasHoverCell;
private bool _hasLastPaintCell;
private Vector2Int _lastPaintCell;
private bool _wasLeftDown;
private bool _wasRightDown;
private TilemapTilesetPickerWidget _tilesetPicker;
private TilemapLayerListWidget _layerList;
[Property]
public TilesetResource SelectedTileset { get; set; }
private TileBrush _brush => SelectedTileset == null ? null : TileBrush.GetBrush( SelectedTileset.BrushType );
public TilemapEditor()
{
RebuildSidebarOnSelectionChange = false;
}
public override Widget CreateToolSidebar()
{
var sidebar = new ToolSidebarWidget();
sidebar.AddTitle( "Tilemap Painter", "grid_3x3" );
sidebar.MinimumWidth = 340;
// Layers
{
var group = sidebar.AddGroup( "Layers" );
_layerList = new TilemapLayerListWidget( sidebar );
_layerList.MinimumHeight = 190;
_layerList.HorizontalSizeMode = SizeMode.Flexible;
_layerList.VerticalSizeMode = SizeMode.CanGrow;
_layerList.SetTilemap( _tilemap );
group.Add( _layerList );
var addLayerButton = new Button( "Add Layer", "add" );
addLayerButton.Clicked += () =>
{
if ( _tilemap == null || !_tilemap.IsValid() )
_tilemap = TileMap.GetOrCreate( Scene );
_tilemap?.AddLayer();
_layerList?.SetTilemap( _tilemap );
};
group.Add( addLayerButton );
var removeLayerButton = new Button( "Remove Selected Layer", "delete" );
removeLayerButton.Clicked += () =>
{
if ( _tilemap == null || !_tilemap.IsValid() )
return;
_tilemap.RemoveLayer( _tilemap.ActiveLayerIndex );
_layerList?.SetTilemap( _tilemap );
};
group.Add( removeLayerButton );
}
// Tileset selection
{
var group = sidebar.AddGroup( "Tileset Resources", SizeMode.Flexible );
_tilesetPicker = new TilemapTilesetPickerWidget( sidebar );
_tilesetPicker.MinimumHeight = 420;
_tilesetPicker.HorizontalSizeMode = SizeMode.Flexible;
_tilesetPicker.VerticalSizeMode = SizeMode.Flexible;
_tilesetPicker.SetSelectedTileset( SelectedTileset );
_tilesetPicker.OnTilesetSelected = tileset =>
{
SelectedTileset = tileset;
_tilesetPicker.SetSelectedTileset( SelectedTileset );
};
group.Add( _tilesetPicker );
}
// Actions
{
var group = sidebar.AddGroup( "Actions" );
var refreshButton = new Button( "Refresh Tilesets", "refresh" );
refreshButton.Clicked += () =>
{
_tilesetPicker?.RefreshTilesets();
SelectDefaultTilesetIfNeeded();
_tilesetPicker?.SetSelectedTileset( SelectedTileset );
};
refreshButton.ToolTip = "Reload the list of TilesetResource assets from ResourceLibrary.GetAll<TilesetResource>().";
group.Add( refreshButton );
}
SelectDefaultTilesetIfNeeded();
_tilesetPicker?.SetSelectedTileset( SelectedTileset );
return sidebar;
}
public override void OnEnabled()
{
AllowGameObjectSelection = false;
_tilemap = TileMap.GetOrCreate( Scene );
_layerList?.SetTilemap( _tilemap );
SelectDefaultTilesetIfNeeded();
_tilesetPicker?.SetSelectedTileset( SelectedTileset );
}
public override void OnDisabled()
{
base.OnDisabled();
_hasHoverCell = false;
_hasLastPaintCell = false;
_wasLeftDown = false;
_wasRightDown = false;
}
public override void OnUpdate()
{
if ( _tilemap == null || !_tilemap.IsValid() )
{
_tilemap = TileMap.GetOrCreate( Scene );
}
if ( _tilemap == null )
return;
_layerList?.SetTilemap( _tilemap );
_tilemap.ForceRendererUpdate( Camera );
UpdateHoverCell();
bool leftDown = Gizmo.IsLeftMouseDown;
bool rightDown = Gizmo.IsRightMouseDown;
if ( rightDown )
{
UpdatePaintStroke( eraseMode: true, isNewStroke: !_wasRightDown );
}
else if ( leftDown )
{
UpdatePaintStroke( eraseMode: false, isNewStroke: !_wasLeftDown );
}
else
{
_hasLastPaintCell = false;
}
DrawHoverSquare();
_wasLeftDown = leftDown;
_wasRightDown = rightDown;
}
private void SelectDefaultTilesetIfNeeded()
{
if ( SelectedTileset != null && SelectedTileset.IsValid() )
return;
SelectedTileset = TilemapTilesetPickerWidget.GetFirstProjectTileset();
}
private void UpdateHoverCell()
{
var ray = Gizmo.CurrentRay;
if ( !_tilemap.TryIntersectRayWithPlane( ray, out var hit ) )
{
_hasHoverCell = false;
return;
}
_hoverWorldPosition = hit;
_hasHoverCell = true;
float tileSize = Math.Max( _tilemap.TileSize, 0.0001f );
_hoverCell = _tilemap.WorldToCell( hit, tileSize );
}
private void UpdatePaintStroke( bool eraseMode, bool isNewStroke )
{
if ( !_hasHoverCell )
return;
if ( isNewStroke || !_hasLastPaintCell )
{
PaintLine( _hoverCell, _hoverCell, eraseMode );
_lastPaintCell = _hoverCell;
_hasLastPaintCell = true;
return;
}
if ( _lastPaintCell == _hoverCell )
return;
PaintLine( _lastPaintCell, _hoverCell, eraseMode );
_lastPaintCell = _hoverCell;
_hasLastPaintCell = true;
}
private void PaintLine( Vector2Int from, Vector2Int to, bool eraseMode )
{
if ( _tilemap == null || !_tilemap.IsValid() )
return;
if ( SelectedTileset == null || !SelectedTileset.IsValid() )
return;
if ( _brush == null )
return;
_brush.Draw( _tilemap, SelectedTileset, from, to, eraseMode );
}
private void DrawHoverSquare()
{
if ( !_hasHoverCell )
return;
DrawCellOutline( _hoverCell, Color.FromBytes( 255, 220, 120 ) );
}
private void DrawCellOutline( Vector2Int cell, Color color )
{
if ( _tilemap == null )
return;
var bounds = _tilemap.GetCellBounds( cell, 0.04f );
var prevColor = Gizmo.Draw.Color;
var prevThickness = Gizmo.Draw.LineThickness;
Gizmo.Draw.Color = color;
Gizmo.Draw.LineThickness = 1.5f;
Gizmo.Draw.LineBBox( bounds );
Gizmo.Draw.Color = prevColor;
Gizmo.Draw.LineThickness = prevThickness;
}
}