Editor/Tilemap/TilemapEditor.cs
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 TileMap _layerListTilemap;
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;
_layerListTilemap = null;
UpdateLayerListTilemap();
group.Add( _layerList );
var addLayerButton = new Button( "Add Layer", "add" );
addLayerButton.Clicked += AddLayer;
group.Add( addLayerButton );
var removeLayerButton = new Button( "Remove Selected Layer", "delete" );
removeLayerButton.Clicked += RemoveSelectedLayer;
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 = OnTilesetSelected;
group.Add( _tilesetPicker );
}
// Actions
{
var group = sidebar.AddGroup( "Actions" );
var refreshButton = new Button( "Refresh Tilesets", "refresh" );
refreshButton.Clicked += RefreshTilesets;
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;
SetTilemap( TileMap.GetOrCreate( Scene ) );
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() )
{
SetTilemap( TileMap.GetOrCreate( Scene ) );
}
if ( _tilemap == null ) { return; }
UpdateLayerListTilemap();
//
// Do NOT call _tilemap.ForceRendererUpdate( Camera ) here.
//
// TileMap is ExecuteInEditor and already updates its renderer every frame
// using Application.Editor.Camera. Calling it again from the tool caused
// the complete streaming pass to run twice per editor frame.
//
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 SetTilemap( TileMap tilemap )
{
if ( _tilemap == tilemap && _tilemap != null && _tilemap.IsValid() )
{
UpdateLayerListTilemap();
return;
}
_tilemap = tilemap;
_layerListTilemap = null;
UpdateLayerListTilemap();
}
private void UpdateLayerListTilemap()
{
if ( _layerList == null ) { return; }
//
// Avoid asking the layer widget to re-bind/rebuild every editor frame.
//
if ( _layerListTilemap == _tilemap ) { return; }
_layerList.SetTilemap( _tilemap );
_layerListTilemap = _tilemap;
}
private void AddLayer()
{
if ( _tilemap == null || !_tilemap.IsValid() )
{
SetTilemap( TileMap.GetOrCreate( Scene ) );
}
_tilemap?.AddLayer();
RefreshLayerList();
}
private void RemoveSelectedLayer()
{
if ( _tilemap == null || !_tilemap.IsValid() ) { return; }
_tilemap.RemoveLayer( _tilemap.ActiveLayerIndex );
RefreshLayerList();
}
private void RefreshLayerList()
{
if ( _layerList == null ) { return; }
_layerList.SetTilemap( _tilemap );
_layerListTilemap = _tilemap;
}
private void OnTilesetSelected( TilesetResource tileset )
{
SelectedTileset = tileset;
_tilesetPicker?.SetSelectedTileset( SelectedTileset );
}
private void RefreshTilesets()
{
_tilesetPicker?.RefreshTilesets();
SelectDefaultTilesetIfNeeded();
_tilesetPicker?.SetSelectedTileset( SelectedTileset );
}
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;
}
}