No Description
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 32KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
  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 lockColorLabel = EditorGUIUtility.TrTextContent("Lock Color", "Prevents tilemap from changing color of tile");
  23. public static readonly GUIContent lockTransformLabel = EditorGUIUtility.TrTextContent("Lock Transform", "Prevents tilemap from changing transform of tile");
  24. public static readonly GUIContent gridSelectionPropertiesLabel = EditorGUIUtility.TrTextContent("Grid Selection Properties");
  25. public static readonly GUIContent modifyTilemapLabel = EditorGUIUtility.TrTextContent("Modify Tilemap");
  26. public static readonly GUIContent modifyLabel = EditorGUIUtility.TrTextContent("Modify");
  27. public static readonly GUIContent deleteSelectionLabel = EditorGUIUtility.TrTextContent("Delete Selection");
  28. public static readonly GUIContent noTool =
  29. EditorGUIUtility.TrTextContentWithIcon("None", "No Gizmo in the Scene view", "RectTool");
  30. public static readonly GUIContent moveTool =
  31. EditorGUIUtility.TrTextContentWithIcon("Move", "Shows a Gizmo in the Scene view for changing the offset for the Grid Selection", "MoveTool");
  32. public static readonly GUIContent rotateTool =
  33. EditorGUIUtility.TrTextContentWithIcon("Rotate", "Shows a Gizmo in the Scene view for changing the rotation for the Grid Selection", "RotateTool");
  34. public static readonly GUIContent scaleTool =
  35. EditorGUIUtility.TrTextContentWithIcon("Scale", "Shows a Gizmo in the Scene view for changing the scale for the Grid Selection", "ScaleTool");
  36. public static readonly GUIContent transformTool =
  37. EditorGUIUtility.TrTextContentWithIcon("Transform", "Shows a Gizmo in the Scene view for changing the transform for the Grid Selection", "TransformTool");
  38. public static readonly GUIContent[] selectionTools = new[]
  39. {
  40. noTool
  41. , moveTool
  42. , rotateTool
  43. , scaleTool
  44. , transformTool
  45. };
  46. public static readonly Type[] selectionTypes = new[]
  47. {
  48. typeof(SelectTool)
  49. , typeof(GridSelectionMoveTool)
  50. , typeof(GridSelectionRotateTool)
  51. , typeof(GridSelectionScaleTool)
  52. , typeof(GridSelectionTransformTool)
  53. };
  54. public static readonly string tooltipText = L10n.Tr("Use this brush to paint and erase Tiles from a Tilemap.");
  55. public static readonly string iconPath = "Packages/com.unity.2d.tilemap/Editor/Icons/Tilemap.DefaultBrush.png";
  56. }
  57. /// <summary>
  58. /// Identifiers for operations modifying the Tilemap.
  59. /// </summary>
  60. public enum ModifyCells
  61. {
  62. /// <summary>
  63. /// Inserts a row at the target position.
  64. /// </summary>
  65. InsertRow,
  66. /// <summary>
  67. /// Inserts a column at the target position.
  68. /// </summary>
  69. InsertColumn,
  70. /// <summary>
  71. /// Inserts a row before the target position.
  72. /// </summary>
  73. InsertRowBefore,
  74. /// <summary>
  75. /// Inserts a column before the target position.
  76. /// </summary>
  77. InsertColumnBefore,
  78. /// <summary>
  79. /// Delete a row at the target position.
  80. /// </summary>
  81. DeleteRow,
  82. /// <summary>
  83. /// Delete a column at the target position.
  84. /// </summary>
  85. DeleteColumn,
  86. /// <summary>
  87. /// Delete a row before the target position.
  88. /// </summary>
  89. DeleteRowBefore,
  90. /// <summary>
  91. /// Delete a column before the target position.
  92. /// </summary>
  93. DeleteColumnBefore,
  94. }
  95. private class GridBrushProperties
  96. {
  97. 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");
  98. public static readonly string floodFillPreviewEditorPref = "GridBrush.EnableFloodFillPreview";
  99. }
  100. /// <summary>The GridBrush that is the target for this editor.</summary>
  101. public GridBrush brush { get { return target as GridBrush; } }
  102. private int m_LastPreviewRefreshHash;
  103. // These are used to clean out previews that happened on previous update
  104. private GridLayout m_LastGrid;
  105. private GameObject m_LastBrushTarget;
  106. private BoundsInt? m_LastBounds;
  107. private GridBrushBase.Tool? m_LastTool;
  108. // These are used to handle selection in Selection Inspector
  109. private TileBase[] m_SelectionTiles;
  110. private Color[] m_SelectionColors;
  111. private Matrix4x4[] m_SelectionMatrices;
  112. private TileFlags[] m_SelectionFlagsArray;
  113. private Sprite[] m_SelectionSprites;
  114. private Tile.ColliderType[] m_SelectionColliderTypes;
  115. private int selectionCellCount => Math.Abs(GridSelection.position.size.x * GridSelection.position.size.y * GridSelection.position.size.z);
  116. // These are used to handle insert/delete cells on the Tilemap
  117. private int m_CellCount = 1;
  118. private ModifyCells m_ModifyCells = ModifyCells.InsertRow;
  119. private Texture2D m_Icon;
  120. private static GridSelectionTool[] s_GridSelectionTools;
  121. /// <summary>
  122. /// Initialises the GridBrushEditor.
  123. /// </summary>
  124. protected virtual void OnEnable()
  125. {
  126. Undo.undoRedoPerformed += ClearLastPreview;
  127. if (s_GridSelectionTools == null || s_GridSelectionTools[0] == null)
  128. {
  129. s_GridSelectionTools = new GridSelectionTool[]
  130. {
  131. CreateInstance<GridSelectionMoveTool>(),
  132. CreateInstance<GridSelectionRotateTool>(),
  133. CreateInstance<GridSelectionScaleTool>(),
  134. CreateInstance<GridSelectionTransformTool>()
  135. };
  136. }
  137. }
  138. /// <summary>
  139. /// Deinitialises the GridBrushEditor.
  140. /// </summary>
  141. protected virtual void OnDisable()
  142. {
  143. Undo.undoRedoPerformed -= ClearLastPreview;
  144. ClearLastPreview();
  145. }
  146. private void ClearLastPreview()
  147. {
  148. ClearPreview();
  149. m_LastPreviewRefreshHash = 0;
  150. }
  151. /// <summary>Callback for painting the GUI for the GridBrush in the Scene View.</summary>
  152. /// <param name="gridLayout">Grid that the brush is being used on.</param>
  153. /// <param name="brushTarget">Target of the GridBrushBase::ref::Tool operation. By default the currently selected GameObject.</param>
  154. /// <param name="position">Current selected location of the brush.</param>
  155. /// <param name="tool">Current GridBrushBase::ref::Tool selected.</param>
  156. /// <param name="executing">Whether brush is being used.</param>
  157. public override void OnPaintSceneGUI(GridLayout gridLayout, GameObject brushTarget, BoundsInt position, GridBrushBase.Tool tool, bool executing)
  158. {
  159. BoundsInt gizmoRect = position;
  160. bool refreshPreviews = false;
  161. if (Event.current.type == EventType.Layout || Event.current.type == EventType.Repaint)
  162. {
  163. int newPreviewRefreshHash = GetHash(gridLayout, brushTarget, position, tool, brush);
  164. refreshPreviews = newPreviewRefreshHash != m_LastPreviewRefreshHash;
  165. if (refreshPreviews)
  166. m_LastPreviewRefreshHash = newPreviewRefreshHash;
  167. }
  168. if (tool == GridBrushBase.Tool.Move)
  169. {
  170. if (refreshPreviews && executing)
  171. {
  172. ClearPreview();
  173. PaintPreview(gridLayout, brushTarget, position.min);
  174. }
  175. }
  176. else if (tool == GridBrushBase.Tool.Paint || tool == GridBrushBase.Tool.Erase)
  177. {
  178. if (refreshPreviews)
  179. {
  180. ClearPreview();
  181. if (tool != GridBrushBase.Tool.Erase)
  182. {
  183. PaintPreview(gridLayout, brushTarget, position.min);
  184. }
  185. }
  186. gizmoRect = new BoundsInt(position.min - brush.pivot, brush.size);
  187. }
  188. else if (tool == GridBrushBase.Tool.Box)
  189. {
  190. if (refreshPreviews)
  191. {
  192. ClearPreview();
  193. BoxFillPreview(gridLayout, brushTarget, position);
  194. }
  195. }
  196. else if (tool == GridBrushBase.Tool.FloodFill)
  197. {
  198. if (refreshPreviews)
  199. {
  200. if (CheckFloodFillPreview(gridLayout, brushTarget, position.min))
  201. ClearPreview();
  202. FloodFillPreview(gridLayout, brushTarget, position.min);
  203. }
  204. }
  205. base.OnPaintSceneGUI(gridLayout, brushTarget, gizmoRect, tool, executing);
  206. }
  207. private void UpdateSelection(Tilemap tilemap)
  208. {
  209. var selection = GridSelection.position;
  210. var cellCount = selectionCellCount;
  211. if (m_SelectionTiles == null || m_SelectionTiles.Length != selectionCellCount)
  212. {
  213. m_SelectionTiles = new TileBase[cellCount];
  214. m_SelectionColors = new Color[cellCount];
  215. m_SelectionMatrices = new Matrix4x4[cellCount];
  216. m_SelectionFlagsArray = new TileFlags[cellCount];
  217. m_SelectionSprites = new Sprite[cellCount];
  218. m_SelectionColliderTypes = new Tile.ColliderType[cellCount];
  219. }
  220. int index = 0;
  221. foreach (var p in selection.allPositionsWithin)
  222. {
  223. m_SelectionTiles[index] = tilemap.GetTile(p);
  224. m_SelectionColors[index] = tilemap.GetColor(p);
  225. m_SelectionMatrices[index] = tilemap.GetTransformMatrix(p);
  226. m_SelectionFlagsArray[index] = tilemap.GetTileFlags(p);
  227. m_SelectionSprites[index] = tilemap.GetSprite(p);
  228. m_SelectionColliderTypes[index] = tilemap.GetColliderType(p);
  229. index++;
  230. }
  231. }
  232. /// <summary>Callback for drawing the Inspector GUI when there is an active GridSelection made in a Tilemap.</summary>
  233. public override void OnSelectionInspectorGUI()
  234. {
  235. BoundsInt selection = GridSelection.position;
  236. Tilemap tilemap = GridSelection.target.GetComponent<Tilemap>();
  237. int cellCount = selectionCellCount;
  238. if (tilemap != null && cellCount > 0)
  239. {
  240. base.OnSelectionInspectorGUI();
  241. if (!EditorGUIUtility.editingTextField
  242. && Event.current.type == EventType.KeyDown
  243. && (Event.current.keyCode == KeyCode.Delete
  244. || Event.current.keyCode == KeyCode.Backspace))
  245. {
  246. DeleteSelection(tilemap, selection);
  247. Event.current.Use();
  248. }
  249. GUILayout.Space(10f);
  250. EditorGUILayout.LabelField(Styles.gridSelectionPropertiesLabel, EditorStyles.boldLabel);
  251. UpdateSelection(tilemap);
  252. EditorGUI.BeginChangeCheck();
  253. EditorGUI.showMixedValue = m_SelectionTiles.Any(tile => tile != m_SelectionTiles.First());
  254. var position = new Vector3Int(selection.xMin, selection.yMin, selection.zMin);
  255. TileBase newTile = EditorGUILayout.ObjectField(Styles.tileLabel, tilemap.GetTile(position), typeof(TileBase), false) as TileBase;
  256. if (EditorGUI.EndChangeCheck())
  257. {
  258. Undo.RecordObject(tilemap, "Edit Tilemap");
  259. foreach (var p in selection.allPositionsWithin)
  260. tilemap.SetTile(p, newTile);
  261. }
  262. using (new EditorGUI.DisabledScope(true))
  263. {
  264. EditorGUI.showMixedValue = m_SelectionSprites.Any(sprite => sprite != m_SelectionSprites.First());
  265. EditorGUILayout.ObjectField(Styles.spriteLabel, m_SelectionSprites[0], typeof(Sprite), false, GUILayout.Height(EditorGUI.kSingleLineHeight));
  266. }
  267. bool colorFlagsAllEqual = m_SelectionFlagsArray.All(flags => (flags & TileFlags.LockColor) == (m_SelectionFlagsArray.First() & TileFlags.LockColor));
  268. using (new EditorGUI.DisabledScope(!colorFlagsAllEqual || (m_SelectionFlagsArray[0] & TileFlags.LockColor) != 0))
  269. {
  270. EditorGUI.showMixedValue = m_SelectionColors.Any(color => color != m_SelectionColors.First());
  271. EditorGUI.BeginChangeCheck();
  272. Color newColor = EditorGUILayout.ColorField(Styles.colorLabel, m_SelectionColors[0]);
  273. if (EditorGUI.EndChangeCheck())
  274. {
  275. Undo.RecordObject(tilemap, "Edit Tilemap");
  276. foreach (var p in selection.allPositionsWithin)
  277. tilemap.SetColor(p, newColor);
  278. }
  279. }
  280. using (new EditorGUI.DisabledScope(true))
  281. {
  282. EditorGUI.showMixedValue = m_SelectionColliderTypes.Any(colliderType => colliderType != m_SelectionColliderTypes.First());
  283. EditorGUILayout.EnumPopup(Styles.colliderTypeLabel, m_SelectionColliderTypes[0]);
  284. }
  285. bool transformFlagsAllEqual = m_SelectionFlagsArray.All(flags => (flags & TileFlags.LockTransform) == (m_SelectionFlagsArray.First() & TileFlags.LockTransform));
  286. using (new EditorGUI.DisabledScope(!transformFlagsAllEqual || (m_SelectionFlagsArray[0] & TileFlags.LockTransform) != 0))
  287. {
  288. EditorGUI.showMixedValue = m_SelectionMatrices.Any(matrix => matrix != m_SelectionMatrices.First());
  289. EditorGUI.BeginChangeCheck();
  290. Matrix4x4 newTransformMatrix = TileEditor.TransformMatrixOnGUI(m_SelectionMatrices[0]);
  291. if (EditorGUI.EndChangeCheck())
  292. {
  293. Undo.RecordObject(tilemap, "Edit Tilemap");
  294. foreach (var p in selection.allPositionsWithin)
  295. tilemap.SetTransformMatrix(p, newTransformMatrix);
  296. }
  297. }
  298. using (new EditorGUI.DisabledScope(true))
  299. {
  300. EditorGUI.showMixedValue = !colorFlagsAllEqual;
  301. EditorGUILayout.Toggle(Styles.lockColorLabel, (m_SelectionFlagsArray[0] & TileFlags.LockColor) != 0);
  302. EditorGUI.showMixedValue = !transformFlagsAllEqual;
  303. EditorGUILayout.Toggle(Styles.lockTransformLabel, (m_SelectionFlagsArray[0] & TileFlags.LockTransform) != 0);
  304. }
  305. EditorGUI.showMixedValue = false;
  306. if (GUILayout.Button(Styles.deleteSelectionLabel))
  307. {
  308. DeleteSelection(tilemap, selection);
  309. }
  310. EditorGUILayout.Space();
  311. EditorGUILayout.LabelField(Styles.modifyTilemapLabel, EditorStyles.boldLabel);
  312. EditorGUILayout.Space();
  313. var active = -1;
  314. for (var i = 0; i < Styles.selectionTypes.Length; ++i)
  315. {
  316. if (ToolManager.activeToolType == Styles.selectionTypes[i])
  317. {
  318. active = i;
  319. break;
  320. }
  321. }
  322. EditorGUI.BeginChangeCheck();
  323. var selected = GUILayout.Toolbar(active, Styles.selectionTools);
  324. if (EditorGUI.EndChangeCheck() && selected != -1)
  325. {
  326. ToolManager.SetActiveTool(Styles.selectionTypes[selected]);
  327. }
  328. EditorGUILayout.Space();
  329. GUILayout.BeginHorizontal();
  330. m_ModifyCells = (ModifyCells)EditorGUILayout.EnumPopup(m_ModifyCells);
  331. m_CellCount = EditorGUILayout.IntField(m_CellCount);
  332. if (GUILayout.Button(Styles.modifyLabel))
  333. {
  334. RegisterUndoForTilemap(tilemap, Enum.GetName(typeof(ModifyCells), m_ModifyCells));
  335. switch (m_ModifyCells)
  336. {
  337. case ModifyCells.InsertRow:
  338. {
  339. tilemap.InsertCells(GridSelection.position.position, 0, m_CellCount, 0);
  340. break;
  341. }
  342. case ModifyCells.InsertRowBefore:
  343. {
  344. tilemap.InsertCells(GridSelection.position.position, 0, -m_CellCount, 0);
  345. break;
  346. }
  347. case ModifyCells.InsertColumn:
  348. {
  349. tilemap.InsertCells(GridSelection.position.position, m_CellCount, 0, 0);
  350. break;
  351. }
  352. case ModifyCells.InsertColumnBefore:
  353. {
  354. tilemap.InsertCells(GridSelection.position.position, -m_CellCount, 0, 0);
  355. break;
  356. }
  357. case ModifyCells.DeleteRow:
  358. {
  359. tilemap.DeleteCells(GridSelection.position.position, 0, m_CellCount, 0);
  360. break;
  361. }
  362. case ModifyCells.DeleteRowBefore:
  363. {
  364. tilemap.DeleteCells(GridSelection.position.position, 0, -m_CellCount, 0);
  365. break;
  366. }
  367. case ModifyCells.DeleteColumn:
  368. {
  369. tilemap.DeleteCells(GridSelection.position.position, m_CellCount, 0, 0);
  370. break;
  371. }
  372. case ModifyCells.DeleteColumnBefore:
  373. {
  374. tilemap.DeleteCells(GridSelection.position.position, -m_CellCount, 0, 0);
  375. break;
  376. }
  377. }
  378. }
  379. GUILayout.EndHorizontal();
  380. }
  381. }
  382. private void DeleteSelection(Tilemap tilemap, BoundsInt selection)
  383. {
  384. if (tilemap == null)
  385. return;
  386. RegisterUndo(tilemap.gameObject, GridBrushBase.Tool.Erase);
  387. brush.BoxErase(tilemap.layoutGrid, tilemap.gameObject, selection);
  388. }
  389. /// <summary> Callback when the mouse cursor leaves and editing area. </summary>
  390. /// <remarks> Cleans up brush previews. </remarks>
  391. public override void OnMouseLeave()
  392. {
  393. ClearPreview();
  394. }
  395. /// <summary> Callback when the GridBrush Tool is deactivated. </summary>
  396. /// <param name="tool">GridBrush Tool that is deactivated.</param>
  397. /// <remarks> Cleans up brush previews. </remarks>
  398. public override void OnToolDeactivated(GridBrushBase.Tool tool)
  399. {
  400. ClearPreview();
  401. }
  402. /// <summary> Describes the usage of the GridBrush. </summary>
  403. public override string tooltip
  404. {
  405. get { return Styles.tooltipText; }
  406. }
  407. /// <summary> Returns an icon identifying the Grid Brush. </summary>
  408. public override Texture2D icon
  409. {
  410. get
  411. {
  412. if (m_Icon == null)
  413. {
  414. m_Icon = EditorGUIUtility.LoadIcon(Styles.iconPath);
  415. }
  416. return m_Icon;
  417. }
  418. }
  419. /// <summary> Whether the GridBrush can change Z Position. </summary>
  420. public override bool canChangeZPosition
  421. {
  422. get { return brush.canChangeZPosition; }
  423. set { brush.canChangeZPosition = value; }
  424. }
  425. /// <summary>Callback for registering an Undo action before the GridBrushBase does the current GridBrushBase::ref::Tool action.</summary>
  426. /// <param name="brushTarget">Target of the GridBrushBase::ref::Tool operation. By default the currently selected GameObject.</param>
  427. /// <param name="tool">Current GridBrushBase::ref::Tool selected.</param>
  428. /// <remarks>Implement this for any special Undo behaviours when a brush is used.</remarks>
  429. public override void RegisterUndo(GameObject brushTarget, GridBrushBase.Tool tool)
  430. {
  431. if (brushTarget != null)
  432. {
  433. var tilemap = brushTarget.GetComponent<Tilemap>();
  434. if (tilemap != null)
  435. {
  436. RegisterUndoForTilemap(tilemap, tool.ToString());
  437. }
  438. }
  439. }
  440. /// <summary>Returns all valid targets that the brush can edit.</summary>
  441. /// <remarks>Valid targets for the GridBrush are any GameObjects with a Tilemap component.</remarks>
  442. public override GameObject[] validTargets
  443. {
  444. get
  445. {
  446. StageHandle currentStageHandle = StageUtility.GetCurrentStageHandle();
  447. return currentStageHandle.FindComponentsOfType<Tilemap>().Where(x =>
  448. {
  449. GameObject gameObject;
  450. return (gameObject = x.gameObject).scene.isLoaded
  451. && gameObject.activeInHierarchy
  452. && !gameObject.hideFlags.HasFlag(HideFlags.NotEditable);
  453. }).Select(x => x.gameObject).ToArray();
  454. }
  455. }
  456. /// <summary>Paints preview data into a cell of a grid given the coordinates of the cell.</summary>
  457. /// <param name="gridLayout">Grid to paint data to.</param>
  458. /// <param name="brushTarget">Target of the paint operation. By default the currently selected GameObject.</param>
  459. /// <param name="position">The coordinates of the cell to paint data to.</param>
  460. /// <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>
  461. public virtual void PaintPreview(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
  462. {
  463. Vector3Int min = position - brush.pivot;
  464. Vector3Int max = min + brush.size;
  465. BoundsInt bounds = new BoundsInt(min, max - min);
  466. if (brushTarget != null)
  467. {
  468. Tilemap map = brushTarget.GetComponent<Tilemap>();
  469. foreach (Vector3Int location in bounds.allPositionsWithin)
  470. {
  471. Vector3Int brushPosition = location - min;
  472. GridBrush.BrushCell cell = brush.cells[brush.GetCellIndex(brushPosition)];
  473. if (cell.tile != null && map != null)
  474. {
  475. SetTilemapPreviewCell(map, location, cell.tile, cell.matrix, cell.color);
  476. }
  477. }
  478. }
  479. m_LastGrid = gridLayout;
  480. m_LastBounds = bounds;
  481. m_LastBrushTarget = brushTarget;
  482. m_LastTool = GridBrushBase.Tool.Paint;
  483. }
  484. /// <summary>Does a preview of what happens when a GridBrush.BoxFill is done with the same parameters.</summary>
  485. /// <param name="gridLayout">Grid to box fill data to.</param>
  486. /// <param name="brushTarget">Target of box fill operation. By default the currently selected GameObject.</param>
  487. /// <param name="position">The bounds to box fill data to.</param>
  488. public virtual void BoxFillPreview(GridLayout gridLayout, GameObject brushTarget, BoundsInt position)
  489. {
  490. if (brushTarget != null)
  491. {
  492. Tilemap map = brushTarget.GetComponent<Tilemap>();
  493. if (map != null)
  494. {
  495. foreach (Vector3Int location in position.allPositionsWithin)
  496. {
  497. Vector3Int local = location - position.min;
  498. GridBrush.BrushCell cell = brush.cells[brush.GetCellIndexWrapAround(local.x, local.y, local.z)];
  499. if (cell.tile != null)
  500. {
  501. SetTilemapPreviewCell(map, location, cell.tile, cell.matrix, cell.color);
  502. }
  503. }
  504. }
  505. }
  506. m_LastGrid = gridLayout;
  507. m_LastBounds = position;
  508. m_LastBrushTarget = brushTarget;
  509. m_LastTool = GridBrushBase.Tool.Box;
  510. }
  511. private bool CheckFloodFillPreview(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
  512. {
  513. if (m_LastGrid == gridLayout
  514. && m_LastBrushTarget == brushTarget
  515. && m_LastBounds.HasValue && m_LastBounds.Value.Contains(position)
  516. && brushTarget != null && brush.cellCount > 0)
  517. {
  518. Tilemap map = brushTarget.GetComponent<Tilemap>();
  519. if (map != null)
  520. {
  521. GridBrush.BrushCell cell = brush.cells[0];
  522. if (cell.tile == map.GetEditorPreviewTile(position))
  523. return false;
  524. }
  525. }
  526. return true;
  527. }
  528. /// <summary>Does a preview of what happens when a GridBrush.FloodFill is done with the same parameters.</summary>
  529. /// <param name="gridLayout">Grid to paint data to.</param>
  530. /// <param name="brushTarget">Target of the flood fill operation. By default the currently selected GameObject.</param>
  531. /// <param name="position">The coordinates of the cell to flood fill data to.</param>
  532. public virtual void FloodFillPreview(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
  533. {
  534. // This can be quite taxing on a large Tilemap, so users can choose whether to do this or not
  535. if (!EditorPrefs.GetBool(GridBrushProperties.floodFillPreviewEditorPref, true))
  536. return;
  537. var bounds = new BoundsInt(position, Vector3Int.one);
  538. if (brushTarget != null && brush.cellCount > 0)
  539. {
  540. Tilemap map = brushTarget.GetComponent<Tilemap>();
  541. if (map != null)
  542. {
  543. GridBrush.BrushCell cell = brush.cells[0];
  544. map.EditorPreviewFloodFill(position, cell.tile);
  545. // Set floodfill bounds as tilemap bounds
  546. var origin = map.origin;
  547. bounds.min = origin;
  548. bounds.max = origin + map.size;
  549. }
  550. }
  551. m_LastGrid = gridLayout;
  552. m_LastBounds = bounds;
  553. m_LastBrushTarget = brushTarget;
  554. m_LastTool = GridBrushBase.Tool.FloodFill;
  555. }
  556. [SettingsProvider]
  557. internal static SettingsProvider CreateSettingsProvider()
  558. {
  559. var settingsProvider = new SettingsProvider("Preferences/2D/Grid Brush", SettingsScope.User, SettingsProvider.GetSearchKeywordsFromGUIContentProperties<GridBrushProperties>()) {
  560. guiHandler = _ =>
  561. {
  562. PreferencesGUI();
  563. }
  564. };
  565. return settingsProvider;
  566. }
  567. private static void PreferencesGUI()
  568. {
  569. using (new SettingsWindow.GUIScope())
  570. {
  571. EditorGUI.BeginChangeCheck();
  572. var val = EditorGUILayout.Toggle(GridBrushProperties.floodFillPreviewLabel, EditorPrefs.GetBool(GridBrushProperties.floodFillPreviewEditorPref, true));
  573. if (EditorGUI.EndChangeCheck())
  574. {
  575. EditorPrefs.SetBool(GridBrushProperties.floodFillPreviewEditorPref, val);
  576. }
  577. }
  578. }
  579. /// <summary>Clears any preview drawn previously by the GridBrushEditor.</summary>
  580. public virtual void ClearPreview()
  581. {
  582. if (m_LastGrid == null || m_LastBounds == null || m_LastBrushTarget == null || m_LastTool == null)
  583. return;
  584. Tilemap map = m_LastBrushTarget.GetComponent<Tilemap>();
  585. if (map != null)
  586. {
  587. switch (m_LastTool)
  588. {
  589. case GridBrushBase.Tool.FloodFill:
  590. {
  591. map.ClearAllEditorPreviewTiles();
  592. break;
  593. }
  594. case GridBrushBase.Tool.Box:
  595. {
  596. Vector3Int min = m_LastBounds.Value.position;
  597. Vector3Int max = min + m_LastBounds.Value.size;
  598. BoundsInt bounds = new BoundsInt(min, max - min);
  599. foreach (Vector3Int location in bounds.allPositionsWithin)
  600. {
  601. ClearTilemapPreview(map, location);
  602. }
  603. break;
  604. }
  605. case GridBrushBase.Tool.Paint:
  606. {
  607. BoundsInt bounds = m_LastBounds.Value;
  608. foreach (Vector3Int location in bounds.allPositionsWithin)
  609. {
  610. ClearTilemapPreview(map, location);
  611. }
  612. break;
  613. }
  614. }
  615. }
  616. m_LastBrushTarget = null;
  617. m_LastGrid = null;
  618. m_LastBounds = null;
  619. m_LastTool = null;
  620. }
  621. private void RegisterUndoForTilemap(Tilemap tilemap, string undoMessage)
  622. {
  623. Undo.RegisterCompleteObjectUndo(new Object[] { tilemap, tilemap.gameObject }, undoMessage);
  624. }
  625. private static void SetTilemapPreviewCell(Tilemap map, Vector3Int location, TileBase tile, Matrix4x4 transformMatrix, Color color)
  626. {
  627. if (map == null)
  628. return;
  629. map.SetEditorPreviewTile(location, tile);
  630. map.SetEditorPreviewTransformMatrix(location, transformMatrix);
  631. map.SetEditorPreviewColor(location, color);
  632. }
  633. private static void ClearTilemapPreview(Tilemap map, Vector3Int location)
  634. {
  635. if (map == null)
  636. return;
  637. map.SetEditorPreviewTile(location, null);
  638. map.SetEditorPreviewTransformMatrix(location, Matrix4x4.identity);
  639. map.SetEditorPreviewColor(location, Color.white);
  640. }
  641. private static int GetHash(GridLayout gridLayout, GameObject brushTarget, BoundsInt position, GridBrushBase.Tool tool, GridBrush brush)
  642. {
  643. int hash;
  644. unchecked
  645. {
  646. hash = gridLayout != null ? gridLayout.GetHashCode() : 0;
  647. hash = hash * 33 + (brushTarget != null ? brushTarget.GetHashCode() : 0);
  648. hash = hash * 33 + position.GetHashCode();
  649. hash = hash * 33 + tool.GetHashCode();
  650. hash = hash * 33 + (brush != null ? brush.GetHashCode() : 0);
  651. }
  652. return hash;
  653. }
  654. }
  655. }