testss
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Unity.2D.Tilemap.EditorTests")]
|
||||
[assembly: InternalsVisibleTo("Unity.2D.Tilemap.Package.EditorTests")]
|
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Tilemaps;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
/// <summary>
|
||||
/// Use this attribute to add an option to customize how Tiles are created when dragging and dropping assets to the Tile Palette.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Append this attribute to a method that has a signature of "static TileBase CreateTile(Sprite sprite)".
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code lang="cs"><![CDATA[
|
||||
/// using UnityEditor.Tilemaps;
|
||||
/// using UnityEngine;
|
||||
/// using UnityEngine.Tilemaps;
|
||||
///
|
||||
/// public class CreateBlueTile
|
||||
/// {
|
||||
/// [CreateTileFromPalette]
|
||||
/// public static TileBase BlueTile(Sprite sprite)
|
||||
/// {
|
||||
/// var blueTile = ScriptableObject.CreateInstance<Tile>();
|
||||
/// blueTile.sprite = sprite;
|
||||
/// blueTile.name = sprite.name;
|
||||
/// blueTile.color = Color.blue;
|
||||
/// return blueTile;
|
||||
/// }
|
||||
/// }
|
||||
/// ]]></code>
|
||||
/// </example>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class CreateTileFromPaletteAttribute : Attribute
|
||||
{
|
||||
private static List<MethodInfo> m_CreateTileFromPaletteMethods;
|
||||
internal static List<MethodInfo> createTileFromPaletteMethods
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_CreateTileFromPaletteMethods == null)
|
||||
GetCreateTileFromPaletteAttributeMethods();
|
||||
return m_CreateTileFromPaletteMethods;
|
||||
}
|
||||
}
|
||||
|
||||
[RequiredSignature]
|
||||
private static TileBase CreateTile(Sprite sprite)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void GetCreateTileFromPaletteAttributeMethods()
|
||||
{
|
||||
m_CreateTileFromPaletteMethods = new List<MethodInfo>();
|
||||
foreach (var sortingMethod in EditorAssemblies.GetAllMethodsWithAttribute<CreateTileFromPaletteAttribute>())
|
||||
{
|
||||
m_CreateTileFromPaletteMethods.Add(sortingMethod);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
/// <summary>
|
||||
/// Tool for doing a box fill with the Tile Palette
|
||||
/// </summary>
|
||||
//[EditorTool("Box Tool", typeof(GridLayout))]
|
||||
public sealed class BoxTool : TilemapEditorTool
|
||||
{
|
||||
private static class Styles
|
||||
{
|
||||
public static string tooltipStringFormat = "|Paint a filled box with active brush ({0})";
|
||||
public static string shortcutId = "Grid Painting/Rectangle";
|
||||
public static GUIContent toolContent = EditorGUIUtility.IconContent("Grid.BoxTool", GetTooltipText(tooltipStringFormat, shortcutId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tooltip String Format for the BoxTool
|
||||
/// </summary>
|
||||
protected override string tooltipStringFormat
|
||||
{
|
||||
get { return Styles.tooltipStringFormat; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut Id for the BoxTool
|
||||
/// </summary>
|
||||
protected override string shortcutId
|
||||
{
|
||||
get { return Styles.shortcutId; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toolbar Icon for the BoxTool
|
||||
/// </summary>
|
||||
public override GUIContent toolbarIcon
|
||||
{
|
||||
get { return Styles.toolContent; }
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
/// <summary>
|
||||
/// Tool for doing an erase with the Tile Palette
|
||||
/// </summary>
|
||||
public sealed class EraseTool : TilemapEditorTool
|
||||
{
|
||||
private static class Styles
|
||||
{
|
||||
public static string tooltipStringFormat = "|Erase with active brush ({0})";
|
||||
public static string shortcutId = "Grid Painting/Erase";
|
||||
public static GUIContent toolContent = EditorGUIUtility.IconContent("Grid.EraserTool", GetTooltipText(tooltipStringFormat, shortcutId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tooltip String Format for the EraseTool
|
||||
/// </summary>
|
||||
protected override string tooltipStringFormat
|
||||
{
|
||||
get { return Styles.tooltipStringFormat; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut Id for the EraseTool
|
||||
/// </summary>
|
||||
protected override string shortcutId
|
||||
{
|
||||
get { return Styles.shortcutId; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toolbar Icon for the EraseTool
|
||||
/// </summary>
|
||||
public override GUIContent toolbarIcon
|
||||
{
|
||||
get { return Styles.toolContent; }
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
/// <summary>
|
||||
/// Tool for doing a flood fill with the Tile Palette
|
||||
/// </summary>
|
||||
public sealed class FillTool : TilemapEditorTool
|
||||
{
|
||||
private static class Styles
|
||||
{
|
||||
public static string tooltipStringFormat = "|Flood fill with active brush ({0})";
|
||||
public static string shortcutId = "Grid Painting/Fill";
|
||||
public static GUIContent toolContent = EditorGUIUtility.IconContent("Grid.FillTool", GetTooltipText(tooltipStringFormat, shortcutId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tooltip String Format for the FillTool
|
||||
/// </summary>
|
||||
protected override string tooltipStringFormat
|
||||
{
|
||||
get { return Styles.tooltipStringFormat; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut Id for the FillTool
|
||||
/// </summary>
|
||||
protected override string shortcutId
|
||||
{
|
||||
get { return Styles.shortcutId; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toolbar Icon for the FillTool
|
||||
/// </summary>
|
||||
public override GUIContent toolbarIcon
|
||||
{
|
||||
get { return Styles.toolContent; }
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
/// <summary>
|
||||
/// Tool for doing a move with the Tile Palette
|
||||
/// </summary>
|
||||
public sealed class MoveTool : TilemapEditorTool
|
||||
{
|
||||
private static class Styles
|
||||
{
|
||||
public static string tooltipStringFormat = "|Move selection with active brush ({0})";
|
||||
public static string shortcutId = "Grid Painting/Move";
|
||||
public static GUIContent toolContent = EditorGUIUtility.IconContent("Grid.MoveTool", GetTooltipText(tooltipStringFormat, shortcutId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tooltip String Format for the MoveTool
|
||||
/// </summary>
|
||||
protected override string tooltipStringFormat
|
||||
{
|
||||
get { return Styles.tooltipStringFormat; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut Id for the MoveTool
|
||||
/// </summary>
|
||||
protected override string shortcutId
|
||||
{
|
||||
get { return Styles.shortcutId; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toolbar Icon for the MoveTool
|
||||
/// </summary>
|
||||
public override GUIContent toolbarIcon
|
||||
{
|
||||
get { return Styles.toolContent; }
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
/// <summary>
|
||||
/// Tool for doing a paint with the Tile Palette
|
||||
/// </summary>
|
||||
public sealed class PaintTool : TilemapEditorTool
|
||||
{
|
||||
private static class Styles
|
||||
{
|
||||
public static string tooltipStringFormat = "|Paint with active brush ({0})";
|
||||
public static string shortcutId = "Grid Painting/Brush";
|
||||
public static GUIContent toolContent = EditorGUIUtility.IconContent("Grid.PaintTool", GetTooltipText(tooltipStringFormat, shortcutId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tooltip String Format for the PaintTool
|
||||
/// </summary>
|
||||
protected override string tooltipStringFormat
|
||||
{
|
||||
get { return Styles.tooltipStringFormat; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut Id for the PaintTool
|
||||
/// </summary>
|
||||
protected override string shortcutId
|
||||
{
|
||||
get { return Styles.shortcutId; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toolbar Icon for the PaintTool
|
||||
/// </summary>
|
||||
public override GUIContent toolbarIcon
|
||||
{
|
||||
get { return Styles.toolContent; }
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
/// <summary>
|
||||
/// Tool for doing a picking action with the Tile Palette
|
||||
/// </summary>
|
||||
public sealed class PickingTool : TilemapEditorTool
|
||||
{
|
||||
private static class Styles
|
||||
{
|
||||
public static string tooltipStringFormat = "|Pick or marquee select new brush ({0})";
|
||||
public static string shortcutId = "Grid Painting/Picker";
|
||||
public static GUIContent toolContent = EditorGUIUtility.IconContent("Grid.PickingTool", GetTooltipText(tooltipStringFormat, shortcutId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tooltip String Format for the PickingTool
|
||||
/// </summary>
|
||||
protected override string tooltipStringFormat
|
||||
{
|
||||
get { return Styles.tooltipStringFormat; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut Id for the PickingTool
|
||||
/// </summary>
|
||||
protected override string shortcutId
|
||||
{
|
||||
get { return Styles.shortcutId; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toolbar Icon for the PickingTool
|
||||
/// </summary>
|
||||
public override GUIContent toolbarIcon
|
||||
{
|
||||
get { return Styles.toolContent; }
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
/// <summary>
|
||||
/// Tool for doing a selection with the Tile Palette
|
||||
/// </summary>
|
||||
public sealed class SelectTool : TilemapEditorTool
|
||||
{
|
||||
private static class Styles
|
||||
{
|
||||
public static string tooltipStringFormat = "|Select an area of the grid ({0})";
|
||||
public static string shortcutId = "Grid Painting/Select";
|
||||
public static GUIContent toolContent = EditorGUIUtility.IconContent("Grid.Default", GetTooltipText(tooltipStringFormat, shortcutId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tooltip String Format for the SelectTool
|
||||
/// </summary>
|
||||
protected override string tooltipStringFormat
|
||||
{
|
||||
get { return Styles.tooltipStringFormat; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut Id for the SelectTool
|
||||
/// </summary>
|
||||
protected override string shortcutId
|
||||
{
|
||||
get { return Styles.shortcutId; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toolbar Icon for the SelectTool
|
||||
/// </summary>
|
||||
public override GUIContent toolbarIcon
|
||||
{
|
||||
get { return Styles.toolContent; }
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,187 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEditor.ShortcutManagement;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
/// <summary>
|
||||
/// A base class for Editor Tools which work with the Tile Palette
|
||||
/// and GridBrushes
|
||||
/// </summary>
|
||||
public abstract class TilemapEditorTool : EditorTool
|
||||
{
|
||||
private static EditorTool[] s_TilemapEditorTools = null;
|
||||
private static float s_TilemapEditorToolsToolbarSize = 0.0f;
|
||||
|
||||
/// <summary>
|
||||
/// All currently active Editor Tools which work with the Tile Palette
|
||||
/// </summary>
|
||||
public static EditorTool[] tilemapEditorTools
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsCachedEditorToolsInvalid())
|
||||
InstantiateEditorTools();
|
||||
return s_TilemapEditorTools;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The horizontal size of a Toolbar with all the TilemapEditorTools
|
||||
/// </summary>
|
||||
public static float tilemapEditorToolsToolbarSize
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsCachedEditorToolsInvalid())
|
||||
InstantiateEditorTools();
|
||||
return s_TilemapEditorToolsToolbarSize;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tooltip String format which accepts a shortcut combination as the parameter
|
||||
/// </summary>
|
||||
protected abstract string tooltipStringFormat { get; }
|
||||
/// <summary>
|
||||
/// Shortcut Id for this tool
|
||||
/// </summary>
|
||||
protected abstract string shortcutId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text for the tooltip given a tooltip string format
|
||||
/// and the shortcut combination for a tooltip
|
||||
/// </summary>
|
||||
/// <param name="tooltipStringFormat">String format which accepts a shortcut combination as the parameter</param>
|
||||
/// <param name="shortcutId">Shortcut Id for this tool</param>
|
||||
/// <returns>The final text for the tooltip</returns>
|
||||
protected static string GetTooltipText(string tooltipStringFormat, string shortcutId)
|
||||
{
|
||||
return String.Format(tooltipStringFormat, GetKeysFromToolName(shortcutId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the key combination for triggering the shortcut for this tool
|
||||
/// </summary>
|
||||
/// <param name="id">The shortcut id for this tool</param>
|
||||
/// <returns>The key combination for triggering the shortcut for this tool</returns>
|
||||
protected static string GetKeysFromToolName(string id)
|
||||
{
|
||||
return ShortcutIntegration.instance.GetKeyCombinationFor(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the tooltip whenever there is a change in shortcut combinations
|
||||
/// </summary>
|
||||
protected void UpdateTooltip()
|
||||
{
|
||||
toolbarIcon.tooltip = GetTooltipText(tooltipStringFormat, shortcutId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the tool is available for use
|
||||
/// </summary>
|
||||
/// <returns>Whether the tool is available for use</returns>
|
||||
public override bool IsAvailable()
|
||||
{
|
||||
return (GridPaintPaletteWindow.instances.Count > 0) && GridPaintingState.gridBrush;
|
||||
}
|
||||
|
||||
internal static void UpdateTooltips()
|
||||
{
|
||||
if (s_TilemapEditorTools == null)
|
||||
InstantiateEditorTools();
|
||||
|
||||
foreach (var editorTool in s_TilemapEditorTools)
|
||||
{
|
||||
var tilemapEditorTool = editorTool as TilemapEditorTool;
|
||||
if (tilemapEditorTool == null)
|
||||
return;
|
||||
|
||||
tilemapEditorTool.UpdateTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the state of active editor tool with the type passed in.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will change the current active editor tool if the type passed in
|
||||
/// is not the same as the current active editor tool. Otherwise, it will
|
||||
/// set the View Mode tool as the current active editor tool.
|
||||
/// </remarks>
|
||||
/// <param name="type">
|
||||
/// The type of editor tool. This must be inherited from EditorTool.
|
||||
/// </param>
|
||||
public static void ToggleActiveEditorTool(Type type)
|
||||
{
|
||||
if (ToolManager.activeToolType != type)
|
||||
{
|
||||
SetActiveEditorTool(type);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Switch out of TilemapEditorTool if possible
|
||||
var lastTool = EditorToolManager.GetLastTool(x => !(x is TilemapEditorTool));
|
||||
if (lastTool != null)
|
||||
ToolManager.SetActiveTool(lastTool);
|
||||
else
|
||||
ToolManager.SetActiveTool(typeof(ViewModeTool));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current active editor tool to the type passed in
|
||||
/// </summary>
|
||||
/// <param name="type">The type of editor tool. This must be inherited from TilemapEditorTool</param>
|
||||
/// <exception cref="ArgumentException">Throws this if an invalid type parameter is set</exception>
|
||||
public static void SetActiveEditorTool(Type type)
|
||||
{
|
||||
if (type == null || !type.IsSubclassOf(typeof(TilemapEditorTool)))
|
||||
throw new ArgumentException("The tool to set must be valid and derive from TilemapEditorTool.");
|
||||
|
||||
EditorTool selectedTool = null;
|
||||
foreach (var tool in tilemapEditorTools)
|
||||
{
|
||||
if (tool.GetType() == type)
|
||||
{
|
||||
selectedTool = tool;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedTool != null)
|
||||
{
|
||||
ToolManager.SetActiveTool(selectedTool);
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool IsActive(Type toolType)
|
||||
{
|
||||
return ToolManager.activeToolType != null && ToolManager.activeToolType == toolType;
|
||||
}
|
||||
|
||||
private static bool IsCachedEditorToolsInvalid()
|
||||
{
|
||||
return s_TilemapEditorTools == null || s_TilemapEditorTools.Length == 0 || s_TilemapEditorTools[0] == null;
|
||||
}
|
||||
|
||||
private static void InstantiateEditorTools()
|
||||
{
|
||||
s_TilemapEditorTools = new EditorTool[]
|
||||
{
|
||||
CreateInstance<SelectTool>(),
|
||||
CreateInstance<MoveTool>(),
|
||||
CreateInstance<PaintTool>(),
|
||||
CreateInstance<BoxTool>(),
|
||||
CreateInstance<PickingTool>(),
|
||||
CreateInstance<EraseTool>(),
|
||||
CreateInstance<FillTool>()
|
||||
};
|
||||
GUIStyle toolbarStyle = "Command";
|
||||
s_TilemapEditorToolsToolbarSize = s_TilemapEditorTools.Sum(x => toolbarStyle.CalcSize(x.toolbarIcon).x);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,650 @@
|
||||
using System;
|
||||
using UnityEngine.Tilemaps;
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using UnityEngine.Scripting.APIUpdating;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
/// <summary>
|
||||
/// Default built-in brush for painting or erasing tiles and/or gameobjects on a grid.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Default brush is meant for two things: Authoring GameObjects on a grid and authoring tiles on a grid. It can be used for both at the same time if necessary. Basically the default brush allows you to pick and paste tiles and GameObjects from one area to another.
|
||||
///
|
||||
/// Tiles in a tilemap are considered to be active for brush editing if a GameObject with Tilemap is currently selected.
|
||||
/// GameObjects are considered to be active for brush editing if their parent GameObject is currently selected.
|
||||
///
|
||||
/// For example: A new default brush with size of 2x3 is generated as a result of Scene view picking operation. The new instance contains six cells (GridBrush.BrushCell) and the cells are fed with tiles & GameObjects from the picking area.
|
||||
///
|
||||
/// Later when this brush is used in a painting operation, all the tiles and GameObjects contained in the cells are set/cloned into a new Scene position.
|
||||
///
|
||||
/// When creating custom brushes, it is recommended to inherit GridBrushBase by default. Inheriting GridBrush is possible when similar functionality is required, but extending it has its limits compared to base class.
|
||||
///
|
||||
/// It is also possible to replace the default GridBrush from the Tile Palette brush list completely by using the GridDefaultBrush attribute on one of your custom brushes and promote it to become a new default brush of the project. This is useful when higher level brush can operate as a default and protect designers from accidentally using the built-in one.
|
||||
/// </remarks>
|
||||
[MovedFrom(true, "UnityEditor", "UnityEditor")]
|
||||
public class GridBrush : GridBrushBase
|
||||
{
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
private BrushCell[] m_Cells;
|
||||
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
private Vector3Int m_Size;
|
||||
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
private Vector3Int m_Pivot;
|
||||
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
private bool m_CanChangeZPosition;
|
||||
|
||||
[SerializeField]
|
||||
private bool m_FloodFillContiguousOnly = true;
|
||||
|
||||
private Vector3Int m_StoredSize;
|
||||
private Vector3Int m_StoredPivot;
|
||||
private BrushCell[] m_StoredCells;
|
||||
|
||||
private ArrayList m_Locations;
|
||||
private ArrayList m_Tiles;
|
||||
|
||||
private static readonly Matrix4x4 s_Clockwise = new Matrix4x4(new Vector4(0f, 1f, 0f, 0f), new Vector4(-1f, 0f, 0f, 0f), new Vector4(0f, 0f, 1f, 0f), new Vector4(0f, 0f, 0f, 1f));
|
||||
private static readonly Matrix4x4 s_CounterClockwise = new Matrix4x4(new Vector4(0f, -1f, 0f, 0f), new Vector4(1f, 0f, 0f, 0f), new Vector4(0f, 0f, 1f, 0f), new Vector4(0f, 0f, 0f, 1f));
|
||||
private static readonly Matrix4x4 s_180Rotate = new Matrix4x4(new Vector4(-1f, 0f, 0f, 0f), new Vector4(0f, -1f, 0f, 0f), new Vector4(0f, 0f, 1f, 0f), new Vector4(0f, 0f, 0f, 1f));
|
||||
|
||||
/// <summary>Size of the brush in cells. </summary>
|
||||
public Vector3Int size { get { return m_Size; } set { m_Size = value; SizeUpdated(); } }
|
||||
/// <summary>Pivot of the brush. </summary>
|
||||
public Vector3Int pivot { get { return m_Pivot; } set { m_Pivot = value; } }
|
||||
/// <summary>All the brush cells the brush holds. </summary>
|
||||
public BrushCell[] cells { get { return m_Cells; } }
|
||||
/// <summary>Number of brush cells in the brush.</summary>
|
||||
public int cellCount { get { return m_Cells != null ? m_Cells.Length : 0; } }
|
||||
/// <summary>Whether the brush can change Z Position</summary>
|
||||
public bool canChangeZPosition
|
||||
{
|
||||
get { return m_CanChangeZPosition; }
|
||||
set { m_CanChangeZPosition = value; }
|
||||
}
|
||||
|
||||
private ArrayList locations
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Locations == null)
|
||||
m_Locations = new ArrayList();
|
||||
return m_Locations;
|
||||
}
|
||||
}
|
||||
|
||||
private ArrayList tiles
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Tiles == null)
|
||||
m_Tiles = new ArrayList();
|
||||
return m_Tiles;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default built-in brush for painting or erasing tiles and/or gameobjects on a grid.
|
||||
/// </summary>
|
||||
public GridBrush()
|
||||
{
|
||||
Init(Vector3Int.one, Vector3Int.zero);
|
||||
SizeUpdated();
|
||||
}
|
||||
|
||||
/// <summary>Initializes the content of the GridBrush.</summary>
|
||||
/// <param name="size">Size of the GridBrush.</param>
|
||||
public void Init(Vector3Int size)
|
||||
{
|
||||
Init(size, Vector3Int.zero);
|
||||
SizeUpdated();
|
||||
}
|
||||
|
||||
/// <summary>Initializes the content of the GridBrush.</summary>
|
||||
/// <param name="size">Size of the GridBrush.</param>
|
||||
/// <param name="pivot">Pivot point of the GridBrush.</param>
|
||||
public void Init(Vector3Int size, Vector3Int pivot)
|
||||
{
|
||||
m_Size = size;
|
||||
m_Pivot = pivot;
|
||||
SizeUpdated();
|
||||
}
|
||||
|
||||
/// <summary>Paints tiles and GameObjects into a given position within the selected layers.</summary>
|
||||
/// <param name="gridLayout">Grid used for layout.</param>
|
||||
/// <param name="brushTarget">Target of the paint operation. By default the currently selected GameObject.</param>
|
||||
/// <param name="position">The coordinates of the cell to paint data to.</param>
|
||||
public override void Paint(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
|
||||
{
|
||||
Vector3Int min = position - pivot;
|
||||
BoundsInt bounds = new BoundsInt(min, m_Size);
|
||||
BoxFill(gridLayout, brushTarget, bounds);
|
||||
}
|
||||
|
||||
/// <summary>Erases tiles and GameObjects in a given position within the selected layers.</summary>
|
||||
/// <param name="gridLayout">Grid used for layout.</param>
|
||||
/// <param name="brushTarget">Target of the erase operation. By default the currently selected GameObject.</param>
|
||||
/// <param name="position">The coordinates of the cell to erase data from.</param>
|
||||
public override void Erase(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
|
||||
{
|
||||
Vector3Int min = position - pivot;
|
||||
BoundsInt bounds = new BoundsInt(min, m_Size);
|
||||
BoxErase(gridLayout, brushTarget, bounds);
|
||||
}
|
||||
|
||||
/// <summary>Box fills tiles and GameObjects into given bounds within the selected layers.</summary>
|
||||
/// <param name="gridLayout">Grid to box fill data to.</param>
|
||||
/// <param name="brushTarget">Target of the box fill operation. By default the currently selected GameObject.</param>
|
||||
/// <param name="position">The bounds to box fill data into.</param>
|
||||
public override void BoxFill(GridLayout gridLayout, GameObject brushTarget, BoundsInt position)
|
||||
{
|
||||
if (brushTarget == null)
|
||||
return;
|
||||
|
||||
Tilemap map = brushTarget.GetComponent<Tilemap>();
|
||||
if (map == null)
|
||||
return;
|
||||
|
||||
locations.Clear();
|
||||
tiles.Clear();
|
||||
foreach (Vector3Int location in position.allPositionsWithin)
|
||||
{
|
||||
Vector3Int local = location - position.min;
|
||||
BrushCell cell = m_Cells[GetCellIndexWrapAround(local.x, local.y, local.z)];
|
||||
if (cell.tile == null)
|
||||
continue;
|
||||
|
||||
locations.Add(location);
|
||||
tiles.Add(cell.tile);
|
||||
}
|
||||
map.SetTiles((Vector3Int[])locations.ToArray(typeof(Vector3Int)), (TileBase[])tiles.ToArray(typeof(TileBase)));
|
||||
foreach (Vector3Int location in position.allPositionsWithin)
|
||||
{
|
||||
Vector3Int local = location - position.min;
|
||||
BrushCell cell = m_Cells[GetCellIndexWrapAround(local.x, local.y, local.z)];
|
||||
if (cell.tile == null)
|
||||
continue;
|
||||
|
||||
map.SetTransformMatrix(location, cell.matrix);
|
||||
map.SetColor(location, cell.color);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Erases tiles and GameObjects from given bounds within the selected layers.</summary>
|
||||
/// <param name="gridLayout">Grid to erase data from.</param>
|
||||
/// <param name="brushTarget">Target of the erase operation. By default the currently selected GameObject.</param>
|
||||
/// <param name="position">The bounds to erase data from.</param>
|
||||
public override void BoxErase(GridLayout gridLayout, GameObject brushTarget, BoundsInt position)
|
||||
{
|
||||
if (brushTarget == null)
|
||||
return;
|
||||
|
||||
Tilemap map = brushTarget.GetComponent<Tilemap>();
|
||||
if (map == null)
|
||||
return;
|
||||
|
||||
var emptyTiles = new TileBase[position.size.x * position.size.y * position.size.z];
|
||||
map.SetTilesBlock(position, emptyTiles);
|
||||
foreach (Vector3Int location in position.allPositionsWithin)
|
||||
{
|
||||
map.SetTransformMatrix(location, Matrix4x4.identity);
|
||||
map.SetColor(location, Color.white);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Flood fills tiles and GameObjects starting from a given position within the selected layers.</summary>
|
||||
/// <param name="gridLayout">Grid used for layout.</param>
|
||||
/// <param name="brushTarget">Target of the flood fill operation. By default the currently selected GameObject.</param>
|
||||
/// <param name="position">Starting position of the flood fill.</param>
|
||||
public override void FloodFill(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
|
||||
{
|
||||
if (cellCount == 0)
|
||||
return;
|
||||
|
||||
if (brushTarget == null)
|
||||
return;
|
||||
|
||||
Tilemap map = brushTarget.GetComponent<Tilemap>();
|
||||
if (map == null)
|
||||
return;
|
||||
|
||||
if (m_FloodFillContiguousOnly)
|
||||
{
|
||||
map.FloodFill(position, cells[0].tile);
|
||||
}
|
||||
else
|
||||
{
|
||||
var tile = map.GetTile(position);
|
||||
if (tile != null && tile != cells[0].tile)
|
||||
{
|
||||
map.SwapTile(tile, cells[0].tile);
|
||||
}
|
||||
else
|
||||
{
|
||||
map.FloodFill(position, cells[0].tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Rotates the brush by 90 degrees in the given direction.</summary>
|
||||
/// <param name="direction">Direction to rotate by.</param>
|
||||
/// <param name="layout">Cell Layout for rotating.</param>
|
||||
public override void Rotate(RotationDirection direction, GridLayout.CellLayout layout)
|
||||
{
|
||||
switch (layout)
|
||||
{
|
||||
case GridLayout.CellLayout.Hexagon:
|
||||
RotateHexagon(direction);
|
||||
break;
|
||||
case GridLayout.CellLayout.Isometric:
|
||||
case GridLayout.CellLayout.IsometricZAsY:
|
||||
case GridLayout.CellLayout.Rectangle:
|
||||
{
|
||||
Vector3Int oldSize = m_Size;
|
||||
BrushCell[] oldCells = m_Cells.Clone() as BrushCell[];
|
||||
size = new Vector3Int(oldSize.y, oldSize.x, oldSize.z);
|
||||
BoundsInt oldBounds = new BoundsInt(Vector3Int.zero, oldSize);
|
||||
|
||||
foreach (Vector3Int oldPos in oldBounds.allPositionsWithin)
|
||||
{
|
||||
int newX = direction == RotationDirection.Clockwise ? oldSize.y - oldPos.y - 1 : oldPos.y;
|
||||
int newY = direction == RotationDirection.Clockwise ? oldPos.x : oldSize.x - oldPos.x - 1;
|
||||
int toIndex = GetCellIndex(newX, newY, oldPos.z);
|
||||
int fromIndex = GetCellIndex(oldPos.x, oldPos.y, oldPos.z, oldSize.x, oldSize.y, oldSize.z);
|
||||
m_Cells[toIndex] = oldCells[fromIndex];
|
||||
}
|
||||
|
||||
int newPivotX = direction == RotationDirection.Clockwise ? oldSize.y - pivot.y - 1 : pivot.y;
|
||||
int newPivotY = direction == RotationDirection.Clockwise ? pivot.x : oldSize.x - pivot.x - 1;
|
||||
pivot = new Vector3Int(newPivotX, newPivotY, pivot.z);
|
||||
|
||||
Matrix4x4 rotation = direction == RotationDirection.Clockwise ? s_Clockwise : s_CounterClockwise;
|
||||
Matrix4x4 counterRotation = direction != RotationDirection.Clockwise ? s_Clockwise : s_CounterClockwise;
|
||||
foreach (BrushCell cell in m_Cells)
|
||||
{
|
||||
Matrix4x4 oldMatrix = cell.matrix;
|
||||
bool counter = (oldMatrix.lossyScale.x < 0) ^ (oldMatrix.lossyScale.y < 0);
|
||||
cell.matrix = oldMatrix * (counter ? counterRotation : rotation);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector3Int RotateHexagonPosition(RotationDirection direction, Vector3Int position)
|
||||
{
|
||||
var cube = HexagonToCube(position);
|
||||
Vector3Int rotatedCube = Vector3Int.zero;
|
||||
if (RotationDirection.Clockwise == direction)
|
||||
{
|
||||
rotatedCube.x = -cube.z;
|
||||
rotatedCube.y = -cube.x;
|
||||
rotatedCube.z = -cube.y;
|
||||
}
|
||||
else
|
||||
{
|
||||
rotatedCube.x = -cube.y;
|
||||
rotatedCube.y = -cube.z;
|
||||
rotatedCube.z = -cube.x;
|
||||
}
|
||||
return CubeToHexagon(rotatedCube);
|
||||
}
|
||||
|
||||
private void RotateHexagon(RotationDirection direction)
|
||||
{
|
||||
BrushCell[] oldCells = m_Cells.Clone() as BrushCell[];
|
||||
Vector3Int oldPivot = new Vector3Int(pivot.x, pivot.y, pivot.z);
|
||||
Vector3Int oldSize = new Vector3Int(size.x, size.y, size.z);
|
||||
Vector3Int minSize = Vector3Int.zero;
|
||||
Vector3Int maxSize = Vector3Int.zero;
|
||||
BoundsInt oldBounds = new BoundsInt(Vector3Int.zero, oldSize);
|
||||
foreach (Vector3Int oldPos in oldBounds.allPositionsWithin)
|
||||
{
|
||||
if (oldCells[GetCellIndex(oldPos.x, oldPos.y, oldPos.z, oldSize.x, oldSize.y, oldSize.z)].tile == null)
|
||||
continue;
|
||||
var pos = RotateHexagonPosition(direction, oldPos - oldPivot);
|
||||
minSize.x = Mathf.Min(minSize.x, pos.x);
|
||||
minSize.y = Mathf.Min(minSize.y, pos.y);
|
||||
maxSize.x = Mathf.Max(maxSize.x, pos.x);
|
||||
maxSize.y = Mathf.Max(maxSize.y, pos.y);
|
||||
}
|
||||
Vector3Int newSize = new Vector3Int(1 + maxSize.x - minSize.x, 1 + maxSize.y - minSize.y, oldSize.z);
|
||||
Vector3Int newPivot = new Vector3Int(-minSize.x, -minSize.y, oldPivot.z);
|
||||
UpdateSizeAndPivot(newSize, new Vector3Int(newPivot.x, newPivot.y, newPivot.z));
|
||||
foreach (Vector3Int oldPos in oldBounds.allPositionsWithin)
|
||||
{
|
||||
if (oldCells[GetCellIndex(oldPos.x, oldPos.y, oldPos.z, oldSize.x, oldSize.y, oldSize.z)].tile == null)
|
||||
continue;
|
||||
Vector3Int newPos = RotateHexagonPosition(direction, new Vector3Int(oldPos.x, oldPos.y, oldPos.z) - oldPivot) + newPivot;
|
||||
m_Cells[GetCellIndex(newPos.x, newPos.y, newPos.z)] = oldCells[GetCellIndex(oldPos.x, oldPos.y, oldPos.z, oldSize.x, oldSize.y, oldSize.z)];
|
||||
}
|
||||
// Do not rotate hexagon cell matrix, as hexagon cells are not perfect hexagons
|
||||
}
|
||||
|
||||
private static Vector3Int HexagonToCube(Vector3Int position)
|
||||
{
|
||||
Vector3Int cube = Vector3Int.zero;
|
||||
cube.x = position.x - (position.y - (position.y & 1)) / 2;
|
||||
cube.z = position.y;
|
||||
cube.y = -cube.x - cube.z;
|
||||
return cube;
|
||||
}
|
||||
|
||||
private static Vector3Int CubeToHexagon(Vector3Int position)
|
||||
{
|
||||
Vector3Int hexagon = Vector3Int.zero;
|
||||
hexagon.x = position.x + (position.z - (position.z & 1)) / 2;
|
||||
hexagon.y = position.z;
|
||||
hexagon.z = 0;
|
||||
return hexagon;
|
||||
}
|
||||
|
||||
/// <summary>Flips the brush in the given axis.</summary>
|
||||
/// <param name="flip">Axis to flip by.</param>
|
||||
/// <param name="layout">Cell Layout for flipping.</param>
|
||||
public override void Flip(FlipAxis flip, Grid.CellLayout layout)
|
||||
{
|
||||
if (flip == FlipAxis.X)
|
||||
FlipX(layout);
|
||||
else
|
||||
FlipY(layout);
|
||||
}
|
||||
|
||||
/// <summary>Picks tiles from selected Tilemaps and child GameObjects, given the coordinates of the cells.</summary>
|
||||
/// <param name="gridLayout">Grid to pick data from.</param>
|
||||
/// <param name="brushTarget">Target of the picking operation. By default the currently selected GameObject.</param>
|
||||
/// <param name="position">The coordinates of the cells to paint data from.</param>
|
||||
/// <param name="pickStart">Pivot of the picking brush.</param>
|
||||
public override void Pick(GridLayout gridLayout, GameObject brushTarget, BoundsInt position, Vector3Int pickStart)
|
||||
{
|
||||
Reset();
|
||||
UpdateSizeAndPivot(new Vector3Int(position.size.x, position.size.y, 1), new Vector3Int(pickStart.x, pickStart.y, 0));
|
||||
|
||||
if (brushTarget == null)
|
||||
return;
|
||||
|
||||
Tilemap tilemap = brushTarget.GetComponent<Tilemap>();
|
||||
foreach (Vector3Int pos in position.allPositionsWithin)
|
||||
{
|
||||
Vector3Int brushPosition = new Vector3Int(pos.x - position.x, pos.y - position.y, 0);
|
||||
PickCell(pos, brushPosition, tilemap);
|
||||
}
|
||||
}
|
||||
|
||||
private void PickCell(Vector3Int position, Vector3Int brushPosition, Tilemap tilemap)
|
||||
{
|
||||
if (tilemap == null)
|
||||
return;
|
||||
|
||||
SetTile(brushPosition, tilemap.GetTile(position));
|
||||
SetMatrix(brushPosition, tilemap.GetTransformMatrix(position));
|
||||
SetColor(brushPosition, tilemap.GetColor(position));
|
||||
}
|
||||
|
||||
private void StoreCells()
|
||||
{
|
||||
m_StoredSize = m_Size;
|
||||
m_StoredPivot = m_Pivot;
|
||||
if (m_Cells != null)
|
||||
{
|
||||
m_StoredCells = new BrushCell[m_Cells.Length];
|
||||
for (int i = 0; i < m_Cells.Length; ++i)
|
||||
{
|
||||
m_StoredCells[i] = m_Cells[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_StoredCells = new BrushCell[0];
|
||||
}
|
||||
}
|
||||
|
||||
private void RestoreCells()
|
||||
{
|
||||
m_Size = m_StoredSize;
|
||||
m_Pivot = m_StoredPivot;
|
||||
if (m_StoredCells != null)
|
||||
{
|
||||
m_Cells = new BrushCell[m_StoredCells.Length];
|
||||
for (int i = 0; i < m_StoredCells.Length; ++i)
|
||||
{
|
||||
m_Cells[i] = m_StoredCells[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>MoveStart is called when user starts moving the area previously selected with the selection marquee.</summary>
|
||||
/// <param name="gridLayout">Grid used for layout.</param>
|
||||
/// <param name="brushTarget">Target of the move operation. By default the currently selected GameObject.</param>
|
||||
/// <param name="position">Position where the move operation has started.</param>
|
||||
public override void MoveStart(GridLayout gridLayout, GameObject brushTarget, BoundsInt position)
|
||||
{
|
||||
Tilemap tilemap = brushTarget.GetComponent<Tilemap>();
|
||||
if (tilemap == null)
|
||||
return;
|
||||
|
||||
StoreCells();
|
||||
Reset();
|
||||
UpdateSizeAndPivot(new Vector3Int(position.size.x, position.size.y, 1), Vector3Int.zero);
|
||||
|
||||
foreach (Vector3Int pos in position.allPositionsWithin)
|
||||
{
|
||||
Vector3Int brushPosition = new Vector3Int(pos.x - position.x, pos.y - position.y, 0);
|
||||
PickCell(pos, brushPosition, tilemap);
|
||||
tilemap.SetTile(pos, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>MoveEnd is called when user has ended the move of the area previously selected with the selection marquee.</summary>
|
||||
/// <param name="gridLayout">Grid used for layout.</param>
|
||||
/// <param name="brushTarget">Target of the move operation. By default the currently selected GameObject.</param>
|
||||
/// <param name="position">Position where the move operation has ended.</param>
|
||||
public override void MoveEnd(GridLayout gridLayout, GameObject brushTarget, BoundsInt position)
|
||||
{
|
||||
Paint(gridLayout, brushTarget, position.min);
|
||||
Reset();
|
||||
RestoreCells();
|
||||
}
|
||||
|
||||
/// <summary>Clears all data of the brush.</summary>
|
||||
public void Reset()
|
||||
{
|
||||
UpdateSizeAndPivot(Vector3Int.one, Vector3Int.zero);
|
||||
}
|
||||
|
||||
private void FlipX(GridLayout.CellLayout layout)
|
||||
{
|
||||
BrushCell[] oldCells = m_Cells.Clone() as BrushCell[];
|
||||
BoundsInt oldBounds = new BoundsInt(Vector3Int.zero, m_Size);
|
||||
|
||||
foreach (Vector3Int oldPos in oldBounds.allPositionsWithin)
|
||||
{
|
||||
int newX = m_Size.x - oldPos.x - 1;
|
||||
int toIndex = GetCellIndex(newX, oldPos.y, oldPos.z);
|
||||
int fromIndex = GetCellIndex(oldPos);
|
||||
m_Cells[toIndex] = oldCells[fromIndex];
|
||||
}
|
||||
|
||||
int newPivotX = m_Size.x - pivot.x - 1;
|
||||
pivot = new Vector3Int(newPivotX, pivot.y, pivot.z);
|
||||
FlipCells(ref m_Cells, new Vector3(-1f, 1f, 1f), layout == GridLayout.CellLayout.Hexagon);
|
||||
}
|
||||
|
||||
private void FlipY(GridLayout.CellLayout layout)
|
||||
{
|
||||
BrushCell[] oldCells = m_Cells.Clone() as BrushCell[];
|
||||
BoundsInt oldBounds = new BoundsInt(Vector3Int.zero, m_Size);
|
||||
|
||||
foreach (Vector3Int oldPos in oldBounds.allPositionsWithin)
|
||||
{
|
||||
int newY = m_Size.y - oldPos.y - 1;
|
||||
int toIndex = GetCellIndex(oldPos.x, newY, oldPos.z);
|
||||
int fromIndex = GetCellIndex(oldPos);
|
||||
m_Cells[toIndex] = oldCells[fromIndex];
|
||||
}
|
||||
|
||||
int newPivotY = m_Size.y - pivot.y - 1;
|
||||
pivot = new Vector3Int(pivot.x, newPivotY, pivot.z);
|
||||
FlipCells(ref m_Cells, new Vector3(1f, -1f, 1f), layout == GridLayout.CellLayout.Hexagon);
|
||||
}
|
||||
|
||||
private static void FlipCells(ref BrushCell[] cells, Vector3 scale, bool skipRotation)
|
||||
{
|
||||
Matrix4x4 flip = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, scale);
|
||||
foreach (BrushCell cell in cells)
|
||||
{
|
||||
Matrix4x4 oldMatrix = cell.matrix;
|
||||
if (skipRotation || Mathf.Approximately(oldMatrix.rotation.x + oldMatrix.rotation.y + oldMatrix.rotation.z + oldMatrix.rotation.w, 1.0f))
|
||||
cell.matrix = oldMatrix * flip;
|
||||
else
|
||||
cell.matrix = oldMatrix * s_180Rotate * flip;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Updates the size, pivot and the number of layers of the brush.</summary>
|
||||
/// <param name="size">New size of the brush.</param>
|
||||
/// <param name="pivot">New pivot of the brush.</param>
|
||||
public void UpdateSizeAndPivot(Vector3Int size, Vector3Int pivot)
|
||||
{
|
||||
m_Size = size;
|
||||
m_Pivot = pivot;
|
||||
SizeUpdated();
|
||||
}
|
||||
|
||||
/// <summary>Sets a Tile at the position in the brush.</summary>
|
||||
/// <param name="position">Position to set the tile in the brush.</param>
|
||||
/// <param name="tile">Tile to set in the brush.</param>
|
||||
public void SetTile(Vector3Int position, TileBase tile)
|
||||
{
|
||||
if (ValidateCellPosition(position))
|
||||
m_Cells[GetCellIndex(position)].tile = tile;
|
||||
}
|
||||
|
||||
/// <summary>Sets a transform matrix at the position in the brush. This matrix is used specifically for tiles on a Tilemap and not GameObjects of the brush cell.</summary>
|
||||
/// <param name="position">Position to set the transform matrix in the brush.</param>
|
||||
/// <param name="matrix">Transform matrix to set in the brush.</param>
|
||||
public void SetMatrix(Vector3Int position, Matrix4x4 matrix)
|
||||
{
|
||||
if (ValidateCellPosition(position))
|
||||
m_Cells[GetCellIndex(position)].matrix = matrix;
|
||||
}
|
||||
|
||||
/// <summary>Sets a tint color at the position in the brush.</summary>
|
||||
/// <param name="position">Position to set the color in the brush.</param>
|
||||
/// <param name="color">Tint color to set in the brush.</param>
|
||||
public void SetColor(Vector3Int position, Color color)
|
||||
{
|
||||
if (ValidateCellPosition(position))
|
||||
m_Cells[GetCellIndex(position)].color = color;
|
||||
}
|
||||
|
||||
/// <summary>Gets the index to the GridBrush::ref::BrushCell based on the position of the BrushCell.</summary>
|
||||
/// <param name="brushPosition">Position of the BrushCell.</param>
|
||||
public int GetCellIndex(Vector3Int brushPosition)
|
||||
{
|
||||
return GetCellIndex(brushPosition.x, brushPosition.y, brushPosition.z);
|
||||
}
|
||||
|
||||
/// <summary>Gets the index to the GridBrush::ref::BrushCell based on the position of the BrushCell.</summary>
|
||||
/// <param name="x">X Position of the BrushCell.</param>
|
||||
/// <param name="y">Y Position of the BrushCell.</param>
|
||||
/// <param name="z">Z Position of the BrushCell.</param>
|
||||
public int GetCellIndex(int x, int y, int z)
|
||||
{
|
||||
return x + m_Size.x * y + m_Size.x * m_Size.y * z;
|
||||
}
|
||||
|
||||
/// <summary>Gets the index to the GridBrush::ref::BrushCell based on the position of the BrushCell.</summary>
|
||||
/// <param name="x">X Position of the BrushCell.</param>
|
||||
/// <param name="y">Y Position of the BrushCell.</param>
|
||||
/// <param name="z">Z Position of the BrushCell.</param>
|
||||
/// <param name="sizex">X Size of Brush.</param>
|
||||
/// <param name="sizey">Y Size of Brush.</param>
|
||||
/// <param name="sizez">Z Size of Brush.</param>
|
||||
public int GetCellIndex(int x, int y, int z, int sizex, int sizey, int sizez)
|
||||
{
|
||||
return x + sizex * y + sizex * sizey * z;
|
||||
}
|
||||
|
||||
/// <summary>Gets the index to the GridBrush::ref::BrushCell based on the position of the BrushCell. Wraps each coordinate if it is larger than the size of the GridBrush.</summary>
|
||||
/// <param name="x">X Position of the BrushCell.</param>
|
||||
/// <param name="y">Y Position of the BrushCell.</param>
|
||||
/// <param name="z">Z Position of the BrushCell.</param>
|
||||
/// <returns>Index to the BrushCell.</returns>
|
||||
public int GetCellIndexWrapAround(int x, int y, int z)
|
||||
{
|
||||
return (x % m_Size.x) + m_Size.x * (y % m_Size.y) + m_Size.x * m_Size.y * (z % m_Size.z);
|
||||
}
|
||||
|
||||
private bool ValidateCellPosition(Vector3Int position)
|
||||
{
|
||||
var valid =
|
||||
position.x >= 0 && position.x < size.x &&
|
||||
position.y >= 0 && position.y < size.y &&
|
||||
position.z >= 0 && position.z < size.z;
|
||||
if (!valid)
|
||||
throw new ArgumentException(string.Format("Position {0} is an invalid cell position. Valid range is between [{1}, {2}).", position, Vector3Int.zero, size));
|
||||
return valid;
|
||||
}
|
||||
|
||||
private void SizeUpdated()
|
||||
{
|
||||
m_Cells = new BrushCell[m_Size.x * m_Size.y * m_Size.z];
|
||||
BoundsInt bounds = new BoundsInt(Vector3Int.zero, m_Size);
|
||||
foreach (Vector3Int pos in bounds.allPositionsWithin)
|
||||
{
|
||||
m_Cells[GetCellIndex(pos)] = new BrushCell();
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int hash = 0;
|
||||
unchecked
|
||||
{
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
hash = hash * 33 + cell.GetHashCode();
|
||||
}
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
/// <summary>Brush Cell stores the data to be painted in a grid cell.</summary>
|
||||
[Serializable]
|
||||
public class BrushCell
|
||||
{
|
||||
/// <summary>Tile to be placed when painting.</summary>
|
||||
public TileBase tile { get { return m_Tile; } set { m_Tile = value; } }
|
||||
/// <summary>The transform matrix of the brush cell.</summary>
|
||||
public Matrix4x4 matrix { get { return m_Matrix; } set { m_Matrix = value; } }
|
||||
/// <summary>Color to tint the tile when painting.</summary>
|
||||
public Color color { get { return m_Color; } set { m_Color = value; } }
|
||||
|
||||
[SerializeField] private TileBase m_Tile;
|
||||
[SerializeField] Matrix4x4 m_Matrix = Matrix4x4.identity;
|
||||
[SerializeField] private Color m_Color = Color.white;
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int hash;
|
||||
unchecked
|
||||
{
|
||||
hash = tile != null ? tile.GetInstanceID() : 0;
|
||||
hash = hash * 33 + matrix.GetHashCode();
|
||||
hash = hash * 33 + matrix.rotation.GetHashCode();
|
||||
hash = hash * 33 + color.GetHashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,696 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Tilemaps;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine.Scripting.APIUpdating;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
/// <summary>Editor for GridBrush.</summary>
|
||||
[MovedFrom(true, "UnityEditor", "UnityEditor")]
|
||||
[CustomEditor(typeof(GridBrush))]
|
||||
public class GridBrushEditor : GridBrushEditorBase
|
||||
{
|
||||
private static class Styles
|
||||
{
|
||||
public static readonly GUIContent tileLabel = EditorGUIUtility.TrTextContent("Tile", "Tile set in tilemap");
|
||||
public static readonly GUIContent spriteLabel = EditorGUIUtility.TrTextContent("Sprite", "Sprite set when tile is set in tilemap");
|
||||
public static readonly GUIContent colorLabel = EditorGUIUtility.TrTextContent("Color", "Color set when tile is set in tilemap");
|
||||
public static readonly GUIContent colliderTypeLabel = EditorGUIUtility.TrTextContent("Collider Type", "Collider shape used for tile");
|
||||
public static readonly GUIContent lockColorLabel = EditorGUIUtility.TrTextContent("Lock Color", "Prevents tilemap from changing color of tile");
|
||||
public static readonly GUIContent lockTransformLabel = EditorGUIUtility.TrTextContent("Lock Transform", "Prevents tilemap from changing transform of tile");
|
||||
public static readonly GUIContent gridSelectionPropertiesLabel = EditorGUIUtility.TrTextContent("Grid Selection Properties");
|
||||
public static readonly GUIContent modifyTilemapLabel = EditorGUIUtility.TrTextContent("Modify Tilemap");
|
||||
public static readonly GUIContent modifyLabel = EditorGUIUtility.TrTextContent("Modify");
|
||||
public static readonly GUIContent deleteSelectionLabel = EditorGUIUtility.TrTextContent("Delete Selection");
|
||||
|
||||
public static readonly GUIContent noTool =
|
||||
EditorGUIUtility.TrTextContentWithIcon("None", "No Gizmo in the Scene view", "RectTool");
|
||||
public static readonly GUIContent moveTool =
|
||||
EditorGUIUtility.TrTextContentWithIcon("Move", "Shows a Gizmo in the Scene view for changing the offset for the Grid Selection", "MoveTool");
|
||||
public static readonly GUIContent rotateTool =
|
||||
EditorGUIUtility.TrTextContentWithIcon("Rotate", "Shows a Gizmo in the Scene view for changing the rotation for the Grid Selection", "RotateTool");
|
||||
public static readonly GUIContent scaleTool =
|
||||
EditorGUIUtility.TrTextContentWithIcon("Scale", "Shows a Gizmo in the Scene view for changing the scale for the Grid Selection", "ScaleTool");
|
||||
public static readonly GUIContent transformTool =
|
||||
EditorGUIUtility.TrTextContentWithIcon("Transform", "Shows a Gizmo in the Scene view for changing the transform for the Grid Selection", "TransformTool");
|
||||
|
||||
public static readonly GUIContent[] selectionTools = new[]
|
||||
{
|
||||
noTool
|
||||
, moveTool
|
||||
, rotateTool
|
||||
, scaleTool
|
||||
, transformTool
|
||||
};
|
||||
}
|
||||
|
||||
public enum ModifyCells
|
||||
{
|
||||
InsertRow,
|
||||
InsertColumn,
|
||||
InsertRowBefore,
|
||||
InsertColumnBefore,
|
||||
DeleteRow,
|
||||
DeleteColumn,
|
||||
DeleteRowBefore,
|
||||
DeleteColumnBefore,
|
||||
}
|
||||
|
||||
private class GridBrushProperties
|
||||
{
|
||||
public static readonly GUIContent floodFillPreviewLabel = EditorGUIUtility.TrTextContent("Show Flood Fill Preview", "Whether a preview is shown while painting a Tilemap when Flood Fill mode is enabled");
|
||||
public static readonly string floodFillPreviewEditorPref = "GridBrush.EnableFloodFillPreview";
|
||||
}
|
||||
|
||||
/// <summary>The GridBrush that is the target for this editor.</summary>
|
||||
public GridBrush brush { get { return target as GridBrush; } }
|
||||
private int m_LastPreviewRefreshHash;
|
||||
|
||||
// These are used to clean out previews that happened on previous update
|
||||
private GridLayout m_LastGrid;
|
||||
private GameObject m_LastBrushTarget;
|
||||
private BoundsInt? m_LastBounds;
|
||||
private GridBrushBase.Tool? m_LastTool;
|
||||
|
||||
// These are used to handle selection in Selection Inspector
|
||||
private TileBase[] m_SelectionTiles;
|
||||
private Color[] m_SelectionColors;
|
||||
private Matrix4x4[] m_SelectionMatrices;
|
||||
private TileFlags[] m_SelectionFlagsArray;
|
||||
private Sprite[] m_SelectionSprites;
|
||||
private Tile.ColliderType[] m_SelectionColliderTypes;
|
||||
private int selectionCellCount => GridSelection.position.size.x * GridSelection.position.size.y * GridSelection.position.size.z;
|
||||
|
||||
// These are used to handle transform manipulation on the Tilemap
|
||||
private int m_SelectedTransformTool = 0;
|
||||
|
||||
// These are used to handle insert/delete cells on the Tilemap
|
||||
private int m_CellCount = 1;
|
||||
private ModifyCells m_ModifyCells = ModifyCells.InsertRow;
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
Undo.undoRedoPerformed += ClearLastPreview;
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
Undo.undoRedoPerformed -= ClearLastPreview;
|
||||
ClearLastPreview();
|
||||
}
|
||||
|
||||
private void ClearLastPreview()
|
||||
{
|
||||
ClearPreview();
|
||||
m_LastPreviewRefreshHash = 0;
|
||||
}
|
||||
|
||||
/// <summary>Callback for painting the GUI for the GridBrush in the Scene View.</summary>
|
||||
/// <param name="gridLayout">Grid that the brush is being used on.</param>
|
||||
/// <param name="brushTarget">Target of the GridBrushBase::ref::Tool operation. By default the currently selected GameObject.</param>
|
||||
/// <param name="position">Current selected location of the brush.</param>
|
||||
/// <param name="tool">Current GridBrushBase::ref::Tool selected.</param>
|
||||
/// <param name="executing">Whether brush is being used.</param>
|
||||
public override void OnPaintSceneGUI(GridLayout gridLayout, GameObject brushTarget, BoundsInt position, GridBrushBase.Tool tool, bool executing)
|
||||
{
|
||||
BoundsInt gizmoRect = position;
|
||||
bool refreshPreviews = false;
|
||||
if (Event.current.type == EventType.Layout)
|
||||
{
|
||||
int newPreviewRefreshHash = GetHash(gridLayout, brushTarget, position, tool, brush);
|
||||
refreshPreviews = newPreviewRefreshHash != m_LastPreviewRefreshHash;
|
||||
if (refreshPreviews)
|
||||
m_LastPreviewRefreshHash = newPreviewRefreshHash;
|
||||
}
|
||||
if (tool == GridBrushBase.Tool.Move)
|
||||
{
|
||||
if (refreshPreviews && executing)
|
||||
{
|
||||
ClearPreview();
|
||||
PaintPreview(gridLayout, brushTarget, position.min);
|
||||
}
|
||||
}
|
||||
else if (tool == GridBrushBase.Tool.Paint || tool == GridBrushBase.Tool.Erase)
|
||||
{
|
||||
if (refreshPreviews)
|
||||
{
|
||||
ClearPreview();
|
||||
if (tool != GridBrushBase.Tool.Erase)
|
||||
{
|
||||
PaintPreview(gridLayout, brushTarget, position.min);
|
||||
}
|
||||
}
|
||||
gizmoRect = new BoundsInt(position.min - brush.pivot, brush.size);
|
||||
}
|
||||
else if (tool == GridBrushBase.Tool.Box)
|
||||
{
|
||||
if (refreshPreviews)
|
||||
{
|
||||
ClearPreview();
|
||||
BoxFillPreview(gridLayout, brushTarget, position);
|
||||
}
|
||||
}
|
||||
else if (tool == GridBrushBase.Tool.FloodFill)
|
||||
{
|
||||
if (refreshPreviews)
|
||||
{
|
||||
if (CheckFloodFillPreview(gridLayout, brushTarget, position.min))
|
||||
ClearPreview();
|
||||
FloodFillPreview(gridLayout, brushTarget, position.min);
|
||||
}
|
||||
}
|
||||
|
||||
base.OnPaintSceneGUI(gridLayout, brushTarget, gizmoRect, tool, executing);
|
||||
}
|
||||
|
||||
private void UpdateSelection(Tilemap tilemap)
|
||||
{
|
||||
var selection = GridSelection.position;
|
||||
var cellCount = selectionCellCount;
|
||||
if (m_SelectionTiles == null || m_SelectionTiles.Length != selectionCellCount)
|
||||
{
|
||||
m_SelectionTiles = new TileBase[cellCount];
|
||||
m_SelectionColors = new Color[cellCount];
|
||||
m_SelectionMatrices = new Matrix4x4[cellCount];
|
||||
m_SelectionFlagsArray = new TileFlags[cellCount];
|
||||
m_SelectionSprites = new Sprite[cellCount];
|
||||
m_SelectionColliderTypes = new Tile.ColliderType[cellCount];
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
foreach (var p in selection.allPositionsWithin)
|
||||
{
|
||||
m_SelectionTiles[index] = tilemap.GetTile(p);
|
||||
m_SelectionColors[index] = tilemap.GetColor(p);
|
||||
m_SelectionMatrices[index] = tilemap.GetTransformMatrix(p);
|
||||
m_SelectionFlagsArray[index] = tilemap.GetTileFlags(p);
|
||||
m_SelectionSprites[index] = tilemap.GetSprite(p);
|
||||
m_SelectionColliderTypes[index] = tilemap.GetColliderType(p);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Callback for drawing the Inspector GUI when there is an active GridSelection made in a Tilemap.</summary>
|
||||
public override void OnSelectionInspectorGUI()
|
||||
{
|
||||
BoundsInt selection = GridSelection.position;
|
||||
Tilemap tilemap = GridSelection.target.GetComponent<Tilemap>();
|
||||
|
||||
int cellCount = selection.size.x * selection.size.y * selection.size.z;
|
||||
if (tilemap != null && cellCount > 0)
|
||||
{
|
||||
base.OnSelectionInspectorGUI();
|
||||
|
||||
if (!EditorGUIUtility.editingTextField
|
||||
&& Event.current.type == EventType.KeyDown
|
||||
&& (Event.current.keyCode == KeyCode.Delete
|
||||
|| Event.current.keyCode == KeyCode.Backspace))
|
||||
{
|
||||
DeleteSelection(tilemap, selection);
|
||||
Event.current.Use();
|
||||
}
|
||||
|
||||
GUILayout.Space(10f);
|
||||
|
||||
EditorGUILayout.LabelField(Styles.gridSelectionPropertiesLabel, EditorStyles.boldLabel);
|
||||
|
||||
UpdateSelection(tilemap);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.showMixedValue = m_SelectionTiles.Any(tile => tile != m_SelectionTiles.First());
|
||||
var position = new Vector3Int(selection.xMin, selection.yMin, selection.zMin);
|
||||
TileBase newTile = EditorGUILayout.ObjectField(Styles.tileLabel, tilemap.GetTile(position), typeof(TileBase), false) as TileBase;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(tilemap, "Edit Tilemap");
|
||||
foreach (var p in selection.allPositionsWithin)
|
||||
tilemap.SetTile(p, newTile);
|
||||
}
|
||||
|
||||
using (new EditorGUI.DisabledScope(true))
|
||||
{
|
||||
EditorGUI.showMixedValue = m_SelectionSprites.Any(sprite => sprite != m_SelectionSprites.First());
|
||||
EditorGUILayout.ObjectField(Styles.spriteLabel, m_SelectionSprites[0], typeof(Sprite), false, GUILayout.Height(EditorGUI.kSingleLineHeight));
|
||||
}
|
||||
|
||||
bool colorFlagsAllEqual = m_SelectionFlagsArray.All(flags => (flags & TileFlags.LockColor) == (m_SelectionFlagsArray.First() & TileFlags.LockColor));
|
||||
using (new EditorGUI.DisabledScope(!colorFlagsAllEqual || (m_SelectionFlagsArray[0] & TileFlags.LockColor) != 0))
|
||||
{
|
||||
EditorGUI.showMixedValue = m_SelectionColors.Any(color => color != m_SelectionColors.First());
|
||||
EditorGUI.BeginChangeCheck();
|
||||
Color newColor = EditorGUILayout.ColorField(Styles.colorLabel, m_SelectionColors[0]);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(tilemap, "Edit Tilemap");
|
||||
foreach (var p in selection.allPositionsWithin)
|
||||
tilemap.SetColor(p, newColor);
|
||||
}
|
||||
}
|
||||
|
||||
using (new EditorGUI.DisabledScope(true))
|
||||
{
|
||||
EditorGUI.showMixedValue = m_SelectionColliderTypes.Any(colliderType => colliderType != m_SelectionColliderTypes.First());
|
||||
EditorGUILayout.EnumPopup(Styles.colliderTypeLabel, m_SelectionColliderTypes[0]);
|
||||
}
|
||||
|
||||
bool transformFlagsAllEqual = m_SelectionFlagsArray.All(flags => (flags & TileFlags.LockTransform) == (m_SelectionFlagsArray.First() & TileFlags.LockTransform));
|
||||
using (new EditorGUI.DisabledScope(!transformFlagsAllEqual || (m_SelectionFlagsArray[0] & TileFlags.LockTransform) != 0))
|
||||
{
|
||||
EditorGUI.showMixedValue = m_SelectionMatrices.Any(matrix => matrix != m_SelectionMatrices.First());
|
||||
EditorGUI.BeginChangeCheck();
|
||||
Matrix4x4 newTransformMatrix = TileEditor.TransformMatrixOnGUI(m_SelectionMatrices[0]);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(tilemap, "Edit Tilemap");
|
||||
foreach (var p in selection.allPositionsWithin)
|
||||
tilemap.SetTransformMatrix(p, newTransformMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
using (new EditorGUI.DisabledScope(true))
|
||||
{
|
||||
EditorGUI.showMixedValue = !colorFlagsAllEqual;
|
||||
EditorGUILayout.Toggle(Styles.lockColorLabel, (m_SelectionFlagsArray[0] & TileFlags.LockColor) != 0);
|
||||
EditorGUI.showMixedValue = !transformFlagsAllEqual;
|
||||
EditorGUILayout.Toggle(Styles.lockTransformLabel, (m_SelectionFlagsArray[0] & TileFlags.LockTransform) != 0);
|
||||
}
|
||||
|
||||
EditorGUI.showMixedValue = false;
|
||||
|
||||
if (GUILayout.Button(Styles.deleteSelectionLabel))
|
||||
{
|
||||
DeleteSelection(tilemap, selection);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField(Styles.modifyTilemapLabel, EditorStyles.boldLabel);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
m_SelectedTransformTool = GUILayout.Toolbar(m_SelectedTransformTool, Styles.selectionTools);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
SceneView.RepaintAll();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
m_ModifyCells = (ModifyCells)EditorGUILayout.EnumPopup(m_ModifyCells);
|
||||
m_CellCount = EditorGUILayout.IntField(m_CellCount);
|
||||
if (GUILayout.Button(Styles.modifyLabel))
|
||||
{
|
||||
RegisterUndoForTilemap(tilemap, Enum.GetName(typeof(ModifyCells), m_ModifyCells));
|
||||
switch (m_ModifyCells)
|
||||
{
|
||||
case ModifyCells.InsertRow:
|
||||
{
|
||||
tilemap.InsertCells(GridSelection.position.position, 0, m_CellCount, 0);
|
||||
break;
|
||||
}
|
||||
case ModifyCells.InsertRowBefore:
|
||||
{
|
||||
tilemap.InsertCells(GridSelection.position.position, 0, -m_CellCount, 0);
|
||||
break;
|
||||
}
|
||||
case ModifyCells.InsertColumn:
|
||||
{
|
||||
tilemap.InsertCells(GridSelection.position.position, m_CellCount, 0, 0);
|
||||
break;
|
||||
}
|
||||
case ModifyCells.InsertColumnBefore:
|
||||
{
|
||||
tilemap.InsertCells(GridSelection.position.position, -m_CellCount, 0, 0);
|
||||
break;
|
||||
}
|
||||
case ModifyCells.DeleteRow:
|
||||
{
|
||||
tilemap.DeleteCells(GridSelection.position.position, 0, m_CellCount, 0);
|
||||
break;
|
||||
}
|
||||
case ModifyCells.DeleteRowBefore:
|
||||
{
|
||||
tilemap.DeleteCells(GridSelection.position.position, 0, -m_CellCount, 0);
|
||||
break;
|
||||
}
|
||||
case ModifyCells.DeleteColumn:
|
||||
{
|
||||
tilemap.DeleteCells(GridSelection.position.position, m_CellCount, 0, 0);
|
||||
break;
|
||||
}
|
||||
case ModifyCells.DeleteColumnBefore:
|
||||
{
|
||||
tilemap.DeleteCells(GridSelection.position.position, -m_CellCount, 0, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Callback for painting custom gizmos when there is an active GridSelection made in a GridLayout.</summary>
|
||||
/// <param name="gridLayout">Grid that the brush is being used on.</param>
|
||||
/// <param name="brushTarget">Target of the GridBrushBase::ref::Tool operation. By default the currently selected GameObject.</param>
|
||||
/// <remarks>Override this to show custom gizmos for the current selection.</remarks>
|
||||
public override void OnSelectionSceneGUI(GridLayout gridLayout, GameObject brushTarget)
|
||||
{
|
||||
var tilemap = brushTarget.GetComponent<Tilemap>();
|
||||
if (tilemap == null)
|
||||
return;
|
||||
|
||||
UpdateSelection(tilemap);
|
||||
bool transformFlagsAllEqual = m_SelectionFlagsArray.All(flags => (flags & TileFlags.LockTransform) == (m_SelectionFlagsArray.First() & TileFlags.LockTransform));
|
||||
if (!transformFlagsAllEqual || (m_SelectionFlagsArray[0] & TileFlags.LockTransform) != 0)
|
||||
return;
|
||||
|
||||
var transformMatrix = m_SelectionMatrices[0];
|
||||
var p = (Vector3)transformMatrix.GetColumn(3);
|
||||
var r = transformMatrix.rotation;
|
||||
var s = transformMatrix.lossyScale;
|
||||
Vector3 selectionPosition = GridSelection.position.position;
|
||||
if (selectionCellCount > 1)
|
||||
{
|
||||
selectionPosition.x = GridSelection.position.center.x;
|
||||
selectionPosition.y = GridSelection.position.center.y;
|
||||
}
|
||||
selectionPosition += tilemap.tileAnchor;
|
||||
var gizmoPosition = tilemap.LocalToWorld(tilemap.CellToLocalInterpolated(selectionPosition + p));
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
switch (m_SelectedTransformTool)
|
||||
{
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
{
|
||||
gizmoPosition = Handles.PositionHandle(gizmoPosition, r);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
{
|
||||
r = Handles.RotationHandle(r, gizmoPosition);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
{
|
||||
s = Handles.ScaleHandle(s, gizmoPosition, r, HandleUtility.GetHandleSize(gizmoPosition));
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
{
|
||||
Handles.TransformHandle(ref gizmoPosition, ref r, ref s);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
RegisterUndo(brushTarget, GridBrushBase.Tool.Select);
|
||||
var offset = tilemap.WorldToLocal(tilemap.LocalToCellInterpolated(gizmoPosition)) - selectionPosition;
|
||||
foreach (var position in GridSelection.position.allPositionsWithin)
|
||||
{
|
||||
if (tilemap.HasTile(position))
|
||||
tilemap.SetTransformMatrix(position, Matrix4x4.TRS(offset, r, s));
|
||||
}
|
||||
InspectorWindow.RefreshInspectors();
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteSelection(Tilemap tilemap, BoundsInt selection)
|
||||
{
|
||||
if (tilemap == null)
|
||||
return;
|
||||
|
||||
RegisterUndo(tilemap.gameObject, GridBrushBase.Tool.Erase);
|
||||
brush.BoxErase(tilemap.layoutGrid, tilemap.gameObject, selection);
|
||||
}
|
||||
|
||||
/// <summary> Callback when the mouse cursor leaves and editing area. </summary>
|
||||
/// <remarks> Cleans up brush previews. </remarks>
|
||||
public override void OnMouseLeave()
|
||||
{
|
||||
ClearPreview();
|
||||
}
|
||||
|
||||
/// <summary> Callback when the GridBrush Tool is deactivated. </summary>
|
||||
/// <param name="tool">GridBrush Tool that is deactivated.</param>
|
||||
/// <remarks> Cleans up brush previews. </remarks>
|
||||
public override void OnToolDeactivated(GridBrushBase.Tool tool)
|
||||
{
|
||||
ClearPreview();
|
||||
}
|
||||
|
||||
/// <summary> Whether the GridBrush can change Z Position. </summary>
|
||||
public override bool canChangeZPosition
|
||||
{
|
||||
get { return brush.canChangeZPosition; }
|
||||
set { brush.canChangeZPosition = value; }
|
||||
}
|
||||
|
||||
/// <summary>Callback for registering an Undo action before the GridBrushBase does the current GridBrushBase::ref::Tool action.</summary>
|
||||
/// <param name="brushTarget">Target of the GridBrushBase::ref::Tool operation. By default the currently selected GameObject.</param>
|
||||
/// <param name="tool">Current GridBrushBase::ref::Tool selected.</param>
|
||||
/// <remarks>Implement this for any special Undo behaviours when a brush is used.</remarks>
|
||||
public override void RegisterUndo(GameObject brushTarget, GridBrushBase.Tool tool)
|
||||
{
|
||||
if (brushTarget != null)
|
||||
{
|
||||
var tilemap = brushTarget.GetComponent<Tilemap>();
|
||||
if (tilemap != null)
|
||||
{
|
||||
RegisterUndoForTilemap(tilemap, tool.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Returns all valid targets that the brush can edit.</summary>
|
||||
/// <remarks>Valid targets for the GridBrush are any GameObjects with a Tilemap component.</remarks>
|
||||
public override GameObject[] validTargets
|
||||
{
|
||||
get
|
||||
{
|
||||
StageHandle currentStageHandle = StageUtility.GetCurrentStageHandle();
|
||||
return currentStageHandle.FindComponentsOfType<Tilemap>().Where(x => x.gameObject.scene.isLoaded
|
||||
&& x.gameObject.activeInHierarchy).Select(x => x.gameObject).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Paints preview data into a cell of a grid given the coordinates of the cell.</summary>
|
||||
/// <param name="gridLayout">Grid to paint data to.</param>
|
||||
/// <param name="brushTarget">Target of the paint operation. By default the currently selected GameObject.</param>
|
||||
/// <param name="position">The coordinates of the cell to paint data to.</param>
|
||||
/// <remarks>The grid brush will paint preview sprites in its brush cells onto an associated Tilemap. This will not instantiate objects associated with the painted tiles.</remarks>
|
||||
public virtual void PaintPreview(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
|
||||
{
|
||||
Vector3Int min = position - brush.pivot;
|
||||
Vector3Int max = min + brush.size;
|
||||
BoundsInt bounds = new BoundsInt(min, max - min);
|
||||
|
||||
if (brushTarget != null)
|
||||
{
|
||||
Tilemap map = brushTarget.GetComponent<Tilemap>();
|
||||
foreach (Vector3Int location in bounds.allPositionsWithin)
|
||||
{
|
||||
Vector3Int brushPosition = location - min;
|
||||
GridBrush.BrushCell cell = brush.cells[brush.GetCellIndex(brushPosition)];
|
||||
if (cell.tile != null && map != null)
|
||||
{
|
||||
SetTilemapPreviewCell(map, location, cell.tile, cell.matrix, cell.color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_LastGrid = gridLayout;
|
||||
m_LastBounds = bounds;
|
||||
m_LastBrushTarget = brushTarget;
|
||||
m_LastTool = GridBrushBase.Tool.Paint;
|
||||
}
|
||||
|
||||
/// <summary>Does a preview of what happens when a GridBrush.BoxFill is done with the same parameters.</summary>
|
||||
/// <param name="gridLayout">Grid to box fill data to.</param>
|
||||
/// <param name="brushTarget">Target of box fill operation. By default the currently selected GameObject.</param>
|
||||
/// <param name="position">The bounds to box fill data to.</param>
|
||||
public virtual void BoxFillPreview(GridLayout gridLayout, GameObject brushTarget, BoundsInt position)
|
||||
{
|
||||
if (brushTarget != null)
|
||||
{
|
||||
Tilemap map = brushTarget.GetComponent<Tilemap>();
|
||||
if (map != null)
|
||||
{
|
||||
foreach (Vector3Int location in position.allPositionsWithin)
|
||||
{
|
||||
Vector3Int local = location - position.min;
|
||||
GridBrush.BrushCell cell = brush.cells[brush.GetCellIndexWrapAround(local.x, local.y, local.z)];
|
||||
if (cell.tile != null)
|
||||
{
|
||||
SetTilemapPreviewCell(map, location, cell.tile, cell.matrix, cell.color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_LastGrid = gridLayout;
|
||||
m_LastBounds = position;
|
||||
m_LastBrushTarget = brushTarget;
|
||||
m_LastTool = GridBrushBase.Tool.Box;
|
||||
}
|
||||
|
||||
private bool CheckFloodFillPreview(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
|
||||
{
|
||||
if (m_LastGrid == gridLayout
|
||||
&& m_LastBrushTarget == brushTarget
|
||||
&& m_LastBounds.HasValue && m_LastBounds.Value.Contains(position)
|
||||
&& brushTarget != null && brush.cellCount > 0)
|
||||
{
|
||||
Tilemap map = brushTarget.GetComponent<Tilemap>();
|
||||
if (map != null)
|
||||
{
|
||||
GridBrush.BrushCell cell = brush.cells[0];
|
||||
if (cell.tile == map.GetEditorPreviewTile(position))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Does a preview of what happens when a GridBrush.FloodFill is done with the same parameters.</summary>
|
||||
/// <param name="gridLayout">Grid to paint data to.</param>
|
||||
/// <param name="brushTarget">Target of the flood fill operation. By default the currently selected GameObject.</param>
|
||||
/// <param name="position">The coordinates of the cell to flood fill data to.</param>
|
||||
public virtual void FloodFillPreview(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
|
||||
{
|
||||
// This can be quite taxing on a large Tilemap, so users can choose whether to do this or not
|
||||
if (!EditorPrefs.GetBool(GridBrushProperties.floodFillPreviewEditorPref, true))
|
||||
return;
|
||||
|
||||
var bounds = new BoundsInt(position, Vector3Int.one);
|
||||
if (brushTarget != null && brush.cellCount > 0)
|
||||
{
|
||||
Tilemap map = brushTarget.GetComponent<Tilemap>();
|
||||
if (map != null)
|
||||
{
|
||||
GridBrush.BrushCell cell = brush.cells[0];
|
||||
map.EditorPreviewFloodFill(position, cell.tile);
|
||||
// Set floodfill bounds as tilemap bounds
|
||||
bounds.min = map.origin;
|
||||
bounds.max = map.origin + map.size;
|
||||
}
|
||||
}
|
||||
|
||||
m_LastGrid = gridLayout;
|
||||
m_LastBounds = bounds;
|
||||
m_LastBrushTarget = brushTarget;
|
||||
m_LastTool = GridBrushBase.Tool.FloodFill;
|
||||
}
|
||||
|
||||
[SettingsProvider]
|
||||
internal static SettingsProvider CreateSettingsProvider()
|
||||
{
|
||||
var settingsProvider = new SettingsProvider("Preferences/2D/Grid Brush", SettingsScope.User, SettingsProvider.GetSearchKeywordsFromGUIContentProperties<GridBrushProperties>()) {
|
||||
guiHandler = searchContext =>
|
||||
{
|
||||
PreferencesGUI();
|
||||
}
|
||||
};
|
||||
return settingsProvider;
|
||||
}
|
||||
|
||||
private static void PreferencesGUI()
|
||||
{
|
||||
using (new SettingsWindow.GUIScope())
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var val = EditorGUILayout.Toggle(GridBrushProperties.floodFillPreviewLabel, EditorPrefs.GetBool(GridBrushProperties.floodFillPreviewEditorPref, true));
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
EditorPrefs.SetBool(GridBrushProperties.floodFillPreviewEditorPref, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Clears any preview drawn previously by the GridBrushEditor.</summary>
|
||||
public virtual void ClearPreview()
|
||||
{
|
||||
if (m_LastGrid == null || m_LastBounds == null || m_LastBrushTarget == null || m_LastTool == null)
|
||||
return;
|
||||
|
||||
Tilemap map = m_LastBrushTarget.GetComponent<Tilemap>();
|
||||
if (map != null)
|
||||
{
|
||||
switch (m_LastTool)
|
||||
{
|
||||
case GridBrushBase.Tool.FloodFill:
|
||||
{
|
||||
map.ClearAllEditorPreviewTiles();
|
||||
break;
|
||||
}
|
||||
case GridBrushBase.Tool.Box:
|
||||
{
|
||||
Vector3Int min = m_LastBounds.Value.position;
|
||||
Vector3Int max = min + m_LastBounds.Value.size;
|
||||
BoundsInt bounds = new BoundsInt(min, max - min);
|
||||
foreach (Vector3Int location in bounds.allPositionsWithin)
|
||||
{
|
||||
ClearTilemapPreview(map, location);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GridBrushBase.Tool.Paint:
|
||||
{
|
||||
BoundsInt bounds = m_LastBounds.Value;
|
||||
foreach (Vector3Int location in bounds.allPositionsWithin)
|
||||
{
|
||||
ClearTilemapPreview(map, location);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_LastBrushTarget = null;
|
||||
m_LastGrid = null;
|
||||
m_LastBounds = null;
|
||||
m_LastTool = null;
|
||||
}
|
||||
|
||||
private void RegisterUndoForTilemap(Tilemap tilemap, string undoMessage)
|
||||
{
|
||||
Undo.RegisterCompleteObjectUndo(new Object[] { tilemap, tilemap.gameObject }, undoMessage);
|
||||
}
|
||||
|
||||
private static void SetTilemapPreviewCell(Tilemap map, Vector3Int location, TileBase tile, Matrix4x4 transformMatrix, Color color)
|
||||
{
|
||||
if (map == null)
|
||||
return;
|
||||
map.SetEditorPreviewTile(location, tile);
|
||||
map.SetEditorPreviewTransformMatrix(location, transformMatrix);
|
||||
map.SetEditorPreviewColor(location, color);
|
||||
}
|
||||
|
||||
private static void ClearTilemapPreview(Tilemap map, Vector3Int location)
|
||||
{
|
||||
if (map == null)
|
||||
return;
|
||||
map.SetEditorPreviewTile(location, null);
|
||||
map.SetEditorPreviewTransformMatrix(location, Matrix4x4.identity);
|
||||
map.SetEditorPreviewColor(location, Color.white);
|
||||
}
|
||||
|
||||
private static int GetHash(GridLayout gridLayout, GameObject brushTarget, BoundsInt position, GridBrushBase.Tool tool, GridBrush brush)
|
||||
{
|
||||
int hash = 0;
|
||||
unchecked
|
||||
{
|
||||
hash = hash * 33 + (gridLayout != null ? gridLayout.GetHashCode() : 0);
|
||||
hash = hash * 33 + (brushTarget != null ? brushTarget.GetHashCode() : 0);
|
||||
hash = hash * 33 + position.GetHashCode();
|
||||
hash = hash * 33 + tool.GetHashCode();
|
||||
hash = hash * 33 + (brush != null ? brush.GetHashCode() : 0);
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,158 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Scripting.APIUpdating;
|
||||
using UnityEngine.Tilemaps;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
/// <summary>Base class for Grid Brush Editor.</summary>
|
||||
[MovedFrom(true, "UnityEditor", "UnityEditor")]
|
||||
[CustomEditor(typeof(GridBrushBase))]
|
||||
public class GridBrushEditorBase : Editor
|
||||
{
|
||||
private static class Styles
|
||||
{
|
||||
public static readonly Color activeColor = new Color(1f, .5f, 0f);
|
||||
public static readonly Color executingColor = new Color(1f, .75f, 0.25f);
|
||||
}
|
||||
|
||||
/// <summary>Checks if the Brush allows the changing of Z Position.</summary>
|
||||
/// <returns>Whether the Brush can change Z Position.</returns>
|
||||
public virtual bool canChangeZPosition
|
||||
{
|
||||
get { return true; }
|
||||
set {}
|
||||
}
|
||||
|
||||
/// <summary>Callback for painting the GUI for the GridBrush in the Scene view.</summary>
|
||||
/// <param name="gridLayout">Grid that the brush is being used on.</param>
|
||||
/// <param name="brushTarget">Target of the GridBrushBase::ref::Tool operation. By default the currently selected GameObject.</param>
|
||||
/// <param name="position">Current selected location of the brush.</param>
|
||||
/// <param name="tool">Current GridBrushBase::ref::Tool selected.</param>
|
||||
/// <param name="executing">Whether is brush is being used.</param>
|
||||
/// <remarks>Implement this for any special behaviours when the GridBrush is used on the Scene View.</remarks>
|
||||
public virtual void OnPaintSceneGUI(GridLayout gridLayout, GameObject brushTarget, BoundsInt position, GridBrushBase.Tool tool, bool executing)
|
||||
{
|
||||
OnPaintSceneGUIInternal(gridLayout, brushTarget, position, tool, executing);
|
||||
}
|
||||
|
||||
/// <summary>Callback for painting the inspector GUI for the GridBrush in the tilemap palette.</summary>
|
||||
/// <remarks>Implement this to have a custom editor in the tilemap palette for the GridBrush.</remarks>
|
||||
public virtual void OnPaintInspectorGUI()
|
||||
{
|
||||
OnInspectorGUI();
|
||||
}
|
||||
|
||||
/// <summary>Callback for drawing the Inspector GUI when there is an active GridSelection made in a GridLayout.</summary>
|
||||
/// <remarks>Override this to show custom Inspector GUI for the current selection.</remarks>
|
||||
public virtual void OnSelectionInspectorGUI() {}
|
||||
|
||||
/// <summary>Callback for painting custom gizmos when there is an active GridSelection made in a GridLayout.</summary>
|
||||
/// <param name="gridLayout">Grid that the brush is being used on.</param>
|
||||
/// <param name="brushTarget">Target of the GridBrushBase::ref::Tool operation. By default the currently selected GameObject.</param>
|
||||
/// <remarks>Override this to show custom gizmos for the current selection.</remarks>
|
||||
public virtual void OnSelectionSceneGUI(GridLayout gridLayout, GameObject brushTarget) {}
|
||||
|
||||
/// <summary>
|
||||
/// Callback for painting custom gizmos for the GridBrush for the brush target
|
||||
/// </summary>
|
||||
/// <param name="gridLayout">Grid that the brush is being used on.</param>
|
||||
/// <param name="brushTarget">Target of the GridBrushBase::ref::Tool operation. By default the currently selected GameObject.</param>
|
||||
/// <remarks>Override this to show custom gizmos for the brush target.</remarks>
|
||||
public virtual void OnSceneGUI(GridLayout gridLayout, GameObject brushTarget) {}
|
||||
|
||||
/// <summary>Callback when the mouse cursor leaves a paintable region.</summary>
|
||||
/// <remarks>Implement this for any custom behaviour when the mouse cursor leaves a paintable region.</remarks>
|
||||
public virtual void OnMouseLeave() {}
|
||||
|
||||
/// <summary>Callback when the mouse cursor enters a paintable region.</summary>
|
||||
/// <remarks>Implement this for any custom behaviour when the mouse cursor enters a paintable region.</remarks>
|
||||
public virtual void OnMouseEnter() {}
|
||||
|
||||
/// <summary>Callback when a GridBrushBase.Tool is activated.</summary>
|
||||
/// <param name="tool">Tool that is activated.</param>
|
||||
/// <remarks>Implement this for any special behaviours when a Tool is activated.</remarks>
|
||||
public virtual void OnToolActivated(GridBrushBase.Tool tool) {}
|
||||
|
||||
/// <summary>Callback when a GridBrushBase.Tool is deactivated.</summary>
|
||||
/// <param name="tool">Tool that is deactivated.</param>
|
||||
/// <remarks>Implement this for any special behaviours when a Tool is deactivated.</remarks>
|
||||
public virtual void OnToolDeactivated(GridBrushBase.Tool tool) {}
|
||||
|
||||
/// <summary>Callback for registering an Undo action before the GridBrushBase does the current GridBrushBase::ref::Tool action.</summary>
|
||||
/// <param name="brushTarget">Target of the GridBrushBase::ref::Tool operation. By default the currently selected GameObject.</param>
|
||||
/// <param name="tool">Current GridBrushBase::ref::Tool selected.</param>
|
||||
/// <remarks>Implement this for any special Undo behaviours when a brush is used.</remarks>
|
||||
public virtual void RegisterUndo(GameObject brushTarget, GridBrushBase.Tool tool) {}
|
||||
|
||||
/// <summary>Returns all valid targets that the brush can edit.</summary>
|
||||
public virtual GameObject[] validTargets { get { return null; } }
|
||||
|
||||
internal void OnEditStart(GridLayout gridLayout, GameObject brushTarget)
|
||||
{
|
||||
SetBufferSyncTile(brushTarget, true);
|
||||
}
|
||||
|
||||
internal void OnEditEnd(GridLayout gridLayout, GameObject brushTarget)
|
||||
{
|
||||
SetBufferSyncTile(brushTarget, false);
|
||||
}
|
||||
|
||||
private void SetBufferSyncTile(GameObject brushTarget, bool active)
|
||||
{
|
||||
if (brushTarget == null || !Tilemap.HasSyncTileCallback())
|
||||
return;
|
||||
|
||||
var tilemaps = brushTarget.GetComponentsInChildren<Tilemap>();
|
||||
foreach (var tilemap in tilemaps)
|
||||
{
|
||||
tilemap.bufferSyncTile = active;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void OnSceneGUIInternal(GridLayout gridLayout, GameObject brushTarget, BoundsInt position, GridBrushBase.Tool tool, bool executing)
|
||||
{
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
return;
|
||||
|
||||
if (tool == GridBrushBase.Tool.Select ||
|
||||
tool == GridBrushBase.Tool.Move)
|
||||
{
|
||||
if (GridSelection.active && !executing)
|
||||
{
|
||||
Color color = Styles.activeColor;
|
||||
GridEditorUtility.DrawGridMarquee(gridLayout, position, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void OnPaintSceneGUIInternal(GridLayout gridLayout, GameObject brushTarget, BoundsInt position, GridBrushBase.Tool tool, bool executing)
|
||||
{
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
return;
|
||||
|
||||
Color color = Color.white;
|
||||
if (tool == GridBrushBase.Tool.Pick && executing)
|
||||
color = Color.cyan;
|
||||
if (tool == GridBrushBase.Tool.Paint && executing)
|
||||
color = Color.yellow;
|
||||
|
||||
if (tool == GridBrushBase.Tool.Select ||
|
||||
tool == GridBrushBase.Tool.Move)
|
||||
{
|
||||
if (executing)
|
||||
color = Styles.executingColor;
|
||||
else if (GridSelection.active)
|
||||
color = Styles.activeColor;
|
||||
}
|
||||
|
||||
if (position.zMin != 0)
|
||||
{
|
||||
var zeroBounds = position;
|
||||
zeroBounds.z = 0;
|
||||
GridEditorUtility.DrawGridMarquee(gridLayout, zeroBounds, color);
|
||||
color = Color.blue;
|
||||
}
|
||||
GridEditorUtility.DrawGridMarquee(gridLayout, position, color);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
internal class GridBrushesDropdown : FlexibleMenu
|
||||
{
|
||||
public GridBrushesDropdown(IFlexibleMenuItemProvider itemProvider, int selectionIndex, FlexibleMenuModifyItemUI modifyItemUi, Action<int, object> itemClickedCallback, float minWidth)
|
||||
: base(itemProvider, selectionIndex, modifyItemUi, itemClickedCallback)
|
||||
{
|
||||
minTextWidth = minWidth;
|
||||
}
|
||||
|
||||
internal class MenuItemProvider : IFlexibleMenuItemProvider
|
||||
{
|
||||
public int Count()
|
||||
{
|
||||
return GridPaletteBrushes.brushes.Count;
|
||||
}
|
||||
|
||||
public object GetItem(int index)
|
||||
{
|
||||
return GridPaletteBrushes.brushes[index];
|
||||
}
|
||||
|
||||
public int Add(object obj)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Replace(int index, object newPresetObject)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Remove(int index)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public object Create()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Move(int index, int destIndex, bool insertAfterDestIndex)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string GetName(int index)
|
||||
{
|
||||
return GridPaletteBrushes.brushNames[index];
|
||||
}
|
||||
|
||||
public bool IsModificationAllowed(int index)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public int[] GetSeperatorIndices()
|
||||
{
|
||||
return new int[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,577 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Tilemaps;
|
||||
using Event = UnityEngine.Event;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
internal static class GridEditorUtility
|
||||
{
|
||||
private const int k_GridGizmoVertexCount = 32000;
|
||||
private const float k_GridGizmoDistanceFalloff = 50f;
|
||||
|
||||
public static Vector3Int ClampToGrid(Vector3Int p, Vector2Int origin, Vector2Int gridSize)
|
||||
{
|
||||
return new Vector3Int(
|
||||
Math.Max(Math.Min(p.x, origin.x + gridSize.x - 1), origin.x),
|
||||
Math.Max(Math.Min(p.y, origin.y + gridSize.y - 1), origin.y),
|
||||
p.z
|
||||
);
|
||||
}
|
||||
|
||||
public static Vector3 ScreenToLocal(Transform transform, Vector2 screenPosition)
|
||||
{
|
||||
return ScreenToLocal(transform, screenPosition, new Plane(transform.forward * -1f, transform.position));
|
||||
}
|
||||
|
||||
public static Vector3 ScreenToLocal(Transform transform, Vector2 screenPosition, Plane plane)
|
||||
{
|
||||
Ray ray;
|
||||
if (Camera.current.orthographic)
|
||||
{
|
||||
Vector2 screen = EditorGUIUtility.PointsToPixels(GUIClip.Unclip(screenPosition));
|
||||
screen.y = Screen.height - screen.y;
|
||||
Vector3 cameraWorldPoint = Camera.current.ScreenToWorldPoint(screen);
|
||||
ray = new Ray(cameraWorldPoint, Camera.current.transform.forward);
|
||||
}
|
||||
else
|
||||
{
|
||||
ray = HandleUtility.GUIPointToWorldRay(screenPosition);
|
||||
}
|
||||
|
||||
float result;
|
||||
plane.Raycast(ray, out result);
|
||||
Vector3 world = ray.GetPoint(result);
|
||||
return transform.InverseTransformPoint(world);
|
||||
}
|
||||
|
||||
public static RectInt GetMarqueeRect(Vector2Int p1, Vector2Int p2)
|
||||
{
|
||||
return new RectInt(
|
||||
Math.Min(p1.x, p2.x),
|
||||
Math.Min(p1.y, p2.y),
|
||||
Math.Abs(p2.x - p1.x) + 1,
|
||||
Math.Abs(p2.y - p1.y) + 1
|
||||
);
|
||||
}
|
||||
|
||||
public static BoundsInt GetMarqueeBounds(Vector3Int p1, Vector3Int p2)
|
||||
{
|
||||
return new BoundsInt(
|
||||
Math.Min(p1.x, p2.x),
|
||||
Math.Min(p1.y, p2.y),
|
||||
Math.Min(p1.z, p2.z),
|
||||
Math.Abs(p2.x - p1.x) + 1,
|
||||
Math.Abs(p2.y - p1.y) + 1,
|
||||
Math.Abs(p2.z - p1.z) + 1
|
||||
);
|
||||
}
|
||||
|
||||
// http://ericw.ca/notes/bresenhams-line-algorithm-in-csharp.html
|
||||
public static IEnumerable<Vector2Int> GetPointsOnLine(Vector2Int p1, Vector2Int p2)
|
||||
{
|
||||
int x0 = p1.x;
|
||||
int y0 = p1.y;
|
||||
int x1 = p2.x;
|
||||
int y1 = p2.y;
|
||||
|
||||
bool steep = Math.Abs(y1 - y0) > Math.Abs(x1 - x0);
|
||||
if (steep)
|
||||
{
|
||||
int t;
|
||||
t = x0; // swap x0 and y0
|
||||
x0 = y0;
|
||||
y0 = t;
|
||||
t = x1; // swap x1 and y1
|
||||
x1 = y1;
|
||||
y1 = t;
|
||||
}
|
||||
if (x0 > x1)
|
||||
{
|
||||
int t;
|
||||
t = x0; // swap x0 and x1
|
||||
x0 = x1;
|
||||
x1 = t;
|
||||
t = y0; // swap y0 and y1
|
||||
y0 = y1;
|
||||
y1 = t;
|
||||
}
|
||||
int dx = x1 - x0;
|
||||
int dy = Math.Abs(y1 - y0);
|
||||
int error = dx / 2;
|
||||
int ystep = (y0 < y1) ? 1 : -1;
|
||||
int y = y0;
|
||||
for (int x = x0; x <= x1; x++)
|
||||
{
|
||||
yield return new Vector2Int((steep ? y : x), (steep ? x : y));
|
||||
error = error - dy;
|
||||
if (error < 0)
|
||||
{
|
||||
y += ystep;
|
||||
error += dx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawBatchedHorizontalLine(float x1, float x2, float y)
|
||||
{
|
||||
GL.Vertex3(x1, y, 0f);
|
||||
GL.Vertex3(x2, y, 0f);
|
||||
GL.Vertex3(x2, y + 1, 0f);
|
||||
GL.Vertex3(x1, y + 1, 0f);
|
||||
}
|
||||
|
||||
public static void DrawBatchedVerticalLine(float y1, float y2, float x)
|
||||
{
|
||||
GL.Vertex3(x, y1, 0f);
|
||||
GL.Vertex3(x, y2, 0f);
|
||||
GL.Vertex3(x + 1, y2, 0f);
|
||||
GL.Vertex3(x + 1, y1, 0f);
|
||||
}
|
||||
|
||||
public static void DrawBatchedLine(Vector3 p1, Vector3 p2)
|
||||
{
|
||||
GL.Vertex3(p1.x, p1.y, p1.z);
|
||||
GL.Vertex3(p2.x, p2.y, p2.z);
|
||||
}
|
||||
|
||||
public static void DrawLine(Vector2 p1, Vector2 p2, Color color)
|
||||
{
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
return;
|
||||
|
||||
HandleUtility.ApplyWireMaterial();
|
||||
GL.PushMatrix();
|
||||
GL.MultMatrix(GUI.matrix);
|
||||
GL.Begin(GL.LINES);
|
||||
GL.Color(color);
|
||||
DrawBatchedLine(p1, p2);
|
||||
GL.End();
|
||||
GL.PopMatrix();
|
||||
}
|
||||
|
||||
public static void DrawBox(Rect r, Color color)
|
||||
{
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
return;
|
||||
|
||||
HandleUtility.ApplyWireMaterial();
|
||||
GL.PushMatrix();
|
||||
GL.MultMatrix(GUI.matrix);
|
||||
GL.Begin(GL.LINES);
|
||||
GL.Color(color);
|
||||
DrawBatchedLine(new Vector3(r.xMin, r.yMin, 0f), new Vector3(r.xMax, r.yMin, 0f));
|
||||
DrawBatchedLine(new Vector3(r.xMax, r.yMin, 0f), new Vector3(r.xMax, r.yMax, 0f));
|
||||
DrawBatchedLine(new Vector3(r.xMax, r.yMax, 0f), new Vector3(r.xMin, r.yMax, 0f));
|
||||
DrawBatchedLine(new Vector3(r.xMin, r.yMax, 0f), new Vector3(r.xMin, r.yMin, 0f));
|
||||
GL.End();
|
||||
GL.PopMatrix();
|
||||
}
|
||||
|
||||
public static void DrawFilledBox(Rect r, Color color)
|
||||
{
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
return;
|
||||
|
||||
HandleUtility.ApplyWireMaterial();
|
||||
GL.PushMatrix();
|
||||
GL.MultMatrix(GUI.matrix);
|
||||
GL.Begin(GL.QUADS);
|
||||
GL.Color(color);
|
||||
GL.Vertex3(r.xMin, r.yMin, 0f);
|
||||
GL.Vertex3(r.xMax, r.yMin, 0f);
|
||||
GL.Vertex3(r.xMax, r.yMax, 0f);
|
||||
GL.Vertex3(r.xMin, r.yMax, 0f);
|
||||
GL.End();
|
||||
GL.PopMatrix();
|
||||
}
|
||||
|
||||
public static void DrawGridMarquee(GridLayout gridLayout, BoundsInt area, Color color)
|
||||
{
|
||||
switch (gridLayout.cellLayout)
|
||||
{
|
||||
case GridLayout.CellLayout.Hexagon:
|
||||
DrawSelectedHexGridArea(gridLayout, area, color);
|
||||
break;
|
||||
case GridLayout.CellLayout.Isometric:
|
||||
case GridLayout.CellLayout.IsometricZAsY:
|
||||
case GridLayout.CellLayout.Rectangle:
|
||||
var cellStride = gridLayout.cellSize + gridLayout.cellGap;
|
||||
var cellGap = Vector3.one;
|
||||
if (!Mathf.Approximately(cellStride.x, 0f))
|
||||
{
|
||||
cellGap.x = gridLayout.cellSize.x / cellStride.x;
|
||||
}
|
||||
if (!Mathf.Approximately(cellStride.y, 0f))
|
||||
{
|
||||
cellGap.y = gridLayout.cellSize.y / cellStride.y;
|
||||
}
|
||||
|
||||
Vector3[] cellLocals =
|
||||
{
|
||||
gridLayout.CellToLocal(new Vector3Int(area.xMin, area.yMin, area.zMin)),
|
||||
gridLayout.CellToLocalInterpolated(new Vector3(area.xMax - 1 + cellGap.x, area.yMin, area.zMin)),
|
||||
gridLayout.CellToLocalInterpolated(new Vector3(area.xMax - 1 + cellGap.x, area.yMax - 1 + cellGap.y, area.zMin)),
|
||||
gridLayout.CellToLocalInterpolated(new Vector3(area.xMin, area.yMax - 1 + cellGap.y, area.zMin))
|
||||
};
|
||||
|
||||
HandleUtility.ApplyWireMaterial();
|
||||
GL.PushMatrix();
|
||||
GL.MultMatrix(gridLayout.transform.localToWorldMatrix);
|
||||
GL.Begin(GL.LINES);
|
||||
GL.Color(color);
|
||||
int i = 0;
|
||||
|
||||
for (int j = cellLocals.Length - 1; i < cellLocals.Length; j = i++)
|
||||
DrawBatchedLine(cellLocals[j], cellLocals[i]);
|
||||
|
||||
GL.End();
|
||||
GL.PopMatrix();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawSelectedHexGridArea(GridLayout gridLayout, BoundsInt area, Color color)
|
||||
{
|
||||
int requiredVertices = 4 * (area.size.x + area.size.y) - 2;
|
||||
if (requiredVertices < 0)
|
||||
return;
|
||||
Vector3[] cellLocals = new Vector3[requiredVertices];
|
||||
int horizontalCount = area.size.x * 2;
|
||||
int verticalCount = area.size.y * 2 - 1;
|
||||
int bottom = 0;
|
||||
int top = horizontalCount + verticalCount + horizontalCount - 1;
|
||||
int left = requiredVertices - 1;
|
||||
int right = horizontalCount;
|
||||
Vector3[] cellOffset =
|
||||
{
|
||||
Grid.Swizzle(gridLayout.cellSwizzle, new Vector3(0, gridLayout.cellSize.y / 2, area.zMin)),
|
||||
Grid.Swizzle(gridLayout.cellSwizzle, new Vector3(gridLayout.cellSize.x / 2, gridLayout.cellSize.y / 4, area.zMin)),
|
||||
Grid.Swizzle(gridLayout.cellSwizzle, new Vector3(gridLayout.cellSize.x / 2, -gridLayout.cellSize.y / 4, area.zMin)),
|
||||
Grid.Swizzle(gridLayout.cellSwizzle, new Vector3(0, -gridLayout.cellSize.y / 2, area.zMin)),
|
||||
Grid.Swizzle(gridLayout.cellSwizzle, new Vector3(-gridLayout.cellSize.x / 2, -gridLayout.cellSize.y / 4, area.zMin)),
|
||||
Grid.Swizzle(gridLayout.cellSwizzle, new Vector3(-gridLayout.cellSize.x / 2, gridLayout.cellSize.y / 4, area.zMin))
|
||||
};
|
||||
// Fill Top and Bottom Vertices
|
||||
for (int x = area.min.x; x < area.max.x; x++)
|
||||
{
|
||||
cellLocals[bottom++] = gridLayout.CellToLocal(new Vector3Int(x, area.min.y, area.zMin)) + cellOffset[4];
|
||||
cellLocals[bottom++] = gridLayout.CellToLocal(new Vector3Int(x, area.min.y, area.zMin)) + cellOffset[3];
|
||||
cellLocals[top--] = gridLayout.CellToLocal(new Vector3Int(x, area.max.y - 1, area.zMin)) + cellOffset[0];
|
||||
cellLocals[top--] = gridLayout.CellToLocal(new Vector3Int(x, area.max.y - 1, area.zMin)) + cellOffset[1];
|
||||
}
|
||||
// Fill first Left and Right Vertices
|
||||
cellLocals[left--] = gridLayout.CellToLocal(new Vector3Int(area.min.x, area.min.y, area.zMin)) + cellOffset[5];
|
||||
cellLocals[top--] = gridLayout.CellToLocal(new Vector3Int(area.max.x - 1, area.max.y - 1, area.zMin)) + cellOffset[2];
|
||||
// Fill Left and Right Vertices
|
||||
for (int y = area.min.y + 1; y < area.max.y; y++)
|
||||
{
|
||||
cellLocals[left--] = gridLayout.CellToLocal(new Vector3Int(area.min.x, y, area.zMin)) + cellOffset[4];
|
||||
cellLocals[left--] = gridLayout.CellToLocal(new Vector3Int(area.min.x, y, area.zMin)) + cellOffset[5];
|
||||
}
|
||||
for (int y = area.min.y; y < (area.max.y - 1); y++)
|
||||
{
|
||||
cellLocals[right++] = gridLayout.CellToLocal(new Vector3Int(area.max.x - 1, y, area.zMin)) + cellOffset[2];
|
||||
cellLocals[right++] = gridLayout.CellToLocal(new Vector3Int(area.max.x - 1, y, area.zMin)) + cellOffset[1];
|
||||
}
|
||||
HandleUtility.ApplyWireMaterial();
|
||||
GL.PushMatrix();
|
||||
GL.MultMatrix(gridLayout.transform.localToWorldMatrix);
|
||||
GL.Begin(GL.LINES);
|
||||
GL.Color(color);
|
||||
int i = 0;
|
||||
for (int j = cellLocals.Length - 1; i < cellLocals.Length; j = i++)
|
||||
{
|
||||
DrawBatchedLine(cellLocals[j], cellLocals[i]);
|
||||
}
|
||||
GL.End();
|
||||
GL.PopMatrix();
|
||||
}
|
||||
|
||||
public static void DrawGridGizmo(GridLayout gridLayout, Transform transform, Color color, ref Mesh gridMesh, ref Material gridMaterial)
|
||||
{
|
||||
// TODO: Hook this up with DrawGrid
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
return;
|
||||
|
||||
if (gridMesh == null)
|
||||
gridMesh = GenerateCachedGridMesh(gridLayout, color);
|
||||
|
||||
if (gridMaterial == null)
|
||||
{
|
||||
gridMaterial = (Material)EditorGUIUtility.LoadRequired("SceneView/GridGap.mat");
|
||||
}
|
||||
|
||||
if (gridLayout.cellLayout == GridLayout.CellLayout.Hexagon)
|
||||
{
|
||||
gridMaterial.SetVector("_Gap", new Vector4(1f, 1f / 3f, 1f, 1f));
|
||||
gridMaterial.SetVector("_Stride", new Vector4(1f, 1f, 1f, 1f));
|
||||
}
|
||||
else
|
||||
{
|
||||
gridMaterial.SetVector("_Gap", gridLayout.cellSize);
|
||||
gridMaterial.SetVector("_Stride", gridLayout.cellGap + gridLayout.cellSize);
|
||||
}
|
||||
|
||||
gridMaterial.SetPass(0);
|
||||
GL.PushMatrix();
|
||||
if (gridMesh.GetTopology(0) == MeshTopology.Lines)
|
||||
GL.Begin(GL.LINES);
|
||||
else
|
||||
GL.Begin(GL.QUADS);
|
||||
|
||||
Graphics.DrawMeshNow(gridMesh, transform.localToWorldMatrix);
|
||||
GL.End();
|
||||
GL.PopMatrix();
|
||||
}
|
||||
|
||||
public static Vector3 GetSpriteWorldSize(Sprite sprite)
|
||||
{
|
||||
if (sprite != null && sprite.rect.size.magnitude > 0f)
|
||||
{
|
||||
return new Vector3(
|
||||
sprite.rect.size.x / sprite.pixelsPerUnit,
|
||||
sprite.rect.size.y / sprite.pixelsPerUnit,
|
||||
1f
|
||||
);
|
||||
}
|
||||
return Vector3.one;
|
||||
}
|
||||
|
||||
private static Mesh GenerateCachedGridMesh(GridLayout gridLayout, Color color)
|
||||
{
|
||||
switch (gridLayout.cellLayout)
|
||||
{
|
||||
case GridLayout.CellLayout.Hexagon:
|
||||
return GenerateCachedHexagonalGridMesh(gridLayout, color);
|
||||
case GridLayout.CellLayout.Isometric:
|
||||
case GridLayout.CellLayout.IsometricZAsY:
|
||||
case GridLayout.CellLayout.Rectangle:
|
||||
int min = k_GridGizmoVertexCount / -32;
|
||||
int max = min * -1;
|
||||
int numCells = max - min;
|
||||
RectInt bounds = new RectInt(min, min, numCells, numCells);
|
||||
|
||||
return GenerateCachedGridMesh(gridLayout, color, 0f, bounds, MeshTopology.Lines);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Mesh GenerateCachedGridMesh(GridLayout gridLayout, Color color, float screenPixelSize, RectInt bounds, MeshTopology topology)
|
||||
{
|
||||
Mesh mesh = new Mesh();
|
||||
mesh.hideFlags = HideFlags.HideAndDontSave;
|
||||
|
||||
int vertex = 0;
|
||||
|
||||
int totalVertices = topology == MeshTopology.Quads ?
|
||||
8 * (bounds.size.x + bounds.size.y) :
|
||||
4 * (bounds.size.x + bounds.size.y);
|
||||
|
||||
Vector3 horizontalPixelOffset = new Vector3(screenPixelSize, 0f, 0f);
|
||||
Vector3 verticalPixelOffset = new Vector3(0f, screenPixelSize, 0f);
|
||||
|
||||
Vector3[] vertices = new Vector3[totalVertices];
|
||||
Vector2[] uvs2 = new Vector2[totalVertices];
|
||||
|
||||
Vector3 cellStride = gridLayout.cellSize + gridLayout.cellGap;
|
||||
Vector3Int minPosition = new Vector3Int(0, bounds.min.y, 0);
|
||||
Vector3Int maxPosition = new Vector3Int(0, bounds.max.y, 0);
|
||||
|
||||
Vector3 cellGap = Vector3.zero;
|
||||
if (!Mathf.Approximately(cellStride.x, 0f))
|
||||
{
|
||||
cellGap.x = gridLayout.cellSize.x / cellStride.x;
|
||||
}
|
||||
|
||||
for (int x = bounds.min.x; x < bounds.max.x; x++)
|
||||
{
|
||||
minPosition.x = x;
|
||||
maxPosition.x = x;
|
||||
|
||||
vertices[vertex + 0] = gridLayout.CellToLocal(minPosition);
|
||||
vertices[vertex + 1] = gridLayout.CellToLocal(maxPosition);
|
||||
uvs2[vertex + 0] = Vector2.zero;
|
||||
uvs2[vertex + 1] = new Vector2(0f, cellStride.y * bounds.size.y);
|
||||
if (topology == MeshTopology.Quads)
|
||||
{
|
||||
vertices[vertex + 2] = gridLayout.CellToLocal(maxPosition) + horizontalPixelOffset;
|
||||
vertices[vertex + 3] = gridLayout.CellToLocal(minPosition) + horizontalPixelOffset;
|
||||
uvs2[vertex + 2] = new Vector2(0f, cellStride.y * bounds.size.y);
|
||||
uvs2[vertex + 3] = Vector2.zero;
|
||||
}
|
||||
vertex += topology == MeshTopology.Quads ? 4 : 2;
|
||||
|
||||
vertices[vertex + 0] = gridLayout.CellToLocalInterpolated(minPosition + cellGap);
|
||||
vertices[vertex + 1] = gridLayout.CellToLocalInterpolated(maxPosition + cellGap);
|
||||
uvs2[vertex + 0] = Vector2.zero;
|
||||
uvs2[vertex + 1] = new Vector2(0f, cellStride.y * bounds.size.y);
|
||||
if (topology == MeshTopology.Quads)
|
||||
{
|
||||
vertices[vertex + 2] = gridLayout.CellToLocalInterpolated(maxPosition + cellGap) + horizontalPixelOffset;
|
||||
vertices[vertex + 3] = gridLayout.CellToLocalInterpolated(minPosition + cellGap) + horizontalPixelOffset;
|
||||
uvs2[vertex + 2] = new Vector2(0f, cellStride.y * bounds.size.y);
|
||||
uvs2[vertex + 3] = Vector2.zero;
|
||||
}
|
||||
vertex += topology == MeshTopology.Quads ? 4 : 2;
|
||||
}
|
||||
|
||||
minPosition = new Vector3Int(bounds.min.x, 0, 0);
|
||||
maxPosition = new Vector3Int(bounds.max.x, 0, 0);
|
||||
cellGap = Vector3.zero;
|
||||
if (!Mathf.Approximately(cellStride.y, 0f))
|
||||
{
|
||||
cellGap.y = gridLayout.cellSize.y / cellStride.y;
|
||||
}
|
||||
|
||||
for (int y = bounds.min.y; y < bounds.max.y; y++)
|
||||
{
|
||||
minPosition.y = y;
|
||||
maxPosition.y = y;
|
||||
|
||||
vertices[vertex + 0] = gridLayout.CellToLocal(minPosition);
|
||||
vertices[vertex + 1] = gridLayout.CellToLocal(maxPosition);
|
||||
uvs2[vertex + 0] = Vector2.zero;
|
||||
uvs2[vertex + 1] = new Vector2(cellStride.x * bounds.size.x, 0f);
|
||||
if (topology == MeshTopology.Quads)
|
||||
{
|
||||
vertices[vertex + 2] = gridLayout.CellToLocal(maxPosition) + verticalPixelOffset;
|
||||
vertices[vertex + 3] = gridLayout.CellToLocal(minPosition) + verticalPixelOffset;
|
||||
uvs2[vertex + 2] = new Vector2(cellStride.x * bounds.size.x, 0f);
|
||||
uvs2[vertex + 3] = Vector2.zero;
|
||||
}
|
||||
vertex += topology == MeshTopology.Quads ? 4 : 2;
|
||||
|
||||
vertices[vertex + 0] = gridLayout.CellToLocalInterpolated(minPosition + cellGap);
|
||||
vertices[vertex + 1] = gridLayout.CellToLocalInterpolated(maxPosition + cellGap);
|
||||
uvs2[vertex + 0] = Vector2.zero;
|
||||
uvs2[vertex + 1] = new Vector2(cellStride.x * bounds.size.x, 0f);
|
||||
if (topology == MeshTopology.Quads)
|
||||
{
|
||||
vertices[vertex + 2] = gridLayout.CellToLocalInterpolated(maxPosition + cellGap) + verticalPixelOffset;
|
||||
vertices[vertex + 3] = gridLayout.CellToLocalInterpolated(minPosition + cellGap) + verticalPixelOffset;
|
||||
uvs2[vertex + 2] = new Vector2(cellStride.x * bounds.size.x, 0f);
|
||||
uvs2[vertex + 3] = Vector2.zero;
|
||||
}
|
||||
vertex += topology == MeshTopology.Quads ? 4 : 2;
|
||||
}
|
||||
|
||||
var uv0 = new Vector2(k_GridGizmoDistanceFalloff, 0f);
|
||||
var uvs = new Vector2[vertex];
|
||||
var indices = new int[vertex];
|
||||
var colors = new Color[vertex];
|
||||
var normals = new Vector3[totalVertices]; // Normal channel stores the position of the other end point of the line.
|
||||
var uvs3 = new Vector2[totalVertices]; // UV3 channel stores the UV2 value of the other end point of the line.
|
||||
|
||||
for (int i = 0; i < vertex; i++)
|
||||
{
|
||||
uvs[i] = uv0;
|
||||
indices[i] = i;
|
||||
colors[i] = color;
|
||||
var alternate = i + ((i % 2) == 0 ? 1 : -1);
|
||||
normals[i] = vertices[alternate];
|
||||
uvs3[i] = uvs2[alternate];
|
||||
}
|
||||
|
||||
mesh.vertices = vertices;
|
||||
mesh.uv = uvs;
|
||||
mesh.uv2 = uvs2;
|
||||
mesh.uv3 = uvs3;
|
||||
mesh.colors = colors;
|
||||
mesh.normals = normals;
|
||||
mesh.SetIndices(indices, topology, 0);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
private static Mesh GenerateCachedHexagonalGridMesh(GridLayout gridLayout, Color color)
|
||||
{
|
||||
Mesh mesh = new Mesh();
|
||||
mesh.hideFlags = HideFlags.HideAndDontSave;
|
||||
int vertex = 0;
|
||||
int max = k_GridGizmoVertexCount / (2 * (6 * 2));
|
||||
max = (max / 4) * 4;
|
||||
int min = -max;
|
||||
float numVerticalCells = 6 * (max / 4);
|
||||
int totalVertices = max * 2 * 6 * 2;
|
||||
var cellStrideY = gridLayout.cellGap.y + gridLayout.cellSize.y;
|
||||
var cellOffsetY = gridLayout.cellSize.y / 2;
|
||||
var hexOffset = (1.0f / 3.0f);
|
||||
var drawTotal = numVerticalCells * 2.0f * hexOffset;
|
||||
var drawDiagTotal = 2 * drawTotal;
|
||||
Vector3[] vertices = new Vector3[totalVertices];
|
||||
Vector2[] uvs2 = new Vector2[totalVertices];
|
||||
// Draw Vertical Lines
|
||||
for (int x = min; x < max; x++)
|
||||
{
|
||||
vertices[vertex] = gridLayout.CellToLocal(new Vector3Int(x, min, 0));
|
||||
vertices[vertex + 1] = gridLayout.CellToLocal(new Vector3Int(x, max, 0));
|
||||
uvs2[vertex] = new Vector2(0f, 2 * hexOffset);
|
||||
uvs2[vertex + 1] = new Vector2(0f, 2 * hexOffset + drawTotal);
|
||||
vertex += 2;
|
||||
// Alternate Row Offset
|
||||
vertices[vertex] = gridLayout.CellToLocal(new Vector3Int(x, min - 1, 0));
|
||||
vertices[vertex + 1] = gridLayout.CellToLocal(new Vector3Int(x, max - 1, 0));
|
||||
uvs2[vertex] = new Vector2(0f, 2 * hexOffset);
|
||||
uvs2[vertex + 1] = new Vector2(0f, 2 * hexOffset + drawTotal);
|
||||
vertex += 2;
|
||||
}
|
||||
// Draw Diagonals
|
||||
for (int y = min; y < max; y++)
|
||||
{
|
||||
float drawDiagOffset = ((y + 1) % 3) * hexOffset;
|
||||
var cellOffSet = Grid.Swizzle(gridLayout.cellSwizzle, new Vector3(0f, y * cellStrideY + cellOffsetY, 0.0f));
|
||||
// Slope Up
|
||||
vertices[vertex] = gridLayout.CellToLocal(new Vector3Int(Mathf.RoundToInt(1.5f * min), min, 0)) + cellOffSet;
|
||||
vertices[vertex + 1] = gridLayout.CellToLocal(new Vector3Int(Mathf.RoundToInt(1.5f * max), max, 0)) + cellOffSet;
|
||||
uvs2[vertex] = new Vector2(0f, drawDiagOffset);
|
||||
uvs2[vertex + 1] = new Vector2(0f, drawDiagOffset + drawDiagTotal);
|
||||
vertex += 2;
|
||||
// Slope Down
|
||||
vertices[vertex] = gridLayout.CellToLocal(new Vector3Int(Mathf.RoundToInt(1.5f * max), min, 0)) + cellOffSet;
|
||||
vertices[vertex + 1] = gridLayout.CellToLocal(new Vector3Int(Mathf.RoundToInt(1.5f * min), max, 0)) + cellOffSet;
|
||||
uvs2[vertex] = new Vector2(0f, drawDiagOffset);
|
||||
uvs2[vertex + 1] = new Vector2(0f, drawDiagOffset + drawDiagTotal);
|
||||
vertex += 2;
|
||||
// Alternate Row Offset
|
||||
vertices[vertex] = gridLayout.CellToLocal(new Vector3Int(Mathf.RoundToInt(1.5f * min) + 1, min, 0)) + cellOffSet;
|
||||
vertices[vertex + 1] = gridLayout.CellToLocal(new Vector3Int(Mathf.RoundToInt(1.5f * max) + 1, max, 0)) + cellOffSet;
|
||||
uvs2[vertex] = new Vector2(0f, drawDiagOffset);
|
||||
uvs2[vertex + 1] = new Vector2(0f, drawDiagOffset + drawDiagTotal);
|
||||
vertex += 2;
|
||||
vertices[vertex] = gridLayout.CellToLocal(new Vector3Int(Mathf.RoundToInt(1.5f * max) + 1, min, 0)) + cellOffSet;
|
||||
vertices[vertex + 1] = gridLayout.CellToLocal(new Vector3Int(Mathf.RoundToInt(1.5f * min) + 1, max, 0)) + cellOffSet;
|
||||
uvs2[vertex] = new Vector2(0f, drawDiagOffset);
|
||||
uvs2[vertex + 1] = new Vector2(0f, drawDiagOffset + drawDiagTotal);
|
||||
vertex += 2;
|
||||
}
|
||||
var uv0 = new Vector2(k_GridGizmoDistanceFalloff, 0f);
|
||||
var indices = new int[totalVertices];
|
||||
var uvs = new Vector2[totalVertices];
|
||||
var colors = new Color[totalVertices];
|
||||
var normals = new Vector3[totalVertices]; // Normal channel stores the position of the other end point of the line.
|
||||
var uvs3 = new Vector2[totalVertices]; // UV3 channel stores the UV2 value of the other end point of the line.
|
||||
|
||||
for (int i = 0; i < totalVertices; i++)
|
||||
{
|
||||
uvs[i] = uv0;
|
||||
indices[i] = i;
|
||||
colors[i] = color;
|
||||
var alternate = i + ((i % 2) == 0 ? 1 : -1);
|
||||
normals[i] = vertices[alternate];
|
||||
uvs3[i] = uvs2[alternate];
|
||||
}
|
||||
|
||||
mesh.vertices = vertices;
|
||||
mesh.uv = uvs;
|
||||
mesh.uv2 = uvs2;
|
||||
mesh.uv3 = uvs3;
|
||||
mesh.colors = colors;
|
||||
mesh.normals = normals;
|
||||
mesh.SetIndices(indices, MeshTopology.Lines, 0);
|
||||
return mesh;
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,202 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
internal class GridPaintPaletteWindowPreferences
|
||||
{
|
||||
[SettingsProvider]
|
||||
internal static SettingsProvider CreateSettingsProvider()
|
||||
{
|
||||
var settingsProvider = new SettingsProvider("Preferences/2D/Tile Palette", SettingsScope.User, SettingsProvider.GetSearchKeywordsFromGUIContentProperties<GridPaintPaletteWindowPreferences>())
|
||||
{
|
||||
guiHandler = searchContext =>
|
||||
{
|
||||
GridPaintPaletteWindow.PreferencesGUI();
|
||||
GridPaintActiveTargetsPreferences.PreferencesGUI();
|
||||
SceneViewOpenTilePaletteHelper.PreferencesGUI();
|
||||
TilemapPrefabStageHelper.PreferencesGUI();
|
||||
}
|
||||
};
|
||||
return settingsProvider;
|
||||
}
|
||||
}
|
||||
|
||||
internal class GridPaintActiveTargetsPreferences
|
||||
{
|
||||
public static readonly string targetSortingModeEditorPref = "TilePalette.ActiveTargetsSortingMode";
|
||||
public static readonly string targetSortingModeLookup = "Active Targets Sorting Mode";
|
||||
public static readonly string targetRestoreEditModeSelectionEditorPref = "TilePalette.RestoreEditModeSelection";
|
||||
public static readonly string targetRestoreEditModeSelectionLookup = "Restore Edit Mode Active Target";
|
||||
public static readonly string createTileFromPaletteEditorPref = "TilePalette.CreateTileFromPalette";
|
||||
public static readonly string createTileFromPaletteLookup = "Create Tile Method";
|
||||
|
||||
public static readonly string defaultSortingMode = L10n.Tr("None");
|
||||
|
||||
public static readonly GUIContent targetSortingModeLabel =
|
||||
EditorGUIUtility.TrTextContent(targetSortingModeLookup,
|
||||
"Controls the sorting of the Active Targets in the Tile Palette");
|
||||
|
||||
public static readonly GUIContent targetRestoreEditModeSelectionLabel = EditorGUIUtility.TrTextContent(
|
||||
targetRestoreEditModeSelectionLookup
|
||||
, "When exiting Play Mode, restores the Active Target in the Tile Palette to the last selected target from Edit Mode");
|
||||
|
||||
public static readonly GUIContent createTileFromPaletteLabel = EditorGUIUtility.TrTextContent(
|
||||
createTileFromPaletteLookup
|
||||
, "Method used to create Tiles when drag and dropping assets to the Tile Palette");
|
||||
|
||||
public static bool restoreEditModeSelection
|
||||
{
|
||||
get { return EditorPrefs.GetBool(targetRestoreEditModeSelectionEditorPref, true); }
|
||||
set { EditorPrefs.SetBool(targetRestoreEditModeSelectionEditorPref, value); }
|
||||
}
|
||||
|
||||
private static string[] s_SortingNames;
|
||||
private static int s_SortingSelectionIndex;
|
||||
private static string[] s_CreateTileNames;
|
||||
private static int s_CreateTileIndex;
|
||||
|
||||
private static bool CompareMethodName(string[] methodNames, MethodInfo method)
|
||||
{
|
||||
return methodNames.Length == 2 && methodNames[0] == method.ReflectedType.Name &&
|
||||
methodNames[1] == method.Name;
|
||||
}
|
||||
|
||||
private static bool CompareTypeName(string typeFullName, Type type)
|
||||
{
|
||||
return typeFullName == type.FullName;
|
||||
}
|
||||
|
||||
internal static void PreferencesGUI()
|
||||
{
|
||||
using (new SettingsWindow.GUIScope())
|
||||
{
|
||||
if (s_SortingNames == null)
|
||||
{
|
||||
var sortingTypeFullName = EditorPrefs.GetString(targetSortingModeEditorPref, defaultSortingMode);
|
||||
var sortingMethodNames = sortingTypeFullName.Split('.');
|
||||
s_SortingNames = new string[1 + GridPaintSortingAttribute.sortingMethods.Count +
|
||||
GridPaintSortingAttribute.sortingTypes.Count];
|
||||
int count = 0;
|
||||
s_SortingNames[count++] = defaultSortingMode;
|
||||
foreach (var sortingMethod in GridPaintSortingAttribute.sortingMethods)
|
||||
{
|
||||
if (CompareMethodName(sortingMethodNames, sortingMethod))
|
||||
s_SortingSelectionIndex = count;
|
||||
s_SortingNames[count++] = sortingMethod.Name;
|
||||
}
|
||||
|
||||
foreach (var sortingType in GridPaintSortingAttribute.sortingTypes)
|
||||
{
|
||||
if (CompareTypeName(sortingTypeFullName, sortingType))
|
||||
s_SortingSelectionIndex = count;
|
||||
s_SortingNames[count++] = sortingType.Name;
|
||||
}
|
||||
}
|
||||
|
||||
if (s_CreateTileNames == null)
|
||||
{
|
||||
var createTileFullName = EditorPrefs.GetString(createTileFromPaletteEditorPref, defaultSortingMode);
|
||||
var createTileMethodNames = createTileFullName.Split('.');
|
||||
int count = 0;
|
||||
s_CreateTileNames = new string[CreateTileFromPaletteAttribute.createTileFromPaletteMethods.Count];
|
||||
foreach (var createTileMethod in CreateTileFromPaletteAttribute.createTileFromPaletteMethods)
|
||||
{
|
||||
if (CompareMethodName(createTileMethodNames, createTileMethod))
|
||||
s_CreateTileIndex = count;
|
||||
s_CreateTileNames[count++] = createTileMethod.Name;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var sortingSelection =
|
||||
EditorGUILayout.Popup(targetSortingModeLabel, s_SortingSelectionIndex, s_SortingNames);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
s_SortingSelectionIndex = sortingSelection;
|
||||
var sortingTypeFullName = defaultSortingMode;
|
||||
if (s_SortingSelectionIndex > 0 &&
|
||||
s_SortingSelectionIndex <= GridPaintSortingAttribute.sortingMethods.Count)
|
||||
{
|
||||
var sortingMethod = GridPaintSortingAttribute.sortingMethods[s_SortingSelectionIndex - 1];
|
||||
sortingTypeFullName =
|
||||
String.Format("{0}.{1}", sortingMethod.ReflectedType.Name, sortingMethod.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
var idx = s_SortingSelectionIndex - GridPaintSortingAttribute.sortingMethods.Count - 1;
|
||||
if (idx >= 0 && idx < GridPaintSortingAttribute.sortingTypes.Count)
|
||||
{
|
||||
var sortingType = GridPaintSortingAttribute.sortingTypes[idx];
|
||||
sortingTypeFullName = sortingType.FullName;
|
||||
}
|
||||
}
|
||||
|
||||
EditorPrefs.SetString(targetSortingModeEditorPref, sortingTypeFullName);
|
||||
GridPaintingState.FlushCache();
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var editModeSelection = EditorGUILayout.Toggle(targetRestoreEditModeSelectionLabel, restoreEditModeSelection);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
restoreEditModeSelection = editModeSelection;
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var createTileSelection = EditorGUILayout.Popup(createTileFromPaletteLabel, s_CreateTileIndex, s_CreateTileNames);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
var createTileFullName = defaultSortingMode;
|
||||
s_CreateTileIndex = createTileSelection;
|
||||
if (s_CreateTileIndex < CreateTileFromPaletteAttribute.createTileFromPaletteMethods.Count)
|
||||
{
|
||||
var createTileMethod = CreateTileFromPaletteAttribute.createTileFromPaletteMethods[s_CreateTileIndex];
|
||||
createTileFullName = String.Format("{0}.{1}", createTileMethod.ReflectedType.Name, createTileMethod.Name);
|
||||
}
|
||||
|
||||
EditorPrefs.SetString(createTileFromPaletteEditorPref, createTileFullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IComparer<GameObject> GetTargetComparer()
|
||||
{
|
||||
var sortingTypeFullName = EditorPrefs.GetString(targetSortingModeEditorPref, defaultSortingMode);
|
||||
if (!sortingTypeFullName.Equals(defaultSortingMode))
|
||||
{
|
||||
var sortingMethodNames = sortingTypeFullName.Split('.');
|
||||
foreach (var sortingMethod in GridPaintSortingAttribute.sortingMethods)
|
||||
{
|
||||
if (CompareMethodName(sortingMethodNames, sortingMethod))
|
||||
return sortingMethod.Invoke(null, null) as IComparer<GameObject>;
|
||||
}
|
||||
|
||||
foreach (var sortingType in GridPaintSortingAttribute.sortingTypes)
|
||||
{
|
||||
if (CompareTypeName(sortingTypeFullName, sortingType))
|
||||
return Activator.CreateInstance(sortingType) as IComparer<GameObject>;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static MethodInfo GetCreateTileFromPaletteUsingPreferences()
|
||||
{
|
||||
var createTileFullName = EditorPrefs.GetString(createTileFromPaletteEditorPref, defaultSortingMode);
|
||||
if (!createTileFullName.Equals(defaultSortingMode))
|
||||
{
|
||||
var methodNames = createTileFullName.Split('.');
|
||||
foreach (var createTileMethod in CreateTileFromPaletteAttribute.createTileFromPaletteMethods)
|
||||
{
|
||||
if (CompareMethodName(methodNames, createTileMethod))
|
||||
return createTileMethod;
|
||||
}
|
||||
}
|
||||
return typeof(TileUtility).GetMethod("DefaultTile", BindingFlags.Static | BindingFlags.Public);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
/// <summary>
|
||||
/// Use this attribute to add an option to customize the sorting of Active Targets in the Active Tilemap list of the Tile Palette window.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Append this attribute to a class which inherits from IComparer<GameObject> or to a method which creates an IComparer<GameObject>. The instance of IComparer generated with the attribute is used for comparing and sorting Active Target GameObjects in the Active Tilemaps list.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code lang="cs"><![CDATA[
|
||||
/// using System;
|
||||
/// using System.Collections.Generic;
|
||||
/// using UnityEngine;
|
||||
/// using UnityEditor;
|
||||
///
|
||||
/// [GridPaintSorting]
|
||||
/// class Alphabetical : IComparer<GameObject>
|
||||
/// {
|
||||
/// public int Compare(GameObject go1, GameObject go2)
|
||||
/// {
|
||||
/// return String.Compare(go1.name, go2.name);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// class ReverseAlphabeticalComparer : IComparer<GameObject>
|
||||
/// {
|
||||
/// public int Compare(GameObject go1, GameObject go2)
|
||||
/// {
|
||||
/// return -String.Compare(go1.name, go2.name);
|
||||
/// }
|
||||
///
|
||||
/// [GridPaintSorting]
|
||||
/// public static IComparer<GameObject> ReverseAlphabetical()
|
||||
/// {
|
||||
/// return new ReverseAlphabeticalComparer();
|
||||
/// }
|
||||
/// }
|
||||
/// ]]></code>
|
||||
/// </example>
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
|
||||
public class GridPaintSortingAttribute : Attribute
|
||||
{
|
||||
private static List<MethodInfo> m_SortingMethods;
|
||||
private static List<Type> m_SortingTypes;
|
||||
|
||||
internal static List<MethodInfo> sortingMethods
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_SortingMethods == null)
|
||||
GetUserSortingComparers();
|
||||
return m_SortingMethods;
|
||||
}
|
||||
}
|
||||
|
||||
internal static List<Type> sortingTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_SortingTypes == null)
|
||||
GetUserSortingComparers();
|
||||
return m_SortingTypes;
|
||||
}
|
||||
}
|
||||
|
||||
private static void GetUserSortingComparers()
|
||||
{
|
||||
m_SortingMethods = new List<MethodInfo>();
|
||||
foreach (var sortingMethod in EditorAssemblies.GetAllMethodsWithAttribute<GridPaintSortingAttribute>())
|
||||
{
|
||||
if (!sortingMethod.ReturnType.IsAssignableFrom(typeof(IComparer<GameObject>)))
|
||||
continue;
|
||||
if (sortingMethod.GetGenericArguments().Length > 0)
|
||||
continue;
|
||||
m_SortingMethods.Add(sortingMethod);
|
||||
}
|
||||
|
||||
m_SortingTypes = new List<Type>();
|
||||
foreach (var sortingType in TypeCache.GetTypesWithAttribute<GridPaintSortingAttribute>())
|
||||
{
|
||||
if (sortingType.IsAbstract)
|
||||
continue;
|
||||
m_SortingTypes.Add(sortingType);
|
||||
}
|
||||
}
|
||||
|
||||
[GridPaintSorting]
|
||||
internal class Alphabetical : IComparer<GameObject>
|
||||
{
|
||||
public int Compare(GameObject go1, GameObject go2)
|
||||
{
|
||||
return String.Compare(go1.name, go2.name);
|
||||
}
|
||||
}
|
||||
|
||||
[GridPaintSorting]
|
||||
internal class ReverseAlphabetical : IComparer<GameObject>
|
||||
{
|
||||
public int Compare(GameObject go1, GameObject go2)
|
||||
{
|
||||
return -String.Compare(go1.name, go2.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
internal class GridPaintTargetsDropdown : FlexibleMenu
|
||||
{
|
||||
public GridPaintTargetsDropdown(IFlexibleMenuItemProvider itemProvider, int selectionIndex, FlexibleMenuModifyItemUI modifyItemUi, Action<int, object> itemClickedCallback, float minWidth)
|
||||
: base(itemProvider, selectionIndex, modifyItemUi, itemClickedCallback)
|
||||
{
|
||||
minTextWidth = minWidth;
|
||||
}
|
||||
|
||||
internal class MenuItemProvider : IFlexibleMenuItemProvider
|
||||
{
|
||||
public int Count()
|
||||
{
|
||||
return GridPaintingState.validTargets != null ? GridPaintingState.validTargets.Length : 0;
|
||||
}
|
||||
|
||||
public object GetItem(int index)
|
||||
{
|
||||
return GridPaintingState.validTargets != null ? GridPaintingState.validTargets[index] : GridPaintingState.scenePaintTarget;
|
||||
}
|
||||
|
||||
public int Add(object obj)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Replace(int index, object newPresetObject)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Remove(int index)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public object Create()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Move(int index, int destIndex, bool insertAfterDestIndex)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string GetName(int index)
|
||||
{
|
||||
return GridPaintingState.validTargets != null ? GridPaintingState.validTargets[index].name : GridPaintingState.scenePaintTarget.name;
|
||||
}
|
||||
|
||||
public bool IsModificationAllowed(int index)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public int[] GetSeperatorIndices()
|
||||
{
|
||||
return new int[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,369 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
/// <summary>
|
||||
/// GridPaintingState controls the state of objects for painting with a Tile Palette.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Utilize this class to get and set the current painting target and brush for painting
|
||||
/// with the Tile Palette.
|
||||
/// </remarks>
|
||||
public class GridPaintingState : ScriptableSingleton<GridPaintingState>
|
||||
{
|
||||
[SerializeField] private GameObject m_EditModeScenePaintTarget; // Which GameObject in scene was the last painting target in EditMode
|
||||
[SerializeField] private GameObject m_ScenePaintTarget; // Which GameObject in scene is considered as painting target
|
||||
[SerializeField] private GridBrushBase m_Brush; // Which brush will handle painting callbacks
|
||||
[SerializeField] private PaintableGrid m_ActiveGrid; // Grid that has painting focus (can be palette, too)
|
||||
[SerializeField] private PaintableGrid m_LastActiveGrid; // Grid that last had painting focus (can be palette, too)
|
||||
[SerializeField] private HashSet<Object> m_InterestedPainters = new HashSet<Object>(); // A list of objects that can paint using the GridPaintingState
|
||||
|
||||
private GameObject[] m_CachedPaintTargets;
|
||||
private bool m_FlushPaintTargetCache;
|
||||
private Editor m_CachedEditor;
|
||||
private bool m_SavingPalette;
|
||||
|
||||
/// <summary>
|
||||
/// Callback when the Tile Palette's active target has changed
|
||||
/// </summary>
|
||||
public static event Action<GameObject> scenePaintTargetChanged;
|
||||
/// <summary>
|
||||
/// Callback when the Tile Palette's active brush has changed.
|
||||
/// </summary>
|
||||
public static event Action<GridBrushBase> brushChanged;
|
||||
/// <summary>
|
||||
/// Callback when the Tile Palette's active palette GameObject has changed.
|
||||
/// </summary>
|
||||
public static event Action<GameObject> paletteChanged;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
EditorApplication.hierarchyChanged += HierarchyChanged;
|
||||
EditorApplication.playModeStateChanged += PlayModeStateChanged;
|
||||
Selection.selectionChanged += OnSelectionChange;
|
||||
m_FlushPaintTargetCache = true;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
m_InterestedPainters.Clear();
|
||||
EditorApplication.hierarchyChanged -= HierarchyChanged;
|
||||
EditorApplication.playModeStateChanged -= PlayModeStateChanged;
|
||||
Selection.selectionChanged -= OnSelectionChange;
|
||||
FlushCache();
|
||||
}
|
||||
|
||||
private void OnSelectionChange()
|
||||
{
|
||||
if (hasInterestedPainters && ValidatePaintTarget(Selection.activeGameObject))
|
||||
{
|
||||
scenePaintTarget = Selection.activeGameObject;
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayModeStateChanged(PlayModeStateChange state)
|
||||
{
|
||||
if (state == PlayModeStateChange.ExitingEditMode)
|
||||
{
|
||||
m_EditModeScenePaintTarget = scenePaintTarget;
|
||||
}
|
||||
else if (state == PlayModeStateChange.EnteredEditMode)
|
||||
{
|
||||
if (GridPaintActiveTargetsPreferences.restoreEditModeSelection && m_EditModeScenePaintTarget != null)
|
||||
{
|
||||
scenePaintTarget = m_EditModeScenePaintTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HierarchyChanged()
|
||||
{
|
||||
if (hasInterestedPainters)
|
||||
{
|
||||
m_FlushPaintTargetCache = true;
|
||||
if (validTargets == null || validTargets.Length == 0 || !validTargets.Contains(scenePaintTarget))
|
||||
{
|
||||
// case 1102618: Try to use current Selection as scene paint target if possible
|
||||
if (Selection.activeGameObject != null && hasInterestedPainters && ValidatePaintTarget(Selection.activeGameObject))
|
||||
{
|
||||
scenePaintTarget = Selection.activeGameObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
AutoSelectPaintTarget();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private GameObject[] GetValidTargets()
|
||||
{
|
||||
if (m_FlushPaintTargetCache)
|
||||
{
|
||||
m_CachedPaintTargets = null;
|
||||
if (activeBrushEditor != null)
|
||||
m_CachedPaintTargets = activeBrushEditor.validTargets;
|
||||
if (m_CachedPaintTargets == null || m_CachedPaintTargets.Length == 0)
|
||||
scenePaintTarget = null;
|
||||
else
|
||||
{
|
||||
var comparer = GridPaintActiveTargetsPreferences.GetTargetComparer();
|
||||
if (comparer != null)
|
||||
Array.Sort(m_CachedPaintTargets, comparer);
|
||||
}
|
||||
|
||||
m_FlushPaintTargetCache = false;
|
||||
}
|
||||
return m_CachedPaintTargets;
|
||||
}
|
||||
|
||||
internal static void AutoSelectPaintTarget()
|
||||
{
|
||||
if (activeBrushEditor != null)
|
||||
{
|
||||
if (validTargets != null && validTargets.Length > 0)
|
||||
{
|
||||
scenePaintTarget = validTargets[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The currently active target for the Tile Palette
|
||||
/// </summary>
|
||||
public static GameObject scenePaintTarget
|
||||
{
|
||||
get { return instance.m_ScenePaintTarget; }
|
||||
set
|
||||
{
|
||||
if (value != instance.m_ScenePaintTarget)
|
||||
{
|
||||
instance.m_ScenePaintTarget = value;
|
||||
if (scenePaintTargetChanged != null)
|
||||
scenePaintTargetChanged(instance.m_ScenePaintTarget);
|
||||
RepaintGridPaintPaletteWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The currently active brush for the Tile Palette
|
||||
/// </summary>
|
||||
public static GridBrushBase gridBrush
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance.m_Brush == null)
|
||||
instance.m_Brush = GridPaletteBrushes.instance.GetLastUsedBrush();
|
||||
|
||||
return instance.m_Brush;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (instance.m_Brush != value)
|
||||
{
|
||||
instance.m_Brush = value;
|
||||
instance.m_FlushPaintTargetCache = true;
|
||||
|
||||
if (value != null)
|
||||
GridPaletteBrushes.instance.StoreLastUsedBrush(value);
|
||||
|
||||
// Ensure that current scenePaintTarget is still a valid target after a brush change
|
||||
if (scenePaintTarget != null && !ValidatePaintTarget(scenePaintTarget))
|
||||
scenePaintTarget = null;
|
||||
|
||||
// Use Selection if previous scenePaintTarget was not valid
|
||||
if (scenePaintTarget == null)
|
||||
scenePaintTarget = ValidatePaintTarget(Selection.activeGameObject) ? Selection.activeGameObject : null;
|
||||
|
||||
// Auto select a valid target if there is still no scenePaintTarget
|
||||
if (scenePaintTarget == null)
|
||||
AutoSelectPaintTarget();
|
||||
|
||||
if (null != brushChanged)
|
||||
brushChanged(value);
|
||||
|
||||
RepaintGridPaintPaletteWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all available brushes for the Tile Palette
|
||||
/// </summary>
|
||||
public static IList<GridBrushBase> brushes
|
||||
{
|
||||
get { return GridPaletteBrushes.brushes; }
|
||||
}
|
||||
|
||||
internal static GridBrush defaultBrush
|
||||
{
|
||||
get { return gridBrush as GridBrush; }
|
||||
set { gridBrush = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The currently active palette GameObject for the Tile Palette
|
||||
/// </summary>
|
||||
public static GameObject palette
|
||||
{
|
||||
get
|
||||
{
|
||||
if (GridPaintPaletteWindow.instances.Count > 0)
|
||||
return GridPaintPaletteWindow.instances[0].palette;
|
||||
return null;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null || !GridPalettes.palettes.Contains(value))
|
||||
throw new ArgumentException(L10n.Tr("Unable to set invalid palette"));
|
||||
if (GridPaintPaletteWindow.instances.Count > 0 && GridPaintPaletteWindow.instances[0].palette != value)
|
||||
{
|
||||
GridPaintPaletteWindow.instances[0].palette = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if target GameObject is part of the active Palette.
|
||||
/// </summary>
|
||||
/// <param name="target">GameObject to check.</param>
|
||||
/// <returns>True if the target GameObject is part of the active palette. False if not.</returns>
|
||||
public static bool IsPartOfActivePalette(GameObject target)
|
||||
{
|
||||
if (GridPaintPaletteWindow.instances.Count > 0 && target == GridPaintPaletteWindow.instances[0].paletteInstance)
|
||||
return true;
|
||||
if (target == palette)
|
||||
return true;
|
||||
var parent = target.transform.parent;
|
||||
return parent != null && IsPartOfActivePalette(parent.gameObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all available Palette GameObjects for the Tile Palette
|
||||
/// </summary>
|
||||
public static IList<GameObject> palettes
|
||||
{
|
||||
get { return GridPalettes.palettes; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The currently active editor for the active brush for the Tile Palette
|
||||
/// </summary>
|
||||
public static GridBrushEditorBase activeBrushEditor
|
||||
{
|
||||
get
|
||||
{
|
||||
Editor.CreateCachedEditor(gridBrush, null, ref instance.m_CachedEditor);
|
||||
GridBrushEditorBase baseEditor = instance.m_CachedEditor as GridBrushEditorBase;
|
||||
return baseEditor;
|
||||
}
|
||||
}
|
||||
|
||||
internal static Editor fallbackEditor
|
||||
{
|
||||
get
|
||||
{
|
||||
Editor.CreateCachedEditor(gridBrush, null, ref instance.m_CachedEditor);
|
||||
return instance.m_CachedEditor;
|
||||
}
|
||||
}
|
||||
|
||||
internal static PaintableGrid activeGrid
|
||||
{
|
||||
get { return instance.m_ActiveGrid; }
|
||||
set
|
||||
{
|
||||
instance.m_ActiveGrid = value;
|
||||
if (instance.m_ActiveGrid != null)
|
||||
instance.m_LastActiveGrid = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal static PaintableGrid lastActiveGrid
|
||||
{
|
||||
get { return instance.m_LastActiveGrid; }
|
||||
}
|
||||
|
||||
private static bool ValidatePaintTarget(GameObject candidate)
|
||||
{
|
||||
if (candidate == null || candidate.GetComponentInParent<Grid>() == null && candidate.GetComponent<Grid>() == null)
|
||||
return false;
|
||||
|
||||
if (validTargets != null && validTargets.Length > 0 && !validTargets.Contains(candidate))
|
||||
return false;
|
||||
|
||||
if (PrefabUtility.IsPartOfPrefabAsset(candidate))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void FlushCache()
|
||||
{
|
||||
if (instance.m_CachedEditor != null)
|
||||
{
|
||||
DestroyImmediate(instance.m_CachedEditor);
|
||||
instance.m_CachedEditor = null;
|
||||
}
|
||||
instance.m_FlushPaintTargetCache = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A list of all valid targets that can be set as an active target for the Tile Palette
|
||||
/// </summary>
|
||||
public static GameObject[] validTargets
|
||||
{
|
||||
get { return instance.GetValidTargets(); }
|
||||
}
|
||||
|
||||
internal static bool savingPalette
|
||||
{
|
||||
get { return instance.m_SavingPalette; }
|
||||
set { instance.m_SavingPalette = value; }
|
||||
}
|
||||
|
||||
internal static void OnPaletteChanged(GameObject palette)
|
||||
{
|
||||
if (null != paletteChanged)
|
||||
paletteChanged(palette);
|
||||
}
|
||||
|
||||
internal static void UpdateActiveGridPalette()
|
||||
{
|
||||
if (GridPaintPaletteWindow.instances.Count > 0)
|
||||
GridPaintPaletteWindow.instances[0].DelayedResetPreviewInstance();
|
||||
}
|
||||
|
||||
internal static void RepaintGridPaintPaletteWindow()
|
||||
{
|
||||
if (GridPaintPaletteWindow.instances.Count > 0)
|
||||
GridPaintPaletteWindow.instances[0].Repaint();
|
||||
}
|
||||
|
||||
internal static void UnlockGridPaintPaletteClipboardForEditing()
|
||||
{
|
||||
if (GridPaintPaletteWindow.instances.Count > 0)
|
||||
GridPaintPaletteWindow.instances[0].clipboardView.UnlockAndEdit();
|
||||
}
|
||||
|
||||
internal static void RegisterPainterInterest(Object painter)
|
||||
{
|
||||
instance.m_InterestedPainters.Add(painter);
|
||||
}
|
||||
|
||||
internal static void UnregisterPainterInterest(Object painter)
|
||||
{
|
||||
instance.m_InterestedPainters.Remove(painter);
|
||||
}
|
||||
|
||||
private bool hasInterestedPainters
|
||||
{
|
||||
get { return m_InterestedPainters.Count > 0; }
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,195 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
internal class GridPaletteAddPopup : EditorWindow
|
||||
{
|
||||
static class Styles
|
||||
{
|
||||
public static readonly GUIContent nameLabel = EditorGUIUtility.TrTextContent("Name");
|
||||
public static readonly GUIContent ok = EditorGUIUtility.TrTextContent("Create");
|
||||
public static readonly GUIContent cancel = EditorGUIUtility.TrTextContent("Cancel");
|
||||
public static readonly GUIContent header = EditorGUIUtility.TrTextContent("Create New Palette");
|
||||
public static readonly GUIContent gridLabel = EditorGUIUtility.TrTextContent("Grid");
|
||||
public static readonly GUIContent sizeLabel = EditorGUIUtility.TrTextContent("Cell Size");
|
||||
public static readonly GUIContent hexagonLabel = EditorGUIUtility.TrTextContent("Hexagon Type");
|
||||
public static readonly GUIContent[] hexagonSwizzleTypeLabel =
|
||||
{
|
||||
EditorGUIUtility.TrTextContent("Point Top"),
|
||||
EditorGUIUtility.TrTextContent("Flat Top"),
|
||||
};
|
||||
public static readonly GridLayout.CellSwizzle[] hexagonSwizzleTypeValue =
|
||||
{
|
||||
GridLayout.CellSwizzle.XYZ,
|
||||
GridLayout.CellSwizzle.YXZ,
|
||||
};
|
||||
|
||||
public static readonly GUIContent transparencySortModeLabel =
|
||||
EditorGUIUtility.TrTextContent("Sort Mode");
|
||||
public static readonly GUIContent transparencySortAxisLabel =
|
||||
EditorGUIUtility.TrTextContent("Sort Axis");
|
||||
}
|
||||
|
||||
private static long s_LastClosedTime;
|
||||
private string m_Name = "New Palette";
|
||||
private static GridPaletteAddPopup s_Instance;
|
||||
private GridPaintPaletteWindow m_Owner;
|
||||
private GridLayout.CellLayout m_Layout;
|
||||
private int m_HexagonLayout;
|
||||
private GridPalette.CellSizing m_CellSizing;
|
||||
private Vector3 m_CellSize;
|
||||
private TransparencySortMode m_TransparencySortMode;
|
||||
private Vector3 m_TransparencySortAxis = new Vector3(0f, 0f, 1f);
|
||||
|
||||
void Init(Rect buttonRect, GridPaintPaletteWindow owner)
|
||||
{
|
||||
m_Owner = owner;
|
||||
m_CellSize = new Vector3(1, 1, 0);
|
||||
buttonRect = GUIUtility.GUIToScreenRect(buttonRect);
|
||||
ShowAsDropDown(buttonRect, new Vector2(312, 185));
|
||||
}
|
||||
|
||||
internal void OnGUI()
|
||||
{
|
||||
GUI.Label(new Rect(0, 0, position.width, position.height), GUIContent.none, "grey_border");
|
||||
GUILayout.Space(3);
|
||||
|
||||
GUILayout.Label(Styles.header, EditorStyles.boldLabel);
|
||||
GUILayout.Space(4);
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label(Styles.nameLabel, GUILayout.Width(90f));
|
||||
m_Name = EditorGUILayout.TextField(m_Name);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label(Styles.gridLabel, GUILayout.Width(90f));
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var newLayout = (GridLayout.CellLayout)EditorGUILayout.EnumPopup(m_Layout);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
// Set useful user settings for certain layouts
|
||||
switch (newLayout)
|
||||
{
|
||||
case GridLayout.CellLayout.Rectangle:
|
||||
case GridLayout.CellLayout.Hexagon:
|
||||
{
|
||||
m_CellSizing = GridPalette.CellSizing.Automatic;
|
||||
m_CellSize = new Vector3(1, 1, 0);
|
||||
break;
|
||||
}
|
||||
case GridLayout.CellLayout.Isometric:
|
||||
{
|
||||
m_CellSizing = GridPalette.CellSizing.Manual;
|
||||
m_CellSize = new Vector3(1, 0.5f, 1);
|
||||
break;
|
||||
}
|
||||
case GridLayout.CellLayout.IsometricZAsY:
|
||||
{
|
||||
m_CellSizing = GridPalette.CellSizing.Manual;
|
||||
m_CellSize = new Vector3(1, 0.5f, 1);
|
||||
m_TransparencySortMode = TransparencySortMode.CustomAxis;
|
||||
m_TransparencySortAxis = new Vector3(0f, 1f, -0.25f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_Layout = newLayout;
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (m_Layout == GridLayout.CellLayout.Hexagon)
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
float oldLabelWidth = UnityEditor.EditorGUIUtility.labelWidth;
|
||||
EditorGUIUtility.labelWidth = 94;
|
||||
m_HexagonLayout = EditorGUILayout.Popup(Styles.hexagonLabel, m_HexagonLayout, Styles.hexagonSwizzleTypeLabel);
|
||||
EditorGUIUtility.labelWidth = oldLabelWidth;
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label(Styles.sizeLabel, GUILayout.Width(90f));
|
||||
m_CellSizing = (GridPalette.CellSizing)EditorGUILayout.EnumPopup(m_CellSizing);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
using (new EditorGUI.DisabledScope(m_CellSizing == GridPalette.CellSizing.Automatic))
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label(GUIContent.none, GUILayout.Width(90f));
|
||||
m_CellSize = EditorGUILayout.Vector3Field(GUIContent.none, m_CellSize);
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label(Styles.transparencySortModeLabel, GUILayout.Width(90f));
|
||||
m_TransparencySortMode = (TransparencySortMode)EditorGUILayout.EnumPopup(m_TransparencySortMode);
|
||||
GUILayout.EndHorizontal();
|
||||
using (new EditorGUI.DisabledScope(m_TransparencySortMode != TransparencySortMode.CustomAxis))
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label(Styles.transparencySortAxisLabel, GUILayout.Width(90f));
|
||||
m_TransparencySortAxis = EditorGUILayout.Vector3Field("", m_TransparencySortAxis);
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
// Cancel, Ok
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Space(10);
|
||||
if (GUILayout.Button(Styles.cancel))
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
using (new EditorGUI.DisabledScope(!Utils.Paths.IsValidAssetPath(m_Name)))
|
||||
{
|
||||
if (GUILayout.Button(Styles.ok))
|
||||
{
|
||||
s_LastClosedTime = System.DateTime.Now.Ticks / System.TimeSpan.TicksPerMillisecond;
|
||||
|
||||
// case 1077362: Close window to prevent overlap with OS folder window when saving new palette asset
|
||||
Close();
|
||||
|
||||
var swizzle = GridLayout.CellSwizzle.XYZ;
|
||||
if (m_Layout == GridLayout.CellLayout.Hexagon)
|
||||
swizzle = Styles.hexagonSwizzleTypeValue[m_HexagonLayout];
|
||||
|
||||
GameObject go = GridPaletteUtility.CreateNewPaletteAtCurrentFolder(m_Name, m_Layout, m_CellSizing, m_CellSize
|
||||
, swizzle, m_TransparencySortMode, m_TransparencySortAxis);
|
||||
if (go != null)
|
||||
{
|
||||
m_Owner.palette = go;
|
||||
m_Owner.Repaint();
|
||||
}
|
||||
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.Space(10);
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
internal static bool ShowAtPosition(Rect buttonRect, GridPaintPaletteWindow owner)
|
||||
{
|
||||
// We could not use realtimeSinceStartUp since it is set to 0 when entering/exitting playmode, we assume an increasing time when comparing time.
|
||||
long nowMilliSeconds = System.DateTime.Now.Ticks / System.TimeSpan.TicksPerMillisecond;
|
||||
bool justClosed = nowMilliSeconds < s_LastClosedTime + 50;
|
||||
if (!justClosed)
|
||||
{
|
||||
Event.current.Use();
|
||||
if (s_Instance == null)
|
||||
s_Instance = ScriptableObject.CreateInstance<GridPaletteAddPopup>();
|
||||
|
||||
s_Instance.Init(buttonRect, owner);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// namespace
|
@@ -0,0 +1,237 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEditorInternal;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
internal class GridPaletteBrushes : ScriptableSingleton<GridPaletteBrushes>
|
||||
{
|
||||
internal static readonly string s_SessionStateLastUsedBrush = "GridPaletteBrushes.LastUsedBrush";
|
||||
|
||||
private static readonly string s_LibraryPath = "Library/GridBrush";
|
||||
private static readonly string s_GridBrushExtension = ".asset";
|
||||
|
||||
private static bool s_RefreshCache;
|
||||
|
||||
[SerializeField] private List<GridBrushBase> m_Brushes;
|
||||
public static List<GridBrushBase> brushes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance.m_Brushes == null || instance.m_Brushes.Count == 0 || s_RefreshCache)
|
||||
{
|
||||
instance.RefreshBrushesCache();
|
||||
s_RefreshCache = false;
|
||||
}
|
||||
|
||||
return instance.m_Brushes;
|
||||
}
|
||||
}
|
||||
|
||||
private string[] m_BrushNames;
|
||||
public static string[] brushNames
|
||||
{
|
||||
get
|
||||
{
|
||||
return instance.m_BrushNames;
|
||||
}
|
||||
}
|
||||
|
||||
public GridBrushBase GetLastUsedBrush()
|
||||
{
|
||||
var sessionIndex = SessionState.GetInt(s_SessionStateLastUsedBrush, -1);
|
||||
if (sessionIndex >= 0 && brushes.Count > sessionIndex)
|
||||
return brushes[sessionIndex];
|
||||
return brushes[0];
|
||||
}
|
||||
|
||||
public void StoreLastUsedBrush(GridBrushBase brush)
|
||||
{
|
||||
int index = brushes.IndexOf(brush);
|
||||
SessionState.SetInt(s_SessionStateLastUsedBrush, index);
|
||||
}
|
||||
|
||||
public static Type GetDefaultBrushType()
|
||||
{
|
||||
Type defaultType = typeof(GridBrush);
|
||||
int count = 0;
|
||||
foreach (var type in TypeCache.GetTypesWithAttribute<CustomGridBrushAttribute>())
|
||||
{
|
||||
var attrs = type.GetCustomAttributes(typeof(CustomGridBrushAttribute), false) as CustomGridBrushAttribute[];
|
||||
if (attrs != null && attrs.Length > 0)
|
||||
{
|
||||
if (attrs[0].defaultBrush)
|
||||
{
|
||||
defaultType = type;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count > 1)
|
||||
{
|
||||
Debug.LogWarning("Multiple occurrences of defaultBrush == true found. It should only be declared once.");
|
||||
}
|
||||
return defaultType;
|
||||
}
|
||||
|
||||
public static void ActiveGridBrushAssetChanged()
|
||||
{
|
||||
if (GridPaintingState.gridBrush == null)
|
||||
return;
|
||||
|
||||
if (IsLibraryBrush(GridPaintingState.gridBrush))
|
||||
{
|
||||
instance.SaveLibraryGridBrushAsset(GridPaintingState.gridBrush);
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshBrushesCache()
|
||||
{
|
||||
if (m_Brushes == null)
|
||||
m_Brushes = new List<GridBrushBase>();
|
||||
|
||||
if (m_Brushes.Count == 0 || !(m_Brushes[0] is GridBrush))
|
||||
{
|
||||
Type defaultType = GetDefaultBrushType();
|
||||
GridBrushBase defaultBrush = LoadOrCreateLibraryGridBrushAsset(defaultType);
|
||||
m_Brushes.Insert(0, defaultBrush);
|
||||
m_Brushes[0].name = GetBrushDropdownName(m_Brushes[0]);
|
||||
}
|
||||
|
||||
var brushTypes = TypeCache.GetTypesDerivedFrom<GridBrushBase>().Where(t => t != typeof(GridBrush));
|
||||
foreach (var brushType in brushTypes)
|
||||
{
|
||||
if (IsDefaultInstanceVisibleGridBrushType(brushType))
|
||||
{
|
||||
var brush = LoadOrCreateLibraryGridBrushAsset(brushType);
|
||||
if (brush != null)
|
||||
m_Brushes.Add(brush);
|
||||
}
|
||||
}
|
||||
|
||||
string[] guids = AssetDatabase.FindAssets("t:GridBrushBase");
|
||||
foreach (string guid in guids)
|
||||
{
|
||||
string path = AssetDatabase.GUIDToAssetPath(guid);
|
||||
var brush = AssetDatabase.LoadAssetAtPath(path, typeof(GridBrushBase)) as GridBrushBase;
|
||||
if (brush != null && IsAssetVisibleGridBrushType(brush.GetType()))
|
||||
m_Brushes.Add(brush);
|
||||
}
|
||||
|
||||
m_BrushNames = new string[m_Brushes.Count];
|
||||
for (int i = 0; i < m_Brushes.Count; i++)
|
||||
{
|
||||
m_BrushNames[i] = m_Brushes[i].name;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsDefaultInstanceVisibleGridBrushType(Type brushType)
|
||||
{
|
||||
CustomGridBrushAttribute[] customBrushes = brushType.GetCustomAttributes(typeof(CustomGridBrushAttribute), false) as CustomGridBrushAttribute[];
|
||||
if (customBrushes != null && customBrushes.Length > 0)
|
||||
{
|
||||
return !customBrushes[0].hideDefaultInstance;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsAssetVisibleGridBrushType(Type brushType)
|
||||
{
|
||||
CustomGridBrushAttribute[] customBrushes = brushType.GetCustomAttributes(typeof(CustomGridBrushAttribute), false) as CustomGridBrushAttribute[];
|
||||
if (customBrushes != null && customBrushes.Length > 0)
|
||||
{
|
||||
return !customBrushes[0].hideAssetInstances;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SaveLibraryGridBrushAsset(GridBrushBase brush)
|
||||
{
|
||||
var gridBrushPath = GenerateGridBrushInstanceLibraryPath(brush.GetType());
|
||||
string folderPath = Path.GetDirectoryName(gridBrushPath);
|
||||
if (!Directory.Exists(folderPath))
|
||||
{
|
||||
Directory.CreateDirectory(folderPath);
|
||||
}
|
||||
InternalEditorUtility.SaveToSerializedFileAndForget(new[] { brush }, gridBrushPath, true);
|
||||
}
|
||||
|
||||
private GridBrushBase LoadOrCreateLibraryGridBrushAsset(Type brushType)
|
||||
{
|
||||
var serializedObjects = InternalEditorUtility.LoadSerializedFileAndForget(GenerateGridBrushInstanceLibraryPath(brushType));
|
||||
if (serializedObjects != null && serializedObjects.Length > 0)
|
||||
{
|
||||
GridBrushBase brush = serializedObjects[0] as GridBrushBase;
|
||||
if (brush != null && brush.GetType() == brushType)
|
||||
return brush;
|
||||
}
|
||||
return CreateLibraryGridBrushAsset(brushType);
|
||||
}
|
||||
|
||||
private GridBrushBase CreateLibraryGridBrushAsset(Type brushType)
|
||||
{
|
||||
GridBrushBase brush = ScriptableObject.CreateInstance(brushType) as GridBrushBase;
|
||||
brush.hideFlags = HideFlags.DontSave;
|
||||
brush.name = GetBrushDropdownName(brush);
|
||||
SaveLibraryGridBrushAsset(brush);
|
||||
return brush;
|
||||
}
|
||||
|
||||
private string GenerateGridBrushInstanceLibraryPath(Type brushType)
|
||||
{
|
||||
var path = FileUtil.CombinePaths(s_LibraryPath, brushType.ToString() + s_GridBrushExtension);
|
||||
path = FileUtil.NiceWinPath(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
private string GetBrushDropdownName(GridBrushBase brush)
|
||||
{
|
||||
// Asset Brushes use the asset name
|
||||
if (!IsLibraryBrush(brush))
|
||||
return brush.name;
|
||||
|
||||
// Library Brushes
|
||||
CustomGridBrushAttribute[] customBrushes = brush.GetType().GetCustomAttributes(typeof(CustomGridBrushAttribute), false) as CustomGridBrushAttribute[];
|
||||
if (customBrushes != null && customBrushes.Length > 0 && customBrushes[0].defaultName.Length > 0)
|
||||
return customBrushes[0].defaultName;
|
||||
|
||||
if (brush.GetType() == typeof(GridBrush))
|
||||
return "Default Brush";
|
||||
|
||||
return brush.GetType().Name;
|
||||
}
|
||||
|
||||
private static bool IsLibraryBrush(GridBrushBase brush)
|
||||
{
|
||||
return !AssetDatabase.Contains(brush);
|
||||
}
|
||||
|
||||
// TODO: Better way of clearing caches than AssetPostprocessor
|
||||
public class AssetProcessor : AssetPostprocessor
|
||||
{
|
||||
public override int GetPostprocessOrder()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromPath)
|
||||
{
|
||||
if (!GridPaintingState.savingPalette)
|
||||
FlushCache();
|
||||
}
|
||||
}
|
||||
|
||||
internal static void FlushCache()
|
||||
{
|
||||
s_RefreshCache = true;
|
||||
if (instance.m_Brushes != null)
|
||||
{
|
||||
instance.m_Brushes.Clear();
|
||||
GridPaintingState.FlushCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
/// <summary>
|
||||
/// Editor for GridPalette
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(GridPalette))]
|
||||
public class GridPaletteEditor : Editor
|
||||
{
|
||||
private static class Styles
|
||||
{
|
||||
public static readonly GUIContent cellSizingLabel = EditorGUIUtility.TrTextContent("Cell Sizing", "Determines the sizing of cells based on Tiles in the Palette");
|
||||
public static readonly GUIContent transparencySortModeLabel = EditorGUIUtility.TrTextContent("Sort Mode", "Determines the transparency sorting mode of renderers in the Palette");
|
||||
public static readonly GUIContent transparencySortAxisLabel = EditorGUIUtility.TrTextContent("Sort Axis", "Determines the sorting axis if the transparency sort mode is set to Custom Axis Sort");
|
||||
}
|
||||
|
||||
private SerializedProperty m_CellSizing;
|
||||
private SerializedProperty m_TransparencySortMode;
|
||||
private SerializedProperty m_TransparencySortAxis;
|
||||
|
||||
private int m_CustomAxisIndex;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
m_CellSizing = serializedObject.FindProperty("cellSizing");
|
||||
m_TransparencySortMode = serializedObject.FindProperty("m_TransparencySortMode");
|
||||
m_TransparencySortAxis = serializedObject.FindProperty("m_TransparencySortAxis");
|
||||
m_CustomAxisIndex = Array.IndexOf(Enum.GetValues(typeof(TransparencySortMode)), TransparencySortMode.CustomAxis);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the Inspector GUI for a GridPalette
|
||||
/// </summary>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
m_SerializedObject.Update();
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_CellSizing, Styles.cellSizingLabel);
|
||||
EditorGUILayout.PropertyField(m_TransparencySortMode, Styles.transparencySortModeLabel);
|
||||
using (new EditorGUI.DisabledScope(m_TransparencySortMode.enumValueIndex != m_CustomAxisIndex))
|
||||
{
|
||||
EditorGUILayout.PropertyField(m_TransparencySortAxis, Styles.transparencySortAxisLabel);
|
||||
}
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
m_SerializedObject.ApplyModifiedProperties();
|
||||
if (AssetDatabase.GetAssetPath(GridPaintingState.palette) == AssetDatabase.GetAssetPath(target))
|
||||
{
|
||||
GridPaintingState.UpdateActiveGridPalette();
|
||||
GridPaintingState.RepaintGridPaintPaletteWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,265 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Tilemaps;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility Class for creating Palettes
|
||||
/// </summary>
|
||||
public static class GridPaletteUtility
|
||||
{
|
||||
internal static readonly Vector3 defaultSortAxis = new Vector3(0f, 0f, 1f);
|
||||
|
||||
internal static RectInt GetBounds(GameObject palette)
|
||||
{
|
||||
if (palette == null)
|
||||
return new RectInt();
|
||||
|
||||
Vector2Int min = new Vector2Int(int.MaxValue, int.MaxValue);
|
||||
Vector2Int max = new Vector2Int(int.MinValue, int.MinValue);
|
||||
|
||||
foreach (var tilemap in palette.GetComponentsInChildren<Tilemap>())
|
||||
{
|
||||
Vector3Int p1 = tilemap.editorPreviewOrigin;
|
||||
Vector3Int p2 = p1 + tilemap.editorPreviewSize;
|
||||
Vector2Int tilemapMin = new Vector2Int(Mathf.Min(p1.x, p2.x), Mathf.Min(p1.y, p2.y));
|
||||
Vector2Int tilemapMax = new Vector2Int(Mathf.Max(p1.x, p2.x), Mathf.Max(p1.y, p2.y));
|
||||
min = new Vector2Int(Mathf.Min(min.x, tilemapMin.x), Mathf.Min(min.y, tilemapMin.y));
|
||||
max = new Vector2Int(Mathf.Max(max.x, tilemapMax.x), Mathf.Max(max.y, tilemapMax.y));
|
||||
}
|
||||
|
||||
return GridEditorUtility.GetMarqueeRect(min, max);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Palette Asset at the current selected folder path. This will show a popup allowing you to choose
|
||||
/// a different folder path for saving the Palette Asset if required.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the Palette Asset.</param>
|
||||
/// <param name="layout">Grid Layout of the Palette Asset.</param>
|
||||
/// <param name="cellSizing">Cell Sizing of the Palette Asset.</param>
|
||||
/// <param name="cellSize">Cell Size of the Palette Asset.</param>
|
||||
/// <param name="swizzle">Cell Swizzle of the Palette.</param>
|
||||
/// <returns>The created Palette Asset if successful.</returns>
|
||||
public static GameObject CreateNewPaletteAtCurrentFolder(string name, GridLayout.CellLayout layout, GridPalette.CellSizing cellSizing, Vector3 cellSize, GridLayout.CellSwizzle swizzle)
|
||||
{
|
||||
return CreateNewPaletteAtCurrentFolder(name, layout, cellSizing, cellSize, swizzle
|
||||
, TransparencySortMode.Default, defaultSortAxis);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Palette Asset at the current selected folder path. This will show a popup allowing you to choose
|
||||
/// a different folder path for saving the Palette Asset if required.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the Palette Asset.</param>
|
||||
/// <param name="layout">Grid Layout of the Palette Asset.</param>
|
||||
/// <param name="cellSizing">Cell Sizing of the Palette Asset.</param>
|
||||
/// <param name="cellSize">Cell Size of the Palette Asset.</param>
|
||||
/// <param name="swizzle">Cell Swizzle of the Palette.</param>
|
||||
/// <param name="sortMode">Transparency Sort Mode for the Palette</param>
|
||||
/// <param name="sortAxis">Transparency Sort Axis for the Palette</param>
|
||||
/// <returns>The created Palette Asset if successful.</returns>
|
||||
public static GameObject CreateNewPaletteAtCurrentFolder(string name
|
||||
, GridLayout.CellLayout layout
|
||||
, GridPalette.CellSizing cellSizing
|
||||
, Vector3 cellSize
|
||||
, GridLayout.CellSwizzle swizzle
|
||||
, TransparencySortMode sortMode
|
||||
, Vector3 sortAxis)
|
||||
{
|
||||
string defaultPath = ProjectBrowser.s_LastInteractedProjectBrowser ? ProjectBrowser.s_LastInteractedProjectBrowser.GetActiveFolderPath() : "Assets";
|
||||
string folderPath = EditorUtility.SaveFolderPanel("Create palette into folder ", defaultPath, "");
|
||||
folderPath = FileUtil.GetProjectRelativePath(folderPath);
|
||||
|
||||
if (string.IsNullOrEmpty(folderPath))
|
||||
return null;
|
||||
|
||||
return CreateNewPalette(folderPath, name, layout, cellSizing, cellSize, swizzle, sortMode, sortAxis);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Palette Asset at the given folder path.
|
||||
/// </summary>
|
||||
/// <param name="folderPath">Folder Path of the Palette Asset.</param>
|
||||
/// <param name="name">Name of the Palette Asset.</param>
|
||||
/// <param name="layout">Grid Layout of the Palette Asset.</param>
|
||||
/// <param name="cellSizing">Cell Sizing of the Palette Asset.</param>
|
||||
/// <param name="cellSize">Cell Size of the Palette Asset.</param>
|
||||
/// <param name="swizzle">Cell Swizzle of the Palette.</param>
|
||||
/// <returns>The created Palette Asset if successful.</returns>
|
||||
public static GameObject CreateNewPalette(string folderPath
|
||||
, string name
|
||||
, GridLayout.CellLayout layout
|
||||
, GridPalette.CellSizing cellSizing
|
||||
, Vector3 cellSize
|
||||
, GridLayout.CellSwizzle swizzle)
|
||||
{
|
||||
return CreateNewPalette(folderPath, name, layout, cellSizing, cellSize, swizzle,
|
||||
TransparencySortMode.Default, defaultSortAxis);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Palette Asset at the given folder path.
|
||||
/// </summary>
|
||||
/// <param name="folderPath">Folder Path of the Palette Asset.</param>
|
||||
/// <param name="name">Name of the Palette Asset.</param>
|
||||
/// <param name="layout">Grid Layout of the Palette Asset.</param>
|
||||
/// <param name="cellSizing">Cell Sizing of the Palette Asset.</param>
|
||||
/// <param name="cellSize">Cell Size of the Palette Asset.</param>
|
||||
/// <param name="swizzle">Cell Swizzle of the Palette.</param>
|
||||
/// <param name="sortMode">Transparency Sort Mode for the Palette</param>
|
||||
/// <param name="sortAxis">Transparency Sort Axis for the Palette</param>
|
||||
/// <returns>The created Palette Asset if successful.</returns>
|
||||
public static GameObject CreateNewPalette(string folderPath
|
||||
, string name
|
||||
, GridLayout.CellLayout layout
|
||||
, GridPalette.CellSizing cellSizing
|
||||
, Vector3 cellSize
|
||||
, GridLayout.CellSwizzle swizzle
|
||||
, TransparencySortMode sortMode
|
||||
, Vector3 sortAxis)
|
||||
{
|
||||
GameObject temporaryGO = new GameObject(name);
|
||||
Grid grid = temporaryGO.AddComponent<Grid>();
|
||||
|
||||
// We set size to kEpsilon to mark this as new uninitialized palette
|
||||
// Nice default size can be decided when first asset is dragged in
|
||||
grid.cellSize = cellSize;
|
||||
grid.cellLayout = layout;
|
||||
grid.cellSwizzle = swizzle;
|
||||
CreateNewLayer(temporaryGO, "Layer1", layout);
|
||||
|
||||
string path = AssetDatabase.GenerateUniqueAssetPath(folderPath + "/" + name + ".prefab");
|
||||
|
||||
Object prefab = PrefabUtility.SaveAsPrefabAssetAndConnect(temporaryGO, path, InteractionMode.AutomatedAction);
|
||||
GridPalette palette = CreateGridPalette(cellSizing, sortMode, sortAxis);
|
||||
AssetDatabase.AddObjectToAsset(palette, prefab);
|
||||
PrefabUtility.ApplyPrefabInstance(temporaryGO, InteractionMode.AutomatedAction);
|
||||
AssetDatabase.Refresh();
|
||||
|
||||
Object.DestroyImmediate(temporaryGO);
|
||||
return AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
||||
}
|
||||
|
||||
private static GameObject CreateNewLayer(GameObject paletteGO, string name, GridLayout.CellLayout layout)
|
||||
{
|
||||
GameObject newLayerGO = new GameObject(name);
|
||||
var tilemap = newLayerGO.AddComponent<Tilemap>();
|
||||
var renderer = newLayerGO.AddComponent<TilemapRenderer>();
|
||||
newLayerGO.transform.parent = paletteGO.transform;
|
||||
newLayerGO.layer = paletteGO.layer;
|
||||
|
||||
// Set defaults for certain layouts
|
||||
switch (layout)
|
||||
{
|
||||
case GridLayout.CellLayout.Hexagon:
|
||||
{
|
||||
tilemap.tileAnchor = Vector3.zero;
|
||||
break;
|
||||
}
|
||||
case GridLayout.CellLayout.Isometric:
|
||||
{
|
||||
renderer.sortOrder = TilemapRenderer.SortOrder.TopRight;
|
||||
break;
|
||||
}
|
||||
case GridLayout.CellLayout.IsometricZAsY:
|
||||
{
|
||||
renderer.sortOrder = TilemapRenderer.SortOrder.TopRight;
|
||||
renderer.mode = TilemapRenderer.Mode.Individual;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return newLayerGO;
|
||||
}
|
||||
|
||||
internal static GridPalette GetGridPaletteFromPaletteAsset(Object palette)
|
||||
{
|
||||
string assetPath = AssetDatabase.GetAssetPath(palette);
|
||||
GridPalette paletteAsset = AssetDatabase.LoadAssetAtPath<GridPalette>(assetPath);
|
||||
return paletteAsset;
|
||||
}
|
||||
|
||||
internal static GridPalette CreateGridPalette(GridPalette.CellSizing cellSizing)
|
||||
{
|
||||
return CreateGridPalette(cellSizing, TransparencySortMode.Default, defaultSortAxis);
|
||||
}
|
||||
|
||||
internal static GridPalette CreateGridPalette(GridPalette.CellSizing cellSizing
|
||||
, TransparencySortMode sortMode
|
||||
, Vector3 sortAxis
|
||||
)
|
||||
{
|
||||
var palette = ScriptableObject.CreateInstance<GridPalette>();
|
||||
palette.name = "Palette Settings";
|
||||
palette.cellSizing = cellSizing;
|
||||
palette.transparencySortMode = sortMode;
|
||||
palette.transparencySortAxis = sortAxis;
|
||||
return palette;
|
||||
}
|
||||
|
||||
internal static Vector3 CalculateAutoCellSize(Grid grid, Vector3 defaultValue)
|
||||
{
|
||||
Tilemap[] tilemaps = grid.GetComponentsInChildren<Tilemap>();
|
||||
Sprite[] sprites = null;
|
||||
var maxSize = Vector2.negativeInfinity;
|
||||
var minSize = Vector2.positiveInfinity;
|
||||
|
||||
// Get minimum and maximum sizes for Sprites
|
||||
foreach (var tilemap in tilemaps)
|
||||
{
|
||||
var spriteCount = tilemap.GetUsedSpritesCount();
|
||||
if (sprites == null || sprites.Length < spriteCount)
|
||||
sprites = new Sprite[spriteCount];
|
||||
tilemap.GetUsedSpritesNonAlloc(sprites);
|
||||
for (int i = 0; i < spriteCount; ++i)
|
||||
{
|
||||
Sprite sprite = sprites[i];
|
||||
if (sprite != null)
|
||||
{
|
||||
var cellSize = new Vector3(sprite.rect.width, sprite.rect.height, 0f) / sprite.pixelsPerUnit;
|
||||
if (tilemap.cellSwizzle == GridLayout.CellSwizzle.YXZ)
|
||||
{
|
||||
var swap = cellSize.x;
|
||||
cellSize.x = cellSize.y;
|
||||
cellSize.y = swap;
|
||||
}
|
||||
minSize.x = Mathf.Min(cellSize.x, minSize.x);
|
||||
minSize.y = Mathf.Min(cellSize.y, minSize.y);
|
||||
maxSize.x = Mathf.Max(cellSize.x, maxSize.x);
|
||||
maxSize.y = Mathf.Max(cellSize.y, maxSize.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Validate that Sprites are in multiples of sizes
|
||||
foreach (var tilemap in tilemaps)
|
||||
{
|
||||
var spriteCount = tilemap.GetUsedSpritesCount();
|
||||
if (sprites == null || sprites.Length < spriteCount)
|
||||
sprites = new Sprite[spriteCount];
|
||||
tilemap.GetUsedSpritesNonAlloc(sprites);
|
||||
for (int i = 0; i < spriteCount; ++i)
|
||||
{
|
||||
Sprite sprite = sprites[i];
|
||||
if (sprite != null)
|
||||
{
|
||||
var cellSize = new Vector3(sprite.rect.width, sprite.rect.height, 0f) / sprite.pixelsPerUnit;
|
||||
if (tilemap.cellSwizzle == GridLayout.CellSwizzle.YXZ)
|
||||
{
|
||||
var swap = cellSize.x;
|
||||
cellSize.x = cellSize.y;
|
||||
cellSize.y = swap;
|
||||
}
|
||||
// Return maximum size if sprites are not multiples of the smallest size
|
||||
if (cellSize.x % minSize.x > 0)
|
||||
return maxSize.x * maxSize.y <= 0f ? defaultValue : new Vector3(maxSize.x, maxSize.y, 0f);
|
||||
if (cellSize.y % minSize.y > 0)
|
||||
return maxSize.x * maxSize.y <= 0f ? defaultValue : new Vector3(maxSize.x, maxSize.y, 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
return minSize.x * minSize.y <= 0f || minSize == Vector2.positiveInfinity ? defaultValue : new Vector3(minSize.x, minSize.y, 0f);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
internal class GridPalettes : ScriptableSingleton<GridPalettes>
|
||||
{
|
||||
private static bool s_RefreshCache;
|
||||
|
||||
[SerializeField] private List<GameObject> m_PalettesCache;
|
||||
|
||||
public static List<GameObject> palettes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance.m_PalettesCache == null || s_RefreshCache)
|
||||
{
|
||||
instance.RefreshPalettesCache();
|
||||
s_RefreshCache = false;
|
||||
}
|
||||
|
||||
return instance.m_PalettesCache;
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshPalettesCache()
|
||||
{
|
||||
if (instance.m_PalettesCache == null)
|
||||
instance.m_PalettesCache = new List<GameObject>();
|
||||
|
||||
string[] guids = AssetDatabase.FindAssets("t:GridPalette");
|
||||
foreach (string guid in guids)
|
||||
{
|
||||
string path = AssetDatabase.GUIDToAssetPath(guid);
|
||||
GridPalette paletteAsset = AssetDatabase.LoadAssetAtPath(path, typeof(GridPalette)) as GridPalette;
|
||||
if (paletteAsset != null)
|
||||
{
|
||||
string assetPath = AssetDatabase.GetAssetPath(paletteAsset);
|
||||
GameObject palette = AssetDatabase.LoadMainAssetAtPath(assetPath) as GameObject;
|
||||
if (palette != null)
|
||||
{
|
||||
m_PalettesCache.Add(palette);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_PalettesCache.Sort((x, y) => String.Compare(x.name, y.name, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public class AssetProcessor : AssetPostprocessor
|
||||
{
|
||||
public override int GetPostprocessOrder()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromPath)
|
||||
{
|
||||
if (!GridPaintingState.savingPalette)
|
||||
CleanCache();
|
||||
}
|
||||
}
|
||||
|
||||
internal static void CleanCache()
|
||||
{
|
||||
instance.m_PalettesCache = null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
internal class GridPalettesDropdown : FlexibleMenu
|
||||
{
|
||||
public GridPalettesDropdown(IFlexibleMenuItemProvider itemProvider, int selectionIndex, FlexibleMenuModifyItemUI modifyItemUi, Action<int, object> itemClickedCallback, float minWidth)
|
||||
: base(itemProvider, selectionIndex, modifyItemUi, itemClickedCallback)
|
||||
{
|
||||
minTextWidth = minWidth;
|
||||
}
|
||||
|
||||
internal class MenuItemProvider : IFlexibleMenuItemProvider
|
||||
{
|
||||
public int Count()
|
||||
{
|
||||
return GridPalettes.palettes.Count + 1;
|
||||
}
|
||||
|
||||
public object GetItem(int index)
|
||||
{
|
||||
if (index < GridPalettes.palettes.Count)
|
||||
return GridPalettes.palettes[index];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public int Add(object obj)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Replace(int index, object newPresetObject)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Remove(int index)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public object Create()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Move(int index, int destIndex, bool insertAfterDestIndex)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string GetName(int index)
|
||||
{
|
||||
if (index < GridPalettes.palettes.Count)
|
||||
return GridPalettes.palettes[index].name;
|
||||
else if (index == GridPalettes.palettes.Count)
|
||||
return "Create New Palette";
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public bool IsModificationAllowed(int index)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public int[] GetSeperatorIndices()
|
||||
{
|
||||
return new int[] { GridPalettes.palettes.Count - 1 };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Scripting.APIUpdating;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
/// <summary>Stores the selection made on a GridLayout.</summary>
|
||||
[MovedFrom(true, "UnityEditor", "UnityEditor")]
|
||||
[Serializable]
|
||||
public class GridSelection : ScriptableObject
|
||||
{
|
||||
public static string kUpdateGridSelection = L10n.Tr("Update Grid Selection");
|
||||
|
||||
/// <summary>Callback for when the active GridSelection has changed.</summary>
|
||||
public static event Action gridSelectionChanged;
|
||||
[SerializeField]
|
||||
private BoundsInt m_Position;
|
||||
private GameObject m_Target;
|
||||
[SerializeField]
|
||||
private Object m_PreviousSelection;
|
||||
|
||||
/// <summary>Whether there is an active GridSelection made on a GridLayout.</summary>
|
||||
public static bool active { get { return Selection.activeObject is GridSelection && selection.m_Target != null; } }
|
||||
|
||||
private static GridSelection selection { get { return Selection.activeObject as GridSelection; } }
|
||||
|
||||
/// <summary>The cell coordinates of the active GridSelection made on the GridLayout.</summary>
|
||||
public static BoundsInt position
|
||||
{
|
||||
get { return selection != null ? selection.m_Position : new BoundsInt(); }
|
||||
set
|
||||
{
|
||||
if (selection != null && selection.m_Position != value)
|
||||
{
|
||||
RegisterUndo();
|
||||
selection.m_Position = value;
|
||||
if (gridSelectionChanged != null)
|
||||
gridSelectionChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The GameObject of the GridLayout where the active GridSelection was made.</summary>
|
||||
public static GameObject target { get { return selection != null ? selection.m_Target : null; } }
|
||||
|
||||
/// <summary>The Grid of the target of the active GridSelection.</summary>
|
||||
public static Grid grid { get { return selection != null && selection.m_Target != null ? selection.m_Target.GetComponentInParent<Grid>() : null; } }
|
||||
|
||||
/// <summary>Creates a new GridSelection and sets it as the active GridSelection.</summary>
|
||||
/// <param name="target">The target GameObject for the GridSelection.</param>
|
||||
/// <param name="bounds">The cell coordinates of selection made.</param>
|
||||
public static void Select(Object target, BoundsInt bounds)
|
||||
{
|
||||
GridSelection newSelection = CreateInstance<GridSelection>();
|
||||
newSelection.m_PreviousSelection = Selection.activeObject;
|
||||
newSelection.m_Target = target as GameObject;
|
||||
newSelection.m_Position = bounds;
|
||||
Selection.activeObject = newSelection;
|
||||
if (gridSelectionChanged != null)
|
||||
gridSelectionChanged();
|
||||
}
|
||||
|
||||
/// <summary>Clears the active GridSelection.</summary>
|
||||
public static void Clear()
|
||||
{
|
||||
if (active)
|
||||
{
|
||||
RegisterUndo();
|
||||
selection.m_Position = new BoundsInt();
|
||||
Selection.activeObject = selection.m_PreviousSelection;
|
||||
if (gridSelectionChanged != null)
|
||||
gridSelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RegisterUndo()
|
||||
{
|
||||
if (selection != null)
|
||||
Undo.RegisterCompleteObjectUndo(selection, kUpdateGridSelection);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
[CustomEditor(typeof(GridSelection))]
|
||||
internal class GridSelectionEditor : Editor
|
||||
{
|
||||
private const float iconSize = 32f;
|
||||
|
||||
static class Styles
|
||||
{
|
||||
public static readonly GUIContent gridSelectionLabel = EditorGUIUtility.TrTextContent("Grid Selection");
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
// Give focus to Inspector window for keyboard actions
|
||||
EditorWindow.FocusWindowIfItsOpen<InspectorWindow>();
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
if (GridPaintingState.activeBrushEditor && GridSelection.active)
|
||||
{
|
||||
GridPaintingState.activeBrushEditor.OnSelectionInspectorGUI();
|
||||
}
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if (GridPaintingState.IsPartOfActivePalette(GridSelection.target))
|
||||
{
|
||||
GridPaintingState.UnlockGridPaintPaletteClipboardForEditing();
|
||||
GridPaintingState.RepaintGridPaintPaletteWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnHeaderGUI()
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal(EditorStyles.inspectorBig);
|
||||
Texture2D icon = AssetPreview.GetMiniTypeThumbnail(typeof(Grid));
|
||||
GUILayout.Label(icon, GUILayout.Width(iconSize), GUILayout.Height(iconSize));
|
||||
EditorGUILayout.BeginVertical();
|
||||
GUILayout.Label(Styles.gridSelectionLabel);
|
||||
GridSelection.position = EditorGUILayout.BoundsIntField(GUIContent.none, GridSelection.position);
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
DrawHeaderHelpAndSettingsGUI(GUILayoutUtility.GetLastRect());
|
||||
}
|
||||
|
||||
public bool HasFrameBounds()
|
||||
{
|
||||
return GridSelection.active;
|
||||
}
|
||||
|
||||
public Bounds OnGetFrameBounds()
|
||||
{
|
||||
Bounds bounds = new Bounds();
|
||||
if (GridSelection.active)
|
||||
{
|
||||
Vector3Int gridMin = GridSelection.position.min;
|
||||
Vector3Int gridMax = GridSelection.position.max;
|
||||
|
||||
Vector3 min = GridSelection.grid.CellToWorld(gridMin);
|
||||
Vector3 max = GridSelection.grid.CellToWorld(gridMax);
|
||||
|
||||
bounds = new Bounds((max + min) * .5f, max - min);
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
internal static class DefaultAssetCreation
|
||||
{
|
||||
const int k_TilePaletteAssetMenuPriority = 4;
|
||||
|
||||
static internal Action<int, ProjectWindowCallback.EndNameEditAction, string, Texture2D, string> StartNewAssetNameEditingDelegate = ProjectWindowUtil.StartNameEditingIfProjectWindowExists;
|
||||
|
||||
[MenuItem("Assets/Create/2D/Tile Palette/Rectangular", priority = k_TilePaletteAssetMenuPriority)]
|
||||
static void MenuItem_AssetsCreate2DTilePaletteRectangular(MenuCommand menuCommand)
|
||||
{
|
||||
CreateAssetObject("Rectangular Palette", GridLayout.CellLayout.Rectangle, GridLayout.CellSwizzle.XYZ, GridPalette.CellSizing.Automatic, new Vector3(1, 1, 0));
|
||||
}
|
||||
|
||||
[MenuItem("Assets/Create/2D/Tile Palette/Hexagonal Flat-Top", priority = k_TilePaletteAssetMenuPriority)]
|
||||
static void MenuItem_AssetsCreate2DTilePaletteHexagonalFlatTop(MenuCommand menuCommand)
|
||||
{
|
||||
CreateAssetObject("Hexagon Flat-Top Palette", GridLayout.CellLayout.Hexagon, GridLayout.CellSwizzle.YXZ, GridPalette.CellSizing.Manual, new Vector3(0.8659766f, 1, 0));
|
||||
}
|
||||
|
||||
[MenuItem("Assets/Create/2D/Tile Palette/Hexagonal Pointed-Top", priority = k_TilePaletteAssetMenuPriority)]
|
||||
static void MenuItem_AssetsCreate2DTilePaletteHexagonalPointedTop(MenuCommand menuCommand)
|
||||
{
|
||||
CreateAssetObject("Hexagon Pointed-Top Palette", GridLayout.CellLayout.Hexagon, GridLayout.CellSwizzle.XYZ, GridPalette.CellSizing.Manual, new Vector3(0.8659766f, 1, 0));
|
||||
}
|
||||
|
||||
[MenuItem("Assets/Create/2D/Tile Palette/Isometric", priority = k_TilePaletteAssetMenuPriority)]
|
||||
static void MenuItem_AssetsCreate2DTilePaletteIsometric(MenuCommand menuCommand)
|
||||
{
|
||||
CreateAssetObject("Isometric Palette", GridLayout.CellLayout.Isometric, GridLayout.CellSwizzle.XYZ, GridPalette.CellSizing.Manual, new Vector3(1, 0.5f, 0));
|
||||
}
|
||||
|
||||
static void CreateAssetObject(string defaultAssetName, GridLayout.CellLayout layout, GridLayout.CellSwizzle swizzle, GridPalette.CellSizing cellSizing, Vector3 cellSize)
|
||||
{
|
||||
var assetSelectionPath = AssetDatabase.GetAssetPath(Selection.activeObject);
|
||||
var isFolder = false;
|
||||
if (!string.IsNullOrEmpty(assetSelectionPath))
|
||||
isFolder = File.GetAttributes(assetSelectionPath).HasFlag(FileAttributes.Directory);
|
||||
var path = ProjectWindowUtil.GetActiveFolderPath();
|
||||
if (isFolder)
|
||||
{
|
||||
path = assetSelectionPath;
|
||||
}
|
||||
|
||||
var destName = AssetDatabase.GenerateUniqueAssetPath(Path.Combine(path, defaultAssetName));
|
||||
var icon = EditorGUIUtility.IconContent<GameObject>().image as Texture2D;
|
||||
CreateAssetEndNameEditAction action = ScriptableObject.CreateInstance<CreateAssetEndNameEditAction>();
|
||||
action.swizzle = swizzle;
|
||||
action.layout = layout;
|
||||
action.cellSize = cellSize;
|
||||
action.cellSizing = cellSizing;
|
||||
StartNewAssetNameEditingDelegate(0, action, destName, icon, "");
|
||||
}
|
||||
|
||||
internal class CreateAssetEndNameEditAction : ProjectWindowCallback.EndNameEditAction
|
||||
{
|
||||
public GridLayout.CellLayout layout { get; set; }
|
||||
public GridLayout.CellSwizzle swizzle { get; set; }
|
||||
public Vector3 cellSize { get; set; }
|
||||
public GridPalette.CellSizing cellSizing { get; set; }
|
||||
|
||||
public override void Action(int instanceId, string pathName, string resourceFile)
|
||||
{
|
||||
GridPaletteUtility.CreateNewPalette(Path.GetDirectoryName(pathName), Path.GetFileName(pathName), layout,
|
||||
cellSizing, cellSize, swizzle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,124 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Tilemaps;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
static class GameObjectCreation
|
||||
{
|
||||
private static class Styles
|
||||
{
|
||||
public static readonly string pointTopHexagonCreateUndo = L10n.Tr("Hexagonal Point Top Tilemap");
|
||||
public static readonly string flatTopHexagonCreateUndo = L10n.Tr("Hexagonal Flat Top Tilemap");
|
||||
public static readonly string isometricCreateUndo = L10n.Tr("Isometric Tilemap");
|
||||
public static readonly string isometricZAsYCreateUndo = L10n.Tr("Isometric Z As Y Tilemap");
|
||||
}
|
||||
|
||||
const int k_MenuPriority = 3;
|
||||
|
||||
[MenuItem("GameObject/2D Object/Tilemap/Rectangular", priority = k_MenuPriority)]
|
||||
internal static void CreateRectangularTilemap()
|
||||
{
|
||||
var root = FindOrCreateRootGrid();
|
||||
var uniqueName = GameObjectUtility.GetUniqueNameForSibling(root.transform, "Tilemap");
|
||||
var tilemapGO = ObjectFactory.CreateGameObject(uniqueName, typeof(Tilemap), typeof(TilemapRenderer));
|
||||
Undo.SetTransformParent(tilemapGO.transform, root.transform, "");
|
||||
tilemapGO.transform.position = Vector3.zero;
|
||||
|
||||
Selection.activeGameObject = tilemapGO;
|
||||
Undo.SetCurrentGroupName("Create Tilemap");
|
||||
}
|
||||
|
||||
[MenuItem("GameObject/2D Object/Tilemap/Hexagonal - Pointed-Top", priority = k_MenuPriority)]
|
||||
internal static void CreateHexagonalPointTopTilemap()
|
||||
{
|
||||
CreateHexagonalTilemap(GridLayout.CellSwizzle.XYZ, Styles.pointTopHexagonCreateUndo, new Vector3(0.8659766f, 1, 1));
|
||||
}
|
||||
|
||||
[MenuItem("GameObject/2D Object/Tilemap/Hexagonal - Flat-Top", priority = k_MenuPriority)]
|
||||
internal static void CreateHexagonalFlatTopTilemap()
|
||||
{
|
||||
CreateHexagonalTilemap(GridLayout.CellSwizzle.YXZ, Styles.flatTopHexagonCreateUndo, new Vector3(0.8659766f, 1, 1));
|
||||
}
|
||||
|
||||
[MenuItem("GameObject/2D Object/Tilemap/Isometric", priority = k_MenuPriority)]
|
||||
internal static void CreateIsometricTilemap()
|
||||
{
|
||||
CreateIsometricTilemap(GridLayout.CellLayout.Isometric, Styles.isometricCreateUndo);
|
||||
}
|
||||
|
||||
[MenuItem("GameObject/2D Object/Tilemap/Isometric Z as Y", priority = k_MenuPriority)]
|
||||
internal static void CreateIsometricZAsYTilemap()
|
||||
{
|
||||
CreateIsometricTilemap(GridLayout.CellLayout.IsometricZAsY, Styles.isometricZAsYCreateUndo);
|
||||
}
|
||||
|
||||
private static void CreateIsometricTilemap(GridLayout.CellLayout isometricLayout, string undoMessage)
|
||||
{
|
||||
var root = FindOrCreateRootGrid();
|
||||
var uniqueName = GameObjectUtility.GetUniqueNameForSibling(root.transform, "Tilemap");
|
||||
var tilemapGO = ObjectFactory.CreateGameObject(uniqueName, typeof(Tilemap), typeof(TilemapRenderer));
|
||||
tilemapGO.transform.SetParent(root.transform);
|
||||
tilemapGO.transform.position = Vector3.zero;
|
||||
|
||||
var grid = root.GetComponent<Grid>();
|
||||
// Case 1071703: Do not reset cell size if adding a new Tilemap to an existing Grid of the same layout
|
||||
if (isometricLayout != grid.cellLayout)
|
||||
{
|
||||
grid.cellLayout = isometricLayout;
|
||||
grid.cellSize = new Vector3(1.0f, 0.5f, 1.0f);
|
||||
}
|
||||
|
||||
var tilemapRenderer = tilemapGO.GetComponent<TilemapRenderer>();
|
||||
tilemapRenderer.sortOrder = TilemapRenderer.SortOrder.TopRight;
|
||||
|
||||
Selection.activeGameObject = tilemapGO;
|
||||
Undo.RegisterCreatedObjectUndo(tilemapGO, undoMessage);
|
||||
}
|
||||
|
||||
private static void CreateHexagonalTilemap(GridLayout.CellSwizzle swizzle, string undoMessage, Vector3 cellSize)
|
||||
{
|
||||
var root = FindOrCreateRootGrid();
|
||||
var uniqueName = GameObjectUtility.GetUniqueNameForSibling(root.transform, "Tilemap");
|
||||
var tilemapGO = ObjectFactory.CreateGameObject(uniqueName, typeof(Tilemap), typeof(TilemapRenderer));
|
||||
tilemapGO.transform.SetParent(root.transform);
|
||||
tilemapGO.transform.position = Vector3.zero;
|
||||
var grid = root.GetComponent<Grid>();
|
||||
grid.cellLayout = Grid.CellLayout.Hexagon;
|
||||
grid.cellSwizzle = swizzle;
|
||||
grid.cellSize = cellSize;
|
||||
var tilemap = tilemapGO.GetComponent<Tilemap>();
|
||||
tilemap.tileAnchor = Vector3.zero;
|
||||
Selection.activeGameObject = tilemapGO;
|
||||
Undo.RegisterCreatedObjectUndo(tilemapGO, undoMessage);
|
||||
}
|
||||
|
||||
private static GameObject FindOrCreateRootGrid()
|
||||
{
|
||||
GameObject gridGO = null;
|
||||
|
||||
// Check if active object has a Grid and can be a parent for the Tile Map
|
||||
if (Selection.activeObject is GameObject)
|
||||
{
|
||||
var go = (GameObject)Selection.activeObject;
|
||||
var parentGrid = go.GetComponentInParent<Grid>();
|
||||
if (parentGrid != null)
|
||||
{
|
||||
gridGO = parentGrid.gameObject;
|
||||
}
|
||||
}
|
||||
|
||||
// If neither the active object nor its parent has a grid, create a grid for the tilemap
|
||||
if (gridGO == null)
|
||||
{
|
||||
gridGO = ObjectFactory.CreateGameObject("Grid", typeof(Grid));
|
||||
gridGO.transform.position = Vector3.zero;
|
||||
|
||||
var grid = gridGO.GetComponent<Grid>();
|
||||
grid.cellSize = new Vector3(1.0f, 1.0f, 0.0f);
|
||||
Undo.SetCurrentGroupName("Create Grid");
|
||||
}
|
||||
|
||||
return gridGO;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,569 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Tilemaps;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
internal abstract class PaintableGrid : ScriptableObject
|
||||
{
|
||||
private const int k_MaxMouseCellDelta = 500;
|
||||
|
||||
public enum MarqueeType { None = 0, Pick, Box, Select }
|
||||
private int m_PermanentControlID;
|
||||
|
||||
public abstract void Repaint();
|
||||
protected abstract void RegisterUndo();
|
||||
protected abstract void Paint(Vector3Int position);
|
||||
protected abstract void Erase(Vector3Int position);
|
||||
protected abstract void BoxFill(BoundsInt position);
|
||||
protected abstract void BoxErase(BoundsInt position);
|
||||
protected abstract void FloodFill(Vector3Int position);
|
||||
protected abstract void PickBrush(BoundsInt position, Vector3Int pickStart);
|
||||
protected abstract void Select(BoundsInt position);
|
||||
protected abstract void Move(BoundsInt from, BoundsInt to);
|
||||
protected abstract void MoveStart(BoundsInt position);
|
||||
protected abstract void MoveEnd(BoundsInt position);
|
||||
protected abstract bool ValidateFloodFillPosition(Vector3Int position);
|
||||
protected abstract Vector2Int ScreenToGrid(Vector2 screenPosition);
|
||||
protected abstract bool PickingIsDefaultTool();
|
||||
protected abstract bool CanPickOutsideEditMode();
|
||||
protected abstract Grid.CellLayout CellLayout();
|
||||
protected abstract void ClearGridSelection();
|
||||
|
||||
protected virtual void OnBrushPickStarted() {}
|
||||
protected virtual void OnBrushPickDragged(BoundsInt position) {}
|
||||
|
||||
protected virtual void OnBrushPickCancelled() {}
|
||||
|
||||
protected virtual void OnEditStart() {}
|
||||
protected virtual void OnEditEnd() {}
|
||||
|
||||
internal static PaintableGrid s_LastActivePaintableGrid;
|
||||
|
||||
private Vector2Int m_PreviousMouseGridPosition;
|
||||
private Vector2Int m_MouseGridPosition;
|
||||
private bool m_MouseGridPositionChanged;
|
||||
private bool m_PositionChangeRepaintDone;
|
||||
protected Vector2Int? m_PreviousMove;
|
||||
protected Vector2Int? m_MarqueeStart;
|
||||
private MarqueeType m_MarqueeType = MarqueeType.None;
|
||||
private bool m_IsExecuting;
|
||||
private Type m_TypeBeforeExecution;
|
||||
private int m_ZPosition;
|
||||
|
||||
public Vector2Int mouseGridPosition { get { return m_MouseGridPosition; } }
|
||||
public bool isPicking { get { return m_MarqueeType == MarqueeType.Pick; } }
|
||||
public bool isBoxing { get { return m_MarqueeType == MarqueeType.Box; } }
|
||||
public GridLayout.CellLayout cellLayout { get { return CellLayout(); } }
|
||||
public int zPosition { get { return m_ZPosition; } set { m_ZPosition = value; } }
|
||||
|
||||
protected bool executing
|
||||
{
|
||||
get { return m_IsExecuting; }
|
||||
set
|
||||
{
|
||||
var isExecuting = value && isHotControl;
|
||||
if (isExecuting != m_IsExecuting)
|
||||
{
|
||||
if (isExecuting)
|
||||
OnEditStart();
|
||||
else
|
||||
OnEditEnd();
|
||||
}
|
||||
m_IsExecuting = isExecuting;
|
||||
}
|
||||
}
|
||||
|
||||
protected bool isNearestControl { get { return HandleUtility.nearestControl == m_PermanentControlID; } }
|
||||
protected bool isHotControl { get { return GUIUtility.hotControl == m_PermanentControlID; } }
|
||||
protected bool mouseGridPositionChanged { get { return m_MouseGridPositionChanged; } }
|
||||
protected bool inEditMode { get { return PaintableGrid.InGridEditMode(); } }
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
m_PermanentControlID = GUIUtility.GetPermanentControlID();
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OnGUI()
|
||||
{
|
||||
var evt = Event.current;
|
||||
|
||||
if (evt.type == EventType.Layout)
|
||||
HandleUtility.AddDefaultControl(m_PermanentControlID);
|
||||
|
||||
if (CanPickOutsideEditMode() || inEditMode)
|
||||
HandleBrushPicking();
|
||||
|
||||
if (inEditMode)
|
||||
{
|
||||
HandleBrushPaintAndErase();
|
||||
HandleSelectTool();
|
||||
HandleMoveTool();
|
||||
HandleEditModeChange();
|
||||
HandleFloodFill();
|
||||
HandleBoxTool();
|
||||
}
|
||||
else if (isHotControl && !IsPickingEvent(evt))
|
||||
{
|
||||
// Release hot control if it still has it while not in picking or grid edit mode
|
||||
GUIUtility.hotControl = 0;
|
||||
}
|
||||
|
||||
if (mouseGridPositionChanged && !m_PositionChangeRepaintDone)
|
||||
{
|
||||
Repaint();
|
||||
m_PositionChangeRepaintDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected void ResetPreviousMousePositionToCurrentPosition()
|
||||
{
|
||||
m_PreviousMouseGridPosition = m_MouseGridPosition;
|
||||
}
|
||||
|
||||
protected void UpdateMouseGridPosition(bool forceUpdate = false)
|
||||
{
|
||||
if (Event.current.type == EventType.MouseDrag
|
||||
|| Event.current.type == EventType.MouseMove
|
||||
// Case 1075857: Mouse Down when window is not in focus needs to update mouse grid position
|
||||
|| Event.current.type == EventType.MouseDown
|
||||
|| Event.current.type == EventType.DragUpdated
|
||||
|| forceUpdate)
|
||||
{
|
||||
Vector2Int newGridPosition = ScreenToGrid(Event.current.mousePosition);
|
||||
if (newGridPosition != m_MouseGridPosition)
|
||||
{
|
||||
var delta = newGridPosition - m_MouseGridPosition;
|
||||
// Case 1024422: Limit mouse cell delta changes for Grid/Tilemap input handling due to camera changes when switching modes/axis views
|
||||
if (Mathf.Abs(delta.x) > k_MaxMouseCellDelta)
|
||||
newGridPosition.x = m_MouseGridPosition.x + Math.Sign(delta.x) * k_MaxMouseCellDelta;
|
||||
if (Mathf.Abs(delta.y) > k_MaxMouseCellDelta)
|
||||
newGridPosition.y = m_MouseGridPosition.y + Math.Sign(delta.y) * k_MaxMouseCellDelta;
|
||||
ResetPreviousMousePositionToCurrentPosition();
|
||||
m_MouseGridPosition = newGridPosition;
|
||||
MouseGridPositionChanged();
|
||||
}
|
||||
else if (!forceUpdate)
|
||||
{
|
||||
m_MouseGridPositionChanged = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MouseGridPositionChanged()
|
||||
{
|
||||
m_MouseGridPositionChanged = true;
|
||||
m_PositionChangeRepaintDone = false;
|
||||
}
|
||||
|
||||
private void HandleEditModeChange()
|
||||
{
|
||||
// Handles changes in EditMode while tool is expected to be in the same mode
|
||||
if (isPicking && !TilemapEditorTool.IsActive(typeof(PickingTool)))
|
||||
{
|
||||
m_MarqueeStart = null;
|
||||
m_MarqueeType = MarqueeType.None;
|
||||
if (isHotControl)
|
||||
{
|
||||
GUI.changed = true;
|
||||
GUIUtility.hotControl = 0;
|
||||
}
|
||||
}
|
||||
if (isBoxing && !TilemapEditorTool.IsActive(typeof(BoxTool)))
|
||||
{
|
||||
m_MarqueeStart = null;
|
||||
m_MarqueeType = MarqueeType.None;
|
||||
if (isHotControl)
|
||||
{
|
||||
GUI.changed = true;
|
||||
GUIUtility.hotControl = 0;
|
||||
}
|
||||
}
|
||||
if (!TilemapEditorTool.IsActive(typeof(SelectTool)) && !TilemapEditorTool.IsActive(typeof(MoveTool)))
|
||||
{
|
||||
ClearGridSelection();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleBrushPicking()
|
||||
{
|
||||
Event evt = Event.current;
|
||||
|
||||
if (isNearestControl && evt.type == EventType.MouseDown && IsPickingEvent(evt) && !isHotControl)
|
||||
{
|
||||
m_TypeBeforeExecution = typeof(PaintTool);
|
||||
if (inEditMode && !TilemapEditorTool.IsActive(typeof(PickingTool)))
|
||||
{
|
||||
m_TypeBeforeExecution = UnityEditor.EditorTools.ToolManager.activeToolType;
|
||||
TilemapEditorTool.SetActiveEditorTool(typeof(PickingTool));
|
||||
}
|
||||
|
||||
m_MarqueeStart = mouseGridPosition;
|
||||
m_MarqueeType = MarqueeType.Pick;
|
||||
s_LastActivePaintableGrid = this;
|
||||
Event.current.Use();
|
||||
GUI.changed = true;
|
||||
GUIUtility.hotControl = m_PermanentControlID;
|
||||
OnBrushPickStarted();
|
||||
}
|
||||
if (evt.type == EventType.MouseDrag && isHotControl && m_MarqueeStart.HasValue && m_MarqueeType == MarqueeType.Pick && IsPickingEvent(evt))
|
||||
{
|
||||
RectInt rect = GridEditorUtility.GetMarqueeRect(m_MarqueeStart.Value, mouseGridPosition);
|
||||
OnBrushPickDragged(new BoundsInt(new Vector3Int(rect.xMin, rect.yMin, zPosition), new Vector3Int(rect.size.x, rect.size.y, 1)));
|
||||
Event.current.Use();
|
||||
GUI.changed = true;
|
||||
}
|
||||
if (evt.rawType == EventType.MouseUp && isHotControl && m_MarqueeStart.HasValue && m_MarqueeType == MarqueeType.Pick && IsPickingEvent(evt))
|
||||
{
|
||||
// Check if event only occurred in the PaintableGrid window as evt.type will filter for this
|
||||
if (evt.type == EventType.MouseUp && m_MarqueeType == MarqueeType.Pick)
|
||||
{
|
||||
RectInt rect = GridEditorUtility.GetMarqueeRect(m_MarqueeStart.Value, mouseGridPosition);
|
||||
Vector2Int pivot = GetMarqueePivot(m_MarqueeStart.Value, mouseGridPosition);
|
||||
PickBrush(new BoundsInt(new Vector3Int(rect.xMin, rect.yMin, zPosition), new Vector3Int(rect.size.x, rect.size.y, 1)), new Vector3Int(pivot.x, pivot.y, 0));
|
||||
|
||||
if (inEditMode && UnityEditor.EditorTools.ToolManager.activeToolType != m_TypeBeforeExecution)
|
||||
{
|
||||
if (PickingIsDefaultTool()
|
||||
&& (m_TypeBeforeExecution == typeof(EraseTool)
|
||||
|| m_TypeBeforeExecution == typeof(MoveTool)))
|
||||
{
|
||||
// If Picking is default, change to a Paint Tool to facilitate editing if previous tool does not allow for painting
|
||||
TilemapEditorTool.SetActiveEditorTool(typeof(PaintTool));
|
||||
}
|
||||
else
|
||||
{
|
||||
TilemapEditorTool.SetActiveEditorTool(m_TypeBeforeExecution);
|
||||
}
|
||||
}
|
||||
|
||||
GridPaletteBrushes.ActiveGridBrushAssetChanged();
|
||||
s_LastActivePaintableGrid = this;
|
||||
Event.current.Use();
|
||||
GUI.changed = true;
|
||||
}
|
||||
else
|
||||
// Event occurred outside of PaintableGrid window, cancel the pick event
|
||||
{
|
||||
OnBrushPickCancelled();
|
||||
}
|
||||
m_MarqueeType = MarqueeType.None;
|
||||
m_MarqueeStart = null;
|
||||
GUIUtility.hotControl = 0;
|
||||
InspectorWindow.RepaintAllInspectors();
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsPickingEvent(Event evt)
|
||||
{
|
||||
return ((evt.control && !TilemapEditorTool.IsActive(typeof(MoveTool)))
|
||||
|| TilemapEditorTool.IsActive(typeof(PickingTool))
|
||||
|| !TilemapEditorTool.IsActive(typeof(SelectTool)) && PickingIsDefaultTool())
|
||||
&& evt.button == 0 && !evt.alt;
|
||||
}
|
||||
|
||||
private void HandleSelectTool()
|
||||
{
|
||||
Event evt = Event.current;
|
||||
|
||||
if (isNearestControl && evt.type == EventType.MouseDown && evt.button == 0 && !evt.alt && (TilemapEditorTool.IsActive(typeof(SelectTool)) || (TilemapEditorTool.IsActive(typeof(MoveTool)) && evt.control)))
|
||||
{
|
||||
if (TilemapEditorTool.IsActive(typeof(MoveTool)) && evt.control)
|
||||
TilemapEditorTool.SetActiveEditorTool(typeof(SelectTool));
|
||||
|
||||
m_PreviousMove = null;
|
||||
m_MarqueeStart = mouseGridPosition;
|
||||
m_MarqueeType = MarqueeType.Select;
|
||||
|
||||
s_LastActivePaintableGrid = this;
|
||||
GUIUtility.hotControl = m_PermanentControlID;
|
||||
Event.current.Use();
|
||||
}
|
||||
if (evt.rawType == EventType.MouseUp && evt.button == 0 && !evt.alt && m_MarqueeStart.HasValue && isHotControl && TilemapEditorTool.IsActive(typeof(SelectTool)))
|
||||
{
|
||||
// Check if event only occurred in the PaintableGrid window as evt.type will filter for this
|
||||
if (evt.type == EventType.MouseUp && m_MarqueeType == MarqueeType.Select)
|
||||
{
|
||||
RectInt rect = GridEditorUtility.GetMarqueeRect(m_MarqueeStart.Value, mouseGridPosition);
|
||||
Select(new BoundsInt(new Vector3Int(rect.xMin, rect.yMin, zPosition), new Vector3Int(rect.size.x, rect.size.y, 1)));
|
||||
Event.current.Use();
|
||||
}
|
||||
if (evt.control)
|
||||
TilemapEditorTool.SetActiveEditorTool(typeof(MoveTool));
|
||||
m_MarqueeStart = null;
|
||||
m_MarqueeType = MarqueeType.None;
|
||||
InspectorWindow.RepaintAllInspectors();
|
||||
GUIUtility.hotControl = 0;
|
||||
}
|
||||
if (evt.type == EventType.KeyDown && evt.keyCode == KeyCode.Escape && !m_MarqueeStart.HasValue && !m_PreviousMove.HasValue)
|
||||
{
|
||||
ClearGridSelection();
|
||||
Event.current.Use();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleMoveTool()
|
||||
{
|
||||
Event evt = Event.current;
|
||||
|
||||
if (isNearestControl && evt.type == EventType.MouseDown && evt.button == 0 && !evt.alt && TilemapEditorTool.IsActive(typeof(MoveTool)))
|
||||
{
|
||||
RegisterUndo();
|
||||
Vector3Int mouse3D = new Vector3Int(mouseGridPosition.x, mouseGridPosition.y, GridSelection.position.zMin);
|
||||
if (GridSelection.active && GridSelection.position.Contains(mouse3D))
|
||||
{
|
||||
GUIUtility.hotControl = m_PermanentControlID;
|
||||
executing = true;
|
||||
m_MarqueeStart = null;
|
||||
m_MarqueeType = MarqueeType.None;
|
||||
m_PreviousMove = mouseGridPosition;
|
||||
MoveStart(GridSelection.position);
|
||||
s_LastActivePaintableGrid = this;
|
||||
}
|
||||
Event.current.Use();
|
||||
}
|
||||
if (evt.type == EventType.MouseDrag && evt.button == 0 && TilemapEditorTool.IsActive(typeof(MoveTool)) && isHotControl)
|
||||
{
|
||||
if (m_MouseGridPositionChanged && m_PreviousMove.HasValue)
|
||||
{
|
||||
executing = true;
|
||||
BoundsInt previousRect = GridSelection.position;
|
||||
BoundsInt previousBounds = new BoundsInt(new Vector3Int(previousRect.xMin, previousRect.yMin, GridSelection.position.zMin), new Vector3Int(previousRect.size.x, previousRect.size.y, 1));
|
||||
|
||||
Vector2Int direction = mouseGridPosition - m_PreviousMove.Value;
|
||||
BoundsInt pos = GridSelection.position;
|
||||
pos.position = new Vector3Int(pos.x + direction.x, pos.y + direction.y, pos.z);
|
||||
GridSelection.position = pos;
|
||||
Move(previousBounds, pos);
|
||||
m_PreviousMove = mouseGridPosition;
|
||||
Event.current.Use();
|
||||
}
|
||||
}
|
||||
if (evt.type == EventType.MouseUp && evt.button == 0 && m_PreviousMove.HasValue && TilemapEditorTool.IsActive(typeof(MoveTool)) && isHotControl)
|
||||
{
|
||||
m_PreviousMove = null;
|
||||
MoveEnd(GridSelection.position);
|
||||
executing = false;
|
||||
GUIUtility.hotControl = 0;
|
||||
Event.current.Use();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleBrushPaintAndErase()
|
||||
{
|
||||
Event evt = Event.current;
|
||||
if (!IsPaintingEvent(evt) && !IsErasingEvent(evt))
|
||||
return;
|
||||
|
||||
switch (evt.type)
|
||||
{
|
||||
case EventType.MouseDown:
|
||||
if (isNearestControl)
|
||||
{
|
||||
RegisterUndo();
|
||||
GUIUtility.hotControl = m_PermanentControlID;
|
||||
executing = true;
|
||||
m_TypeBeforeExecution = EditorTools.ToolManager.activeToolType;
|
||||
if (IsErasingEvent(evt))
|
||||
{
|
||||
if (!TilemapEditorTool.IsActive(typeof(EraseTool)))
|
||||
TilemapEditorTool.SetActiveEditorTool(typeof(EraseTool));
|
||||
Erase(new Vector3Int(mouseGridPosition.x, mouseGridPosition.y, zPosition));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!TilemapEditorTool.IsActive(typeof(PaintTool)))
|
||||
TilemapEditorTool.SetActiveEditorTool(typeof(PaintTool));
|
||||
Paint(new Vector3Int(mouseGridPosition.x, mouseGridPosition.y, zPosition));
|
||||
}
|
||||
|
||||
Event.current.Use();
|
||||
GUI.changed = true;
|
||||
}
|
||||
break;
|
||||
case EventType.MouseDrag:
|
||||
executing = true;
|
||||
if (isHotControl && mouseGridPositionChanged)
|
||||
{
|
||||
List<Vector2Int> points = GridEditorUtility.GetPointsOnLine(m_PreviousMouseGridPosition, mouseGridPosition).ToList();
|
||||
if (points[0] == mouseGridPosition)
|
||||
points.Reverse();
|
||||
|
||||
if (!evt.shift && !TilemapEditorTool.IsActive(typeof(PaintTool)) && m_TypeBeforeExecution == typeof(PaintTool))
|
||||
TilemapEditorTool.SetActiveEditorTool(typeof(PaintTool));
|
||||
else if (evt.shift && TilemapEditorTool.IsActive(typeof(PaintTool)))
|
||||
TilemapEditorTool.SetActiveEditorTool(typeof(EraseTool));
|
||||
|
||||
for (int i = 1; i < points.Count; i++)
|
||||
{
|
||||
if (IsErasingEvent(evt))
|
||||
{
|
||||
Erase(new Vector3Int(points[i].x, points[i].y, zPosition));
|
||||
}
|
||||
else
|
||||
{
|
||||
Paint(new Vector3Int(points[i].x, points[i].y, zPosition));
|
||||
}
|
||||
}
|
||||
Event.current.Use();
|
||||
GUI.changed = true;
|
||||
}
|
||||
break;
|
||||
case EventType.MouseUp:
|
||||
executing = false;
|
||||
if (isHotControl)
|
||||
{
|
||||
if (!TilemapEditorTool.IsActive(typeof(PaintTool)) && m_TypeBeforeExecution == typeof(PaintTool))
|
||||
{
|
||||
TilemapEditorTool.SetActiveEditorTool(typeof(PaintTool));
|
||||
}
|
||||
|
||||
Event.current.Use();
|
||||
GUI.changed = true;
|
||||
GUIUtility.hotControl = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsPaintingEvent(Event evt)
|
||||
{
|
||||
return (evt.button == 0 && !evt.control && !evt.alt && TilemapEditorTool.IsActive(typeof(PaintTool)));
|
||||
}
|
||||
|
||||
private bool IsErasingEvent(Event evt)
|
||||
{
|
||||
return (evt.button == 0 && !evt.control && !evt.alt
|
||||
&& ((evt.shift && !TilemapEditorTool.IsActive(typeof(BoxTool))
|
||||
&& !TilemapEditorTool.IsActive(typeof(FillTool))
|
||||
&& !TilemapEditorTool.IsActive(typeof(SelectTool))
|
||||
&& !TilemapEditorTool.IsActive(typeof(MoveTool)))
|
||||
|| TilemapEditorTool.IsActive(typeof(EraseTool))));
|
||||
}
|
||||
|
||||
private void HandleFloodFill()
|
||||
{
|
||||
if (TilemapEditorTool.IsActive(typeof(FillTool)) && GridPaintingState.gridBrush != null && ValidateFloodFillPosition(new Vector3Int(mouseGridPosition.x, mouseGridPosition.y, 0)))
|
||||
{
|
||||
Event evt = Event.current;
|
||||
if (isNearestControl && evt.type == EventType.MouseDown && evt.button == 0 && !evt.alt)
|
||||
{
|
||||
GUIUtility.hotControl = m_PermanentControlID;
|
||||
GUI.changed = true;
|
||||
executing = true;
|
||||
Event.current.Use();
|
||||
}
|
||||
if (evt.type == EventType.MouseUp && evt.button == 0 && isHotControl)
|
||||
{
|
||||
RegisterUndo();
|
||||
FloodFill(new Vector3Int(mouseGridPosition.x, mouseGridPosition.y, zPosition));
|
||||
executing = false;
|
||||
GUI.changed = true;
|
||||
Event.current.Use();
|
||||
GUIUtility.hotControl = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleBoxTool()
|
||||
{
|
||||
Event evt = Event.current;
|
||||
|
||||
if (isNearestControl && evt.type == EventType.MouseDown && evt.button == 0 && !evt.alt && TilemapEditorTool.IsActive(typeof(BoxTool)))
|
||||
{
|
||||
m_MarqueeStart = mouseGridPosition;
|
||||
m_MarqueeType = MarqueeType.Box;
|
||||
Event.current.Use();
|
||||
GUI.changed = true;
|
||||
executing = true;
|
||||
GUIUtility.hotControl = m_PermanentControlID;
|
||||
}
|
||||
if (evt.type == EventType.MouseDrag && evt.button == 0 && TilemapEditorTool.IsActive(typeof(BoxTool)))
|
||||
{
|
||||
if (isHotControl && m_MarqueeStart.HasValue)
|
||||
{
|
||||
Event.current.Use();
|
||||
executing = true;
|
||||
GUI.changed = true;
|
||||
}
|
||||
}
|
||||
if (evt.type == EventType.MouseUp && evt.button == 0 && TilemapEditorTool.IsActive(typeof(BoxTool)))
|
||||
{
|
||||
if (isHotControl && m_MarqueeStart.HasValue)
|
||||
{
|
||||
RegisterUndo();
|
||||
RectInt rect = GridEditorUtility.GetMarqueeRect(m_MarqueeStart.Value, mouseGridPosition);
|
||||
if (evt.shift)
|
||||
BoxErase(new BoundsInt(rect.x, rect.y, zPosition, rect.size.x, rect.size.y, 1));
|
||||
else
|
||||
BoxFill(new BoundsInt(rect.x, rect.y, zPosition, rect.size.x, rect.size.y, 1));
|
||||
Event.current.Use();
|
||||
executing = false;
|
||||
GUI.changed = true;
|
||||
GUIUtility.hotControl = 0;
|
||||
}
|
||||
m_MarqueeStart = null;
|
||||
m_MarqueeType = MarqueeType.None;
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2Int GetMarqueePivot(Vector2Int start, Vector2Int end)
|
||||
{
|
||||
Vector2Int pivot = new Vector2Int(
|
||||
Math.Max(end.x - start.x, 0),
|
||||
Math.Max(end.y - start.y, 0)
|
||||
);
|
||||
return pivot;
|
||||
}
|
||||
|
||||
public void ChangeZPosition(int change)
|
||||
{
|
||||
m_ZPosition += change;
|
||||
MouseGridPositionChanged();
|
||||
Repaint();
|
||||
}
|
||||
|
||||
public void ResetZPosition()
|
||||
{
|
||||
if (m_ZPosition == 0)
|
||||
return;
|
||||
|
||||
m_ZPosition = 0;
|
||||
MouseGridPositionChanged();
|
||||
Repaint();
|
||||
}
|
||||
|
||||
public static bool InGridEditMode()
|
||||
{
|
||||
return UnityEditor.EditorTools.ToolManager.activeToolType != null
|
||||
&& UnityEditor.EditorTools.ToolManager.activeToolType.IsSubclassOf(typeof(TilemapEditorTool));
|
||||
}
|
||||
|
||||
// TODO: Someday EditMode or its future incarnation will be public and we can get rid of this
|
||||
// TODO: Temporarily use ActiveTool's type to determine brush tool
|
||||
public static GridBrushBase.Tool EditTypeToBrushTool(Type activeToolType)
|
||||
{
|
||||
if (activeToolType == typeof(BoxTool))
|
||||
return GridBrushBase.Tool.Box;
|
||||
if (activeToolType == typeof(EraseTool))
|
||||
return GridBrushBase.Tool.Erase;
|
||||
if (activeToolType == typeof(FillTool))
|
||||
return GridBrushBase.Tool.FloodFill;
|
||||
if (activeToolType == typeof(PaintTool))
|
||||
return GridBrushBase.Tool.Paint;
|
||||
if (activeToolType == typeof(PickingTool))
|
||||
return GridBrushBase.Tool.Pick;
|
||||
if (activeToolType == typeof(SelectTool))
|
||||
return GridBrushBase.Tool.Select;
|
||||
if (activeToolType == typeof(MoveTool))
|
||||
return GridBrushBase.Tool.Move;
|
||||
return GridBrushBase.Tool.Paint;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,380 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Tilemaps;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
internal class PaintableSceneViewGrid : PaintableGrid
|
||||
{
|
||||
private Transform gridTransform { get { return grid != null ? grid.transform : null; } }
|
||||
private Grid grid { get { return brushTarget != null ? brushTarget.GetComponentInParent<Grid>() : (Selection.activeGameObject != null ? Selection.activeGameObject.GetComponentInParent<Grid>() : null); } }
|
||||
private GridBrushBase gridBrush { get { return GridPaintingState.gridBrush; } }
|
||||
private SceneView activeSceneView;
|
||||
private int sceneViewTransformHash;
|
||||
|
||||
private GameObject brushTarget
|
||||
{
|
||||
get { return GridPaintingState.scenePaintTarget; }
|
||||
}
|
||||
|
||||
public Tilemap tilemap
|
||||
{
|
||||
get
|
||||
{
|
||||
if (brushTarget != null)
|
||||
{
|
||||
return brushTarget.GetComponent<Tilemap>();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
SceneView.duringSceneGui += OnSceneGUI;
|
||||
Undo.undoRedoPerformed += UndoRedoPerformed;
|
||||
GridSelection.gridSelectionChanged += OnGridSelectionChanged;
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
SceneView.duringSceneGui -= OnSceneGUI;
|
||||
Undo.undoRedoPerformed -= UndoRedoPerformed;
|
||||
GridSelection.gridSelectionChanged -= OnGridSelectionChanged;
|
||||
base.OnDisable();
|
||||
}
|
||||
|
||||
private void OnGridSelectionChanged()
|
||||
{
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
private Rect GetSceneViewPositionRect(SceneView sceneView)
|
||||
{
|
||||
return new Rect(0, 0, sceneView.position.width, sceneView.position.height);
|
||||
}
|
||||
|
||||
public void OnSceneGUI(SceneView sceneView)
|
||||
{
|
||||
HandleMouseEnterLeave(sceneView);
|
||||
|
||||
CallOnSceneGUI();
|
||||
|
||||
// Case 1093801: Handle only the currently active scene view
|
||||
if (sceneView != activeSceneView)
|
||||
return;
|
||||
|
||||
// Case 1077400: SceneView camera transform changes may update the mouse grid position even though the mouse position has not changed
|
||||
var currentSceneViewTransformHash = sceneView.camera.transform.localToWorldMatrix.GetHashCode();
|
||||
UpdateMouseGridPosition(currentSceneViewTransformHash == sceneViewTransformHash);
|
||||
sceneViewTransformHash = currentSceneViewTransformHash;
|
||||
|
||||
var dot = 1.0f;
|
||||
var gridView = GetGridView();
|
||||
if (gridView != null)
|
||||
{
|
||||
dot = Math.Abs(Vector3.Dot(sceneView.camera.transform.forward, Grid.Swizzle(gridView.cellSwizzle, gridView.transform.forward)));
|
||||
}
|
||||
|
||||
// Case 1021655: Validate that grid is not totally parallel to view (+-5 degrees), otherwise tiles could be accidentally painted on large positions
|
||||
if (dot > 0.1f)
|
||||
{
|
||||
base.OnGUI();
|
||||
if (InGridEditMode())
|
||||
{
|
||||
if ((grid != null) && (GridPaintingState.activeGrid == this || GridSelection.active))
|
||||
{
|
||||
CallOnPaintSceneGUI();
|
||||
}
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
EditorGUIUtility.AddCursorRect(GetSceneViewPositionRect(sceneView), MouseCursor.CustomCursor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleMouseEnterLeave(SceneView sceneView)
|
||||
{
|
||||
if (inEditMode)
|
||||
{
|
||||
if (Event.current.type == EventType.MouseEnterWindow)
|
||||
{
|
||||
OnMouseEnter(sceneView);
|
||||
}
|
||||
else if (Event.current.type == EventType.MouseLeaveWindow)
|
||||
{
|
||||
OnMouseLeave(sceneView);
|
||||
}
|
||||
// Case 1043365: When docked, the docking area is considered part of the window and MouseEnter/LeaveWindow events are not considered when entering the docking area
|
||||
else if (sceneView.docked)
|
||||
{
|
||||
var guiPoint = Event.current.mousePosition;
|
||||
var sceneViewPosition = GetSceneViewPositionRect(sceneView);
|
||||
if (sceneViewPosition.Contains(guiPoint))
|
||||
{
|
||||
if (GridPaintingState.activeGrid != this)
|
||||
{
|
||||
OnMouseEnter(sceneView);
|
||||
}
|
||||
}
|
||||
else if (activeSceneView == sceneView)
|
||||
{
|
||||
if (GridPaintingState.activeGrid == this)
|
||||
{
|
||||
OnMouseLeave(sceneView);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseEnter(SceneView sceneView)
|
||||
{
|
||||
if (GridPaintingState.activeBrushEditor != null)
|
||||
GridPaintingState.activeBrushEditor.OnMouseEnter();
|
||||
GridPaintingState.activeGrid = this;
|
||||
activeSceneView = sceneView;
|
||||
ResetPreviousMousePositionToCurrentPosition();
|
||||
}
|
||||
|
||||
private void OnMouseLeave(SceneView sceneView)
|
||||
{
|
||||
if (GridPaintingState.activeBrushEditor != null)
|
||||
GridPaintingState.activeBrushEditor.OnMouseLeave();
|
||||
GridPaintingState.activeGrid = null;
|
||||
activeSceneView = null;
|
||||
}
|
||||
|
||||
private void UndoRedoPerformed()
|
||||
{
|
||||
RefreshAllTiles();
|
||||
}
|
||||
|
||||
private void RefreshAllTiles()
|
||||
{
|
||||
if (tilemap != null)
|
||||
tilemap.RefreshAllTiles();
|
||||
}
|
||||
|
||||
protected override void RegisterUndo()
|
||||
{
|
||||
if (GridPaintingState.activeBrushEditor != null)
|
||||
{
|
||||
GridPaintingState.activeBrushEditor.RegisterUndo(brushTarget, EditTypeToBrushTool(UnityEditor.EditorTools.ToolManager.activeToolType));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Paint(Vector3Int position)
|
||||
{
|
||||
if (grid != null)
|
||||
gridBrush.Paint(grid, brushTarget, position);
|
||||
}
|
||||
|
||||
protected override void Erase(Vector3Int position)
|
||||
{
|
||||
if (grid != null)
|
||||
gridBrush.Erase(grid, brushTarget, position);
|
||||
}
|
||||
|
||||
protected override void BoxFill(BoundsInt position)
|
||||
{
|
||||
if (grid != null)
|
||||
gridBrush.BoxFill(grid, brushTarget, position);
|
||||
}
|
||||
|
||||
protected override void BoxErase(BoundsInt position)
|
||||
{
|
||||
if (grid != null)
|
||||
gridBrush.BoxErase(grid, brushTarget, position);
|
||||
}
|
||||
|
||||
protected override void FloodFill(Vector3Int position)
|
||||
{
|
||||
if (grid != null)
|
||||
gridBrush.FloodFill(grid, brushTarget, position);
|
||||
}
|
||||
|
||||
protected override void PickBrush(BoundsInt position, Vector3Int pickStart)
|
||||
{
|
||||
if (grid != null)
|
||||
gridBrush.Pick(grid, brushTarget, position, pickStart);
|
||||
}
|
||||
|
||||
protected override void Select(BoundsInt position)
|
||||
{
|
||||
if (grid != null)
|
||||
{
|
||||
GridSelection.Select(brushTarget, position);
|
||||
gridBrush.Select(grid, brushTarget, position);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Move(BoundsInt from, BoundsInt to)
|
||||
{
|
||||
if (grid != null)
|
||||
gridBrush.Move(grid, brushTarget, from, to);
|
||||
}
|
||||
|
||||
protected override void MoveStart(BoundsInt position)
|
||||
{
|
||||
if (grid != null)
|
||||
gridBrush.MoveStart(grid, brushTarget, position);
|
||||
}
|
||||
|
||||
protected override void MoveEnd(BoundsInt position)
|
||||
{
|
||||
if (grid != null)
|
||||
gridBrush.MoveEnd(grid, brushTarget, position);
|
||||
}
|
||||
|
||||
protected override void OnEditStart()
|
||||
{
|
||||
if (GridPaintingState.activeBrushEditor != null && grid != null)
|
||||
GridPaintingState.activeBrushEditor.OnEditStart(grid, brushTarget);
|
||||
}
|
||||
|
||||
protected override void OnEditEnd()
|
||||
{
|
||||
if (GridPaintingState.activeBrushEditor != null && grid != null)
|
||||
GridPaintingState.activeBrushEditor.OnEditEnd(grid, brushTarget);
|
||||
}
|
||||
|
||||
protected override void ClearGridSelection()
|
||||
{
|
||||
GridSelection.Clear();
|
||||
}
|
||||
|
||||
public override void Repaint()
|
||||
{
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
protected override bool ValidateFloodFillPosition(Vector3Int position)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override Vector2Int ScreenToGrid(Vector2 screenPosition)
|
||||
{
|
||||
if (tilemap != null)
|
||||
{
|
||||
var transform = tilemap.transform;
|
||||
Plane plane = new Plane(GetGridForward(tilemap), transform.position);
|
||||
Vector3Int cell = LocalToGrid(tilemap, GridEditorUtility.ScreenToLocal(transform, screenPosition, plane));
|
||||
return new Vector2Int(cell.x, cell.y);
|
||||
}
|
||||
if (grid != null)
|
||||
{
|
||||
Vector3Int cell = LocalToGrid(grid, GridEditorUtility.ScreenToLocal(gridTransform, screenPosition, GetGridPlane(grid)));
|
||||
return new Vector2Int(cell.x, cell.y);
|
||||
}
|
||||
return Vector2Int.zero;
|
||||
}
|
||||
|
||||
protected override bool PickingIsDefaultTool()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override bool CanPickOutsideEditMode()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override GridLayout.CellLayout CellLayout()
|
||||
{
|
||||
return grid.cellLayout;
|
||||
}
|
||||
|
||||
Vector3Int LocalToGrid(GridLayout gridLayout, Vector3 local)
|
||||
{
|
||||
return gridLayout.LocalToCell(local);
|
||||
}
|
||||
|
||||
private Vector3 GetGridForward(GridLayout gridLayout)
|
||||
{
|
||||
switch (gridLayout.cellSwizzle)
|
||||
{
|
||||
case GridLayout.CellSwizzle.XYZ:
|
||||
return gridLayout.transform.forward * -1f;
|
||||
case GridLayout.CellSwizzle.XZY:
|
||||
return gridLayout.transform.up * -1f;
|
||||
case GridLayout.CellSwizzle.YXZ:
|
||||
return gridLayout.transform.forward;
|
||||
case GridLayout.CellSwizzle.YZX:
|
||||
return gridLayout.transform.up;
|
||||
case GridLayout.CellSwizzle.ZXY:
|
||||
return gridLayout.transform.right;
|
||||
case GridLayout.CellSwizzle.ZYX:
|
||||
return gridLayout.transform.right * -1f;
|
||||
}
|
||||
return gridLayout.transform.forward * -1f;
|
||||
}
|
||||
|
||||
private Plane GetGridPlane(Grid grid)
|
||||
{
|
||||
return new Plane(GetGridForward(grid), grid.transform.position);
|
||||
}
|
||||
|
||||
private GridLayout GetGridView()
|
||||
{
|
||||
if (tilemap != null)
|
||||
return tilemap;
|
||||
if (grid != null)
|
||||
return grid;
|
||||
return null;
|
||||
}
|
||||
|
||||
void CallOnPaintSceneGUI()
|
||||
{
|
||||
bool hasSelection = GridSelection.active && GridSelection.target == brushTarget;
|
||||
if (!hasSelection && GridPaintingState.activeGrid != this)
|
||||
return;
|
||||
|
||||
RectInt rect = new RectInt(mouseGridPosition, new Vector2Int(1, 1));
|
||||
|
||||
if (m_MarqueeStart.HasValue)
|
||||
rect = GridEditorUtility.GetMarqueeRect(mouseGridPosition, m_MarqueeStart.Value);
|
||||
else if (hasSelection)
|
||||
rect = new RectInt(GridSelection.position.xMin, GridSelection.position.yMin, GridSelection.position.size.x, GridSelection.position.size.y);
|
||||
|
||||
var layoutGrid = tilemap != null ? tilemap.layoutGrid : grid as GridLayout;
|
||||
BoundsInt brushBounds = new BoundsInt(new Vector3Int(rect.x, rect.y, zPosition), new Vector3Int(rect.width, rect.height, 1));
|
||||
if (GridPaintingState.activeBrushEditor != null)
|
||||
{
|
||||
GridPaintingState.activeBrushEditor.OnPaintSceneGUI(layoutGrid, brushTarget, brushBounds
|
||||
, EditTypeToBrushTool(UnityEditor.EditorTools.ToolManager.activeToolType), m_MarqueeStart.HasValue || executing);
|
||||
}
|
||||
else // Fallback when user hasn't defined custom editor
|
||||
{
|
||||
GridBrushEditorBase.OnPaintSceneGUIInternal(layoutGrid, brushTarget, brushBounds
|
||||
, EditTypeToBrushTool(UnityEditor.EditorTools.ToolManager.activeToolType), m_MarqueeStart.HasValue || executing);
|
||||
}
|
||||
}
|
||||
|
||||
void CallOnSceneGUI()
|
||||
{
|
||||
var gridLayout = tilemap != null ? tilemap : grid as GridLayout;
|
||||
bool hasSelection = GridSelection.active && GridSelection.target == brushTarget;
|
||||
if (GridPaintingState.activeBrushEditor != null)
|
||||
{
|
||||
GridPaintingState.activeBrushEditor.OnSceneGUI(gridLayout, brushTarget);
|
||||
if (hasSelection)
|
||||
{
|
||||
GridPaintingState.activeBrushEditor.OnSelectionSceneGUI(gridLayout, brushTarget);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSelection)
|
||||
{
|
||||
RectInt rect = new RectInt(GridSelection.position.xMin, GridSelection.position.yMin, GridSelection.position.size.x, GridSelection.position.size.y);
|
||||
BoundsInt brushBounds = new BoundsInt(new Vector3Int(rect.x, rect.y, zPosition), new Vector3Int(rect.width, rect.height, 1));
|
||||
GridBrushEditorBase.OnSceneGUIInternal(gridLayout, brushTarget, brushBounds
|
||||
, EditTypeToBrushTool(UnityEditor.EditorTools.ToolManager.activeToolType), m_MarqueeStart.HasValue || executing);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,234 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
/// <summary> This class is in charge of handling Grid component based grid in the scene view (rendering, snapping).
|
||||
/// It will hide global scene view grid when it has something to render</summary>
|
||||
internal class SceneViewGridManager : ScriptableSingleton<SceneViewGridManager>
|
||||
{
|
||||
internal static readonly PrefColor sceneViewGridComponentGizmo = new PrefColor("Scene/Grid Component", 255.0f / 255.0f, 255.0f / 255.0f, 255.0f / 255.0f, 25.5f / 255.0f);
|
||||
|
||||
private static Mesh s_GridProxyMesh;
|
||||
private static Material s_GridProxyMaterial;
|
||||
private static int s_LastGridProxyHash;
|
||||
|
||||
[SerializeField]
|
||||
private GridLayout m_ActiveGridProxy;
|
||||
|
||||
private Dictionary<SceneView, bool> m_SceneViewShowGridMap;
|
||||
|
||||
private bool m_RegisteredEventHandlers;
|
||||
|
||||
private bool active { get { return m_ActiveGridProxy != null; } }
|
||||
internal GridLayout activeGridProxy { get { return m_ActiveGridProxy; } }
|
||||
|
||||
private UnityType m_GridType;
|
||||
|
||||
[InitializeOnLoadMethod]
|
||||
private static void Initialize()
|
||||
{
|
||||
instance.RegisterEventHandlers();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
m_SceneViewShowGridMap = new Dictionary<SceneView, bool>();
|
||||
RegisterEventHandlers();
|
||||
}
|
||||
|
||||
private void RegisterEventHandlers()
|
||||
{
|
||||
if (m_RegisteredEventHandlers)
|
||||
return;
|
||||
|
||||
SceneView.duringSceneGui += OnSceneGuiDelegate;
|
||||
Selection.selectionChanged += UpdateCache;
|
||||
EditorApplication.hierarchyChanged += UpdateCache;
|
||||
UnityEditor.EditorTools.ToolManager.activeToolChanged += ActiveToolChanged;
|
||||
EditorApplication.quitting += EditorQuitting;
|
||||
GridPaintingState.brushChanged += OnBrushChanged;
|
||||
GridPaintingState.scenePaintTargetChanged += OnScenePaintTargetChanged;
|
||||
GridSnapping.snapPosition = OnSnapPosition;
|
||||
GridSnapping.activeFunc = GetActive;
|
||||
|
||||
m_GridType = UnityType.FindTypeByName("Grid");
|
||||
|
||||
m_RegisteredEventHandlers = true;
|
||||
}
|
||||
|
||||
private void OnBrushChanged(GridBrushBase brush)
|
||||
{
|
||||
UpdateCache();
|
||||
}
|
||||
|
||||
private void ActiveToolChanged()
|
||||
{
|
||||
UpdateCache();
|
||||
}
|
||||
|
||||
private void OnScenePaintTargetChanged(GameObject scenePaintTarget)
|
||||
{
|
||||
UpdateCache();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
FlushCachedGridProxy();
|
||||
RestoreSceneViewShowGrid();
|
||||
SceneView.duringSceneGui -= OnSceneGuiDelegate;
|
||||
Selection.selectionChanged -= UpdateCache;
|
||||
EditorApplication.hierarchyChanged -= UpdateCache;
|
||||
EditorApplication.quitting -= EditorQuitting;
|
||||
UnityEditor.EditorTools.ToolManager.activeToolChanged -= ActiveToolChanged;
|
||||
GridPaintingState.brushChanged -= OnBrushChanged;
|
||||
GridPaintingState.scenePaintTargetChanged -= OnScenePaintTargetChanged;
|
||||
GridSnapping.snapPosition = null;
|
||||
GridSnapping.activeFunc = null;
|
||||
m_RegisteredEventHandlers = false;
|
||||
}
|
||||
|
||||
private void UpdateCache()
|
||||
{
|
||||
GridLayout gridProxy;
|
||||
if (PaintableGrid.InGridEditMode())
|
||||
gridProxy = GridPaintingState.scenePaintTarget != null ? GridPaintingState.scenePaintTarget.GetComponentInParent<GridLayout>() : null;
|
||||
else
|
||||
gridProxy = Selection.activeGameObject != null ? Selection.activeGameObject.GetComponentInParent<GridLayout>() : null;
|
||||
|
||||
if (gridProxy != m_ActiveGridProxy)
|
||||
{
|
||||
if (m_ActiveGridProxy == null)
|
||||
{
|
||||
// Disable SceneView grid if there is now a GridProxy. Store user settings to be restored.
|
||||
StoreSceneViewShowGrid(false);
|
||||
}
|
||||
else if (gridProxy == null)
|
||||
{
|
||||
RestoreSceneViewShowGrid();
|
||||
}
|
||||
m_ActiveGridProxy = gridProxy;
|
||||
FlushCachedGridProxy();
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
}
|
||||
|
||||
private void EditorQuitting()
|
||||
{
|
||||
if (NeedsRestoreSceneViewShowGrid())
|
||||
{
|
||||
RestoreSceneViewShowGrid();
|
||||
// SceneView.showGrid is part of default window preferences
|
||||
WindowLayout.SaveDefaultWindowPreferences();
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsGridAnnotationEnabled()
|
||||
{
|
||||
var annotations = AnnotationUtility.GetAnnotations();
|
||||
foreach (var annotation in annotations)
|
||||
{
|
||||
if (annotation.classID == m_GridType.persistentTypeID)
|
||||
{
|
||||
return annotation.gizmoEnabled > 0;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnSceneGuiDelegate(SceneView sceneView)
|
||||
{
|
||||
if (active && sceneView.drawGizmos && IsGridAnnotationEnabled())
|
||||
DrawGrid(activeGridProxy);
|
||||
}
|
||||
|
||||
private static int GenerateHash(GridLayout layout, Color color)
|
||||
{
|
||||
int hash = 0x7ed55d16;
|
||||
hash ^= layout.cellSize.GetHashCode();
|
||||
hash ^= layout.cellLayout.GetHashCode() << 23;
|
||||
hash ^= (layout.cellGap.GetHashCode() << 4) + 0x165667b1;
|
||||
hash ^= layout.cellSwizzle.GetHashCode() << 7;
|
||||
hash ^= color.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
|
||||
private static void DrawGrid(GridLayout gridLayout)
|
||||
{
|
||||
int gridHash = GenerateHash(gridLayout, sceneViewGridComponentGizmo.Color);
|
||||
if (s_LastGridProxyHash != gridHash)
|
||||
{
|
||||
FlushCachedGridProxy();
|
||||
s_LastGridProxyHash = gridHash;
|
||||
}
|
||||
GridEditorUtility.DrawGridGizmo(gridLayout, gridLayout.transform, sceneViewGridComponentGizmo.Color, ref s_GridProxyMesh, ref s_GridProxyMaterial);
|
||||
}
|
||||
|
||||
private bool NeedsRestoreSceneViewShowGrid()
|
||||
{
|
||||
return m_SceneViewShowGridMap.Count > 0;
|
||||
}
|
||||
|
||||
private void StoreSceneViewShowGrid(bool value)
|
||||
{
|
||||
m_SceneViewShowGridMap.Clear();
|
||||
foreach (SceneView sceneView in SceneView.sceneViews)
|
||||
{
|
||||
m_SceneViewShowGridMap.Add(sceneView, sceneView.showGrid);
|
||||
sceneView.showGrid = value;
|
||||
}
|
||||
}
|
||||
|
||||
private void RestoreSceneViewShowGrid()
|
||||
{
|
||||
foreach (var item in m_SceneViewShowGridMap)
|
||||
{
|
||||
var sceneView = item.Key;
|
||||
if (sceneView != null)
|
||||
sceneView.showGrid = item.Value;
|
||||
}
|
||||
m_SceneViewShowGridMap.Clear();
|
||||
}
|
||||
|
||||
private bool GetActive()
|
||||
{
|
||||
return active;
|
||||
}
|
||||
|
||||
internal Vector3 OnSnapPosition(Vector3 position)
|
||||
{
|
||||
Vector3 result = position;
|
||||
if (active && (EditorSnapSettings.hotkeyActive || EditorSnapSettings.gridSnapActive))
|
||||
{
|
||||
// This will automatically prefer the Grid
|
||||
Vector3 local = activeGridProxy.WorldToLocal(position);
|
||||
Vector3 interpolatedCell = activeGridProxy.LocalToCellInterpolated(local);
|
||||
|
||||
Vector3 inverse = Vector3.one;
|
||||
inverse.x = Mathf.Approximately(EditorSnapSettings.move.x, 0.0f) ? 1.0f : 1.0f / EditorSnapSettings.move.x;
|
||||
inverse.y = Mathf.Approximately(EditorSnapSettings.move.y, 0.0f) ? 1.0f : 1.0f / EditorSnapSettings.move.y;
|
||||
inverse.z = Mathf.Approximately(EditorSnapSettings.move.z, 0.0f) ? 1.0f : 1.0f / EditorSnapSettings.move.z;
|
||||
|
||||
Vector3 roundedCell = new Vector3(
|
||||
Mathf.Round(inverse.x * interpolatedCell.x) / inverse.x,
|
||||
Mathf.Round(inverse.y * interpolatedCell.y) / inverse.y,
|
||||
Mathf.Round(inverse.z * interpolatedCell.z) / inverse.z
|
||||
);
|
||||
|
||||
local = activeGridProxy.CellToLocalInterpolated(roundedCell);
|
||||
result = activeGridProxy.LocalToWorld(local);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static void FlushCachedGridProxy()
|
||||
{
|
||||
if (s_GridProxyMesh == null)
|
||||
return;
|
||||
|
||||
DestroyImmediate(s_GridProxyMesh);
|
||||
s_GridProxyMesh = null;
|
||||
s_GridProxyMaterial = null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,155 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Tilemaps;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
internal class SceneViewOpenTilePaletteHelper : ScriptableSingleton<SceneViewOpenTilePaletteHelper>
|
||||
{
|
||||
private class Styles
|
||||
{
|
||||
public static readonly GUIContent overlayTitleLabel = EditorGUIUtility.TrTextContent("Open Tile Palette");
|
||||
public static readonly GUIContent openContent = EditorGUIUtility.IconContent("Tilemap Icon", "Open Tile Palette|Opens Tile Palette Window");
|
||||
}
|
||||
|
||||
private bool m_RegisteredEventHandlers;
|
||||
private bool m_IsSelectionValid;
|
||||
|
||||
[InitializeOnLoadMethod]
|
||||
private static void Initialize()
|
||||
{
|
||||
instance.RegisterEventHandlers();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
RegisterEventHandlers();
|
||||
}
|
||||
|
||||
private void RegisterEventHandlers()
|
||||
{
|
||||
if (m_RegisteredEventHandlers)
|
||||
return;
|
||||
|
||||
SceneView.duringSceneGui += DuringSceneGUI;
|
||||
Selection.selectionChanged += SelectionChanged;
|
||||
EditorApplication.hierarchyChanged += SelectionChanged;
|
||||
|
||||
m_IsSelectionValid = IsSelectionValid();
|
||||
|
||||
m_RegisteredEventHandlers = true;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
SceneView.duringSceneGui -= DuringSceneGUI;
|
||||
Selection.selectionChanged -= SelectionChanged;
|
||||
EditorApplication.hierarchyChanged -= SelectionChanged;
|
||||
m_RegisteredEventHandlers = false;
|
||||
}
|
||||
|
||||
internal static void OpenTilePalette()
|
||||
{
|
||||
GridPaintPaletteWindow.OpenTilemapPalette();
|
||||
|
||||
var target = Selection.activeGameObject;
|
||||
if (target != null)
|
||||
{
|
||||
if (PrefabUtility.IsPartOfPrefabAsset(target))
|
||||
{
|
||||
var path = AssetDatabase.GetAssetPath(target);
|
||||
if (AssetDatabase.LoadAssetAtPath<GridPalette>(path))
|
||||
{
|
||||
GridPaintingState.palette = AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
||||
}
|
||||
}
|
||||
else if (GridPaintingState.validTargets != null)
|
||||
{
|
||||
var grid = target.GetComponent<GridLayout>();
|
||||
if (grid != null)
|
||||
{
|
||||
foreach (var validTarget in GridPaintingState.validTargets)
|
||||
{
|
||||
if (validTarget == target)
|
||||
{
|
||||
GridPaintingState.scenePaintTarget = target;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool IsActive()
|
||||
{
|
||||
if (GridPaintPaletteWindow.isActive)
|
||||
return false;
|
||||
return instance.m_IsSelectionValid;
|
||||
}
|
||||
|
||||
internal static bool IsSelectionValid()
|
||||
{
|
||||
if (Selection.activeObject == null)
|
||||
return false;
|
||||
if (Selection.activeObject is TileBase)
|
||||
return true;
|
||||
if (Selection.activeGameObject != null && Selection.activeGameObject.GetComponent<GridLayout>() != null)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void DuringSceneGUI(SceneView sceneView)
|
||||
{
|
||||
if (!showInSceneViewActive || !IsActive())
|
||||
return;
|
||||
|
||||
SceneViewOverlay.Window(Styles.overlayTitleLabel, OnSceneViewDisplayGUI, (int)SceneViewOverlay.Ordering.TilemapRenderer + 1, SceneViewOverlay.WindowDisplayOption.OneWindowPerTitle);
|
||||
}
|
||||
|
||||
private void SelectionChanged()
|
||||
{
|
||||
m_IsSelectionValid = IsSelectionValid();
|
||||
}
|
||||
|
||||
private void OnSceneViewDisplayGUI(Object displayTarget, SceneView sceneView)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button(Styles.openContent, GUILayout.Height(32), GUILayout.Width(32)))
|
||||
{
|
||||
OpenTilePalette();
|
||||
}
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
internal class SceneViewOpenTilePaletteProperties
|
||||
{
|
||||
public static readonly string showInSceneViewEditorPref = "OpenTilePalette.ShowInSceneView";
|
||||
public static readonly string showInSceneViewLookup = "Show Open Tile Palette in Scene View";
|
||||
|
||||
public static readonly GUIContent showInSceneViewLabel = EditorGUIUtility.TrTextContent(showInSceneViewLookup, "Shows an overlay in the SceneView for opening the Tile Palette when selecting an object that interacts with the Tile Palette.");
|
||||
}
|
||||
|
||||
internal static bool showInSceneViewActive
|
||||
{
|
||||
get { return EditorPrefs.GetBool(SceneViewOpenTilePaletteProperties.showInSceneViewEditorPref, true); }
|
||||
set { EditorPrefs.SetBool(SceneViewOpenTilePaletteProperties.showInSceneViewEditorPref, value); }
|
||||
}
|
||||
|
||||
internal static void PreferencesGUI()
|
||||
{
|
||||
using (new SettingsWindow.GUIScope())
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var val = EditorGUILayout.Toggle(SceneViewOpenTilePaletteProperties.showInSceneViewLabel, showInSceneViewActive);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
showInSceneViewActive = val;
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,496 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Tilemaps;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
internal static class TileDragAndDrop
|
||||
{
|
||||
private enum UserTileCreationMode
|
||||
{
|
||||
Overwrite,
|
||||
CreateUnique,
|
||||
Reuse,
|
||||
}
|
||||
|
||||
private static readonly string k_TileExtension = "asset";
|
||||
|
||||
private static List<Sprite> GetSpritesFromTexture(Texture2D texture)
|
||||
{
|
||||
string path = AssetDatabase.GetAssetPath(texture);
|
||||
Object[] assets = AssetDatabase.LoadAllAssetsAtPath(path);
|
||||
List<Sprite> sprites = new List<Sprite>();
|
||||
|
||||
foreach (Object asset in assets)
|
||||
{
|
||||
if (asset is Sprite)
|
||||
{
|
||||
sprites.Add(asset as Sprite);
|
||||
}
|
||||
}
|
||||
|
||||
return sprites;
|
||||
}
|
||||
|
||||
private static bool AllSpritesAreSameSizeOrMultiples(List<Sprite> sprites)
|
||||
{
|
||||
if (sprites.Count == 0)
|
||||
return false;
|
||||
if (sprites.Count == 1)
|
||||
return true;
|
||||
|
||||
var size = new Vector2(sprites[0].rect.width, sprites[0].rect.height);
|
||||
for (int i = 1; i < sprites.Count; i++)
|
||||
{
|
||||
var rect = sprites[i].rect;
|
||||
if (rect.width < size.x)
|
||||
size.x = rect.width;
|
||||
if (rect.height < size.y)
|
||||
size.y = rect.height;
|
||||
}
|
||||
foreach (var sprite in sprites)
|
||||
{
|
||||
var rect = sprite.rect;
|
||||
if (rect.width % size.x > 0)
|
||||
return false;
|
||||
if (rect.height % size.y > 0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts Objects that can be laid out in the Tile Palette and organises them for placement into a given CellLayout
|
||||
/// </summary>
|
||||
/// <param name="sheetTextures">Textures containing 2-N equal sized Sprites</param>
|
||||
/// <param name="singleSprites">All the leftover Sprites that were in same texture but different sizes or just dragged in as Sprite</param>
|
||||
/// <param name="tiles">Just plain tiles</param>
|
||||
/// <param name="cellLayout">Cell Layout to place objects on</param>
|
||||
/// <returns>Dictionary mapping the positions of the Objects on the Grid Layout with details of how to place the Objects</returns>
|
||||
public static Dictionary<Vector2Int, TileDragAndDropHoverData> CreateHoverData(List<Texture2D> sheetTextures, List<Sprite> singleSprites, List<TileBase> tiles, GridLayout.CellLayout cellLayout)
|
||||
{
|
||||
Dictionary<Vector2Int, TileDragAndDropHoverData> result = new Dictionary<Vector2Int, TileDragAndDropHoverData>();
|
||||
|
||||
Vector2Int currentPosition = new Vector2Int(0, 0);
|
||||
int width = 0;
|
||||
|
||||
if (sheetTextures != null)
|
||||
{
|
||||
foreach (Texture2D sheetTexture in sheetTextures)
|
||||
{
|
||||
Dictionary<Vector2Int, TileDragAndDropHoverData> sheet = CreateHoverData(sheetTexture, cellLayout);
|
||||
foreach (KeyValuePair<Vector2Int, TileDragAndDropHoverData> item in sheet)
|
||||
{
|
||||
result.Add(item.Key + currentPosition, item.Value);
|
||||
}
|
||||
Vector2Int min = GetMinMaxRect(sheet.Keys.ToList()).min;
|
||||
currentPosition += new Vector2Int(0, min.y - 1);
|
||||
}
|
||||
}
|
||||
if (currentPosition.x > 0)
|
||||
currentPosition = new Vector2Int(0, currentPosition.y - 1);
|
||||
|
||||
if (singleSprites != null)
|
||||
{
|
||||
width = Mathf.RoundToInt(Mathf.Sqrt(singleSprites.Count));
|
||||
foreach (Sprite sprite in singleSprites)
|
||||
{
|
||||
result.Add(currentPosition, new TileDragAndDropHoverData(sprite));
|
||||
currentPosition += new Vector2Int(1, 0);
|
||||
if (currentPosition.x >= width)
|
||||
currentPosition = new Vector2Int(0, currentPosition.y - 1);
|
||||
}
|
||||
}
|
||||
if (currentPosition.x > 0)
|
||||
currentPosition = new Vector2Int(0, currentPosition.y - 1);
|
||||
|
||||
if (tiles != null)
|
||||
{
|
||||
width = Math.Max(Mathf.RoundToInt(Mathf.Sqrt(tiles.Count)), width);
|
||||
foreach (TileBase tile in tiles)
|
||||
{
|
||||
result.Add(currentPosition, new TileDragAndDropHoverData(tile));
|
||||
currentPosition += new Vector2Int(1, 0);
|
||||
if (currentPosition.x >= width)
|
||||
currentPosition = new Vector2Int(0, currentPosition.y - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get all textures that are valid spritesheets. More than one Sprites and all equal size.
|
||||
public static List<Texture2D> GetValidSpritesheets(Object[] objects)
|
||||
{
|
||||
List<Texture2D> result = new List<Texture2D>();
|
||||
foreach (Object obj in objects)
|
||||
{
|
||||
if (obj is Texture2D)
|
||||
{
|
||||
Texture2D texture = obj as Texture2D;
|
||||
List<Sprite> sprites = GetSpritesFromTexture(texture);
|
||||
if (sprites.Count > 1 && AllSpritesAreSameSizeOrMultiples(sprites))
|
||||
{
|
||||
result.Add(texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get all single Sprite(s) and all Sprite(s) that are part of Texture2D that is not valid sheet (it sprites of varying sizes)
|
||||
public static List<Sprite> GetValidSingleSprites(Object[] objects)
|
||||
{
|
||||
List<Sprite> result = new List<Sprite>();
|
||||
foreach (Object obj in objects)
|
||||
{
|
||||
if (obj is Sprite)
|
||||
{
|
||||
result.Add(obj as Sprite);
|
||||
}
|
||||
else if (obj is Texture2D)
|
||||
{
|
||||
Texture2D texture = obj as Texture2D;
|
||||
List<Sprite> sprites = GetSpritesFromTexture(texture);
|
||||
if (sprites.Count == 1 || !AllSpritesAreSameSizeOrMultiples(sprites))
|
||||
{
|
||||
result.AddRange(sprites);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<TileBase> GetValidTiles(Object[] objects)
|
||||
{
|
||||
List<TileBase> result = new List<TileBase>();
|
||||
foreach (Object obj in objects)
|
||||
{
|
||||
if (obj is TileBase)
|
||||
{
|
||||
result.Add(obj as TileBase);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Vector2Int GetMinimum(List<Sprite> sprites, Func<Sprite, float> minX, Func<Sprite, float> minY)
|
||||
{
|
||||
Vector2 minVector = new Vector2(Int32.MaxValue, Int32.MaxValue);
|
||||
foreach (var sprite in sprites)
|
||||
{
|
||||
minVector.x = Mathf.Min(minVector.x, minX(sprite));
|
||||
minVector.y = Mathf.Min(minVector.y, minY(sprite));
|
||||
}
|
||||
return Vector2Int.FloorToInt(minVector);
|
||||
}
|
||||
|
||||
public static Vector2Int EstimateGridPixelSize(List<Sprite> sprites)
|
||||
{
|
||||
if (sprites.Count == 0 || sprites.Any(sprite => sprite == null))
|
||||
{
|
||||
return Vector2Int.zero;
|
||||
}
|
||||
|
||||
if (sprites.Count == 1)
|
||||
return Vector2Int.FloorToInt(sprites[0].rect.size);
|
||||
|
||||
return GetMinimum(sprites, s => s.rect.width, s => s.rect.height);
|
||||
}
|
||||
|
||||
public static Vector2Int EstimateGridOffsetSize(List<Sprite> sprites)
|
||||
{
|
||||
if (sprites.Count == 0 || sprites.Any(sprite => sprite == null))
|
||||
return Vector2Int.zero;
|
||||
|
||||
if (sprites.Count == 1)
|
||||
return Vector2Int.FloorToInt(sprites[0].rect.position);
|
||||
|
||||
return GetMinimum(sprites, s => s.rect.xMin, s => s.rect.yMin);
|
||||
}
|
||||
|
||||
public static Vector2Int EstimateGridPaddingSize(List<Sprite> sprites, Vector2Int cellSize, Vector2Int offsetSize)
|
||||
{
|
||||
if (sprites.Count < 2 || sprites.Any(sprite => sprite == null))
|
||||
return Vector2Int.zero;
|
||||
|
||||
var paddingSize = GetMinimum(sprites
|
||||
, (s =>
|
||||
{
|
||||
var xMin = s.rect.xMin - cellSize.x - offsetSize.x;
|
||||
return xMin >= 0 ? xMin : Int32.MaxValue;
|
||||
})
|
||||
, (s =>
|
||||
{
|
||||
var yMin = s.rect.yMin - cellSize.y - offsetSize.y;
|
||||
return yMin >= 0 ? yMin : Int32.MaxValue;
|
||||
})
|
||||
);
|
||||
|
||||
// Assume there is no padding if the detected padding is greater than the cell size
|
||||
if (paddingSize.x >= cellSize.x)
|
||||
paddingSize.x = 0;
|
||||
if (paddingSize.y >= cellSize.y)
|
||||
paddingSize.y = 0;
|
||||
return paddingSize;
|
||||
}
|
||||
|
||||
// Turn texture pixel position into integer grid position based on cell size, offset size and padding
|
||||
private static void GetGridPosition(Sprite sprite, Vector2Int cellPixelSize, Vector2Int offsetSize, Vector2Int paddingSize, out Vector2Int cellPosition, out Vector3 positionOffset)
|
||||
{
|
||||
var position = new Vector2(
|
||||
((sprite.rect.center.x - offsetSize.x) / (cellPixelSize.x + paddingSize.x)),
|
||||
(-(sprite.texture.height - sprite.rect.center.y - offsetSize.y) / (cellPixelSize.y + paddingSize.y)) + 1
|
||||
);
|
||||
cellPosition = new Vector2Int(Mathf.FloorToInt(position.x), Mathf.FloorToInt(position.y));
|
||||
positionOffset = position - cellPosition;
|
||||
}
|
||||
|
||||
// Turn texture pixel position into integer isometric grid position based on cell size and offset size
|
||||
private static void GetIsometricGridPosition(Sprite sprite, Vector2Int cellPixelSize, Vector2Int offsetSize, out Vector2Int cellPosition)
|
||||
{
|
||||
var offsetPosition = new Vector2(sprite.rect.center.x - offsetSize.x, sprite.rect.center.y - offsetSize.y);
|
||||
var cellStride = new Vector2(cellPixelSize.x, cellPixelSize.y) * 0.5f;
|
||||
var invCellStride = new Vector2(1.0f / cellStride.x, 1.0f / cellStride.y);
|
||||
|
||||
var position = offsetPosition * invCellStride;
|
||||
position.y = (position.y - position.x) * 0.5f;
|
||||
position.x += position.y;
|
||||
cellPosition = new Vector2Int(Mathf.FloorToInt(position.x), Mathf.FloorToInt(position.y));
|
||||
}
|
||||
|
||||
// Organizes all the sprites in a single texture nicely on a 2D "table" based on their original texture position
|
||||
// Only call this with spritesheet with all Sprites equal size
|
||||
public static Dictionary<Vector2Int, TileDragAndDropHoverData> CreateHoverData(Texture2D sheet, GridLayout.CellLayout cellLayout)
|
||||
{
|
||||
Dictionary<Vector2Int, TileDragAndDropHoverData> result = new Dictionary<Vector2Int, TileDragAndDropHoverData>();
|
||||
List<Sprite> sprites = GetSpritesFromTexture(sheet);
|
||||
Vector2Int cellPixelSize = EstimateGridPixelSize(sprites);
|
||||
|
||||
// Get Offset
|
||||
Vector2Int offsetSize = EstimateGridOffsetSize(sprites);
|
||||
|
||||
// Get Padding
|
||||
Vector2Int paddingSize = EstimateGridPaddingSize(sprites, cellPixelSize, offsetSize);
|
||||
|
||||
if ((cellLayout == GridLayout.CellLayout.Isometric
|
||||
|| cellLayout == GridLayout.CellLayout.IsometricZAsY)
|
||||
&& (HasSpriteRectOverlaps(sprites)))
|
||||
{
|
||||
foreach (Sprite sprite in sprites)
|
||||
{
|
||||
GetIsometricGridPosition(sprite, cellPixelSize, offsetSize, out Vector2Int position);
|
||||
result[position] = new TileDragAndDropHoverData(sprite, Vector3.zero, (Vector2)cellPixelSize / sprite.pixelsPerUnit, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Sprite sprite in sprites)
|
||||
{
|
||||
GetGridPosition(sprite, cellPixelSize, offsetSize, paddingSize, out Vector2Int position, out Vector3 offset);
|
||||
result[position] = new TileDragAndDropHoverData(sprite, offset, (Vector2)cellPixelSize / sprite.pixelsPerUnit);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool HasSpriteRectOverlaps(IReadOnlyList<Sprite> sprites)
|
||||
{
|
||||
var count = sprites.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var rect = sprites[i].rect;
|
||||
for (int j = i + 1; j < count; j++)
|
||||
{
|
||||
if (rect.Overlaps(sprites[j].rect))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static string GenerateUniqueNameForNamelessSprite(Sprite sprite, HashSet<string> uniqueNames, ref int count)
|
||||
{
|
||||
var baseName = "Nameless";
|
||||
if (sprite.texture != null)
|
||||
baseName = sprite.texture.name;
|
||||
string name;
|
||||
do
|
||||
{
|
||||
name = $"{baseName}_{count++}";
|
||||
}
|
||||
while (uniqueNames.Contains(name));
|
||||
return name;
|
||||
}
|
||||
|
||||
public static List<TileBase> ConvertToTileSheet(Dictionary<Vector2Int, TileDragAndDropHoverData> sheet)
|
||||
{
|
||||
List<TileBase> result = new List<TileBase>();
|
||||
|
||||
string defaultPath = TileDragAndDropManager.GetDefaultTileAssetPath();
|
||||
|
||||
// Early out if all objects are already tiles
|
||||
if (sheet.Values.ToList().FindAll(data => data.hoverObject is TileBase).Count == sheet.Values.Count)
|
||||
{
|
||||
foreach (var item in sheet.Values)
|
||||
{
|
||||
result.Add(item.hoverObject as TileBase);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
UserTileCreationMode userTileCreationMode = UserTileCreationMode.Overwrite;
|
||||
string path = "";
|
||||
bool multipleTiles = sheet.Count > 1;
|
||||
int i = 0;
|
||||
HashSet<String> uniqueNames = new HashSet<string>();
|
||||
if (multipleTiles)
|
||||
{
|
||||
bool userInterventionRequired = false;
|
||||
path = EditorUtility.SaveFolderPanel("Generate tiles into folder ", defaultPath, "");
|
||||
path = FileUtil.GetProjectRelativePath(path);
|
||||
|
||||
// Check if this will overwrite any existing assets
|
||||
foreach (var item in sheet.Values)
|
||||
{
|
||||
if (item.hoverObject is Sprite sprite)
|
||||
{
|
||||
var name = sprite.name;
|
||||
if (String.IsNullOrEmpty(name) || uniqueNames.Contains(name))
|
||||
{
|
||||
name = GenerateUniqueNameForNamelessSprite(sprite, uniqueNames, ref i);
|
||||
}
|
||||
uniqueNames.Add(name);
|
||||
var tilePath = FileUtil.CombinePaths(path, String.Format("{0}.{1}", name, k_TileExtension));
|
||||
if (File.Exists(tilePath))
|
||||
{
|
||||
userInterventionRequired = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// There are existing tile assets in the folder with names matching the items to be created
|
||||
if (userInterventionRequired)
|
||||
{
|
||||
var option = EditorUtility.DisplayDialogComplex("Overwrite?", String.Format("Assets exist at {0}. Do you wish to overwrite existing assets?", path), "Overwrite", "Create New Copy", "Reuse");
|
||||
switch (option)
|
||||
{
|
||||
case 0: // Overwrite
|
||||
{
|
||||
userTileCreationMode = UserTileCreationMode.Overwrite;
|
||||
}
|
||||
break;
|
||||
case 1: // Create New Copy
|
||||
{
|
||||
userTileCreationMode = UserTileCreationMode.CreateUnique;
|
||||
}
|
||||
break;
|
||||
case 2: // Reuse
|
||||
{
|
||||
userTileCreationMode = UserTileCreationMode.Reuse;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Do not check if this will overwrite new tile as user has explicitly selected the file to save to
|
||||
path = EditorUtility.SaveFilePanelInProject("Generate new tile", sheet.Values.First().hoverObject.name, k_TileExtension, "Generate new tile", defaultPath);
|
||||
}
|
||||
TileDragAndDropManager.SetUserTileAssetPath(path);
|
||||
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return result;
|
||||
|
||||
i = 0;
|
||||
uniqueNames.Clear();
|
||||
EditorUtility.DisplayProgressBar("Generating Tile Assets (" + i + "/" + sheet.Count + ")", "Generating tiles", 0f);
|
||||
|
||||
try
|
||||
{
|
||||
MethodInfo createTileMethod = GridPaintActiveTargetsPreferences.GetCreateTileFromPaletteUsingPreferences();
|
||||
if (createTileMethod == null)
|
||||
return null;
|
||||
|
||||
foreach (KeyValuePair<Vector2Int, TileDragAndDropHoverData> item in sheet)
|
||||
{
|
||||
TileBase tile;
|
||||
string tilePath = "";
|
||||
if (item.Value.hoverObject is Sprite sprite)
|
||||
{
|
||||
tile = createTileMethod.Invoke(null, new object[] {sprite}) as TileBase;
|
||||
if (tile == null)
|
||||
continue;
|
||||
|
||||
var name = tile.name;
|
||||
if (String.IsNullOrEmpty(name) || uniqueNames.Contains(name))
|
||||
{
|
||||
name = GenerateUniqueNameForNamelessSprite(sprite, uniqueNames, ref i);
|
||||
}
|
||||
uniqueNames.Add(name);
|
||||
|
||||
tilePath = multipleTiles
|
||||
? FileUtil.CombinePaths(path, String.Format("{0}.{1}", name, k_TileExtension))
|
||||
: path;
|
||||
// Case 1216101: Fix path slashes for Windows
|
||||
tilePath = FileUtil.NiceWinPath(tilePath);
|
||||
switch (userTileCreationMode)
|
||||
{
|
||||
case UserTileCreationMode.CreateUnique:
|
||||
{
|
||||
if (File.Exists(tilePath))
|
||||
tilePath = AssetDatabase.GenerateUniqueAssetPath(tilePath);
|
||||
AssetDatabase.CreateAsset(tile, tilePath);
|
||||
}
|
||||
break;
|
||||
case UserTileCreationMode.Overwrite:
|
||||
{
|
||||
AssetDatabase.CreateAsset(tile, tilePath);
|
||||
}
|
||||
break;
|
||||
case UserTileCreationMode.Reuse:
|
||||
{
|
||||
if (File.Exists(tilePath))
|
||||
tile = AssetDatabase.LoadAssetAtPath<TileBase>(tilePath);
|
||||
else
|
||||
AssetDatabase.CreateAsset(tile, tilePath);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tile = item.Value.hoverObject as TileBase;
|
||||
}
|
||||
EditorUtility.DisplayProgressBar("Generating Tile Assets (" + i + "/" + sheet.Count + ")", "Generating " + tilePath, (float)i++ / sheet.Count);
|
||||
result.Add(tile);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
|
||||
AssetDatabase.Refresh();
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static RectInt GetMinMaxRect(List<Vector2Int> positions)
|
||||
{
|
||||
if (positions == null || positions.Count == 0)
|
||||
return new RectInt();
|
||||
|
||||
return GridEditorUtility.GetMarqueeRect(
|
||||
new Vector2Int(positions.Min(p1 => p1.x), positions.Min(p1 => p1.y)),
|
||||
new Vector2Int(positions.Max(p1 => p1.x), positions.Max(p1 => p1.y))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
internal struct TileDragAndDropHoverData
|
||||
{
|
||||
public Object hoverObject;
|
||||
public Vector3 positionOffset;
|
||||
public Vector3 scaleFactor;
|
||||
public bool hasOffset;
|
||||
|
||||
public TileDragAndDropHoverData(Object hoverObject) : this(hoverObject, Vector3.zero, Vector3.one, false)
|
||||
{
|
||||
}
|
||||
|
||||
public TileDragAndDropHoverData(Object hoverObject, Vector3 positionOffset, Vector3 scaleFactor, bool hasOffset = true)
|
||||
{
|
||||
this.hoverObject = hoverObject;
|
||||
this.positionOffset = positionOffset;
|
||||
if (scaleFactor.z <= 0.0f)
|
||||
scaleFactor.z = 1.0f;
|
||||
this.scaleFactor = scaleFactor;
|
||||
this.hasOffset = hasOffset;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,184 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Tilemaps;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
/// <summary> This class is in charge of drag'n'drops of Tile assets on scene view </summary>
|
||||
internal class TileDragAndDropManager : ScriptableSingleton<TileDragAndDropManager>
|
||||
{
|
||||
private bool m_RegisteredEventHandlers;
|
||||
private Dictionary<Vector2Int, TileDragAndDropHoverData> m_HoverData;
|
||||
|
||||
[SerializeField]
|
||||
private string m_LastUserTileAssetPath;
|
||||
|
||||
[InitializeOnLoadMethod]
|
||||
static void Initialize()
|
||||
{
|
||||
instance.RegisterEventHandlers();
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
RegisterEventHandlers();
|
||||
}
|
||||
|
||||
void RegisterEventHandlers()
|
||||
{
|
||||
if (m_RegisteredEventHandlers)
|
||||
return;
|
||||
|
||||
SceneView.duringSceneGui += DuringSceneGui;
|
||||
m_RegisteredEventHandlers = true;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
SceneView.duringSceneGui -= DuringSceneGui;
|
||||
m_RegisteredEventHandlers = false;
|
||||
}
|
||||
|
||||
private void DuringSceneGui(SceneView sceneView)
|
||||
{
|
||||
Event evt = Event.current;
|
||||
if (evt.type != EventType.DragUpdated && evt.type != EventType.DragPerform && evt.type != EventType.DragExited && evt.type != EventType.Repaint)
|
||||
return;
|
||||
|
||||
Grid activeGrid = GetActiveGrid();
|
||||
if (activeGrid == null || DragAndDrop.objectReferences.Length == 0)
|
||||
return;
|
||||
|
||||
Vector3 localMouse = GridEditorUtility.ScreenToLocal(activeGrid.transform, evt.mousePosition);
|
||||
Vector3Int mouseGridPosition = activeGrid.LocalToCell(localMouse);
|
||||
|
||||
switch (evt.type)
|
||||
{
|
||||
//TODO: Cache this
|
||||
case EventType.DragUpdated:
|
||||
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
|
||||
List<TileBase> tiles = TileDragAndDrop.GetValidTiles(DragAndDrop.objectReferences);
|
||||
instance.m_HoverData = TileDragAndDrop.CreateHoverData(null, null, tiles, activeGrid.cellLayout);
|
||||
if (instance.m_HoverData.Count > 0)
|
||||
{
|
||||
Event.current.Use();
|
||||
GUI.changed = true;
|
||||
}
|
||||
break;
|
||||
case EventType.DragPerform:
|
||||
if (instance.m_HoverData.Count > 0)
|
||||
{
|
||||
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
|
||||
var tileSheet = TileDragAndDrop.ConvertToTileSheet(instance.m_HoverData);
|
||||
Tilemap tilemap = GetOrCreateActiveTilemap();
|
||||
tilemap.ClearAllEditorPreviewTiles();
|
||||
int i = 0;
|
||||
foreach (KeyValuePair<Vector2Int, TileDragAndDropHoverData> item in instance.m_HoverData)
|
||||
{
|
||||
Vector3Int position = new Vector3Int(mouseGridPosition.x + item.Key.x, mouseGridPosition.y + item.Key.y, 0);
|
||||
tilemap.SetTile(position, tileSheet[i++]);
|
||||
tilemap.SetTransformMatrix(position, Matrix4x4.TRS(
|
||||
item.Value.hasOffset ? item.Value.positionOffset - tilemap.tileAnchor : Vector3.zero
|
||||
, Quaternion.identity
|
||||
, Vector3.one));
|
||||
}
|
||||
instance.m_HoverData = null;
|
||||
GUI.changed = true;
|
||||
Event.current.Use();
|
||||
}
|
||||
break;
|
||||
case EventType.Repaint:
|
||||
if (instance.m_HoverData != null)
|
||||
{
|
||||
Tilemap map = Selection.activeGameObject.GetComponentInParent<Tilemap>();
|
||||
|
||||
if (map != null)
|
||||
map.ClearAllEditorPreviewTiles();
|
||||
|
||||
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
|
||||
foreach (KeyValuePair<Vector2Int, TileDragAndDropHoverData> item in instance.m_HoverData)
|
||||
{
|
||||
Vector3Int gridPos = mouseGridPosition + new Vector3Int(item.Key.x, item.Key.y, 0);
|
||||
if (item.Value.hoverObject is TileBase)
|
||||
{
|
||||
TileBase tile = item.Value.hoverObject as TileBase;
|
||||
if (map != null)
|
||||
{
|
||||
map.SetEditorPreviewTile(gridPos, tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (instance.m_HoverData != null && (
|
||||
Event.current.type == EventType.DragExited ||
|
||||
Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Escape))
|
||||
{
|
||||
if (instance.m_HoverData.Count > 0)
|
||||
{
|
||||
Tilemap map = Selection.activeGameObject.GetComponentInParent<Tilemap>();
|
||||
if (map != null)
|
||||
map.ClearAllEditorPreviewTiles();
|
||||
|
||||
Event.current.Use();
|
||||
}
|
||||
|
||||
instance.m_HoverData = null;
|
||||
}
|
||||
}
|
||||
|
||||
internal static string GetDefaultTileAssetPath()
|
||||
{
|
||||
var path = instance.m_LastUserTileAssetPath;
|
||||
if (String.IsNullOrEmpty(path))
|
||||
{
|
||||
path = ProjectBrowser.s_LastInteractedProjectBrowser != null
|
||||
? ProjectBrowser.s_LastInteractedProjectBrowser.GetActiveFolderPath()
|
||||
: "Assets";
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
internal static void SetUserTileAssetPath(string path)
|
||||
{
|
||||
instance.m_LastUserTileAssetPath = path;
|
||||
}
|
||||
|
||||
static Tilemap GetOrCreateActiveTilemap()
|
||||
{
|
||||
if (Selection.activeGameObject != null)
|
||||
{
|
||||
Tilemap tilemap = Selection.activeGameObject.GetComponentInParent<Tilemap>();
|
||||
if (tilemap == null)
|
||||
{
|
||||
Grid grid = Selection.activeGameObject.GetComponentInParent<Grid>();
|
||||
tilemap = CreateNewTilemap(grid);
|
||||
}
|
||||
return tilemap;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static Tilemap CreateNewTilemap(Grid grid)
|
||||
{
|
||||
GameObject go = new GameObject("Tilemap");
|
||||
go.transform.SetParent(grid.gameObject.transform);
|
||||
Tilemap map = go.AddComponent<Tilemap>();
|
||||
go.AddComponent<TilemapRenderer>();
|
||||
return map;
|
||||
}
|
||||
|
||||
static Grid GetActiveGrid()
|
||||
{
|
||||
if (Selection.activeGameObject != null)
|
||||
{
|
||||
return Selection.activeGameObject.GetComponentInParent<Grid>();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Tilemaps;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class for creating Tiles
|
||||
/// </summary>
|
||||
public class TileUtility
|
||||
{
|
||||
internal static void CreateNewTile()
|
||||
{
|
||||
string message = string.Format("Save tile'{0}':", "tile");
|
||||
string newAssetPath = EditorUtility.SaveFilePanelInProject("Save tile", "New Tile", "asset", message, ProjectWindowUtil.GetActiveFolderPath());
|
||||
|
||||
// If user canceled or save path is invalid, we can't create the tile
|
||||
if (string.IsNullOrEmpty(newAssetPath))
|
||||
return;
|
||||
|
||||
AssetDatabase.CreateAsset(CreateDefaultTile(), newAssetPath);
|
||||
}
|
||||
|
||||
/// <summary>Creates a Tile with defaults based on the Tile preset</summary>
|
||||
/// <returns>A Tile with defaults based on the Tile preset</returns>
|
||||
public static Tile CreateDefaultTile()
|
||||
{
|
||||
return ObjectFactory.CreateInstance<Tile>();
|
||||
}
|
||||
|
||||
/// <summary>Creates a Tile with defaults based on the Tile preset and a Sprite set</summary>
|
||||
/// <param name="sprite">A Sprite to set the Tile with</param>
|
||||
/// <returns>A Tile with defaults based on the Tile preset and a Sprite set</returns>
|
||||
[CreateTileFromPalette]
|
||||
public static TileBase DefaultTile(Sprite sprite)
|
||||
{
|
||||
Tile tile = CreateDefaultTile();
|
||||
tile.name = sprite.name;
|
||||
tile.sprite = sprite;
|
||||
tile.color = Color.white;
|
||||
return tile;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,208 @@
|
||||
using UnityEditor.Experimental.SceneManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.Tilemaps;
|
||||
|
||||
namespace UnityEditor.Tilemaps
|
||||
{
|
||||
internal class TilemapPrefabStageHelper : ScriptableSingleton<TilemapPrefabStageHelper>
|
||||
{
|
||||
private bool m_RegisteredEventHandlers;
|
||||
|
||||
private static Grid m_Grid;
|
||||
|
||||
[InitializeOnLoadMethod]
|
||||
private static void Initialize()
|
||||
{
|
||||
instance.RegisterEventHandlers();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
RegisterEventHandlers();
|
||||
}
|
||||
|
||||
private void RegisterEventHandlers()
|
||||
{
|
||||
if (m_RegisteredEventHandlers)
|
||||
return;
|
||||
|
||||
PrefabStage.prefabStageOpened += OnPrefabStageOpened;
|
||||
PrefabStage.prefabStageClosing += OnPrefabStageClosing;
|
||||
m_RegisteredEventHandlers = true;
|
||||
}
|
||||
|
||||
private GameObject CreateGridGameObject(GameObject prefabInstanceRoot)
|
||||
{
|
||||
// Create Grid root for the Tilemap
|
||||
var gridGameObject = EditorUtility.CreateGameObjectWithHideFlags("Grid (Environment)", HideFlags.DontSave);
|
||||
SceneManager.MoveGameObjectToScene(gridGameObject, prefabInstanceRoot.scene);
|
||||
prefabInstanceRoot.transform.SetParent(gridGameObject.transform, false);
|
||||
return gridGameObject;
|
||||
}
|
||||
|
||||
private void OnPrefabStageOpened(PrefabStage prefabStage)
|
||||
{
|
||||
var prefabInstanceRoot = prefabStage.prefabContentsRoot;
|
||||
if (prefabStage.mode == PrefabStage.Mode.InIsolation && prefabInstanceRoot.transform.parent != null)
|
||||
return;
|
||||
|
||||
var tilemap = prefabInstanceRoot.GetComponentInChildren<Tilemap>();
|
||||
if (tilemap == null)
|
||||
return;
|
||||
|
||||
if (tilemap.layoutGrid == null)
|
||||
{
|
||||
GameObject gridGameObject;
|
||||
if (prefabStage.mode == PrefabStage.Mode.InIsolation)
|
||||
{
|
||||
gridGameObject = CreateGridGameObject(prefabInstanceRoot);
|
||||
}
|
||||
else
|
||||
{
|
||||
gridGameObject = prefabInstanceRoot.transform.parent != null
|
||||
? prefabInstanceRoot.transform.parent.gameObject
|
||||
: CreateGridGameObject(prefabInstanceRoot);
|
||||
}
|
||||
if (gridGameObject != null)
|
||||
{
|
||||
m_Grid = gridGameObject.AddComponent<Grid>();
|
||||
}
|
||||
|
||||
if (m_Grid != null)
|
||||
{
|
||||
m_Grid.cellSize = cellSize;
|
||||
m_Grid.cellGap = cellGap;
|
||||
m_Grid.cellLayout = cellLayout;
|
||||
m_Grid.cellSwizzle = cellSwizzle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPrefabStageClosing(PrefabStage prefabStage)
|
||||
{
|
||||
m_Grid = null;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
PrefabStage.prefabStageClosing -= OnPrefabStageClosing;
|
||||
PrefabStage.prefabStageOpened -= OnPrefabStageOpened;
|
||||
m_RegisteredEventHandlers = false;
|
||||
}
|
||||
|
||||
internal class OpenTilemapInPrefabModeProperties
|
||||
{
|
||||
public static readonly string cellSizeXEditorPref = "OpenTilemapInPrefabMode.CellSize.X";
|
||||
public static readonly string cellSizeYEditorPref = "OpenTilemapInPrefabMode.CellSize.Y";
|
||||
public static readonly string cellSizeZEditorPref = "OpenTilemapInPrefabMode.CellSize.Z";
|
||||
public static readonly string cellGapXEditorPref = "OpenTilemapInPrefabMode.CellGap.X";
|
||||
public static readonly string cellGapYEditorPref = "OpenTilemapInPrefabMode.CellGap.Y";
|
||||
public static readonly string cellGapZEditorPref = "OpenTilemapInPrefabMode.CellGap.Z";
|
||||
public static readonly string cellLayoutEditorPref = "OpenTilemapInPrefabMode.CellLayout";
|
||||
public static readonly string cellSwizzleEditorPref = "OpenTilemapInPrefabMode.CellSwizzle";
|
||||
|
||||
public static readonly string cellSizeLookup = "Prefab Mode Grid Cell Size";
|
||||
public static readonly string cellGapLookup = "Prefab Mode Grid Cell Gap";
|
||||
public static readonly string cellLayoutLookup = "Prefab Mode Grid Cell Layout";
|
||||
public static readonly string cellSwizzleLookup = "Prefab Mode Grid Cell Swizzle";
|
||||
|
||||
public static readonly GUIContent cellSizeLabel = EditorGUIUtility.TrTextContent(cellSizeLookup, "Cell Size for Grid when opening a Tilemap in Prefab mode without a Grid.");
|
||||
public static readonly GUIContent cellGapLabel = EditorGUIUtility.TrTextContent(cellGapLookup, "Cell Gap for Grid when opening a Tilemap in Prefab mode without a Grid.");
|
||||
public static readonly GUIContent cellLayoutLabel = EditorGUIUtility.TrTextContent(cellLayoutLookup, "Cell Layout for Grid when opening a Tilemap in Prefab mode without a Grid.");
|
||||
public static readonly GUIContent cellSwizzleLabel = EditorGUIUtility.TrTextContent(cellSwizzleLookup, "Cell Swizzle for Grid when opening a Tilemap in Prefab mode without a Grid.");
|
||||
}
|
||||
|
||||
internal static Vector3 cellSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Vector3(
|
||||
EditorPrefs.GetFloat(OpenTilemapInPrefabModeProperties.cellSizeXEditorPref, 1.0f)
|
||||
, EditorPrefs.GetFloat(OpenTilemapInPrefabModeProperties.cellSizeYEditorPref, 1.0f)
|
||||
, EditorPrefs.GetFloat(OpenTilemapInPrefabModeProperties.cellSizeZEditorPref, 0.0f));
|
||||
}
|
||||
set
|
||||
{
|
||||
EditorPrefs.SetFloat(OpenTilemapInPrefabModeProperties.cellSizeXEditorPref, value.x);
|
||||
EditorPrefs.SetFloat(OpenTilemapInPrefabModeProperties.cellSizeYEditorPref, value.y);
|
||||
EditorPrefs.SetFloat(OpenTilemapInPrefabModeProperties.cellSizeZEditorPref, value.z);
|
||||
}
|
||||
}
|
||||
|
||||
internal static Vector3 cellGap
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Vector3(
|
||||
EditorPrefs.GetFloat(OpenTilemapInPrefabModeProperties.cellGapXEditorPref, 0.0f)
|
||||
, EditorPrefs.GetFloat(OpenTilemapInPrefabModeProperties.cellGapYEditorPref, 0.0f)
|
||||
, EditorPrefs.GetFloat(OpenTilemapInPrefabModeProperties.cellGapZEditorPref, 0.0f));
|
||||
}
|
||||
set
|
||||
{
|
||||
EditorPrefs.SetFloat(OpenTilemapInPrefabModeProperties.cellGapXEditorPref, value.x);
|
||||
EditorPrefs.SetFloat(OpenTilemapInPrefabModeProperties.cellGapYEditorPref, value.y);
|
||||
EditorPrefs.SetFloat(OpenTilemapInPrefabModeProperties.cellGapZEditorPref, value.z);
|
||||
}
|
||||
}
|
||||
|
||||
internal static GridLayout.CellLayout cellLayout
|
||||
{
|
||||
get
|
||||
{
|
||||
return (GridLayout.CellLayout)EditorPrefs.GetInt(
|
||||
OpenTilemapInPrefabModeProperties.cellLayoutEditorPref, (int)GridLayout.CellLayout.Rectangle);
|
||||
}
|
||||
set
|
||||
{
|
||||
EditorPrefs.SetInt(OpenTilemapInPrefabModeProperties.cellLayoutEditorPref, (int)value);
|
||||
}
|
||||
}
|
||||
|
||||
internal static GridLayout.CellSwizzle cellSwizzle
|
||||
{
|
||||
get
|
||||
{
|
||||
return (GridLayout.CellSwizzle)EditorPrefs.GetInt(
|
||||
OpenTilemapInPrefabModeProperties.cellSwizzleEditorPref, (int)GridLayout.CellSwizzle.XYZ);
|
||||
}
|
||||
set
|
||||
{
|
||||
EditorPrefs.SetInt(OpenTilemapInPrefabModeProperties.cellSwizzleEditorPref, (int)value);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void PreferencesGUI()
|
||||
{
|
||||
using (new SettingsWindow.GUIScope())
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var newCellSize = EditorGUILayout.Vector3Field(OpenTilemapInPrefabModeProperties.cellSizeLabel, cellSize);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
cellSize = newCellSize;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var newCellGap = EditorGUILayout.Vector3Field(OpenTilemapInPrefabModeProperties.cellGapLabel, cellGap);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
cellGap = newCellGap;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var newCellLayout = (GridLayout.CellLayout)EditorGUILayout.EnumPopup(OpenTilemapInPrefabModeProperties.cellLayoutLabel, cellLayout);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
cellLayout = newCellLayout;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var newCellSwizzle = (GridLayout.CellSwizzle)EditorGUILayout.EnumPopup(OpenTilemapInPrefabModeProperties.cellSwizzleLabel, cellSwizzle);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
cellSwizzle = newCellSwizzle;
|
||||
if (EditorGUI.EndChangeCheck() && m_Grid != null)
|
||||
{
|
||||
m_Grid.cellSize = newCellSize;
|
||||
m_Grid.cellGap = newCellGap;
|
||||
m_Grid.cellLayout = newCellLayout;
|
||||
m_Grid.cellSwizzle = newCellSwizzle;
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "Unity.2D.Tilemap.Editor",
|
||||
"references": [
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": []
|
||||
}
|
Reference in New Issue
Block a user