0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Unity】Prefab や Scene 上で被らない連番を割り振る Attribute

Last updated at Posted at 2025-02-01

背景

下図のように、Prefab に複数の GameObject を配置し、各 GameObject に複数の Component を持たせ、各 Component に UniqueId を割り振る機会がありました。

image.png

人力では手間とミスが問題になるので、私は Prefab と Scene を表示・保存した際に Prefab と Scene 上のオブジェクト同士で被らないように連番を割り振る Attribute を作りました。
※ 他の Prefab や Scene の間では値が被ります

使い方

test.cs
using UnityEngine;

public class test : MonoBehaviour
{
    // 引数に文字列を入れると、同じ文字列グループによる連番になります
    [SerializeField, NumberingInt(nameof(uniqueId))] int uniqueId;
    // 引数を省略すると「default」グループの連番になります
    [SerializeField, NumberingInt] int eventId1, eventId2;
    // int 型以外では利用できません
    // [SerializeField, NumberingInt] long eventId3;
}

コード

NumberingIntAttribute.cs
using System;
using System.Collections.Generic;

#if UNITY_EDITOR
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
#endif

[AttributeUsage(AttributeTargets.Field)]
public sealed class NumberingIntAttribute : Attribute, INumberingIntAttribute
{
    private readonly string m_numberingId;
    public string NumberingId => m_numberingId;
    public NumberingIntAttribute(string numberingId = "default")
    {
        m_numberingId = numberingId;
    }
#if UNITY_EDITOR
    public void Inject(SerializedProperty serializedProperty, int index)
    {
        serializedProperty.intValue = index;
    }
#endif
}

public interface INumberingIntAttribute
{
#if UNITY_EDITOR
    void Inject(SerializedProperty serializedProperty, int index);
    string NumberingId { get; }
#endif
}

#if UNITY_EDITOR
internal static class NumberingIntAttributeInjector
{
    [DidReloadScripts]
    private static void OnReloadScripts()
    {
        EditorSceneManager.sceneOpened += OnSceneOpened;
        EditorSceneManager.sceneSaving += OnSceneSaving;
        PrefabStage.prefabStageOpened += OnPrefabStageOpened;
        PrefabStage.prefabSaving += OnPrefabSaving;
        EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
        InjectAll(true);
    }

    private static void OnSceneOpened(Scene scene, OpenSceneMode mode) => InjectAll(true);
    private static void OnSceneSaving(Scene scene, string path) => InjectAll(false);
    private static void OnPrefabStageOpened(PrefabStage prefabStage) => InjectAll(true);
    private static void OnPrefabSaving(GameObject prefab) => InjectAll(false);
    private static void OnPlayModeStateChanged(PlayModeStateChange change)
    {
        if (change != PlayModeStateChange.ExitingEditMode) return;
        InjectAll(true);
    }

    private static void InjectAll(bool canUndo)
    {
        if (EditorApplication.isPlaying) return;

        var monoBehaviours = Resources
            .FindObjectsOfTypeAll<MonoBehaviour>()
            .Where(x => x.gameObject.scene.isLoaded)
            .Where(x => x.gameObject.hideFlags == HideFlags.None)
            .ToArray();

        Dictionary<string, int> indexDict = new();
        foreach (var mb in monoBehaviours) indexDict = Inject(indexDict, mb, canUndo);
    }
    private static Dictionary<string, int> Inject(
        Dictionary<string, int> indexDict, MonoBehaviour monoBehaviour, bool canUndo)
    {
        var serializedObject = new SerializedObject(monoBehaviour);
        var fieldInfos = monoBehaviour
            .GetType()
            .GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

        foreach (var fieldInfo in fieldInfos)
        {
            var attribute = fieldInfo
                    .GetCustomAttributes()
                    .OfType<INumberingIntAttribute>()
                    .FirstOrDefault();
            if (attribute == null) continue;

            if (indexDict.ContainsKey(attribute.NumberingId) == false) indexDict.Add(attribute.NumberingId, 0);
            attribute.Inject(serializedObject.FindProperty(fieldInfo.Name), indexDict[attribute.NumberingId]++);
        }

        if (canUndo) serializedObject.ApplyModifiedProperties();
        else serializedObject.ApplyModifiedPropertiesWithoutUndo();
        return indexDict;
    }
}
#endif
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?