Search Unity

Tilemap - How to Rotate Tiles?

Discussion in '2D' started by JasonBricco, Nov 4, 2017.

  1. JasonBricco

    JasonBricco

    Joined:
    Jul 15, 2013
    Posts:
    956
    Hey guys,

    Just wondering how to rotate tiles already placed on the tilemap.

    I want to rotate when right clicking it.

    Code (CSharp):
    1. if (!cursorInvalid && Input.GetMouseButtonDown(1))
    2. {
    3.     TileBase tile = map.GetTile(tileMousePos);
    4.  
    5.     if (tile != null)
    6.     {
    7.         tile.rotation = (tile.rotation + 90) % 360;
    8.         map.RefreshTile(tileMousePos);
    9.     }
    10. }
    Right click, if we have a tile, set a rotation value on it, call refresh. That calls GetTileData again.

    Which ends up calling:

    Code (CSharp):
    1. public override void GetTileData(Vector3Int position, ITilemap tilemap, ref TileData tileData)
    2. {
    3.     Quaternion rot = Quaternion.Euler(0.0f, 0.0f, rotation);
    4.     tileData.transform = Matrix4x4.TRS(Vector3.zero, rot, Vector3.one);
    5. }
    That's it. Why don't I see anything rotated? Very confused... (that function does other stuff in a overridden function, this is the function a level higher).

    Thanks for your help!
     
  2. JasonBricco

    JasonBricco

    Joined:
    Jul 15, 2013
    Posts:
    956
    So, I found out that when I set the rotation value 'before' I place the tile, everything works fine and it rotates.

    But it doesn't visually change if I try to rotate it 'after' it's been placed already. Think this is yet another Unity bug?
     
  3. r3tNull

    r3tNull

    Joined:
    Apr 29, 2015
    Posts:
    51
    Is tile null? have you debugged and put a breakpoint on Matrix4x4.TRS? Does the inspector show any rotation changes when .TRS() is called? Step through the code and let us know what you find.
     
  4. JasonBricco

    JasonBricco

    Joined:
    Jul 15, 2013
    Posts:
    956
    I right clicked. The tile wasn't null, it set the rotation value to 90.

    The rotation value in the transform (tileData.transform.rotation) was 0.0, 0.0, 0.0, 1.0 before I set the transform with Matrix4x4.TRS.

    After, it was 0.0, 0.0, 0.7, 0.7.
     
  5. r3tNull

    r3tNull

    Joined:
    Apr 29, 2015
    Posts:
    51
    0.7 doesn't sound right unless you are tweening? What values are being passed into .Euler()? 90f is going in and .7 is coming out?
     
  6. r3tNull

    r3tNull

    Joined:
    Apr 29, 2015
    Posts:
    51
    To rule out Matrix4x4 maybe you can try transform.Rotate instead?
     
  7. JasonBricco

    JasonBricco

    Joined:
    Jul 15, 2013
    Posts:
    956
    Rotation value is 90 when entering GetTileData.
    rot.eulerAngles returns 0.0, 0.0, 90.0, but it comes out as 0.7 when using tileData.transform.rotation.

    I can't use transform.Rotate because tiledata.trasform is Matrix4x4, not the Transform class. But I can use Matrix4x4.Rotate.

    Doing so produces the same 0.0, 0.0, 0.7, 0.7 result without any visible rotation occurring.
     
  8. r3tNull

    r3tNull

    Joined:
    Apr 29, 2015
    Posts:
    51
    Sounds like you might have found a bug. I'm going to start a blank project and test this. brb.
     
  9. r3tNull

    r3tNull

    Joined:
    Apr 29, 2015
    Posts:
    51
    a quaternion with (0,0,.7,.7) is normal for 90f rotation. The code you provided is working perfectly for me. Make sure you are actually setting the GameObject's rotation to Matrix4x4.rotation.
     
  10. r3tNull

    r3tNull

    Joined:
    Apr 29, 2015
    Posts:
    51
    Here's my test code that works with a 90f rotation:

    Code (CSharp):
    1. Quaternion testq = Quaternion.Euler(0, 0, 90f);
    2. var m = Matrix4x4.Rotate(testq);
    3. // at this point, m.rotation is (0,0,.7,.7)
    4. _settingsMenu.transform.rotation = m.rotation;
    5. // in the inspector, _settingsMenu's rotation is 0, 0, 90
     
  11. JasonBricco

    JasonBricco

    Joined:
    Jul 15, 2013
    Posts:
    956
    Using tileData.transform does allow for rotation. If I run my code before I place the tile then when I do place it it is rotated properly. But changing it on an already set tile doesn't work (even when tilemap.RefreshTile() is called).

    I'm considering it another bug...
     
  12. r3tNull

    r3tNull

    Joined:
    Apr 29, 2015
    Posts:
    51
    I think you are right. You will have to delete the tile and replace it with a rotated one. Not cool
     
    JasonBricco likes this.
  13. JasonBricco

    JasonBricco

    Joined:
    Jul 15, 2013
    Posts:
    956
    Yeah, I can get around it that way. I suppose it works for now. But the bugs in this tilemap system...
     
  14. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    The docs have an example of Scriptable Tiles that change and rotate a tile based on neighboring tiles.
    https://docs.unity3d.com/Manual/Tilemap-ScriptableTiles-Example.html

    They mention setting TileFlags on the TileData to allow transformations from code that override the brush/user set rotations.
    Code (CSharp):
    1. tileData.flags = TileFlags.LockTransform;
    Also just fyi you can call SetTRS directly on the "tileData.transform" since it is a Matrix4x4.

    Hope that helps.
     
    Last edited: Nov 6, 2017
  15. JasonBricco

    JasonBricco

    Joined:
    Jul 15, 2013
    Posts:
    956
    I tested both of those and neither did anything. I wonder if Unity even tests the examples they put up. They probably only work that way in the editor, not programmatically like I'm doing things.

    Thanks for the answer, nonetheless!
     
  16. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
  17. St000

    St000

    Joined:
    Oct 31, 2017
    Posts:
    2
  18. Jhsosa

    Jhsosa

    Joined:
    Oct 8, 2017
    Posts:
    5
    Just wanted to bump this as it is still an issue. I have the same code running as in the scriptable tiles example and I'm not getting any rotation at anytime. Runtime nor in the editor. Has anyone found anything else on it?
     
  19. Leo-Yaik

    Leo-Yaik

    Unity Technologies

    Joined:
    Aug 13, 2014
    Posts:
    436
    rakkarage likes this.
  20. neuroTrophy

    neuroTrophy

    Joined:
    Jan 22, 2015
    Posts:
    11
    Just to throw in my experience. I was trying to rotate all tiles on a Tilemap by using the Custom orientation. I wanted them to billboard so that they were always facing the camera (using a 'perspective' camera that can pan and orbit similar to the camera in editor). The idea was to get a sort of pop-up book effect. I was able to accomplish that with this:

    Code (CSharp):
    1.      Quaternion testq = Quaternion.Euler( Camera.main.transform.eulerAngles.x, Camera.main.transform.eulerAngles.y, Camera.main.transform.eulerAngles.z);
    2.         var m = Matrix4x4.TRS(Vector3.zero, testq, Vector3.one);
    3.         tileMap.orientation = Tilemap.Orientation.Custom;
    4.         tileMap.orientationMatrix = m;
    I know this isn't exactly what you were trying to do, but there aren't many posts related to the matter, so I thought I would drop it here.
     
    rakkarage likes this.
  21. rakkarage

    rakkarage

    Joined:
    Feb 3, 2014
    Posts:
    683
    added random flipping and rotating and box and flood fill to random brush

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Tilemaps;
    5. namespace UnityEditor
    6. {
    7.     [Flags]
    8.     public enum FlipFlags
    9.     {
    10.         None = 0,
    11.         FlipX = 1,
    12.         FlipY = (1 << 1),
    13.         Rot90 = (1 << 2),
    14.     }
    15.     [CreateAssetMenu]
    16.     [CustomGridBrush(false, true, false, "Random Brush")]
    17.     public class RandomBrush : GridBrush
    18.     {
    19.         public FlipFlags flipFlags = FlipFlags.None;
    20.         public bool flipX { get { return (flipFlags & FlipFlags.FlipX) == FlipFlags.FlipX ? UnityEngine.Random.value > .5f : false; } }
    21.         public bool flipY { get { return (flipFlags & FlipFlags.FlipY) == FlipFlags.FlipY ? UnityEngine.Random.value > .5f : false; } }
    22.         public bool rot90 { get { return (flipFlags & FlipFlags.Rot90) == FlipFlags.Rot90 ? UnityEngine.Random.value > .5f : false; } }
    23.         public TileBase[] randomTiles;
    24.         public TileBase randomTile { get { return randomTiles[(int)(randomTiles.Length * UnityEngine.Random.value)]; } }
    25.         public override void Paint(GridLayout gridLayout, GameObject targetBrush, Vector3Int position)
    26.         {
    27.             if (randomTiles != null && randomTiles.Length > 0)
    28.                 Common(gridLayout, targetBrush, position);
    29.         }
    30.         public override void BoxFill(GridLayout gridLayout, GameObject targetBrush, BoundsInt position)
    31.         {
    32.             if (randomTiles != null && randomTiles.Length > 0)
    33.                 foreach (var i in position.allPositionsWithin)
    34.                     Common(gridLayout, targetBrush, i);
    35.         }
    36.         public override void FloodFill(GridLayout gridLayout, GameObject targetBrush, Vector3Int position)
    37.         {
    38.             if (randomTiles != null && randomTiles.Length > 0)
    39.             {
    40.                 if (targetBrush == null)
    41.                     return;
    42.                 var map = targetBrush.GetComponent<Tilemap>();
    43.                 if (map == null)
    44.                     return;
    45.                 FloodFill(map, gridLayout, targetBrush, position);
    46.             }
    47.         }
    48.         private void FloodFill(Tilemap map, GridLayout gridLayout, GameObject targetBrush, Vector3Int position)
    49.         {
    50.             var exist = map.GetTile(position);
    51.             var points = new Stack<Vector3Int>();
    52.             points.Push(position);
    53.             while (points.Count > 0)
    54.             {
    55.                 var p = points.Pop();
    56.                 Common(map, gridLayout, targetBrush, p);
    57.                 for (var y = p.y - 1; y <= p.y + 1; y++)
    58.                 {
    59.                     for (var x = p.x - 1; x <= p.x + 1; x++)
    60.                     {
    61.                         var test = new Vector3Int(x, y, p.z);
    62.                         if ((test.y != p.y || test.x != p.x) && map.cellBounds.Contains(test) && (exist ? map.GetTile(test) : !map.GetTile(test)))
    63.                             points.Push(test);
    64.                     }
    65.                 }
    66.             }
    67.         }
    68.         private void Common(GridLayout gridLayout, GameObject targetBrush, Vector3Int position)
    69.         {
    70.             if (targetBrush == null)
    71.                 return;
    72.             var map = targetBrush.GetComponent<Tilemap>();
    73.             if (map == null)
    74.                 return;
    75.             Common(map, gridLayout, targetBrush, position);
    76.         }
    77.         private void Common(Tilemap map, GridLayout gridLayout, GameObject targetBrush, Vector3Int position)
    78.         {
    79.             map.SetTile(position, randomTile);
    80.             map.SetTransformMatrix(position, Matrix4x4.TRS(Vector2.zero,
    81.                 rot90 ? Quaternion.Euler(0, 0, 90f) : Quaternion.identity,
    82.                 new Vector3(flipX ? -1f : 1f, flipY ? -1f : 1f, 1f)));
    83.         }
    84.     }
    85.     [CustomEditor(typeof(RandomBrush))]
    86.     public class RandomBrushEditor : GridBrushEditor
    87.     {
    88.         private RandomBrush randomBrush { get { return target as RandomBrush; } }
    89.         private GameObject lastBrush;
    90.         public override void PaintPreview(GridLayout gridLayout, GameObject targetBrush, Vector3Int position)
    91.         {
    92.             if (randomBrush.randomTiles != null && randomBrush.randomTiles.Length > 0)
    93.                 Common(gridLayout, targetBrush, position);
    94.         }
    95.         public override void BoxFillPreview(GridLayout gridLayout, GameObject targetBrush, BoundsInt position)
    96.         {
    97.             if (randomBrush.randomTiles != null && randomBrush.randomTiles.Length > 0)
    98.                 foreach (var i in position.allPositionsWithin)
    99.                     Common(gridLayout, targetBrush, i);
    100.         }
    101.         public override void FloodFillPreview(GridLayout gridLayout, GameObject targetBrush, Vector3Int position)
    102.         {
    103.             if (randomBrush.randomTiles != null && randomBrush.randomTiles.Length > 0)
    104.             {
    105.                 if (targetBrush == null)
    106.                     return;
    107.                 var map = targetBrush.GetComponent<Tilemap>();
    108.                 if (map == null)
    109.                     return;
    110.                 FloodFillTest(map, gridLayout, targetBrush, position);
    111.             }
    112.         }
    113.         private void FloodFillTest(Tilemap map, GridLayout gridLayout, GameObject targetBrush, Vector3Int position)
    114.         {
    115.             var exist = map.GetTile(position);
    116.             var points = new Stack<Vector3Int>();
    117.             points.Push(position);
    118.             while (points.Count > 0)
    119.             {
    120.                 var p = points.Pop();
    121.                 Common(map, gridLayout, targetBrush, p);
    122.                 for (var y = p.y - 1; y <= p.y + 1; y++)
    123.                 {
    124.                     for (var x = p.x - 1; x <= p.x + 1; x++)
    125.                     {
    126.                         var test = new Vector3Int(x, y, p.z);
    127.                         if ((test.y != p.y || test.x != p.x) && map.cellBounds.Contains(test) && (exist ? map.GetTile(test) : !map.GetTile(test)) && !map.GetEditorPreviewTile(test))
    128.                             points.Push(test);
    129.                     }
    130.                 }
    131.             }
    132.         }
    133.         private void Common(GridLayout gridLayout, GameObject targetBrush, Vector3Int position)
    134.         {
    135.             if (targetBrush == null)
    136.                 return;
    137.             var map = targetBrush.GetComponent<Tilemap>();
    138.             if (map == null)
    139.                 return;
    140.             Common(map, gridLayout, targetBrush, position);
    141.         }
    142.         private void Common(Tilemap map, GridLayout gridLayout, GameObject targetBrush, Vector3Int position)
    143.         {
    144.             map.SetEditorPreviewTile(position, randomBrush.randomTile);
    145.             map.SetEditorPreviewTransformMatrix(position, Matrix4x4.TRS(Vector2.zero,
    146.                 randomBrush.rot90 ? Quaternion.Euler(0, 0, 90f) : Quaternion.identity,
    147.                 new Vector3(randomBrush.flipX ? -1f : 1f, randomBrush.flipY ? -1f : 1f, 1f)));
    148.             lastBrush = targetBrush;
    149.         }
    150.         public override void ClearPreview()
    151.         {
    152.             if (lastBrush != null)
    153.             {
    154.                 var map = lastBrush.GetComponent<Tilemap>();
    155.                 if (map == null)
    156.                     return;
    157.                 map.ClearAllEditorPreviewTiles();
    158.                 lastBrush = null;
    159.             }
    160.             else
    161.                 base.ClearPreview();
    162.         }
    163.         public override void OnPaintInspectorGUI()
    164.         {
    165.             GUI();
    166.         }
    167.         public override void OnInspectorGUI()
    168.         {
    169.             GUI();
    170.         }
    171.         private void GUI()
    172.         {
    173.             EditorGUI.BeginChangeCheck();
    174.             randomBrush.flipFlags = (FlipFlags)EditorGUILayout.EnumFlagsField("Flags", randomBrush.flipFlags);
    175.             EditorGUILayout.PropertyField(serializedObject.FindProperty("randomTiles"), new GUIContent("Tiles"), true);
    176.             if (EditorGUI.EndChangeCheck())
    177.                 EditorUtility.SetDirty(randomBrush);
    178.         }
    179.     }
    180. }
    181.  
     
    Last edited: Apr 13, 2018
  22. Mr_Purple

    Mr_Purple

    Joined:
    Apr 2, 2018
    Posts:
    4
    Anybody has worked it out ? Iam at the same point.
    I want to rotate a prefab that instanced with a scriptabletile from code.
    But i cant cahnge the rotation after Startup.

    I can rotate it on StartUp, but after that i have no idea how to manipulate the rotation of the gameobject created by the tile.
    How can i change the rotation in RefreshTile for example ?

    Edit: I found a solution in this thread:

    https://forum.unity.com/threads/can...antiated-go-when-placing-tile-on-grid.536194/
     
    Last edited: Sep 20, 2019
  23. NathanJSmith

    NathanJSmith

    Joined:
    May 11, 2018
    Posts:
    57
    It's easy to rotate the tile on tilemap. Just use Tilemap.SetTransformMatrix
    Here is the code:
    Code (CSharp):
    1. private void SetTile(Vector3Int pos, Quaternion rot, Tilemap tilemap, TileBase tile)
    2.         {
    3.             tilemap.SetTile(pos, tile);
    4.             tilemap.SetTransformMatrix(pos, Matrix4x4.TRS(Vector3.zero, rot, Vector3.one));
    5.         }
     
    SpyralMalus, IvanM71 and blu3drag0n like this.
  24. blu3drag0n

    blu3drag0n

    Joined:
    Nov 9, 2018
    Posts:
    94
    You Sir are my Hero :)

    If you want to copy for example a tile from a Tilemap to another Tilemap and also want to take the rotation information with you then it will look like this
    Code (CSharp):
    1.                        
    2. var pos = new Vector3Int(tileXCoordinate, tileYCoordinate, 0);
    3. myTargetTilemap.SetTile(pos, mySourceTilemap.GetTile(pos));
    4. myTargetTilemap.SetTransformMatrix(pos, Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0f,0f, mySourceTilemap.GetTransformMatrix(pos).rotation.eulerAngles.z), Vector3.one));
     
    NathanJSmith likes this.
  25. IvanM71

    IvanM71

    Joined:
    Oct 28, 2019
    Posts:
    7
    Oh god, thank you so much!