うにてぃブログ

主にUnityとC#に関する記事を書いていきます

【Unity】ScriptableSingleton を利用した Selection や Console のログ管理クラス

hacchi-man.hatenablog.com

以前の記事でざっくりとしか ScriptableSingleton の使用例を書いてなかったので
使えるように整形したので紹介します

※ ScriptableSingleton は UnityEditor でしか利用できません

Selection ログ

Hierarchy の選択ログと ProjectWindow の選択ログを保持するクラス

using System;
using System.Collections.Generic;
using System.Text;
using UnityEditor;
using UnityEngine;

namespace Log
{
    [Serializable]
    public class HierarchyData
    {
        public string SceneName;
        public string Path;

        public override bool Equals(object obj)
        {
            var data =  obj as HierarchyData;
            return data.SceneName == SceneName && data.Path == Path;
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }
    
    [InitializeOnLoad]
    public static class SelectionLog
    {
        private static readonly StringBuilder BuilderCache = new StringBuilder();
        
        // Hierarchyのログ
        public static IEnumerable<HierarchyData> HierarchyLogs { get { return HierarchyLogData.instance.Logs; } }
        // Projectのログ
        public static IEnumerable<string> ProjectLogs { get { return ProjectLogData.instance.Logs; } }
        
        private class HierarchyLogData : ScriptableSingleton<HierarchyLogData>
        {
            public IEnumerable<HierarchyData> Logs { get { return instance._logs; } }

            [SerializeField]
            private List<HierarchyData> _logs = new List<HierarchyData>(LogMax + 1);
            private const int LogMax = 50;
            
            internal void AddLog(HierarchyData data)
            {
                _logs.RemoveAll(l => l.Equals(data));
                _logs.Insert(0, data);
                while (_logs.Count > LogMax)
                    _logs.RemoveAt(LogMax);
            }
        }
        
        private class ProjectLogData : ScriptableSingleton<ProjectLogData>
        {
            public IEnumerable<string> Logs { get { return instance._logs; } }

            [SerializeField]
            private List<string> _logs = new List<string>(LogMax + 1);
            private const int LogMax = 50;
            
            internal void AddLog(string data)
            {
                _logs.RemoveAll(l => l.Equals(data));
                _logs.Insert(0, data);
                while (_logs.Count > LogMax)
                    _logs.RemoveAt(LogMax);
            }
        }

        static SelectionLog()
        {
            Selection.selectionChanged += SelectionChanged;
        }
        
        private static void SelectionChanged()
        {
            foreach (var transform in Selection.transforms)
            {
                var data = new HierarchyData
                {
                    SceneName = transform.gameObject.scene.name,
                    Path = HierarchyPath(transform),
                };

                HierarchyLogData.instance.AddLog(data);
            }

            foreach (var guid in Selection.assetGUIDs)
                ProjectLogData.instance.AddLog(guid);
        }

        private static string HierarchyPath(Transform transform)
        {
            BuilderCache.Length = 0;
            BuilderCache.Append(transform.name);
            while (transform.parent != null)
            {
                BuilderCache.Insert(0, transform.parent.name + "/");
                transform = transform.parent;
            }
            return BuilderCache.ToString();
        }
    }
}

Console ログ

Console のログを保持するクラス

using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

namespace Log
{
    [Serializable]
    public class ConsoleData
    {
        public string Condition;
        public string Stacktrace;
        public LogType Type;

        public ConsoleData(string condition, string stacktrace, LogType type)
        {
            Condition = condition;
            Stacktrace = stacktrace;
            Type = type;
        }

        public override bool Equals(object obj)
        {
            var data =  obj as ConsoleData;
            return data.Type == Type && data.Condition == Condition && data.Stacktrace == Stacktrace;
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }
    
    [InitializeOnLoad]
    public static class ConsoleLog
    {
        public static IEnumerable<ConsoleData> HierarchyLogs { get { return ConsoleLogData.instance.Logs; } }
        
        private class ConsoleLogData : ScriptableSingleton<ConsoleLogData>
        {
            public IEnumerable<ConsoleData> Logs { get { return instance._logs; } }

            [SerializeField]
            private List<ConsoleData> _logs = new List<ConsoleData>(LogMax + 1);
            
            private const int LogMax = 100;
            
            internal void AddLog(ConsoleData data)
            {
                _logs.RemoveAll(l => l.Equals(data));
                _logs.Insert(0, data);
                while (_logs.Count > LogMax)
                    _logs.RemoveAt(LogMax);
            }
        }
        
        static ConsoleLog()
        {
            Application.logMessageReceived += logMessageReceived;
        }
        
        private static void logMessageReceived(string condition, string stacktrace, LogType type)
        {
            ConsoleLogData.instance.AddLog(new ConsoleData(condition, stacktrace, type));
        }
    }
}

注意

処理が同じなため、汎用的なクラスを利用したかったが ScriptableSingleton に Generic なクラスを指定することができないため同じ処理を記述する必要がある

   public class LogData<T> : ScriptableSingleton<LogData<T>>
    {
        public IEnumerable<T> Logs { get { return instance._logs; } }

        [SerializeField]
        private List<T> _logs = new List<T>(LogMax + 1);
            
        private const int LogMax = 50;
            
        internal void AddLog(T data)
        {
            _logs.RemoveAll(l => l.Equals(data));
            _logs.Insert(0, data);
            while (_logs.Count > LogMax)
                _logs.RemoveAt(LogMax);
        }
    }