Setting the m_Name field for MonoBehaviours in YAML files

Hello,

Is there a way to set the m_Name field in MonoBehaviour blocks in YAML files? It would be easier to parse diffs if MmonoBehaviours had human-readable names. Maybe it’s something extra we could be setting programmatically?

GameObject has its m_Name set via the GameObject.name property but I can’t find any way to set it for MonoBehaviours.

Thanks,
Vladimir

Components do not have their own name. All components share the name with the gameobject they are attached to as you can read in the documentation.

It does in the YAML though, it’s just that the field is empty.

It would be easier to figure out which component was added to a GameObject just by looking at the diff (while reviewing a PR, for example) if the component had the m_Name filled out.
Right now we have to look at serialized fields and divine which component was edited (if it has exposed fields at all).

It’s empty because Component.name just points to GameObject.name. The field is otherwise unused by components, even if it has an empty serialised field for it.

I imagine it is probably possible by making a SerializedObject out of a component, and getting the SerializedProperty for m_Name and writing to that.

Hey @spiney199 , I just tested that approach and it works. Now if I could expand this to affect ALL components, not just ones I’ve written and hook up elegantly (ie. not require a per-class call from something like OnValidate to a function that sets m_Name or doing a MonoBehaviour subclass).

Maybe some MonoBehaviour or Editor event I could hook into?

I guess this is a use case for ObjectChangeEvents: Example of how to use ObjectChangeEvents in Unity · GitHub

Though it’s not the most specific, so the following code will likely run a lot more often than you would like it to:

#if UNITY_EDITOR
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

[InitializeOnLoad]
public static class ComponentNameEnforcer
{
	static ComponentNameEnforcer()
	{
		ObjectChangeEvents.changesPublished += HandleChangesPublished;
	}

	private readonly static List<Component> _componentsCache = new();

	private static void HandleChangesPublished(ref ObjectChangeEventStream stream)
	{
		int length = stream.length;
		for (int i = 0; i < length; i++)
		{
			var eventType = stream.GetEventType(i);

			if (eventType == ObjectChangeKind.ChangeGameObjectStructure)
			{
				stream.GetChangeGameObjectStructureEvent(i, out var data);
				EnforceComponentNames(data.instanceId);
			}

			if (eventType == ObjectChangeKind.ChangeGameObjectOrComponentProperties)
			{
				stream.GetChangeGameObjectOrComponentPropertiesEvent(i, out var data);
				EnforceComponentNames(data.instanceId);
			}
		}
	}

	private static void EnforceComponentNames(int instanceID)
	{
		var gameObject = EditorUtility.InstanceIDToObject(instanceID) as GameObject;
		if (gameObject == null)
		{
			return;
		}

		gameObject.GetComponents<Component>(_componentsCache);
		foreach (var component in _componentsCache)
		{
			var serialisedObject = new SerializedObject(component);
			var nameProperty = serialisedObject.FindProperty("m_Name");
			if (nameProperty != null)
			{
				nameProperty.stringValue = gameObject.name;
			}
			serialisedObject.ApplyModifiedProperties();
			serialisedObject.Dispose();
		}

		_componentsCache.Clear();
	}
}
#endif

Particularly ObjectChangeKind.ChangeGameObjectOrComponentProperties, which changes whenever any property on a game object changes, including anything in a component inspector. Though that’s the only way to capture when a game object’s name changes.

Yup, that seems to be working albeit for custom components only. Unity components (like Rigidbody2D or Animator) seem to not raise this event.

I did add a slight modification, since the instanceID reported in ChangeGameObjectStructureEventArgs can refer to a Component, not just a GameObject and I wanted the component type to be written into m_Name:

#if UNITY_EDITOR
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

[InitializeOnLoad]
public static class ComponentNameEnforcer
{
	static ComponentNameEnforcer()
	{
		ObjectChangeEvents.changesPublished += HandleChangesPublished;
	}

	private static readonly List<Component> ComponentsCache = new();

	private static void HandleChangesPublished(ref ObjectChangeEventStream stream)
	{
		var length = stream.length;
		for (var i = 0; i < length; i++)
		{
			var eventType = stream.GetEventType(i);

			if (eventType == ObjectChangeKind.ChangeGameObjectStructure)
			{
				stream.GetChangeGameObjectStructureEvent(i, out var data);
				EnforceComponentNames(data.instanceId);
			}

			if (eventType == ObjectChangeKind.ChangeGameObjectOrComponentProperties)
			{
				stream.GetChangeGameObjectOrComponentPropertiesEvent(i, out var data);
				EnforceComponentNames(data.instanceId);
			}
		}
	}

	private static void EnforceComponentNames(int instanceID)
	{
		var goOrObject = EditorUtility.InstanceIDToObject(instanceID);
		if (goOrObject is GameObject go)
		{
			go.GetComponents(ComponentsCache);
		}
		else if (goOrObject is Component comp)
		{
			ComponentsCache.Add(comp);
		}
		else
		{
			return;
		}

		foreach (var component in ComponentsCache)
		{
			var serialisedObject = new SerializedObject(component);
			var nameProperty = serialisedObject.FindProperty("m_Name");
			if (nameProperty != null)
			{
				nameProperty.stringValue = component.GetType().Name;
			}

			serialisedObject.ApplyModifiedProperties();
			serialisedObject.Dispose();
		}

		ComponentsCache.Clear();
	}
}
#endif

It’s partially what I needed. Thank you @spiney199 for helping out!

1 Like