hi there !
I’m having a hard time wrapping my head around the new tilemap tools.
Basically what I m looking for is a simple way to duplicatea palette (with the rule tiling already implemented) and just change the sprites/tiles in the palette/rule so I can create quick variation of my levels : ice ground, stone ground etc.
Right now I have to repeat the whole process everytime wich is kind of a pain in the neck ^^
Thank u all ! <3
Great question! Did you solve it? Just spent 4 hours making rule tiles for a topdown game. Daarn it was complex. Should be able to replace the image files, maybe with a script? 
i am completly new so dont ask me
1 Like
So I came across the same issue when dealing with tile maps. I tend to have a set pattern for my different tile sets (see attached example of a basic tile set for convex shapes) and then each different tile set has the same layout in the source image. Given these assumptions I’ve made the following script to effectively duplicate a RuleTile and swap out the sprites for those from another texture.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Linq;
public class RuleTileCloner : EditorWindow
{
private RuleTile referenceTile;
private Texture2D sprite;
[MenuItem("Tools/RuleTile Cloner")]
public static void ShowTool()
{
GetWindow<RuleTileCloner>().Show();
}
public void OnGUI()
{
referenceTile = EditorGUILayout.ObjectField(referenceTile, typeof(RuleTile), false) as RuleTile;
sprite = EditorGUILayout.ObjectField(sprite, typeof(Texture2D), false) as Texture2D;
if (GUILayout.Button("Clone"))
{
string origPath = AssetDatabase.GetAssetPath(referenceTile);
string spritePath = AssetDatabase.GetAssetPath(sprite);
string targetPath = $"{spritePath.Substring(0, spritePath.LastIndexOf('/'))}/{sprite.name}_RuleTile.asset";
AssetDatabase.CopyAsset(origPath, targetPath);
RuleTile oldTile = AssetDatabase.LoadAssetAtPath<RuleTile>(origPath);
RuleTile newTile = AssetDatabase.LoadAssetAtPath<RuleTile>(targetPath);
if (newTile != null)
{
CloneBySpriteIndex(spritePath, newTile, oldTile);
}
AssetDatabase.SaveAssets();
}
}
private void CloneBySpriteIndex(string spritePath, RuleTile newTile, RuleTile refTile)
{
// First load in the data for the reference tile.
Texture2D refTex = newTile.m_TilingRules[0].m_Sprites[0].texture;
string refPath = AssetDatabase.GetAssetPath(refTex);
Sprite[] refSprites = AssetDatabase.LoadAllAssetsAtPath(refPath).OfType<Sprite>().ToArray();
// New rule tile created, now to swap out the sprites.
Sprite[] newSprites = AssetDatabase.LoadAllAssetsAtPath(spritePath).OfType<Sprite>().ToArray();
for (int i = 0; i < newTile.m_TilingRules.Count; i++)
{
RuleTile.TilingRule rule = newTile.m_TilingRules[i];
for (int j = 0; j < rule.m_Sprites.Length; j++)
{
int refIndex = FindIndex(refSprites, rule.m_Sprites[j]);
rule.m_Sprites[j] = newSprites[refIndex];
}
}
if (referenceTile.m_DefaultSprite != null)
{
newTile.m_DefaultSprite = newSprites[15];
}
}
private int FindIndex(Sprite[] spriteArray, Sprite sprite)
{
for (int i = 0; i < spriteArray.Length; i++)
{
if (spriteArray[i] == sprite)
return i;
}
return -1;
}
}
NOTE: This script only supports rule tiles using a single source texture for all it’s tiles. There are also various cases that I’ve not tested for sprite settings on the rules. So this is provided AS IS with any errors it may contain, It’s just a starting point for providing a duplication feature that can be built on by others to be more flexible if necessary.
To use:
- Open the window from the toolbar (Tools / RuleTile Cloner).
- Select the RuleTile that you wish to duplicate.
- Select the new texture that you want to use for the new RuleTile.
- Click the clone button.
The new RuleTile will be created in the same location as the selected texture, with the suffix “_RuleTile” to the name.
Hope this helps out anyone who finds this post and fancies speeding up their work-flow just a little bit.

