using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Tilemaps; namespace UnityEditor.Tilemaps { #if UNITY_2021_2_OR_NEWER /// /// This Brush helps to place random Tiles onto a Tilemap. /// Use this as an example to create brushes which store specific data per brush and to make brushes which randomize behaviour. /// [HelpURL("https://docs.unity3d.com/Packages/com.unity.2d.tilemap.extras@latest/index.html?subfolder=/manual/RandomBrush.html")] [CustomGridBrush(false, false, false, "Random Brush")] public class RandomBrush : GridBrush { internal struct SizeEnumerator : IEnumerator { private readonly Vector3Int _min, _max, _delta; private Vector3Int _current; public SizeEnumerator(Vector3Int min, Vector3Int max, Vector3Int delta) { _min = _current = min; _max = max; _delta = delta; Reset(); } public SizeEnumerator GetEnumerator() { return this; } public bool MoveNext() { if (_current.z >= _max.z) return false; _current.x += _delta.x; if (_current.x >= _max.x) { _current.x = _min.x; _current.y += _delta.y; if (_current.y >= _max.y) { _current.y = _min.y; _current.z += _delta.z; if (_current.z >= _max.z) return false; } } return true; } public void Reset() { _current = _min; _current.x -= _delta.x; } public Vector3Int Current { get { return _current; } } object IEnumerator.Current { get { return Current; } } void IDisposable.Dispose() {} } /// /// A data structure for storing a set of Tiles used for randomization /// [Serializable] public struct RandomTileSet { /// /// A set of tiles to be painted as a set /// public TileBase[] randomTiles; } [Serializable] public struct RandomTileChangeDataSet { /// /// A set of tiles to be painted as a set with transform and color data /// public TileChangeData[] randomTileChangeData; } /// /// The size of a RandomTileSet /// public Vector3Int randomTileSetSize = Vector3Int.one; /// /// An array of RandomTileSets to choose from when randomizing /// public RandomTileSet[] randomTileSets; /// /// An array of RandomTileSets to choose from when randomizing /// public RandomTileChangeDataSet[] randomTileChangeDataSets; /// /// A flag to determine if picking will add new RandomTileSets /// public bool pickRandomTiles; /// /// A flag to determine if picking will add to existing RandomTileSets /// public bool addToRandomTiles; private void OnEnable() { // Update brush from original randomTileSet if (randomTileSets == null || (randomTileChangeDataSets != null && randomTileChangeDataSets.Length == randomTileSets.Length)) return; randomTileChangeDataSets = new RandomTileChangeDataSet[randomTileSets.Length]; for (var i = 0; i < randomTileSets.Length; ++i) { var sizeCount = randomTileSetSize.x * randomTileSetSize.y * randomTileSetSize.z; randomTileChangeDataSets[i].randomTileChangeData = new TileChangeData[sizeCount]; for (var j = 0; j < sizeCount; ++j) { randomTileChangeDataSets[i].randomTileChangeData[j].tile = randomTileSets[i].randomTiles[j]; randomTileChangeDataSets[i].randomTileChangeData[j].transform = Matrix4x4.identity; randomTileChangeDataSets[i].randomTileChangeData[j].color = Color.white; } } } /// /// Paints RandomTileSets into a given position within the selected layers. /// The RandomBrush overrides this to provide randomized painting functionality. /// /// Grid used for layout. /// Target of the paint operation. By default the currently selected GameObject. /// The coordinates of the cell to paint data to. public override void Paint(GridLayout grid, GameObject brushTarget, Vector3Int position) { if (randomTileChangeDataSets != null && randomTileChangeDataSets.Length > 0) { if (brushTarget == null) return; var tilemap = brushTarget.GetComponent(); if (tilemap == null) return; var min = position - pivot; foreach (var startLocation in new SizeEnumerator(min, min + size, randomTileSetSize)) { var randomTileChangeDataSet = randomTileChangeDataSets[(int) (randomTileChangeDataSets.Length * UnityEngine.Random.value)]; var randomBounds = new BoundsInt(startLocation, randomTileSetSize); var i = 0; foreach (var pos in randomBounds.allPositionsWithin) { randomTileChangeDataSet.randomTileChangeData[i++].position = pos; } tilemap.SetTiles(randomTileChangeDataSet.randomTileChangeData, false); } } else { base.Paint(grid, brushTarget, position); } } /// /// Picks RandomTileSets given the coordinates of the cells. /// The RandomBrush overrides this to provide picking functionality for RandomTileSets. /// /// Grid to pick data from. /// Target of the picking operation. By default the currently selected GameObject. /// The coordinates of the cells to paint data from. /// Pivot of the picking brush. public override void Pick(GridLayout gridLayout, GameObject brushTarget, BoundsInt bounds, Vector3Int pickStart) { base.Pick(gridLayout, brushTarget, bounds, pickStart); if (!pickRandomTiles) return; var tilemap = brushTarget.GetComponent(); if (tilemap == null) return; var i = 0; var count = ((bounds.size.x + randomTileSetSize.x - 1) / randomTileSetSize.x) * ((bounds.size.y + randomTileSetSize.y - 1) / randomTileSetSize.y) * ((bounds.size.z + randomTileSetSize.z - 1) / randomTileSetSize.z); if (addToRandomTiles) { i = randomTileSets != null ? randomTileSets.Length : 0; count += i; } Array.Resize(ref randomTileSets, count); Array.Resize(ref randomTileChangeDataSets, count); foreach (var startLocation in new SizeEnumerator(bounds.min, bounds.max, randomTileSetSize)) { randomTileSets[i].randomTiles = new TileBase[randomTileSetSize.x * randomTileSetSize.y * randomTileSetSize.z]; randomTileChangeDataSets[i].randomTileChangeData = new TileChangeData[randomTileSetSize.x * randomTileSetSize.y * randomTileSetSize.z]; var randomBounds = new BoundsInt(startLocation, randomTileSetSize); var j = 0; foreach (var pos in randomBounds.allPositionsWithin) { var inBounds = pos.x < bounds.max.x && pos.y < bounds.max.y && pos.z < bounds.max.z; var tile = inBounds ? tilemap.GetTile(pos) : null; randomTileSets[i].randomTiles[j] = tile; randomTileChangeDataSets[i].randomTileChangeData[j].tile = tile; randomTileChangeDataSets[i].randomTileChangeData[j].transform = inBounds ? tilemap.GetTransformMatrix(pos) : Matrix4x4.identity; randomTileChangeDataSets[i].randomTileChangeData[j].color = inBounds ? tilemap.GetColor(pos) : Color.white; j++; } i++; } } } /// /// The Brush Editor for a Random Brush. /// [CustomEditor(typeof(RandomBrush))] public class RandomBrushEditor : GridBrushEditor { private RandomBrush randomBrush { get { return target as RandomBrush; } } private GameObject lastBrushTarget; /// /// Paints preview data into a cell of a grid given the coordinates of the cell. /// The RandomBrush Editor overrides this to draw the preview of the brush for RandomTileSets /// /// Grid to paint data to. /// Target of the paint operation. By default the currently selected GameObject. /// The coordinates of the cell to paint data to. public override void PaintPreview(GridLayout grid, GameObject brushTarget, Vector3Int position) { if (randomBrush.randomTileChangeDataSets != null && randomBrush.randomTileChangeDataSets.Length > 0) { base.PaintPreview(grid, null, position); if (brushTarget == null) return; var tilemap = brushTarget.GetComponent(); if (tilemap == null) return; var min = position - randomBrush.pivot; foreach (var startLocation in new RandomBrush.SizeEnumerator(min, min + randomBrush.size, randomBrush.randomTileSetSize)) { var randomTileChangeDataSet = randomBrush.randomTileChangeDataSets[(int) (randomBrush.randomTileChangeDataSets.Length * UnityEngine.Random.value)]; var randomBounds = new BoundsInt(startLocation, randomBrush.randomTileSetSize); var j = 0; foreach (Vector3Int pos in randomBounds.allPositionsWithin) { tilemap.SetEditorPreviewTile(pos, randomTileChangeDataSet.randomTileChangeData[j].tile); tilemap.SetEditorPreviewTransformMatrix(pos, randomTileChangeDataSet.randomTileChangeData[j].transform); tilemap.SetEditorPreviewColor(pos, randomTileChangeDataSet.randomTileChangeData[j].color); j++; } } lastBrushTarget = brushTarget; } else { base.PaintPreview(grid, brushTarget, position); } } /// /// Clears all RandomTileSet previews. /// public override void ClearPreview() { if (lastBrushTarget != null) { var tilemap = lastBrushTarget.GetComponent(); if (tilemap == null) return; tilemap.ClearAllEditorPreviewTiles(); lastBrushTarget = null; } else { base.ClearPreview(); } } /// /// Callback for painting the inspector GUI for the RandomBrush in the Tile Palette. /// The RandomBrush Editor overrides this to have a custom inspector for this Brush. /// public override void OnPaintInspectorGUI() { EditorGUI.BeginChangeCheck(); randomBrush.pickRandomTiles = EditorGUILayout.Toggle("Pick Random Tiles", randomBrush.pickRandomTiles); using (new EditorGUI.DisabledScope(!randomBrush.pickRandomTiles)) { randomBrush.addToRandomTiles = EditorGUILayout.Toggle("Add To Random Tiles", randomBrush.addToRandomTiles); } EditorGUI.BeginChangeCheck(); randomBrush.randomTileSetSize = EditorGUILayout.Vector3IntField("Tile Set Size", randomBrush.randomTileSetSize); if (EditorGUI.EndChangeCheck()) { for (var i = 0; i < randomBrush.randomTileSets.Length; ++i) { var sizeCount = randomBrush.randomTileSetSize.x * randomBrush.randomTileSetSize.y * randomBrush.randomTileSetSize.z; randomBrush.randomTileSets[i].randomTiles = new TileBase[sizeCount]; randomBrush.randomTileChangeDataSets[i].randomTileChangeData = new TileChangeData[sizeCount]; for (var j = 0; j < sizeCount; ++j) { randomBrush.randomTileChangeDataSets[i].randomTileChangeData[j].tile = randomBrush.randomTileSets[i].randomTiles[j]; randomBrush.randomTileChangeDataSets[i].randomTileChangeData[j].transform = Matrix4x4.identity; randomBrush.randomTileChangeDataSets[i].randomTileChangeData[j].color = Color.white; } } } int randomTileSetCount = EditorGUILayout.DelayedIntField("Number of Tiles", randomBrush.randomTileSets != null ? randomBrush.randomTileSets.Length : 0); if (randomTileSetCount < 0) randomTileSetCount = 0; if (randomBrush.randomTileSets == null || randomBrush.randomTileSets.Length != randomTileSetCount) { Array.Resize(ref randomBrush.randomTileSets, randomTileSetCount); Array.Resize(ref randomBrush.randomTileChangeDataSets, randomTileSetCount); for (var i = 0; i < randomBrush.randomTileSets.Length; ++i) { var sizeCount = randomBrush.randomTileSetSize.x * randomBrush.randomTileSetSize.y * randomBrush.randomTileSetSize.z; if (randomBrush.randomTileSets[i].randomTiles == null || randomBrush.randomTileSets[i].randomTiles.Length != sizeCount) { randomBrush.randomTileSets[i].randomTiles = new TileBase[sizeCount]; randomBrush.randomTileChangeDataSets[i].randomTileChangeData = new TileChangeData[sizeCount]; for (var j = 0; j < sizeCount; ++j) { randomBrush.randomTileChangeDataSets[i].randomTileChangeData[j].tile = randomBrush.randomTileSets[i].randomTiles[j]; randomBrush.randomTileChangeDataSets[i].randomTileChangeData[j].transform = Matrix4x4.identity; randomBrush.randomTileChangeDataSets[i].randomTileChangeData[j].color = Color.white; } } } } if (randomTileSetCount > 0) { EditorGUILayout.Space(); EditorGUILayout.LabelField("Place random tiles."); for (var i = 0; i < randomTileSetCount; i++) { EditorGUILayout.LabelField("Tile Set " + (i+1)); for (var j = 0; j < randomBrush.randomTileSets[i].randomTiles.Length; ++j) { randomBrush.randomTileSets[i].randomTiles[j] = (TileBase) EditorGUILayout.ObjectField("Tile " + (j+1), randomBrush.randomTileSets[i].randomTiles[j], typeof(TileBase), false, null); if (randomBrush.randomTileChangeDataSets != null && randomBrush.randomTileChangeDataSets.Length > i) { randomBrush.randomTileChangeDataSets[i].randomTileChangeData ??= new TileChangeData[randomBrush.randomTileSets[i].randomTiles.Length]; randomBrush.randomTileChangeDataSets[i].randomTileChangeData[j].tile = randomBrush.randomTileSets[i].randomTiles[j]; } } } } if (EditorGUI.EndChangeCheck()) { EditorUtility.SetDirty(randomBrush); } } } #else /// /// This Brush helps to place random Tiles onto a Tilemap. /// Use this as an example to create brushes which store specific data per brush and to make brushes which randomize behaviour. /// [HelpURL("https://docs.unity3d.com/Packages/com.unity.2d.tilemap.extras@latest/index.html?subfolder=/manual/RandomBrush.html")] [CustomGridBrush(false, false, false, "Random Brush")] public class RandomBrush : GridBrush { internal struct SizeEnumerator : IEnumerator { private readonly Vector3Int _min, _max, _delta; private Vector3Int _current; public SizeEnumerator(Vector3Int min, Vector3Int max, Vector3Int delta) { _min = _current = min; _max = max; _delta = delta; Reset(); } public SizeEnumerator GetEnumerator() { return this; } public bool MoveNext() { if (_current.z >= _max.z) return false; _current.x += _delta.x; if (_current.x >= _max.x) { _current.x = _min.x; _current.y += _delta.y; if (_current.y >= _max.y) { _current.y = _min.y; _current.z += _delta.z; if (_current.z >= _max.z) return false; } } return true; } public void Reset() { _current = _min; _current.x -= _delta.x; } public Vector3Int Current { get { return _current; } } object IEnumerator.Current { get { return Current; } } void IDisposable.Dispose() {} } /// /// A data structure for storing a set of Tiles used for randomization /// [Serializable] public struct RandomTileSet { /// /// A set of tiles to be painted as a set /// public TileBase[] randomTiles; } /// /// The size of a RandomTileSet /// public Vector3Int randomTileSetSize = Vector3Int.one; /// /// An array of RandomTileSets to choose from when randomizing /// public RandomTileSet[] randomTileSets; /// /// A flag to determine if picking will add new RandomTileSets /// public bool pickRandomTiles; /// /// A flag to determine if picking will add to existing RandomTileSets /// public bool addToRandomTiles; /// /// Paints RandomTileSets into a given position within the selected layers. /// The RandomBrush overrides this to provide randomized painting functionality. /// /// Grid used for layout. /// Target of the paint operation. By default the currently selected GameObject. /// The coordinates of the cell to paint data to. public override void Paint(GridLayout grid, GameObject brushTarget, Vector3Int position) { if (randomTileSets != null && randomTileSets.Length > 0) { if (brushTarget == null) return; var tilemap = brushTarget.GetComponent(); if (tilemap == null) return; Vector3Int min = position - pivot; foreach (var startLocation in new SizeEnumerator(min, min + size, randomTileSetSize)) { var randomTileSet = randomTileSets[(int) (randomTileSets.Length * UnityEngine.Random.value)]; var randomBounds = new BoundsInt(startLocation, randomTileSetSize); tilemap.SetTilesBlock(randomBounds, randomTileSet.randomTiles); } } else { base.Paint(grid, brushTarget, position); } } /// /// Picks RandomTileSets given the coordinates of the cells. /// The RandomBrush overrides this to provide picking functionality for RandomTileSets. /// /// Grid to pick data from. /// Target of the picking operation. By default the currently selected GameObject. /// The coordinates of the cells to paint data from. /// Pivot of the picking brush. public override void Pick(GridLayout gridLayout, GameObject brushTarget, BoundsInt bounds, Vector3Int pickStart) { base.Pick(gridLayout, brushTarget, bounds, pickStart); if (!pickRandomTiles) return; Tilemap tilemap = brushTarget.GetComponent(); if (tilemap == null) return; int i = 0; int count = ((bounds.size.x + randomTileSetSize.x - 1) / randomTileSetSize.x) * ((bounds.size.y + randomTileSetSize.y - 1) / randomTileSetSize.y) * ((bounds.size.z + randomTileSetSize.z - 1) / randomTileSetSize.z); if (addToRandomTiles) { i = randomTileSets != null ? randomTileSets.Length : 0; count += i; } Array.Resize(ref randomTileSets, count); foreach (var startLocation in new SizeEnumerator(bounds.min, bounds.max, randomTileSetSize)) { randomTileSets[i].randomTiles = new TileBase[randomTileSetSize.x * randomTileSetSize.y * randomTileSetSize.z]; var randomBounds = new BoundsInt(startLocation, randomTileSetSize); int j = 0; foreach (Vector3Int pos in randomBounds.allPositionsWithin) { var tile = (pos.x < bounds.max.x && pos.y < bounds.max.y && pos.z < bounds.max.z) ? tilemap.GetTile(pos) : null; randomTileSets[i].randomTiles[j++] = tile; } i++; } } } /// /// The Brush Editor for a Random Brush. /// [CustomEditor(typeof(RandomBrush))] public class RandomBrushEditor : GridBrushEditor { private RandomBrush randomBrush { get { return target as RandomBrush; } } private GameObject lastBrushTarget; /// /// Paints preview data into a cell of a grid given the coordinates of the cell. /// The RandomBrush Editor overrides this to draw the preview of the brush for RandomTileSets /// /// Grid to paint data to. /// Target of the paint operation. By default the currently selected GameObject. /// The coordinates of the cell to paint data to. public override void PaintPreview(GridLayout grid, GameObject brushTarget, Vector3Int position) { if (randomBrush.randomTileSets != null && randomBrush.randomTileSets.Length > 0) { base.PaintPreview(grid, null, position); if (brushTarget == null) return; var tilemap = brushTarget.GetComponent(); if (tilemap == null) return; Vector3Int min = position - randomBrush.pivot; foreach (var startLocation in new RandomBrush.SizeEnumerator(min, min + randomBrush.size, randomBrush.randomTileSetSize)) { var randomTileSet = randomBrush.randomTileSets[(int) (randomBrush.randomTileSets.Length * UnityEngine.Random.value)]; var randomBounds = new BoundsInt(startLocation, randomBrush.randomTileSetSize); int j = 0; foreach (Vector3Int pos in randomBounds.allPositionsWithin) { tilemap.SetEditorPreviewTile(pos, randomTileSet.randomTiles[j++]); } } lastBrushTarget = brushTarget; } else { base.PaintPreview(grid, brushTarget, position); } } /// /// Clears all RandomTileSet previews. /// public override void ClearPreview() { if (lastBrushTarget != null) { var tilemap = lastBrushTarget.GetComponent(); if (tilemap == null) return; tilemap.ClearAllEditorPreviewTiles(); lastBrushTarget = null; } else { base.ClearPreview(); } } /// /// Callback for painting the inspector GUI for the RandomBrush in the Tile Palette. /// The RandomBrush Editor overrides this to have a custom inspector for this Brush. /// public override void OnPaintInspectorGUI() { EditorGUI.BeginChangeCheck(); randomBrush.pickRandomTiles = EditorGUILayout.Toggle("Pick Random Tiles", randomBrush.pickRandomTiles); using (new EditorGUI.DisabledScope(!randomBrush.pickRandomTiles)) { randomBrush.addToRandomTiles = EditorGUILayout.Toggle("Add To Random Tiles", randomBrush.addToRandomTiles); } EditorGUI.BeginChangeCheck(); randomBrush.randomTileSetSize = EditorGUILayout.Vector3IntField("Tile Set Size", randomBrush.randomTileSetSize); if (EditorGUI.EndChangeCheck()) { for (int i = 0; i < randomBrush.randomTileSets.Length; ++i) { int sizeCount = randomBrush.randomTileSetSize.x * randomBrush.randomTileSetSize.y * randomBrush.randomTileSetSize.z; randomBrush.randomTileSets[i].randomTiles = new TileBase[sizeCount]; } } int randomTileSetCount = EditorGUILayout.DelayedIntField("Number of Tiles", randomBrush.randomTileSets != null ? randomBrush.randomTileSets.Length : 0); if (randomTileSetCount < 0) randomTileSetCount = 0; if (randomBrush.randomTileSets == null || randomBrush.randomTileSets.Length != randomTileSetCount) { Array.Resize(ref randomBrush.randomTileSets, randomTileSetCount); for (int i = 0; i < randomBrush.randomTileSets.Length; ++i) { int sizeCount = randomBrush.randomTileSetSize.x * randomBrush.randomTileSetSize.y * randomBrush.randomTileSetSize.z; if (randomBrush.randomTileSets[i].randomTiles == null || randomBrush.randomTileSets[i].randomTiles.Length != sizeCount) randomBrush.randomTileSets[i].randomTiles = new TileBase[sizeCount]; } } if (randomTileSetCount > 0) { EditorGUILayout.Space(); EditorGUILayout.LabelField("Place random tiles."); for (int i = 0; i < randomTileSetCount; i++) { EditorGUILayout.LabelField("Tile Set " + (i+1)); for (int j = 0; j < randomBrush.randomTileSets[i].randomTiles.Length; ++j) { randomBrush.randomTileSets[i].randomTiles[j] = (TileBase) EditorGUILayout.ObjectField("Tile " + (j+1), randomBrush.randomTileSets[i].randomTiles[j], typeof(TileBase), false, null); } } } if (EditorGUI.EndChangeCheck()) EditorUtility.SetDirty(randomBrush); } } #endif }