Hi All
Is it possible at all to add layers by script to the Layer Manager, Iam not seeing any obvious ways of doing it.
Chris
Hi All
Is it possible at all to add layers by script to the Layer Manager, Iam not seeing any obvious ways of doing it.
Chris
No its not possible to set layer names from code
While there appears to be no way to add new Layers via script, the following methods may help in identifying those that have already been defined.
string LayerMask.LayerToName(int number)
Given a layer number, returns the name of the layer as defined in either a Builtin or a User Layer in the Tag Manager.
int LayerMask.NameToLayer(string name)
Given a layer name, returns the layer index as defined by either a Builtin or a User Layer in the Tag Manager.
Thanks for the help, shame about not being able to add layers, oh well. And thankyou for the methods for getting layer info
Chris
Yeah this is unfortunate. I’m writing an editor tool and it would be great to be able to set layer names through script to cut end users out of this setup step.
This seems to be a serious shortcoming, and so I’m wondering how other developers deal with it.
First, since this means that we can’t assume that a user has any named layers other than the built-in ones, we can’t refer to custom layers by names in our scripts - we have to set variables for layers by number (int).
That’s bad enough, but it gets worse. If we then write a companion Editor script that includes a EditorGUILayout.LayerField referring to that custom layer variable (which is unnamed), it appears as a blank entry in the pulldown menu. The user has no idea what the LayerField is set to. They might accidentally select a named layer - and now they have no way to return to the default setting. They can select “Add layer” and name the layer themselves, but of course they have no way of knowing what the LayerField was set to by default, and no way of recovering that information.
Does anyone have any trick for dealing with this? Is there any rationale for why we can’t set layer names via script?
(At the very least, perhaps the LayerField could display the layer number if the layer is unnamed)
About the only thing I could come up with was to throw an exception if I couldn’t find the layer name i need to let the programmer know they need to add it manually.
I considered just searching for an unused layer number and attempting to use it without it having been assigned… but that would get us into undefined behavior territory as far as Unity went.
drawspaceLayer = LayerMask.NameToLayer("AnyGUIDrawspace");
if (drawspaceLayer==-1){
throw new UnassignedReferenceException("Layer AnyGUIDrawspace must be assigned in Layer Manager.");
}
That would have been very nice to me! Since Raycasting could perform a lot better if I could add Layers. I’m gonna post this on the “Wishlist”
For what it’s worth, it is actually “possible”, for very very loose, undocumented and hacky definitions of “possible”. You can grab EditorApplication’s tagManager property via reflection and use SerializedProperties, which is what the Unity editor does… but you really really shouldn’t, especially if you’re making an asset for others to re-use.
As was already written the tagManager will do it. For those who are still looking for a solution, you can use the following script:
using UnityEngine;
using System.Collections;
using UnityEditor;
[InitializeOnLoad]
public classTags{
//STARTUP
static Tags()
{
CreateLayer();
}
//creates a new layer
static void CreateLayer(){
SerializedObjecttagManager = new SerializedObject(AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/TagManager.asset")[0]);
SerializedProperty it = tagManager.GetIterator();
bool showChildren = true;
while (it.NextVisible(showChildren))
{
//set your tags here
if (it.name == "User Layer 30")
{
it.stringValue = "My New Layer Name";
}
}
tagManager.ApplyModifiedProperties();
}
}
Just change the layer names with whatever you want and place this script in your Editor folder. It will work automatically.
Hey Rhandros, just wanted to say thanks! I’ve been looking for a solution to this for quite a while, and your code pointed me in the right direction.
Glad to hear it @zangad !
Thanks Rhandros - cool idea that I hadn’t thought of!
This doesn’t appear to be working anymore as-written in Unity 5.1, so I updated the script:
[InitializeOnLoad]
public static class LayerUtils
{
static LayerUtils()
{
CreateLayer();
}
static void CreateLayer()
{
SerializedObject tagManager = new SerializedObject(AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/TagManager.asset")[0]);
SerializedProperty layers = tagManager.FindProperty("layers");
if (layers == null || !layers.isArray)
{
Debug.LogWarning("Can't set up the layers. It's possible the format of the layers and tags data has changed in this version of Unity.");
Debug.LogWarning("Layers is null: " + (layers == null));
return;
}
for ([some loop of layers you want to change / insert])
{
SerializedProperty layerSP = layers.GetArrayElementAtIndex([layer number]);
if (layerSP.stringValue != [the layer name you want])
{
Debug.Log("Setting up layers. Layer " + [layer number] + " is now called " + [new layer name]);
layerSP.stringValue = [new layer name];
}
}
tagManager.ApplyModifiedProperties();
}
}
Here is how I did it in one of my projects. This shows Layers, Tags and the Sorting Layers (the ones used in 2D).
// will check if the specified layer names are present and add any missing ones
// it will simply add them from the first open slots so do not depend on any
// specific order but rather grab layers from the layer names at runtime
public static void CheckLayers(string[] layerNames)
{
SerializedObject manager = new SerializedObject(AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/TagManager.asset")[0]);
#if !UNITY_4
SerializedProperty layersProp = manager.FindProperty("layers");
#endif
foreach (string name in layerNames)
{
// check if layer is present
bool found = false;
for (int i = 0; i <= 31; i++)
{
#if UNITY_4
string nm = "User Layer " + i;
SerializedProperty sp = manager.FindProperty(nm);
#else
SerializedProperty sp = layersProp.GetArrayElementAtIndex(i);
#endif
if (sp != null && name.Equals(sp.stringValue))
{
found = true;
break;
}
}
// not found, add into 1st open slot
if (!found)
{
SerializedProperty slot = null;
for (int i = 8; i <= 31; i++)
{
#if UNITY_4
string nm = "User Layer " + i;
SerializedProperty sp = manager.FindProperty(nm);
#else
SerializedProperty sp = layersProp.GetArrayElementAtIndex(i);
#endif
if (sp != null && string.IsNullOrEmpty(sp.stringValue))
{
slot = sp;
break;
}
}
if (slot != null)
{
slot.stringValue = name;
}
else
{
Debug.LogError("Could not find an open Layer Slot for: " + name);
}
}
}
// save
manager.ApplyModifiedProperties();
}
public static void CheckTags(string[] tagNames)
{
SerializedObject manager = new SerializedObject(AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/TagManager.asset")[0]);
SerializedProperty tagsProp = manager.FindProperty("tags");
List<string> DefaultTags = new List<string>(){ "Untagged", "Respawn", "Finish", "EditorOnly", "MainCamera", "Player", "GameController" };
foreach (string name in tagNames)
{
if (DefaultTags.Contains(name)) continue;
// check if tag is present
bool found = false;
for (int i = 0; i < tagsProp.arraySize; i++)
{
SerializedProperty t = tagsProp.GetArrayElementAtIndex(i);
if (t.stringValue.Equals(name)) { found = true; break; }
}
// if not found, add it
if (!found)
{
tagsProp.InsertArrayElementAtIndex(0);
SerializedProperty n = tagsProp.GetArrayElementAtIndex(0);
n.stringValue = name;
}
}
// save
manager.ApplyModifiedProperties();
}
public static void CheckSortLayers(string[] tagNames)
{
SerializedObject manager = new SerializedObject(AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/TagManager.asset")[0]);
SerializedProperty sortLayersProp = manager.FindProperty("m_SortingLayers");
//for (int i = 0; i < sortLayersProp.arraySize; i++)
//{ // used to figure out how all of this works and what properties values look like
// SerializedProperty entry = sortLayersProp.GetArrayElementAtIndex(i);
// SerializedProperty name = entry.FindPropertyRelative("name");
// SerializedProperty unique = entry.FindPropertyRelative("uniqueID");
// SerializedProperty locked = entry.FindPropertyRelative("locked");
// Debug.Log(name.stringValue + " => " + unique.intValue + " => " + locked.boolValue);
//}
foreach (string name in tagNames)
{
// check if tag is present
bool found = false;
for (int i = 0; i < sortLayersProp.arraySize; i++)
{
SerializedProperty entry = sortLayersProp.GetArrayElementAtIndex(i);
SerializedProperty t = entry.FindPropertyRelative("name");
if (t.stringValue.Equals(name)) { found = true; break; }
}
// if not found, add it
if (!found)
{
manager.ApplyModifiedProperties();
AddSortingLayer();
manager.Update();
int idx = sortLayersProp.arraySize - 1;
SerializedProperty entry = sortLayersProp.GetArrayElementAtIndex(idx);
SerializedProperty t = entry.FindPropertyRelative("name");
t.stringValue = name;
}
}
// save
manager.ApplyModifiedProperties();
}
// you need 'using System.Reflection;' for these
private static Assembly editorAsm;
private static MethodInfo AddSortingLayer_Method;
/// <summary> add a new sorting layer with default name </summary>
public static void AddSortingLayer()
{
if (AddSortingLayer_Method == null)
{
if (editorAsm == null) editorAsm = Assembly.GetAssembly(typeof(Editor));
System.Type t = editorAsm.GetType("UnityEditorInternal.InternalEditorUtility");
AddSortingLayer_Method = t.GetMethod("AddSortingLayer", (BindingFlags.Static | BindingFlags.NonPublic), null, new System.Type[0], null);
}
AddSortingLayer_Method.Invoke(null, null);
}
Thanks guys, this helped me. I needed this for my asset fog volume. I did this:
void CreateLayer()
{
// https://forum.unity3d.com/threads/adding-layer-by-script.41970/reply?quote=2274824
SerializedObject tagManager = new SerializedObject(AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/TagManager.asset")[0]);
SerializedProperty layers = tagManager.FindProperty("layers");
bool ExistLayer = false;
print(ExistLayer);
for (int i = 8; i < layers.arraySize; i++)
{
SerializedProperty layerSP = layers.GetArrayElementAtIndex(i);
//print(layerSP.stringValue);
if (layerSP.stringValue == "FogVolume")
{
ExistLayer = true;
break;
}
}
for (int j = 8; j < layers.arraySize; j++)
{
SerializedProperty layerSP = layers.GetArrayElementAtIndex(j);
if (layerSP.stringValue == "" && !ExistLayer)
{
layerSP.stringValue = "FogVolume";
tagManager.ApplyModifiedProperties();
break;
}
}
// print(layers.arraySize);
}
It will insert the string in the first empty slot available
For those who may find it useful, here is my variation of creating a layer.
This function too will create a layer at the next available index. If the layer already exists, it returns silently. If the maximum number of layers have been exceeded, it displays a log error.
using UnityEditor;
public class LayerMaskEx
{
/// <summary>
/// Create a layer at the next available index. Returns silently if layer already exists.
/// </summary>
/// <param name="name">Name of the layer to create</param>
public static void CreateLayer(string name)
{
if (string.IsNullOrEmpty(name))
throw new System.ArgumentNullException("name", "New layer name string is either null or empty.");
var tagManager = new SerializedObject(AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/TagManager.asset")[0]);
var layerProps = tagManager.FindProperty("layers");
var propCount = layerProps.arraySize;
SerializedProperty firstEmptyProp = null;
for (var i = 0; i < propCount; i++)
{
var layerProp = layerProps.GetArrayElementAtIndex(i);
var stringValue = layerProp.stringValue;
if (stringValue == name) return;
if (i < 8 || stringValue != string.Empty) continue;
if (firstEmptyProp == null)
firstEmptyProp = layerProp;
}
if (firstEmptyProp == null)
{
UnityEngine.Debug.LogError("Maximum limit of " + propCount + " layers exceeded. Layer \"" + name + "\" not created.");
return;
}
firstEmptyProp.stringValue = name;
tagManager.ApplyModifiedProperties();
}
}
Please note that because both SerializedObject and AssetDatabase depend on UnityEditor, this function will result in build errors unless placed in an “Editor” folder or enclosed in “#if UNITY_EDITOR/#endif” or similar preprocessor conditionals.
- S.
#if UNITY_EDITOR
using UnityEditor;
#endif
#if UNITY_EDITOR
public void addLayer(string layerName)
{
UnityEngine.Object[] asset = AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/TagManager.asset");
if ((asset != null) && (asset.Length > 0))
{
SerializedObject serializedObject = new SerializedObject(asset[0]);
SerializedProperty layers = serializedObject.FindProperty("layers");
for (int i = 0; i < layers.arraySize; ++i)
{
if (layers.GetArrayElementAtIndex(i).stringValue == layerName)
{
return; // Layer already present, nothing to do.
}
}
// layers.InsertArrayElementAtIndex(0);
// layers.GetArrayElementAtIndex(0).stringValue = layerName;
for (int i = 0; i < layers.arraySize; i++)
{
if (layers.GetArrayElementAtIndex(i).stringValue == "")
{
// layers.InsertArrayElementAtIndex(i);
layers.GetArrayElementAtIndex(i).stringValue = layerName;
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
if (layers.GetArrayElementAtIndex(i).stringValue == layerName)
{
return; // to avoid unity locked layer
}
}
}
}
}
#endif
Won’t this try to set the name of the first Built-in layer with no name? Might want to start your search for an unnamed layer at 8, not 0.
hi,
this is really helpful, but im not too familiar with editor scripts… how do i use this? do i need to enclose this in a class? do i have to call this function from somewhere?
thanks
I wrote this little helper tool to setup layers via code. Maybe it will help you understand. It will spawn a menu in the top toolbar.
using UnityEditor;
using UnityEngine;
public static class SetupTools
{
[MenuItem("Tools/Setup Layers")]
public static void SetupLayers()
{
Debug.Log("Adding Layers.");
Object[] asset = AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/TagManager.asset");
if (asset != null && asset.Length > 0)
{
SerializedObject serializedObject = new SerializedObject(asset[0]);
SerializedProperty layers = serializedObject.FindProperty("layers");
// Add your layers here, these are just examples. Keep in mind: indices below 6 are the built in layers.
AddLayerAt(layers, 8, "Bike");
AddLayerAt(layers, 9, "BikeWheels");
AddLayerAt(layers, 10, "BikeTrigger");
AddLayerAt(layers, 11, "Ground");
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
}
}
static void AddLayerAt(SerializedProperty layers, int index, string layerName, bool tryOtherIndex = true)
{
// Skip if a layer with the name already exists.
for (int i = 0; i < layers.arraySize; ++i)
{
if (layers.GetArrayElementAtIndex(i).stringValue == layerName)
{
Debug.Log("Skipping layer '" + layerName + "' because it already exists.");
return;
}
}
// Extend layers if necessary
if (index >= layers.arraySize)
layers.arraySize = index + 1;
// set layer name at index
var element = layers.GetArrayElementAtIndex(index);
if (string.IsNullOrEmpty(element.stringValue))
{
element.stringValue = layerName;
Debug.Log("Added layer '" + layerName + "' at index " + index + ".");
}
else
{
Debug.LogWarning("Could not add layer at index " + index + " because there already is another layer '" + element.stringValue + "'." );
if (tryOtherIndex)
{
// Go up in layer indices and try to find an empty spot.
for (int i = index + 1; i < 32; ++i)
{
// Extend layers if necessary
if (i >= layers.arraySize)
layers.arraySize = i + 1;
element = layers.GetArrayElementAtIndex(i);
if (string.IsNullOrEmpty(element.stringValue))
{
element.stringValue = layerName;
Debug.Log("Added layer '" + layerName + "' at index " + i + " instead of " + index + ".");
return;
}
}
Debug.LogError("Could not add layer " + layerName + " because there is no space left in the layers array.");
}
}
}
}