Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

We need more custom brushes, especially an easy-to-use random brush

Discussion in '2D Experimental Preview' started by AssembledVS, Apr 5, 2018.

  1. AssembledVS

    AssembledVS

    Joined:
    Feb 23, 2014
    Posts:
    248
    The custom random brush included in the 2D Extras is painful to use if you have many different tiles that you'd like to randomize. As it is right now, you need to define a set of tiles to be random by dragging and dropping them into the Inspector fields in a specific brush for only those tiles. This may be OK for a few tiles, but I have swathes of apartment flooring, grassy fields, walls, cliffs, and everything in between that I need to randomize. The process now would be unbearable for me to do.

    Not only that, but the brush seems to be random only per tilemap, not per cell - as in, a random tile is chosen for every cell in the tilemap, not for every cell that you hover your mouse over, which means that if you don't like what tile the random brush gave you for a specific cell, you can't re-roll and choose a different random tile for that cell because it's been preordained.

    Before using Unity's tilemap, I've used the Tiled Map Editor and Tiled2Unity. In Tiled, you select the parts of the tile palette that you'd like to use as a brush (much like in Unity), either via a box or Ctrl+click to add new tiles to your current selection, and you get a toggle between regular mode and random mode. When you select random mode, a random tile is chosen from that selection. No workflow-defeating dragging of tiles into separate brushes. No hundreds of brushes to make and choose from when you want a certain randomized variety of tiles. It's just much quicker and easier.
     
  2. rakkarage

    rakkarage

    Joined:
    Feb 3, 2014
    Posts:
    683
    you can drag a bunch of tiles to the RandomBrush (or most any array in inspector) at once... just drop them on name of array in inspector

    ya it is kinda painfull
    i got most of it working (fixed the two busg u noticed, and a few others i think) after a while, still working on Move though
    seems like it should be easier

    adds random orientation option and probability option and fixes floodfill etc

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using UnityEngine;
    5. using UnityEngine.Tilemaps;
    6. namespace UnityEditor
    7. {
    8.     [CreateAssetMenu, CustomGridBrush(false, true, false, "Random Brush")]
    9.     public class RandomBrush : GridBrush
    10.     {
    11.         [Flags]
    12.         public enum Orientation
    13.         {
    14.             None = 0,
    15.             FlipX = 1,
    16.             FlipY = (1 << 1),
    17.             Rot90 = (1 << 2),
    18.         }
    19.         public Orientation orientation = Orientation.None;
    20.         private bool randomBool => UnityEngine.Random.value > .5f;
    21.         private bool flipX => (orientation & Orientation.FlipX) == Orientation.FlipX ? randomBool : false;
    22.         private bool flipY => (orientation & Orientation.FlipY) == Orientation.FlipY ? randomBool : false;
    23.         private bool rot90 => (orientation & Orientation.Rot90) == Orientation.Rot90 ? randomBool : false;
    24.         private static Quaternion rotateClockwise = Quaternion.Euler(0, 0, -90f);
    25.         private static Quaternion rotateCounter = Quaternion.Euler(0, 0, 90f);
    26.         private Matrix4x4 randomMatrix => Matrix4x4.TRS(Vector3.zero,
    27.             rot90 ? rotateClockwise : Quaternion.identity, new Vector3(flipX ? -1f : 1f, flipY ? -1f : 1f, 1f));
    28.         public TileBase[] randomTiles;
    29.         public int[] probabilities;
    30.         private TileBase randomTile
    31.         {
    32.             get
    33.             {
    34.                 if (probabilities?.Length == randomTiles.Length)
    35.                 {
    36.                     var total = 0f;
    37.                     var roll = UnityEngine.Random.Range(0, probabilities.Sum());
    38.                     for (var i = 0; i < probabilities.Length; i++)
    39.                     {
    40.                         total += probabilities[i];
    41.                         if (roll < total)
    42.                             return randomTiles[i];
    43.                     }
    44.                     return null;
    45.                 }
    46.                 else
    47.                     return randomTiles[UnityEngine.Random.Range(0, randomTiles.Length)];
    48.             }
    49.         }
    50.         private Vector3Int? lastPosition;
    51.         private Dictionary<Vector3Int, Tuple<TileBase, Matrix4x4>> cache = new Dictionary<Vector3Int, Tuple<TileBase, Matrix4x4>>();
    52.         public void CacheClear(BoundsInt position)
    53.         {
    54.             foreach (var i in position.allPositionsWithin)
    55.                 CacheClear(i);
    56.         }
    57.         public void CacheClear(Vector3Int position)
    58.         {
    59.             if (lastPosition != position)
    60.                 cache.Clear();
    61.         }
    62.         private FlipAxis? flipAxis;
    63.         private RotationDirection? rotationDirection;
    64.         public void CacheUpdate()
    65.         {
    66.             if (flipAxis.HasValue || rotationDirection.HasValue)
    67.             {
    68.                 foreach (var i in cache.ToList())
    69.                 {
    70.                     var m = i.Value.Item2;
    71.                     var p = m.GetColumn(3);
    72.                     var r = Quaternion.LookRotation(m.GetColumn(2), m.GetColumn(1));
    73.                     var s = new Vector3(m.GetColumn(0).magnitude, m.GetColumn(1).magnitude, m.GetColumn(2).magnitude);
    74.                     if (flipAxis.HasValue)
    75.                         s = new Vector3(flipAxis.Value == FlipAxis.X ? s.x * -1f : s.x,
    76.                             flipAxis.Value == FlipAxis.Y ? s.y * -1f : s.y, s.z);
    77.                     if (rotationDirection.HasValue)
    78.                         r *= rotationDirection.Value == RotationDirection.Clockwise ? rotateClockwise : rotateCounter;
    79.                     m.SetTRS(p, r, s);
    80.                     cache[i.Key] = new Tuple<TileBase, Matrix4x4>(i.Value.Item1, m);
    81.                 }
    82.                 flipAxis = null;
    83.                 rotationDirection = null;
    84.             }
    85.         }
    86.         public Tuple<TileBase, Matrix4x4> SetCache(Vector3Int position) => SetCache(position, randomTile, randomMatrix);
    87.         public Tuple<TileBase, Matrix4x4> SetCache(Vector3Int position, TileBase tile, Matrix4x4 matrix)
    88.         {
    89.             if (!cache.ContainsKey(position))
    90.                 cache[position] = new Tuple<TileBase, Matrix4x4>(tile, matrix);
    91.             lastPosition = position;
    92.             return cache[position];
    93.         }
    94.         public override void Flip(FlipAxis flip, GridLayout.CellLayout layout)
    95.         {
    96.             flipAxis = flip;
    97.             base.Flip(flip, layout);
    98.         }
    99.         public override void Rotate(RotationDirection direction, GridLayout.CellLayout layout)
    100.         {
    101.             rotationDirection = direction;
    102.             base.Rotate(direction, layout);
    103.         }
    104.         public bool moving { get; set; }
    105.         public override void MoveStart(GridLayout gridLayout, GameObject brushTarget, BoundsInt position)
    106.         {
    107.             Debug.Log("MoveStart");
    108.             base.MoveStart(gridLayout, brushTarget, position);
    109.         }
    110.         public override void Move(GridLayout gridLayout, GameObject brushTarget, BoundsInt from, BoundsInt to)
    111.         {
    112.             Debug.Log("Move");
    113.             base.Move(gridLayout, brushTarget, from, to);
    114.         }
    115.         public override void MoveEnd(GridLayout gridLayout, GameObject brushTarget, BoundsInt position)
    116.         {
    117.             Debug.Log("MoveEnd");
    118.             base.MoveEnd(gridLayout, brushTarget, position);
    119.         }
    120.         public override void Paint(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
    121.         {
    122.             if (randomTiles?.Length > 0)
    123.                 Common(gridLayout, brushTarget, position);
    124.         }
    125.         public override void BoxFill(GridLayout gridLayout, GameObject brushTarget, BoundsInt position)
    126.         {
    127.             if (randomTiles?.Length > 0)
    128.                 foreach (var i in position.allPositionsWithin)
    129.                     Common(gridLayout, brushTarget, i);
    130.         }
    131.         public override void FloodFill(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
    132.         {
    133.             if (randomTiles?.Length > 0)
    134.             {
    135.                 var map = brushTarget?.GetComponent<Tilemap>();
    136.                 if (map == null)
    137.                     return;
    138.                 FloodFill(map, gridLayout, brushTarget, position);
    139.             }
    140.         }
    141.         private void FloodFill(Tilemap map, GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
    142.         {
    143.             var exist = map.GetTile(position);
    144.             var points = new Stack<Vector3Int>();
    145.             var used = new List<Vector3Int>();
    146.             points.Push(position);
    147.             while (points.Count > 0)
    148.             {
    149.                 var p = points.Pop();
    150.                 used.Add(p);
    151.                 Common(map, gridLayout, brushTarget, p);
    152.                 for (var y = p.y - 1; y <= p.y + 1; y++)
    153.                 {
    154.                     for (var x = p.x - 1; x <= p.x + 1; x++)
    155.                     {
    156.                         var test = new Vector3Int(x, y, p.z);
    157.                         if ((test.y != p.y || test.x != p.x) && map.cellBounds.Contains(test) &&
    158.                             (exist ? map.GetTile(test) : !map.GetTile(test)) && !used.Contains(test))
    159.                             points.Push(test);
    160.                     }
    161.                 }
    162.             }
    163.         }
    164.         private void Common(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
    165.         {
    166.             var map = brushTarget?.GetComponent<Tilemap>();
    167.             if (map == null)
    168.                 return;
    169.             Common(map, gridLayout, brushTarget, position);
    170.             foreach (var i in cache.ToList())
    171.                 Common(map, gridLayout, brushTarget, i.Key);
    172.         }
    173.         private void Common(Tilemap map, GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
    174.         {
    175.             var cache = SetCache(position);
    176.             map.SetTile(position, cache.Item1);
    177.             map.SetTransformMatrix(position, cache.Item2);
    178.         }
    179.     }
    180.     [CustomEditor(typeof(RandomBrush))]
    181.     public class RandomBrushEditor : GridBrushEditor
    182.     {
    183.         private RandomBrush randomBrush => target as RandomBrush;
    184.         private GameObject lastBrush;
    185.         public override void PaintPreview(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
    186.         {
    187.             var brush = randomBrush;
    188.             brush.CacheClear(position);
    189.             if (brush.randomTiles?.Length > 0)
    190.                 Common(gridLayout, brushTarget, position);
    191.         }
    192.         public override void BoxFillPreview(GridLayout gridLayout, GameObject brushTarget, BoundsInt position)
    193.         {
    194.             var brush = randomBrush;
    195.             brush.CacheClear(position);
    196.             if (brush.randomTiles?.Length > 0)
    197.                 foreach (var i in position.allPositionsWithin)
    198.                     Common(gridLayout, brushTarget, i);
    199.         }
    200.         public override void FloodFillPreview(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
    201.         {
    202.             var brush = randomBrush;
    203.             brush.CacheClear(position);
    204.             if (brush.randomTiles?.Length > 0)
    205.             {
    206.                 var map = brushTarget?.GetComponent<Tilemap>();
    207.                 if (map == null)
    208.                     return;
    209.                 FloodFill(map, gridLayout, brushTarget, position);
    210.             }
    211.         }
    212.         private void FloodFill(Tilemap map, GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
    213.         {
    214.             var exist = map.GetTile(position);
    215.             var points = new Stack<Vector3Int>();
    216.             var used = new List<Vector3Int>();
    217.             points.Push(position);
    218.             while (points.Count > 0)
    219.             {
    220.                 var p = points.Pop();
    221.                 used.Add(p);
    222.                 Common(map, gridLayout, brushTarget, p);
    223.                 for (var y = p.y - 1; y <= p.y + 1; y++)
    224.                 {
    225.                     for (var x = p.x - 1; x <= p.x + 1; x++)
    226.                     {
    227.                         var test = new Vector3Int(x, y, p.z);
    228.                         if ((test.y != p.y || test.x != p.x) && map.cellBounds.Contains(test) &&
    229.                             (exist ? map.GetTile(test) : !map.GetTile(test)) && !used.Contains(test))
    230.                             points.Push(test);
    231.                     }
    232.                 }
    233.             }
    234.         }
    235.         private void Common(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
    236.         {
    237.             var map = brushTarget?.GetComponent<Tilemap>();
    238.             if (map == null)
    239.                 return;
    240.             var min = position - brush.pivot;
    241.             var max = min + brush.size;
    242.             var bounds = new BoundsInt(min, max - min);
    243.             foreach (var i in bounds.allPositionsWithin)
    244.                 Common(map, gridLayout, brushTarget, i);
    245.         }
    246.         private void Common(Tilemap map, GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
    247.         {
    248.             var cache = randomBrush.SetCache(position);
    249.             map.SetEditorPreviewTile(position, cache.Item1);
    250.             map.SetEditorPreviewTransformMatrix(position, cache.Item2);
    251.             lastBrush = brushTarget;
    252.         }
    253.         public override void ClearPreview()
    254.         {
    255.             if (lastBrush != null)
    256.             {
    257.                 randomBrush.CacheUpdate();
    258.                 var map = lastBrush.GetComponent<Tilemap>();
    259.                 if (map == null)
    260.                     return;
    261.                 map.ClearAllEditorPreviewTiles();
    262.                 lastBrush = null;
    263.             }
    264.         }
    265.         public override void OnPaintInspectorGUI() => GUI();
    266.         public override void OnInspectorGUI() => GUI();
    267.         private void GUI()
    268.         {
    269.             var brush = randomBrush;
    270.             EditorGUI.BeginChangeCheck();
    271.             brush.orientation = (RandomBrush.Orientation)EditorGUILayout.EnumFlagsField("Random Orientation", brush.orientation);
    272.             if (EditorGUI.EndChangeCheck())
    273.                 EditorUtility.SetDirty(brush);
    274.             var so = serializedObject;
    275.             EditorGUILayout.PropertyField(so.FindProperty(nameof(brush.randomTiles)), true);
    276.             if (so.ApplyModifiedProperties())
    277.                 EditorUtility.SetDirty(brush);
    278.             EditorGUILayout.PropertyField(so.FindProperty(nameof(brush.probabilities)), true);
    279.             if (so.ApplyModifiedProperties())
    280.                 EditorUtility.SetDirty(brush);
    281.         }
    282.     }
    283. }
    284.  
    Can anyone help me get move working? Thanks.

    EDIT: got it all working https://github.com/rakkarage/TestRandomBrush
     
    Last edited: Apr 21, 2018
    Zelek likes this.