暫無描述
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

GridBrushEditor.cs 41KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901
  1. using System;
  2. using System.Linq;
  3. using UnityEditor.EditorTools;
  4. using UnityEngine;
  5. using UnityEngine.Tilemaps;
  6. using UnityEditor.SceneManagement;
  7. using UnityEngine.Scripting.APIUpdating;
  8. using Object = UnityEngine.Object;
  9. namespace UnityEditor.Tilemaps
  10. {
  11. /// <summary>Editor for GridBrush.</summary>
  12. [MovedFrom(true, "UnityEditor", "UnityEditor")]
  13. [CustomEditor(typeof(GridBrush))]
  14. public class GridBrushEditor : GridBrushEditorBase
  15. {
  16. private static class Styles
  17. {
  18. public static readonly GUIContent tileLabel = EditorGUIUtility.TrTextContent("Tile", "Tile set in tilemap");
  19. public static readonly GUIContent spriteLabel = EditorGUIUtility.TrTextContent("Sprite", "Sprite set when tile is set in tilemap");
  20. public static readonly GUIContent colorLabel = EditorGUIUtility.TrTextContent("Color", "Color set when tile is set in tilemap");
  21. public static readonly GUIContent colliderTypeLabel = EditorGUIUtility.TrTextContent("Collider Type", "Collider shape used for tile");
  22. public static readonly GUIContent gameObjectToInstantiateLabel = EditorGUIUtility.TrTextContent("GameObject to Instantiate", "GameObject to instantiate for tile");
  23. public static readonly GUIContent lockColorLabel = EditorGUIUtility.TrTextContent("Lock Color", "Prevents tilemap from changing color of tile");
  24. public static readonly GUIContent lockTransformLabel = EditorGUIUtility.TrTextContent("Lock Transform", "Prevents tilemap from changing transform of tile");
  25. public static readonly GUIContent gridSelectionPropertiesLabel = EditorGUIUtility.TrTextContent("Grid Selection Properties");
  26. public static readonly GUIContent modifyTilemapLabel = EditorGUIUtility.TrTextContent("Modify Tilemap");
  27. public static readonly GUIContent modifyLabel = EditorGUIUtility.TrTextContent("Modify");
  28. public static readonly GUIContent deleteSelectionLabel = EditorGUIUtility.TrTextContent("Delete Selection");
  29. public static readonly GUIContent noTool =
  30. EditorGUIUtility.TrTextContentWithIcon("None", "No Gizmo in the Scene view", "RectTool");
  31. public static readonly GUIContent moveTool =
  32. EditorGUIUtility.TrTextContentWithIcon("Move", "Shows a Gizmo in the Scene view for changing the offset for the Grid Selection", "MoveTool");
  33. public static readonly GUIContent rotateTool =
  34. EditorGUIUtility.TrTextContentWithIcon("Rotate", "Shows a Gizmo in the Scene view for changing the rotation for the Grid Selection", "RotateTool");
  35. public static readonly GUIContent scaleTool =
  36. EditorGUIUtility.TrTextContentWithIcon("Scale", "Shows a Gizmo in the Scene view for changing the scale for the Grid Selection", "ScaleTool");
  37. public static readonly GUIContent transformTool =
  38. EditorGUIUtility.TrTextContentWithIcon("Transform", "Shows a Gizmo in the Scene view for changing the transform for the Grid Selection", "TransformTool");
  39. public static readonly GUIContent[] selectionTools = new[]
  40. {
  41. noTool
  42. , moveTool
  43. , rotateTool
  44. , scaleTool
  45. , transformTool
  46. };
  47. public static readonly Type[] selectionTypes = new[]
  48. {
  49. typeof(SelectTool)
  50. , typeof(GridSelectionMoveTool)
  51. , typeof(GridSelectionRotateTool)
  52. , typeof(GridSelectionScaleTool)
  53. , typeof(GridSelectionTransformTool)
  54. };
  55. public static readonly string tooltipText = L10n.Tr("Use this brush to paint and erase Tiles from a Tilemap.");
  56. public static readonly string iconPath = "Packages/com.unity.2d.tilemap/Editor/Icons/Tilemap.DefaultBrush.png";
  57. }
  58. /// <summary>
  59. /// Identifiers for operations modifying the Tilemap.
  60. /// </summary>
  61. public enum ModifyCells
  62. {
  63. /// <summary>
  64. /// Inserts a row at the target position.
  65. /// </summary>
  66. InsertRow,
  67. /// <summary>
  68. /// Inserts a column at the target position.
  69. /// </summary>
  70. InsertColumn,
  71. /// <summary>
  72. /// Inserts a row before the target position.
  73. /// </summary>
  74. InsertRowBefore,
  75. /// <summary>
  76. /// Inserts a column before the target position.
  77. /// </summary>
  78. InsertColumnBefore,
  79. /// <summary>
  80. /// Delete a row at the target position.
  81. /// </summary>
  82. DeleteRow,
  83. /// <summary>
  84. /// Delete a column at the target position.
  85. /// </summary>
  86. DeleteColumn,
  87. /// <summary>
  88. /// Delete a row before the target position.
  89. /// </summary>
  90. DeleteRowBefore,
  91. /// <summary>
  92. /// Delete a column before the target position.
  93. /// </summary>
  94. DeleteColumnBefore,
  95. }
  96. private class GridBrushProperties
  97. {
  98. 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");
  99. public static readonly GUIContent floodFillPreviewFillExtentsLabel = EditorGUIUtility.TrTextContent("Flood Fill Preview Fill Extents", "Extents from the selected position when flood filling with a Tile. Set this to 0 to flood fill to the full extents.");
  100. public static readonly GUIContent floodFillPreviewEraseExtentsLabel = EditorGUIUtility.TrTextContent("Flood Fill Preview Erase Extents", "Extents from the selected position when flood filling without a Tile. Set this to 0 to flood erase to the full extents.");
  101. public static readonly string floodFillPreviewEditorPref = "GridBrush.EnableFloodFillPreview";
  102. public static readonly string floodFillPreviewFillExtentsEditorPref = "GridBrush.FloodFillPreviewFillExtents";
  103. public static readonly string floodFillPreviewEraseExtentsEditorPref = "GridBrush.FloodFillPreviewEraseExtents";
  104. }
  105. internal static bool showFloodFillPreview
  106. {
  107. get => EditorPrefs.GetBool(GridBrushProperties.floodFillPreviewEditorPref, true);
  108. set => EditorPrefs.SetBool(GridBrushProperties.floodFillPreviewEditorPref, value);
  109. }
  110. internal static int floodFillPreviewFillExtents
  111. {
  112. get => Math.Max(0, EditorPrefs.GetInt(GridBrushProperties.floodFillPreviewFillExtentsEditorPref, 0));
  113. set => EditorPrefs.SetInt(GridBrushProperties.floodFillPreviewFillExtentsEditorPref, Math.Max(0, value));
  114. }
  115. internal static int floodFillPreviewEraseExtents
  116. {
  117. get => Math.Max(0, EditorPrefs.GetInt(GridBrushProperties.floodFillPreviewEraseExtentsEditorPref, 0));
  118. set => EditorPrefs.SetInt(GridBrushProperties.floodFillPreviewEraseExtentsEditorPref, Math.Max(0, value));
  119. }
  120. /// <summary>The GridBrush that is the target for this editor.</summary>
  121. public GridBrush brush { get { return target as GridBrush; } }
  122. private int m_LastPreviewRefreshHash;
  123. // These are used to clean out previews that happened on previous update
  124. private GridLayout m_LastGrid;
  125. private GameObject m_LastBrushTarget;
  126. private BoundsInt? m_LastBounds;
  127. private GridBrushBase.Tool? m_LastTool;
  128. // These are used to handle selection in Selection Inspector
  129. private TileBase[] m_SelectionTiles;
  130. private Color[] m_SelectionColors;
  131. private Matrix4x4[] m_SelectionMatrices;
  132. private TileFlags[] m_SelectionFlagsArray;
  133. private Sprite[] m_SelectionSprites;
  134. private Tile.ColliderType[] m_SelectionColliderTypes;
  135. private GameObject[] m_SelectionGameObjectToInstantiate;
  136. private int selectionCellCount => Math.Abs(GridSelection.position.size.x * GridSelection.position.size.y * GridSelection.position.size.z);
  137. // These are used to handle insert/delete cells on the Tilemap
  138. private int m_CellCount = 1;
  139. private ModifyCells m_ModifyCells = ModifyCells.InsertRow;
  140. private Texture2D m_Icon;
  141. private static GridSelectionTool[] s_GridSelectionTools;
  142. private static Tile s_EmptySpriteTile;
  143. /// <summary>
  144. /// Initializes the GridBrushEditor.
  145. /// </summary>
  146. protected virtual void OnEnable()
  147. {
  148. Undo.undoRedoPerformed += ClearLastPreview;
  149. if (s_GridSelectionTools == null || s_GridSelectionTools[0] == null)
  150. {
  151. s_GridSelectionTools = new GridSelectionTool[]
  152. {
  153. CreateInstance<GridSelectionMoveTool>(),
  154. CreateInstance<GridSelectionRotateTool>(),
  155. CreateInstance<GridSelectionScaleTool>(),
  156. CreateInstance<GridSelectionTransformTool>()
  157. };
  158. }
  159. if (s_EmptySpriteTile == null)
  160. {
  161. s_EmptySpriteTile = ScriptableObject.CreateInstance<Tile>();
  162. s_EmptySpriteTile.sprite = null;
  163. }
  164. }
  165. /// <summary>
  166. /// Deinitialises the GridBrushEditor.
  167. /// </summary>
  168. protected virtual void OnDisable()
  169. {
  170. Undo.undoRedoPerformed -= ClearLastPreview;
  171. ClearLastPreview();
  172. }
  173. private void ClearLastPreview()
  174. {
  175. ClearPreview();
  176. m_LastPreviewRefreshHash = 0;
  177. }
  178. /// <summary>Callback for painting the GUI for the GridBrush in the Scene View.</summary>
  179. /// <param name="gridLayout">Grid that the brush is being used on.</param>
  180. /// <param name="brushTarget">Target of the GridBrushBase::ref::Tool operation. By default the currently selected GameObject.</param>
  181. /// <param name="position">Current selected location of the brush.</param>
  182. /// <param name="tool">Current GridBrushBase::ref::Tool selected.</param>
  183. /// <param name="executing">Whether brush is being used.</param>
  184. public override void OnPaintSceneGUI(GridLayout gridLayout, GameObject brushTarget, BoundsInt position, GridBrushBase.Tool tool, bool executing)
  185. {
  186. BoundsInt gizmoRect = position;
  187. bool refreshPreviews = false;
  188. if (Event.current.type == EventType.Layout || Event.current.type == EventType.Repaint)
  189. {
  190. int newPreviewRefreshHash = GetHash(gridLayout, brushTarget, position, tool, brush);
  191. refreshPreviews = newPreviewRefreshHash != m_LastPreviewRefreshHash;
  192. if (refreshPreviews)
  193. m_LastPreviewRefreshHash = newPreviewRefreshHash;
  194. }
  195. if (tool == GridBrushBase.Tool.Move)
  196. {
  197. if (refreshPreviews && executing)
  198. {
  199. ClearPreview();
  200. PaintPreview(gridLayout, brushTarget, position.min);
  201. }
  202. }
  203. else if (tool == GridBrushBase.Tool.Paint || tool == GridBrushBase.Tool.Erase)
  204. {
  205. if (refreshPreviews)
  206. {
  207. ClearPreview();
  208. if (tool != GridBrushBase.Tool.Erase)
  209. {
  210. PaintPreview(gridLayout, brushTarget, position.min);
  211. }
  212. else
  213. {
  214. ErasePreview(gridLayout, brushTarget, position.min);
  215. }
  216. }
  217. gizmoRect = new BoundsInt(position.min - brush.pivot, brush.size);
  218. }
  219. else if (tool == GridBrushBase.Tool.Box)
  220. {
  221. if (refreshPreviews)
  222. {
  223. ClearPreview();
  224. BoxFillPreview(gridLayout, brushTarget, position);
  225. }
  226. }
  227. else if (tool == GridBrushBase.Tool.FloodFill)
  228. {
  229. if (refreshPreviews)
  230. {
  231. if (CheckFloodFillPreview(gridLayout, brushTarget, position.min))
  232. ClearPreview();
  233. FloodFillPreview(gridLayout, brushTarget, position.min);
  234. }
  235. }
  236. base.OnPaintSceneGUI(gridLayout, brushTarget, gizmoRect, tool, executing);
  237. }
  238. private void UpdateSelection(Tilemap tilemap)
  239. {
  240. var selection = GridSelection.position;
  241. var cellCount = selectionCellCount;
  242. if (m_SelectionTiles == null || m_SelectionTiles.Length != selectionCellCount)
  243. {
  244. m_SelectionTiles = new TileBase[cellCount];
  245. m_SelectionColors = new Color[cellCount];
  246. m_SelectionMatrices = new Matrix4x4[cellCount];
  247. m_SelectionFlagsArray = new TileFlags[cellCount];
  248. m_SelectionSprites = new Sprite[cellCount];
  249. m_SelectionColliderTypes = new Tile.ColliderType[cellCount];
  250. m_SelectionGameObjectToInstantiate = new GameObject[cellCount];
  251. }
  252. int index = 0;
  253. foreach (var p in selection.allPositionsWithin)
  254. {
  255. m_SelectionTiles[index] = tilemap.GetTile(p);
  256. m_SelectionColors[index] = tilemap.GetColor(p);
  257. m_SelectionMatrices[index] = tilemap.GetTransformMatrix(p);
  258. m_SelectionFlagsArray[index] = tilemap.GetTileFlags(p);
  259. m_SelectionSprites[index] = tilemap.GetSprite(p);
  260. m_SelectionColliderTypes[index] = tilemap.GetColliderType(p);
  261. m_SelectionGameObjectToInstantiate[index] = tilemap.GetObjectToInstantiate(p);
  262. index++;
  263. }
  264. }
  265. /// <summary>Callback for drawing the Inspector GUI when there is an active GridSelection made in a Tilemap.</summary>
  266. public override void OnSelectionInspectorGUI()
  267. {
  268. BoundsInt selection = GridSelection.position;
  269. Tilemap tilemap = GridSelection.target.GetComponent<Tilemap>();
  270. int cellCount = selectionCellCount;
  271. if (tilemap != null && cellCount > 0)
  272. {
  273. base.OnSelectionInspectorGUI();
  274. if (!EditorGUIUtility.editingTextField
  275. && Event.current.type == EventType.KeyDown
  276. && (Event.current.keyCode == KeyCode.Delete
  277. || Event.current.keyCode == KeyCode.Backspace))
  278. {
  279. DeleteSelection(tilemap, selection);
  280. Event.current.Use();
  281. }
  282. GUILayout.Space(10f);
  283. EditorGUILayout.LabelField(Styles.gridSelectionPropertiesLabel, EditorStyles.boldLabel);
  284. UpdateSelection(tilemap);
  285. EditorGUI.BeginChangeCheck();
  286. EditorGUI.showMixedValue = m_SelectionTiles.Any(tile => tile != m_SelectionTiles.First());
  287. var position = new Vector3Int(selection.xMin, selection.yMin, selection.zMin);
  288. TileBase newTile = EditorGUILayout.ObjectField(Styles.tileLabel, tilemap.GetTile(position), typeof(TileBase), false) as TileBase;
  289. if (EditorGUI.EndChangeCheck())
  290. {
  291. Undo.RecordObject(tilemap, "Edit Tilemap");
  292. foreach (var p in selection.allPositionsWithin)
  293. tilemap.SetTile(p, newTile);
  294. }
  295. using (new EditorGUI.DisabledScope(true))
  296. {
  297. EditorGUI.showMixedValue = m_SelectionSprites.Any(sprite => sprite != m_SelectionSprites.First());
  298. EditorGUILayout.ObjectField(Styles.spriteLabel, m_SelectionSprites[0], typeof(Sprite), false, GUILayout.Height(EditorGUI.kSingleLineHeight));
  299. }
  300. bool colorFlagsAllEqual = m_SelectionFlagsArray.All(flags => (flags & TileFlags.LockColor) == (m_SelectionFlagsArray.First() & TileFlags.LockColor));
  301. using (new EditorGUI.DisabledScope(!colorFlagsAllEqual || (m_SelectionFlagsArray[0] & TileFlags.LockColor) != 0))
  302. {
  303. EditorGUI.showMixedValue = m_SelectionColors.Any(color => color != m_SelectionColors.First());
  304. EditorGUI.BeginChangeCheck();
  305. Color newColor = EditorGUILayout.ColorField(Styles.colorLabel, m_SelectionColors[0]);
  306. if (EditorGUI.EndChangeCheck())
  307. {
  308. Undo.RecordObject(tilemap, "Edit Tilemap");
  309. foreach (var p in selection.allPositionsWithin)
  310. tilemap.SetColor(p, newColor);
  311. }
  312. }
  313. using (new EditorGUI.DisabledScope(true))
  314. {
  315. EditorGUI.showMixedValue = m_SelectionColliderTypes.Any(colliderType => colliderType != m_SelectionColliderTypes.First());
  316. EditorGUILayout.EnumPopup(Styles.colliderTypeLabel, m_SelectionColliderTypes[0]);
  317. EditorGUI.showMixedValue = m_SelectionGameObjectToInstantiate.Any(gameObject => gameObject != m_SelectionGameObjectToInstantiate.First());
  318. EditorGUILayout.ObjectField(Styles.gameObjectToInstantiateLabel, m_SelectionGameObjectToInstantiate[0], typeof(GameObject), false);
  319. }
  320. bool transformFlagsAllEqual = m_SelectionFlagsArray.All(flags => (flags & TileFlags.LockTransform) == (m_SelectionFlagsArray.First() & TileFlags.LockTransform));
  321. using (new EditorGUI.DisabledScope(!transformFlagsAllEqual || (m_SelectionFlagsArray[0] & TileFlags.LockTransform) != 0))
  322. {
  323. EditorGUI.showMixedValue = m_SelectionMatrices.Any(matrix => matrix != m_SelectionMatrices.First());
  324. EditorGUI.BeginChangeCheck();
  325. Matrix4x4 newTransformMatrix = TileEditor.TransformMatrixOnGUI(m_SelectionMatrices[0]);
  326. if (EditorGUI.EndChangeCheck())
  327. {
  328. Undo.RecordObject(tilemap, "Edit Tilemap");
  329. foreach (var p in selection.allPositionsWithin)
  330. tilemap.SetTransformMatrix(p, newTransformMatrix);
  331. }
  332. }
  333. using (new EditorGUI.DisabledScope(true))
  334. {
  335. EditorGUI.showMixedValue = !colorFlagsAllEqual;
  336. EditorGUILayout.Toggle(Styles.lockColorLabel, (m_SelectionFlagsArray[0] & TileFlags.LockColor) != 0);
  337. EditorGUI.showMixedValue = !transformFlagsAllEqual;
  338. EditorGUILayout.Toggle(Styles.lockTransformLabel, (m_SelectionFlagsArray[0] & TileFlags.LockTransform) != 0);
  339. }
  340. EditorGUI.showMixedValue = false;
  341. if (GUILayout.Button(Styles.deleteSelectionLabel))
  342. {
  343. DeleteSelection(tilemap, selection);
  344. }
  345. EditorGUILayout.Space();
  346. EditorGUILayout.LabelField(Styles.modifyTilemapLabel, EditorStyles.boldLabel);
  347. EditorGUILayout.Space();
  348. var active = -1;
  349. for (var i = 0; i < Styles.selectionTypes.Length; ++i)
  350. {
  351. if (ToolManager.activeToolType == Styles.selectionTypes[i])
  352. {
  353. active = i;
  354. break;
  355. }
  356. }
  357. EditorGUI.BeginChangeCheck();
  358. var selected = GUILayout.Toolbar(active, Styles.selectionTools);
  359. if (EditorGUI.EndChangeCheck() && selected != -1)
  360. {
  361. ToolManager.SetActiveTool(Styles.selectionTypes[selected]);
  362. }
  363. EditorGUILayout.Space();
  364. GUILayout.BeginHorizontal();
  365. m_ModifyCells = (ModifyCells)EditorGUILayout.EnumPopup(m_ModifyCells);
  366. m_CellCount = EditorGUILayout.IntField(m_CellCount);
  367. if (GUILayout.Button(Styles.modifyLabel))
  368. {
  369. RegisterUndoForTilemap(tilemap, Enum.GetName(typeof(ModifyCells), m_ModifyCells));
  370. switch (m_ModifyCells)
  371. {
  372. case ModifyCells.InsertRow:
  373. {
  374. tilemap.InsertCells(GridSelection.position.position, 0, m_CellCount, 0);
  375. break;
  376. }
  377. case ModifyCells.InsertRowBefore:
  378. {
  379. tilemap.InsertCells(GridSelection.position.position, 0, -m_CellCount, 0);
  380. break;
  381. }
  382. case ModifyCells.InsertColumn:
  383. {
  384. tilemap.InsertCells(GridSelection.position.position, m_CellCount, 0, 0);
  385. break;
  386. }
  387. case ModifyCells.InsertColumnBefore:
  388. {
  389. tilemap.InsertCells(GridSelection.position.position, -m_CellCount, 0, 0);
  390. break;
  391. }
  392. case ModifyCells.DeleteRow:
  393. {
  394. tilemap.DeleteCells(GridSelection.position.position, 0, m_CellCount, 0);
  395. break;
  396. }
  397. case ModifyCells.DeleteRowBefore:
  398. {
  399. tilemap.DeleteCells(GridSelection.position.position, 0, -m_CellCount, 0);
  400. break;
  401. }
  402. case ModifyCells.DeleteColumn:
  403. {
  404. tilemap.DeleteCells(GridSelection.position.position, m_CellCount, 0, 0);
  405. break;
  406. }
  407. case ModifyCells.DeleteColumnBefore:
  408. {
  409. tilemap.DeleteCells(GridSelection.position.position, -m_CellCount, 0, 0);
  410. break;
  411. }
  412. }
  413. }
  414. GUILayout.EndHorizontal();
  415. }
  416. }
  417. private void DeleteSelection(Tilemap tilemap, BoundsInt selection)
  418. {
  419. if (tilemap == null)
  420. return;
  421. RegisterUndo(tilemap.gameObject, GridBrushBase.Tool.Erase);
  422. brush.BoxErase(tilemap.layoutGrid, tilemap.gameObject, selection);
  423. }
  424. /// <summary> Callback when the mouse cursor leaves and editing area. </summary>
  425. /// <remarks> Cleans up brush previews. </remarks>
  426. public override void OnMouseLeave()
  427. {
  428. ClearPreview();
  429. }
  430. /// <summary> Callback when the GridBrush Tool is deactivated. </summary>
  431. /// <param name="tool">GridBrush Tool that is deactivated.</param>
  432. /// <remarks> Cleans up brush previews. </remarks>
  433. public override void OnToolDeactivated(GridBrushBase.Tool tool)
  434. {
  435. ClearPreview();
  436. }
  437. /// <summary> Describes the usage of the GridBrush. </summary>
  438. public override string tooltip
  439. {
  440. get { return Styles.tooltipText; }
  441. }
  442. /// <summary> Returns an icon identifying the Grid Brush. </summary>
  443. public override Texture2D icon
  444. {
  445. get
  446. {
  447. if (m_Icon == null)
  448. {
  449. m_Icon = EditorGUIUtility.LoadIcon(Styles.iconPath);
  450. }
  451. return m_Icon;
  452. }
  453. }
  454. /// <summary> Whether the GridBrush can change Z Position. </summary>
  455. public override bool canChangeZPosition
  456. {
  457. get { return brush.canChangeZPosition; }
  458. set { brush.canChangeZPosition = value; }
  459. }
  460. /// <summary>
  461. /// Whether the Brush is in a state that should be saved for selection.
  462. /// </summary>
  463. public override bool shouldSaveBrushForSelection
  464. {
  465. get
  466. {
  467. if (brush.cells != null)
  468. {
  469. foreach (var cell in brush.cells)
  470. {
  471. if (cell != null && cell.tile != null)
  472. return true;
  473. }
  474. }
  475. return false;
  476. }
  477. }
  478. /// <summary>Callback for registering an Undo action before the GridBrushBase does the current GridBrushBase::ref::Tool action.</summary>
  479. /// <param name="brushTarget">Target of the GridBrushBase::ref::Tool operation. By default the currently selected GameObject.</param>
  480. /// <param name="tool">Current GridBrushBase::ref::Tool selected.</param>
  481. /// <remarks>Implement this for any special Undo behaviours when a brush is used.</remarks>
  482. public override void RegisterUndo(GameObject brushTarget, GridBrushBase.Tool tool)
  483. {
  484. if (brushTarget != null)
  485. {
  486. var tilemap = brushTarget.GetComponent<Tilemap>();
  487. if (tilemap != null)
  488. {
  489. RegisterUndoForTilemap(tilemap, tool.ToString());
  490. }
  491. }
  492. }
  493. /// <summary>Returns all valid targets that the brush can edit.</summary>
  494. /// <remarks>Valid targets for the GridBrush are any GameObjects with a Tilemap component.</remarks>
  495. public override GameObject[] validTargets
  496. {
  497. get
  498. {
  499. StageHandle currentStageHandle = StageUtility.GetCurrentStageHandle();
  500. return currentStageHandle.FindComponentsOfType<Tilemap>().Where(x =>
  501. {
  502. GameObject gameObject;
  503. return (gameObject = x.gameObject).scene.isLoaded
  504. && gameObject.activeInHierarchy
  505. && !gameObject.hideFlags.HasFlag(HideFlags.NotEditable);
  506. }).Select(x => x.gameObject).ToArray();
  507. }
  508. }
  509. /// <summary>Paints preview data into a cell of a grid given the coordinates of the cell.</summary>
  510. /// <param name="gridLayout">The grid to paint data to.</param>
  511. /// <param name="brushTarget">The target of the paint operation. This is the currently selected GameObject by default.</param>
  512. /// <param name="position">The coordinates of the cell to paint data to.</param>
  513. /// <remarks>The GridBrush will paint preview Sprites in its brush cells onto an associated Tilemap. This will not instantiate objects associated with the painted Tiles.</remarks>
  514. public virtual void PaintPreview(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
  515. {
  516. var min = position - brush.pivot;
  517. var max = min + brush.size;
  518. var bounds = new BoundsInt(min, max - min);
  519. if (brushTarget != null)
  520. {
  521. var map = brushTarget.GetComponent<Tilemap>();
  522. if (map != null)
  523. {
  524. foreach (var location in bounds.allPositionsWithin)
  525. {
  526. var brushPosition = location - min;
  527. var cell = brush.cells[brush.GetCellIndex(brushPosition)];
  528. if (cell.tile != null)
  529. {
  530. SetTilemapPreviewCell(map, location, cell.tile, cell.matrix, cell.color);
  531. }
  532. }
  533. }
  534. }
  535. m_LastGrid = gridLayout;
  536. m_LastBounds = bounds;
  537. m_LastBrushTarget = brushTarget;
  538. m_LastTool = GridBrushBase.Tool.Paint;
  539. }
  540. /// <summary>Displays a preview of the Tile (after erasure) on the cell of a grid at the given coordinates of the cell.</summary>
  541. /// <param name="gridLayout">The grid to paint data to.</param>
  542. /// <param name="brushTarget">The target of the erase operation. This is the currently selected GameObject by default.</param>
  543. /// <param name="position">The coordinates of the cell to paint data to.</param>
  544. /// <remarks>The GridBrush will paint preview Sprites of the Tiles (after erasure) into its brush cells on an associated Tilemap. This will not instantiate objects associated with the painted Tiles.</remarks>
  545. public virtual void ErasePreview(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
  546. {
  547. var min = position - brush.pivot;
  548. var max = min + brush.size;
  549. var bounds = new BoundsInt(min, max - min);
  550. if (brushTarget != null)
  551. {
  552. var map = brushTarget.GetComponent<Tilemap>();
  553. if (map != null)
  554. {
  555. foreach (var location in bounds.allPositionsWithin)
  556. {
  557. var brushPosition = location - min;
  558. var cell = brush.cells[brush.GetCellIndex(brushPosition)];
  559. SetTilemapPreviewCell(map, location, s_EmptySpriteTile, cell.matrix, cell.color);
  560. }
  561. }
  562. }
  563. m_LastGrid = gridLayout;
  564. m_LastBounds = bounds;
  565. m_LastBrushTarget = brushTarget;
  566. m_LastTool = GridBrushBase.Tool.Erase;
  567. }
  568. /// <summary>Does a preview of what happens when a GridBrush.BoxFill is done with the same parameters.</summary>
  569. /// <param name="gridLayout">Grid to box fill data to.</param>
  570. /// <param name="brushTarget">Target of box fill operation. By default the currently selected GameObject.</param>
  571. /// <param name="position">The bounds to box fill data to.</param>
  572. public virtual void BoxFillPreview(GridLayout gridLayout, GameObject brushTarget, BoundsInt position)
  573. {
  574. if (brushTarget != null)
  575. {
  576. var map = brushTarget.GetComponent<Tilemap>();
  577. if (map != null)
  578. {
  579. foreach (var location in position.allPositionsWithin)
  580. {
  581. var local = location - position.min;
  582. var cell = brush.cells[brush.GetCellIndexWrapAround(local.x, local.y, local.z)];
  583. if (cell.tile != null)
  584. {
  585. SetTilemapPreviewCell(map, location, cell.tile, cell.matrix, cell.color);
  586. }
  587. }
  588. }
  589. }
  590. m_LastGrid = gridLayout;
  591. m_LastBounds = position;
  592. m_LastBrushTarget = brushTarget;
  593. m_LastTool = GridBrushBase.Tool.Box;
  594. }
  595. private bool CheckFloodFillPreview(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
  596. {
  597. if (m_LastGrid == gridLayout
  598. && m_LastBrushTarget == brushTarget
  599. && m_LastBounds.HasValue && m_LastBounds.Value.Contains(position)
  600. && brushTarget != null && brush.cellCount > 0)
  601. {
  602. var map = brushTarget.GetComponent<Tilemap>();
  603. if (map != null)
  604. {
  605. var cell = brush.cells[0];
  606. var hasTile = cell.tile != null;
  607. if ((hasTile && floodFillPreviewFillExtents == 0 || !hasTile && floodFillPreviewEraseExtents == 0)
  608. && (cell.tile == map.GetEditorPreviewTile(position)))
  609. return false;
  610. }
  611. }
  612. return true;
  613. }
  614. /// <summary>Does a preview of what happens when a GridBrush.FloodFill is done with the same parameters.</summary>
  615. /// <param name="gridLayout">Grid to paint data to.</param>
  616. /// <param name="brushTarget">Target of the flood fill operation. By default the currently selected GameObject.</param>
  617. /// <param name="position">The coordinates of the cell to flood fill data to.</param>
  618. public virtual void FloodFillPreview(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
  619. {
  620. // This can be quite taxing on a large Tilemap, so users can choose whether to do this or not
  621. if (!showFloodFillPreview)
  622. return;
  623. var bounds = new BoundsInt(position, Vector3Int.one);
  624. if (brushTarget != null && brush.cellCount > 0)
  625. {
  626. var map = brushTarget.GetComponent<Tilemap>();
  627. if (map != null)
  628. {
  629. var cell = brush.cells[0];
  630. var eraseExtents = floodFillPreviewEraseExtents;
  631. var fillExtents = floodFillPreviewFillExtents;
  632. var validFloodFillTile = cell.tile != null;
  633. var floodFillTile = validFloodFillTile ? cell.tile : s_EmptySpriteTile;
  634. if (!validFloodFillTile && floodFillPreviewEraseExtents > 0)
  635. {
  636. map.EditorPreviewBoxFill(position, floodFillTile, position.x - eraseExtents, position.y - eraseExtents, position.x + eraseExtents, position.y + eraseExtents);
  637. }
  638. else if (validFloodFillTile && floodFillPreviewFillExtents > 0)
  639. {
  640. map.EditorPreviewBoxFill(position, floodFillTile, position.x - fillExtents, position.y - fillExtents, position.x + fillExtents, position.y + fillExtents);
  641. }
  642. else
  643. {
  644. map.EditorPreviewFloodFill(position, floodFillTile);
  645. }
  646. // Set floodfill bounds as tilemap bounds
  647. var origin = map.origin;
  648. bounds.min = origin;
  649. bounds.max = origin + map.size;
  650. }
  651. }
  652. m_LastGrid = gridLayout;
  653. m_LastBounds = bounds;
  654. m_LastBrushTarget = brushTarget;
  655. m_LastTool = GridBrushBase.Tool.FloodFill;
  656. }
  657. [SettingsProvider]
  658. internal static SettingsProvider CreateSettingsProvider()
  659. {
  660. var settingsProvider = new SettingsProvider("Preferences/2D/Grid Brush", SettingsScope.User, SettingsProvider.GetSearchKeywordsFromGUIContentProperties<GridBrushProperties>()) {
  661. guiHandler = _ =>
  662. {
  663. PreferencesGUI();
  664. }
  665. };
  666. return settingsProvider;
  667. }
  668. private static void PreferencesGUI()
  669. {
  670. using (new SettingsWindow.GUIScope())
  671. {
  672. EditorGUI.BeginChangeCheck();
  673. var val = EditorGUILayout.Toggle(GridBrushProperties.floodFillPreviewLabel, showFloodFillPreview);
  674. if (EditorGUI.EndChangeCheck())
  675. {
  676. showFloodFillPreview = val;
  677. }
  678. EditorGUI.indentLevel++;
  679. using (new EditorGUI.DisabledScope(!val))
  680. {
  681. EditorGUI.BeginChangeCheck();
  682. var fill = EditorGUILayout.IntField(GridBrushProperties.floodFillPreviewFillExtentsLabel, floodFillPreviewFillExtents);
  683. if (EditorGUI.EndChangeCheck())
  684. {
  685. floodFillPreviewFillExtents = fill;
  686. }
  687. EditorGUI.BeginChangeCheck();
  688. var erase = EditorGUILayout.IntField(GridBrushProperties.floodFillPreviewEraseExtentsLabel, floodFillPreviewEraseExtents);
  689. if (EditorGUI.EndChangeCheck())
  690. {
  691. floodFillPreviewEraseExtents = erase;
  692. }
  693. }
  694. EditorGUI.indentLevel--;
  695. }
  696. }
  697. /// <summary>Clears any preview drawn previously by the GridBrushEditor.</summary>
  698. public virtual void ClearPreview()
  699. {
  700. if (m_LastGrid == null || m_LastBounds == null || m_LastBrushTarget == null || m_LastTool == null)
  701. return;
  702. Tilemap map = m_LastBrushTarget.GetComponent<Tilemap>();
  703. if (map != null)
  704. {
  705. switch (m_LastTool)
  706. {
  707. case GridBrushBase.Tool.FloodFill:
  708. {
  709. map.ClearAllEditorPreviewTiles();
  710. break;
  711. }
  712. case GridBrushBase.Tool.Box:
  713. {
  714. Vector3Int min = m_LastBounds.Value.position;
  715. Vector3Int max = min + m_LastBounds.Value.size;
  716. BoundsInt bounds = new BoundsInt(min, max - min);
  717. foreach (Vector3Int location in bounds.allPositionsWithin)
  718. {
  719. ClearTilemapPreview(map, location);
  720. }
  721. break;
  722. }
  723. case GridBrushBase.Tool.Erase:
  724. case GridBrushBase.Tool.Paint:
  725. {
  726. BoundsInt bounds = m_LastBounds.Value;
  727. foreach (Vector3Int location in bounds.allPositionsWithin)
  728. {
  729. ClearTilemapPreview(map, location);
  730. }
  731. break;
  732. }
  733. }
  734. }
  735. m_LastBrushTarget = null;
  736. m_LastGrid = null;
  737. m_LastBounds = null;
  738. m_LastTool = null;
  739. }
  740. /// <summary>
  741. /// Creates a static preview of the GridBrush with its current selection.
  742. /// </summary>
  743. /// <param name="assetPath">The asset to operate on.</param>
  744. /// <param name="subAssets">An array of all Assets at assetPath.</param>
  745. /// <param name="width">Width of the created texture.</param>
  746. /// <param name="height">Height of the created texture.</param>
  747. /// <returns>Generated texture or null.</returns>
  748. public override Texture2D RenderStaticPreview(string assetPath, Object[] subAssets, int width, int height)
  749. {
  750. if (brush == null)
  751. return null;
  752. var previewInstance = new GameObject("Brush Preview", typeof(Grid), typeof(Tilemap), typeof(TilemapRenderer));
  753. var previewGrid = previewInstance.GetComponent<Grid>();
  754. previewGrid.cellLayout = brush.lastPickedCellLayout;
  755. previewGrid.cellSize = brush.lastPickedCellSize;
  756. if (previewGrid.cellLayout != GridLayout.CellLayout.Hexagon)
  757. {
  758. previewGrid.cellGap = brush.lastPickedCellGap;
  759. }
  760. else
  761. {
  762. var tilemap = previewInstance.GetComponent<Tilemap>();
  763. tilemap.tileAnchor = Vector3.zero;
  764. }
  765. previewGrid.cellSwizzle = brush.lastPickedCellSwizzle;
  766. brush.Paint(previewGrid, previewInstance, Vector3Int.zero);
  767. var bounds = previewGrid.GetBoundsLocal(Vector3.zero, brush.size);
  768. var pivotLocal = previewGrid.CellToLocal(brush.pivot);
  769. var center = bounds.center - pivotLocal;
  770. center.z -= 10f;
  771. var rect = new Rect(0, 0, width, height);
  772. var previewUtility = new PreviewRenderUtility(true, true);
  773. previewUtility.camera.orthographic = true;
  774. previewUtility.camera.orthographicSize = 0.5f * Math.Max(brush.size.x, brush.size.y);
  775. if (rect.height > rect.width)
  776. previewUtility.camera.orthographicSize *= rect.height / rect.width;
  777. previewUtility.camera.transform.position = center;
  778. previewUtility.AddSingleGO(previewInstance);
  779. previewUtility.BeginStaticPreview(rect);
  780. previewUtility.camera.Render();
  781. var tex = previewUtility.EndStaticPreview();
  782. previewUtility.Cleanup();
  783. DestroyImmediate(previewInstance);
  784. return tex;
  785. }
  786. private void RegisterUndoForTilemap(Tilemap tilemap, string undoMessage)
  787. {
  788. Undo.RegisterCompleteObjectUndo(new Object[] { tilemap, tilemap.gameObject }, undoMessage);
  789. }
  790. private static void SetTilemapPreviewCell(Tilemap map, Vector3Int location, TileBase tile, Matrix4x4 transformMatrix, Color color)
  791. {
  792. if (map == null)
  793. return;
  794. map.SetEditorPreviewTile(location, tile);
  795. map.SetEditorPreviewTransformMatrix(location, transformMatrix);
  796. map.SetEditorPreviewColor(location, color);
  797. }
  798. private static void ClearTilemapPreview(Tilemap map, Vector3Int location)
  799. {
  800. if (map == null)
  801. return;
  802. map.SetEditorPreviewTile(location, null);
  803. map.SetEditorPreviewTransformMatrix(location, Matrix4x4.identity);
  804. map.SetEditorPreviewColor(location, Color.white);
  805. }
  806. private static int GetHash(GridLayout gridLayout, GameObject brushTarget, BoundsInt position, GridBrushBase.Tool tool, GridBrush brush)
  807. {
  808. int hash;
  809. unchecked
  810. {
  811. hash = gridLayout != null ? gridLayout.GetHashCode() : 0;
  812. hash = hash * 33 + (brushTarget != null ? brushTarget.GetHashCode() : 0);
  813. hash = hash * 33 + position.GetHashCode();
  814. hash = hash * 33 + tool.GetHashCode();
  815. hash = hash * 33 + (brush != null ? brush.GetHashCode() : 0);
  816. }
  817. return hash;
  818. }
  819. }
  820. }