Search Unity

[2D Tilemap] Change the active tilemap by script

Discussion in '2D' started by arkogelul, Oct 31, 2018.

  1. arkogelul

    arkogelul

    Joined:
    Nov 14, 2016
    Posts:
    105
    Hi everyone,

    I'm looking for a way to change the active tilemap depending on the tile I have selected.
    I don't find anything about the tile palette window in the API.

    Thanks !
     
  2. arkogelul

    arkogelul

    Joined:
    Nov 14, 2016
    Posts:
    105
    This is what I'd like to do :

    Here I have 2 tiles, a tree and a barrel.
    There are 2 tilemaps in the scene, Tilemap_BARREL and Tilemap_TREE.



    What I'd like to happen is that when I select my barrel tile, the Tilemap_BARREL automatically gets selected in the Active Tilemap drop-down menu (circled in red in the pictures).



    It would be so much handy not to have to switch tilemaps everytime I want to paint tiles. It'd avoid frequent mistakes as well.

    Thanks.
     
  3. arkogelul

    arkogelul

    Joined:
    Nov 14, 2016
    Posts:
    105
    I'm checking on the brushe exemples from the 2D extra (https://github.com/Unity-Technologies/2d-extras) and I can see that the prefab brush disables the active tilemap menu :


    I'm looking into the script but couldn't find where it happens.
    I wonder if that's the brushTarget.layer attribut, but I don't get what those layers (from 0 to 31) are used for, and if it concerns the tilemaps or something else.
     
  4. rakkarage

    rakkarage

    Joined:
    Feb 3, 2014
    Posts:
    683
    GridBrushInspector (or prob base class) has a validTargets you can override to return valid maps



    Code (CSharp):
    1.         public override GameObject[] validTargets
    2.         {
    3.             get
    4.             {
    5.                 var brush = Brush;
    6.                 var list = from map in GameObject.FindObjectsOfType<Tilemap>()
    7.                           where (map.gameObject.name == brush.BackName) || (map.gameObject.name == brush.ForeName)
    8.                           select map.gameObject;
    9.                 return list.ToArray();
    10.             }
    11.         }
     
    arkogelul likes this.
  5. arkogelul

    arkogelul

    Joined:
    Nov 14, 2016
    Posts:
    105
    Thanks ! Yes it's in GridBrushEditor and GridBrushEditorBase.
    The code seems to work exept for a small name mistake (var brush = Brush ) (which i renamed var var_brush = brush)

    But i don't know how to call "validTargets";
    i know where i want to call it but i don't know how.
     
  6. rakkarage

    rakkarage

    Joined:
    Feb 3, 2014
    Posts:
    683
    it is called automatically before populating that map selection box after selecting a brush
    test.gif
    tho i am not sure it is viable to create a brush and brush editor just to auto select a mep but if you have one already

    ya this is just an example but selects a map

    Code (CSharp):
    1. using UnityEditor;
    2. namespace UnityEngine.Tilemaps
    3. {
    4.     [CreateAssetMenu]
    5.     [CustomGridBrush(false, true, false, "Split Brush")]
    6.     public class SplitBrush : GridBrush
    7.     {
    8.         public TileBase Back;
    9.         public string BackName = "Back";
    10.         public Tilemap BackTilemap;
    11.         public Vector2Int BackOffset;
    12.         public TileBase Fore;
    13.         public string ForeName = "Fore";
    14.         public Tilemap ForeTilemap;
    15.         public Vector2Int ForeOffset;
    16.         public override void Paint(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
    17.         {
    18.             if (BackTilemap != null && ForeTilemap != null)
    19.             {
    20.                 var bp = new Vector3Int(position.x + BackOffset.x, position.y + BackOffset.y, position.z);
    21.                 BackTilemap.SetTile(bp, Back);
    22.                 BackTilemap.SetTransformMatrix(bp, Matrix4x4.identity);
    23.                 var fp = new Vector3Int(position.x + ForeOffset.x, position.y + ForeOffset.y, position.z);
    24.                 ForeTilemap.SetTile(fp, Fore);
    25.                 ForeTilemap.SetTransformMatrix(fp, Matrix4x4.identity);
    26.             }
    27.         }
    28.         public override void Erase(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
    29.         {
    30.             if (BackTilemap != null && ForeTilemap != null)
    31.             {
    32.                 var bp = new Vector3Int(position.x + BackOffset.x, position.y + BackOffset.y, position.z);
    33.                 BackTilemap.SetTile(bp, null);
    34.                 BackTilemap.SetTransformMatrix(bp, Matrix4x4.identity);
    35.                 var fp = new Vector3Int(position.x + ForeOffset.x, position.y + ForeOffset.y, position.z);
    36.                 ForeTilemap.SetTile(fp, null);
    37.                 ForeTilemap.SetTransformMatrix(fp, Matrix4x4.identity);
    38.             }
    39.         }
    40.     }
    41. }
    42.  
    Code (CSharp):
    1. using System.Linq;
    2. using UnityEngine;
    3. using UnityEngine.Tilemaps;
    4. namespace UnityEditor
    5. {
    6.     [CustomEditor(typeof(SplitBrush))]
    7.     public class SplitBrushEditor : GridBrushInspector
    8.     {
    9.         private SplitBrush Brush => target as SplitBrush;
    10.         public override void PaintPreview(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
    11.         {
    12.             var b = Brush;
    13.             var maps = brushTarget?.transform.parent.GetComponentsInChildren<Tilemap>();
    14.             for (var i = 0; i < maps.Length; i++)
    15.             {
    16.                 var map = maps[i];
    17.                 var name = map.name;
    18.                 if (name == b.BackName)
    19.                 {
    20.                     var p = new Vector3Int(position.x + b.BackOffset.x, position.y + b.BackOffset.y, position.z);
    21.                     map.SetEditorPreviewTile(p, b.Back);
    22.                     map.SetEditorPreviewTransformMatrix(p, Matrix4x4.identity);
    23.                     b.BackTilemap = map;
    24.                 }
    25.                 else if (name == b.ForeName)
    26.                 {
    27.                     var p = new Vector3Int(position.x + b.ForeOffset.x, position.y + b.ForeOffset.y, position.z);
    28.                     map.SetEditorPreviewTile(p, b.Fore);
    29.                     map.SetEditorPreviewTransformMatrix(p, Matrix4x4.identity);
    30.                     b.ForeTilemap = map;
    31.                 }
    32.             }
    33.         }
    34.         public override void ClearPreview()
    35.         {
    36.             var b = Brush;
    37.             if (b != null)
    38.             {
    39.                 if (b.BackTilemap != null)
    40.                     b.BackTilemap.ClearAllEditorPreviewTiles();
    41.                 if (b.ForeTilemap != null)
    42.                     b.ForeTilemap.ClearAllEditorPreviewTiles();
    43.             }
    44.         }
    45.         public override GameObject[] validTargets
    46.         {
    47.             get
    48.             {
    49.                 var brush = Brush;
    50.                 var list = from map in GameObject.FindObjectsOfType<Tilemap>()
    51.                            where (map.gameObject.name == brush.BackName) || (map.gameObject.name == brush.ForeName)
    52.                            select map.gameObject;
    53.                 return list.ToArray();
    54.             }
    55.         }
    56.     }
    57. }
    58.  
     
  7. rakkarage

    rakkarage

    Joined:
    Feb 3, 2014
    Posts:
    683
    or just use code can SetTile to any map

    Code (CSharp):
    1.         public Tilemap BackMap;
    2.         public Tilemap ForeMap;
    3.         public void ToggleDoor(Vector3Int p)
    4.         {
    5.             if (IsDoorShut(p))
    6.                 ForeMap.SetTile(p, Tiles.DoorOpen);
    7.             else if (IsDoorOpen(p))
    8.                 ForeMap.SetTile(p, Tiles.DoorShut);
    9.         }
     
  8. arkogelul

    arkogelul

    Joined:
    Nov 14, 2016
    Posts:
    105
    Thanks for sharing and taking the time to answer, that's really appreciated.
    I only use one single brush, and I only would like that when I pick a tile in the palette (Pick), it checks its name and select the right active tilemap accordingly.
    All my ground tiles' name start with "G_" and all my other tiles' names start with "D_".
    I only use 2 tilemaps, "ground" and "doodads".
     
    Last edited: Nov 8, 2018
  9. arkogelul

    arkogelul

    Joined:
    Nov 14, 2016
    Posts:
    105
    Ok i have something a bit weird but it works !

    Code (CSharp):
    1.     [CustomGridBrush(true, true, true, "Ground And Doodads Brush")]
    2.     public class BrushGroundAndDoodads : GridBrush
    3.     {    
    4.         public override void Pick(GridLayout gridLayout, GameObject brushTarget, BoundsInt position, Vector3Int pickStart)
    5.         {
    6.             base.Pick(gridLayout, brushTarget, position, pickStart);
    7.             GameObject object_to_disable = GameObject.FindWithTag("SceneObject");          
    8.             TileBase tile_current = cells[0].tile;
    9.             if (tile_current != null)
    10.             {
    11.                 if (tile_current.name.StartsWith("D")) C.ACTIVE_TILEMAP = "tilemap_doodads";
    12.                 else C.ACTIVE_TILEMAP = "tilemap_ground";
    13.             }
    14.             object_to_disable.SetActive(false); // to force valid targets get
    15.             object_to_disable.SetActive(true);
    16.         }
    17.     }
    and

    Code (CSharp):
    1.     [CustomEditor(typeof(BrushGroundAndDoodads))]
    2.     public class BrushDoodadsEditor : GridBrushEditor
    3.     {
    4.         private BrushGroundAndDoodads brush_ground_and_doodads { get { return target as BrushGroundAndDoodads; } }
    5.  
    6.         public override GameObject[] validTargets
    7.         {
    8.             get
    9.             {
    10.                 var list = from map in GameObject.FindObjectsOfType<Tilemap>()
    11.                            where (map.gameObject.name == C.ACTIVE_TILEMAP)
    12.                            select map.gameObject;
    13.                 return list.ToArray();
    14.             }
    15.         }
    16. }
    Does anyone know a more simple way to call validTargets ?
     
    Last edited: Nov 8, 2018
    dejan-cesarov likes this.
  10. dejan-cesarov

    dejan-cesarov

    Joined:
    Oct 31, 2018
    Posts:
    6
    I got it to work with similar approach, with a bit of hard-coded naming.
    I didn't try to change the active tilemap drop down menu in tile palette window directly, but instead noticed that it would change whenever i manual selected tilemap object in hierarchy. So i would just select the correct tilemap/game object from script and it changes active tilemap to that:)
    Here's the whole script:

    Code (CSharp):
    1.  
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using UnityEditor;
    5. using UnityEngine;
    6. using UnityEngine.Tilemaps;
    7.  
    8. [CustomGridBrush(false, true, true, "Default Brush")]
    9.     public class DefaultBrush : GridBrush
    10.     {
    11.         private Tilemap targetTilemap;
    12.  
    13.         // These are the names of tile objects that i use in the project
    14.         private List<string> coliders = new List<string>()
    15.         {
    16.             "Terrain",
    17.             "Wall",
    18.             "Ladder"
    19.             // etc.
    20.         };
    21.  
    22.         private List<string> items = new List<string>()
    23.         {
    24.             "Coin",
    25.             "Health"
    26.             // etc.
    27.         };
    28.  
    29.         public override void Pick(GridLayout gridLayout, GameObject brushTarget, BoundsInt position, Vector3Int pickStart)
    30.         {
    31.             base.Pick(gridLayout, brushTarget, position, pickStart);
    32.             TileBase currentTile = cells[0].tile;
    33.             if (currentTile != null)
    34.             {
    35.                 string tilemapName = this.GetCorrespondingTilemapName(currentTile.name);
    36.                 List<Tilemap> tilemaps = GameObject.FindObjectsOfType<Tilemap>().ToList();
    37.                 this.targetTilemap = tilemaps.Find(tilemap => tilemap.name.Equals(tilemapName));
    38.                 Selection.activeObject = this.targetTilemap.gameObject;
    39.             }
    40.         }
    41.  
    42.         public override void Paint(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
    43.         {
    44.             if (brushTarget != null)
    45.             {
    46.                 Undo.RegisterCompleteObjectUndo(this.targetTilemap, string.Empty);
    47.                 base.Paint(gridLayout, brushTarget, position);
    48.             }
    49.         }
    50.  
    51.         public override void BoxFill(GridLayout gridLayout, GameObject brushTarget, BoundsInt position)
    52.         {
    53.             if (brushTarget != null)
    54.             {
    55.                 Undo.RegisterCompleteObjectUndo(this.targetTilemap, string.Empty);
    56.                 base.BoxFill(gridLayout, brushTarget, position);
    57.             }
    58.         }
    59.  
    60.         public override void FloodFill(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
    61.         {
    62.             if (brushTarget != null)
    63.             {
    64.                 Undo.RegisterCompleteObjectUndo(this.targetTilemap, string.Empty);
    65.                 base.FloodFill(gridLayout, brushTarget, position);
    66.             }
    67.         }
    68.  
    69.         public override void Erase(GridLayout gridLayout, GameObject brushTarget, Vector3Int position)
    70.         {
    71.             if (brushTarget != null)
    72.             {
    73.                 Undo.RegisterCompleteObjectUndo(this.targetTilemap, string.Empty);
    74.                 base.Erase(gridLayout, brushTarget, position);
    75.             }
    76.         }
    77.  
    78.        
    79.         public override void BoxErase(GridLayout gridLayout, GameObject brushTarget, BoundsInt position)
    80.         {
    81.             if (brushTarget != null)
    82.             {
    83.                 Undo.RegisterCompleteObjectUndo(this.targetTilemap, string.Empty);
    84.                 base.BoxErase(gridLayout, brushTarget, position);
    85.             }
    86.         }
    87.  
    88.         // Here i get the corresponding tilemap name.
    89.         private string GetCorrespondingTilemapName(string tileName)
    90.         {
    91.             if (coliders.Contains(tileName))
    92.             {
    93.                 return "Coliders";
    94.             }
    95.  
    96.             if (items.Contains(tileName))
    97.             {
    98.                 return "Items";
    99.             }
    100.  
    101.             // etc.
    102.  
    103.             return string.Empty;
    104.         }
    105.     }
    So for multiple tilemaps and tiles, i just create more List of strings where i place names of tiles.
    It's a bit of hard-coding names but it seems to work ok and it speeds up drawing with tile brush a lot.
    I needed to override just the pick function so other functions are working as they did.
    However it looks like the undo and redo function needs to be added manually.
    There are probably more things that are not working as they did with unity default brush, but i haven't stumble upon them yet :)

    Your example helped a lot so thanks for that.
    Hope this also helps someone.

    UPDATE: Added undo and redo functionality. Code updated.
     
    Last edited: Nov 20, 2018
    rakkarage likes this.
  11. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    602
    For setting a tilemap to active, the other approaches didn't work for me. Instead, I disabled all active tilemaps except the one I wanted. It causes a slight flicker in the scene view but otherwise seems to work well.

    Code (csharp):
    1. static void SetAsActiveTilemap(Tilemap toSelect) {
    2.   foreach (var tilemap in FindObjectsOfType<Tilemap>()) {
    3.     if (tilemap != toSelect) {
    4.       tilemap.gameObject.SetActive(false);
    5.       EditorApplication.delayCall += () => tilemap.gameObject.SetActive(true);
    6.     }
    7.   }
    8. }
     
  12. ChuanXin

    ChuanXin

    Unity Technologies

    Joined:
    Apr 7, 2015
    Posts:
    1,068
    Properties for setting the current Active Tilemap in the Tile Palette will be made available to make this easier.
     
  13. arkogelul

    arkogelul

    Joined:
    Nov 14, 2016
    Posts:
    105
    That's great ! Thank you guys :)
     
  14. ImperialDynamics

    ImperialDynamics

    Joined:
    Jun 21, 2018
    Posts:
    21
    arkogelul Your code worked for me with a few modifications.

    To force the validTargets I set the root gameobject as dirty.
    In prefab mode:
    Code (CSharp):
    1.  GameObject root = Experimental.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage().prefabContentsRoot;
    2.             EditorUtility.SetDirty(root);
     
  15. theGreatSlutze

    theGreatSlutze

    Joined:
    Jan 7, 2013
    Posts:
    25
    Is there any word on when these properties will be made available?
     
  16. ChuanXin

    ChuanXin

    Unity Technologies

    Joined:
    Apr 7, 2015
    Posts:
    1,068
    They will be available for 2019.2. You can try it out with the current 2019.2 beta. The documentation has not been updated yet, but the relevant APIs can be found in "UnityEditor.Tilemaps.GridPaintingState".
     
    Ruonan and theGreatSlutze like this.
  17. Ruonan

    Ruonan

    Joined:
    Dec 6, 2012
    Posts:
    53
    Help me a lot thanks
     
  18. liuchunyao90

    liuchunyao90

    Joined:
    Mar 31, 2022
    Posts:
    7
    I have reviewed the content you mentioned, but many events in the documentation seem to be inaccessible, and it appears that they are not fully implemented in the source code. How should I proceed with the usage

    // GridPaintingState.beforePaletteChanged += OnScenePaintTargetChanged;
    GridPaintingState.brushChanged += Test;
    // GridPaintingState.brushPickChanged += OnScenePaintTargetChanged;
    // GridPaintingState.brushPickStoreChanged += OnScenePaintTargetChanged;
    // GridPaintingState.brushToolsChanged += OnScenePaintTargetChanged;
    // GridPaintingState.editModeChanged += OnScenePaintTargetChanged;
    GridPaintingState.paletteChanged += Test;
    // GridPaintingState.palettesChanged += OnScenePaintTargetChanged;
    GridPaintingState.scenePaintTargetChanged += Test;
    // GridPaintingState.scenePaintTargetEdited += OnScenePaintTargetChanged;

    Only three of them can be used.