3 Likes
@HenryStrattonFW this is amazingly wonderful! You should put this on the asset store. Thank you.
@HenryStrattonFW This script is a must for anyone who wants to be as productive as possible when making a game. It saves so much time. Thank you so much!
I’ve been using this in my project, and it worked well aside from one major issue: the script wouldn’t copy the sprites in the correct order, meaning I’d have to manually set each sprite on each rule.
In case anybody else has been havinf this problem, I tracked down the issue to the AssetDatabase.LoadAllAssetsAtPath() function sometimes returning out of order, and wrote fix to it here:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Linq;
public class RuleTileCloner : EditorWindow
{
private RuleTile referenceTile;
private Texture2D sprite;
[MenuItem("Tools/RuleTile Cloner")]
public static void ShowTool()
{
GetWindow<RuleTileCloner>().Show();
}
public void OnGUI()
{
referenceTile = EditorGUILayout.ObjectField(referenceTile, typeof(RuleTile), false) as RuleTile;
sprite = EditorGUILayout.ObjectField(sprite, typeof(Texture2D), false) as Texture2D;
if (GUILayout.Button("Clone"))
{
string origPath = AssetDatabase.GetAssetPath(referenceTile);
string spritePath = AssetDatabase.GetAssetPath(sprite);
string targetPath = $"{spritePath.Substring(0, spritePath.LastIndexOf('/'))}/{sprite.name}_RuleTile.asset";
AssetDatabase.CopyAsset(origPath, targetPath);
RuleTile oldTile = AssetDatabase.LoadAssetAtPath<RuleTile>(origPath);
RuleTile newTile = AssetDatabase.LoadAssetAtPath<RuleTile>(targetPath);
if (newTile != null)
{
CloneBySpriteIndex(spritePath, newTile, oldTile);
}
AssetDatabase.SaveAssets();
}
}
private void CloneBySpriteIndex(string spritePath, RuleTile newTile, RuleTile refTile)
{
// First load in the data for the reference tile.
Texture2D refTex = newTile.m_TilingRules[0].m_Sprites[0].texture;
string refPath = AssetDatabase.GetAssetPath(refTex);
Sprite[] refSprites = AssetDatabase.LoadAllAssetsAtPath(refPath).OfType<Sprite>().ToArray();
refSprites = SortArrayByAtlasIndex(refSprites);
//sort the refSprites array, since it might be out of order
//LoadAllAssetsAtPath() can give weird orders on sprite atlases sometimes, idk why
// New rule tile created, now to swap out the sprites.
Sprite[] newSprites = AssetDatabase.LoadAllAssetsAtPath(spritePath).OfType<Sprite>().ToArray();
newSprites = SortArrayByAtlasIndex(newSprites);
//sort the newSprites array, since it might be out of order as well.
for (int i = 0; i < newTile.m_TilingRules.Count; i++)
{
RuleTile.TilingRule rule = newTile.m_TilingRules[i];
for (int j = 0; j < rule.m_Sprites.Length; j++)
{
int refIndex = FindIndex(refSprites, rule.m_Sprites[j]);
rule.m_Sprites[j] = newSprites[refIndex];
}
}
if (referenceTile.m_DefaultSprite != null)
{
newTile.m_DefaultSprite = newSprites[^1];
}
}
private Sprite[] SortArrayByAtlasIndex(Sprite[] sprites)
{
//This code rearranges the sprites in the sprite array to be in the same order as they are in the sprite atlas
Sprite[] returnArray = new Sprite[sprites.Length];
int highestID = int.MinValue;
List<int[]> indexList = new List<int[]>();
for(int i = 0; i < sprites.Length; i++)
{
indexList.Add(new int[2]);
indexList[i][0] = i;
string idString = "";
for(int j = 1; j < sprites[i].name.Length; j++)
{
if (char.IsDigit(sprites[i].name[^j]))
{
idString = sprites[i].name[^j] + idString;
}
else break;
}
int id = int.Parse(idString);
if (id > highestID) highestID = id;
indexList[i][1] = id;
}
int curIndex = 0;
for(int i = 0; curIndex <= returnArray.Length && i <= highestID; i++)
{
foreach(int[] index in indexList)
{
if(index[1] == i)
{
returnArray[i] = sprites[index[0]];
curIndex++;
}
}
}
for(int i = 0; i < returnArray.Length; i++)
{
if (returnArray[i] == null) Debug.Log(i);
}
return returnArray;
}
private int FindIndex(Sprite[] spriteArray, Sprite sprite)
{
for (int i = 0; i < spriteArray.Length; i++)
{
if (spriteArray[i] == sprite)
return i;
}
return -1;
}
}
1 Like
Handy scripts.Thanks.
I began using the version by @SgtGustav and found that the asset wasn’t being copied properly (at least on Unity 6000.0.0f1)
It looked like it copied correctly (all the rules copied, and the new sprites were in the rules correctly), but one hint that something was amiss was that the newly created RuleTIle’s thumbnail in the asset folder was still showing the old RuleTile’s sprite.
To fix this, I changed this:
if (newTile != null)
{
CloneBySpriteIndex(spritePath, newTile, oldTile);
}
To this:
if (newTile != null)
{
CloneBySpriteIndex(spritePath, newTile, oldTile);
EditorUtility.SetDirty(newTile);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
Hope it helps someone else.
1 Like