はじめに
先日このようなエディタ拡張を作成しました。
指定したフォルダ以下に含まれるファイルで使用されている文字列を列挙してくれるエディタ拡張を作ってみました!
— はなちる@ゲーム制作 (@hanaaaaaachiru) September 24, 2020
TextMeshProのテキストを使用するものだけにして容量削減するときに活躍してくれそうhttps://t.co/pyUv4ERTEP#unity pic.twitter.com/YiiFdzw7hc
これを作った目的は、TextMeshPro
のテキストを使用するものだけにして容量削減するというものになります。
使い方はTwitterの動画をみていただければすぐに分かると思うので、今回の記事ではコードを公開しようと思います。
コード
using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using UnityEditor; using UnityEngine; namespace UsedCharEnumerator { public sealed class UsedCharEnumerator : EditorWindow { private const string ASSETS = "Assets"; private FileExtensions _targets; private DefaultAsset _searchFolder; private string _path; private string _output; [MenuItem("Tools/UsedCharEnumerator")] private static void OpenWindow() { var window = GetWindow<UsedCharEnumerator>("UsedCharEnumerator"); window.minSize = new Vector2(500, 500); window._targets = new FileExtensions(); } private void OnGUI() { // 検索するフォルダ. var searchFolder = (DefaultAsset)EditorGUILayout.ObjectField("検索フォルダ", _searchFolder, typeof(DefaultAsset), false); if (searchFolder == null) EditorGUILayout.HelpBox("中で使用されている文字列を知りたいフォルダを選択してください", MessageType.Info); else if (_searchFolder != searchFolder) UpdateSerchFile(searchFolder); // 検索対象を選択するボタン foreach (var item in FileExtensions.Kinds) _targets[item] = EditorGUILayout.Toggle(item, _targets[item]); // 検索ボタン if (GUILayout.Button("Serch", GUILayout.Height(16))) if (_searchFolder != null) _output = EnumerateChars(_path); // 結果を出力するテキスト GUIStyle style = new GUIStyle(GUI.skin.textArea); style.wordWrap = true; EditorGUILayout.TextArea(_output, style, GUILayout.MaxHeight(float.MaxValue)); } private void UpdateSerchFile(DefaultAsset asset) { _searchFolder = asset; _path = AssetDatabase.GetAssetOrScenePath(_searchFolder); string[] folderList = _path.Split('/'); if (folderList[folderList.Length - 1].Contains(".")) { _searchFolder = null; _path = null; } } private string EnumerateChars(string path) { EditorUtility.DisplayProgressBar("検索中","フォルダ内のファイルを走査中……", 0); // 対象のフォルダの絶対パスを生成 var absolutePath = Application.dataPath.Remove(Application.dataPath.LastIndexOf(ASSETS), ASSETS.Length) + path; // ファイル読み込み var info = FileReader.GetFiles(absolutePath, _targets).ToArray(); // 文字抽出 var length = info.Length; var current = 0; var charSet = new HashSet<char>(); foreach (var file in info) { EditorUtility.DisplayProgressBar("読み込み中", $"{file.Name}を読み込み中……", current++ / (float)length); foreach (var line in FileReader.Read(file)) foreach (var c in line) charSet.Add(c); } EditorUtility.ClearProgressBar(); return new string(charSet.ToArray()); } } internal static class FileReader { public static IEnumerable<FileInfo> GetFiles(string absolutePath, FileExtensions target) { var dir = new DirectoryInfo(absolutePath); foreach (var item in target.Targets) { if (item.Value == false) continue; var files = dir.GetFiles(item.Key, SearchOption.AllDirectories); foreach (var file in files) yield return file; } } public static IEnumerable<string> Read(FileInfo file) { using (var reader = new StreamReader(file.FullName, Encoding.UTF8)) { while (!reader.EndOfStream) yield return reader.ReadLine(); } } } internal class FileExtensions { public static readonly string[] Kinds = new string[] { "*.cs", "*.asset", "*.txt", "*.json", "*.xml" }; private readonly Dictionary<string, bool> _targets; public IReadOnlyDictionary<string, bool> Targets => _targets; public FileExtensions() { _targets = new Dictionary<string, bool>(); foreach (var item in Kinds) _targets[item] = true; } public bool this[string key] { get => _targets[key]; set { if (!Kinds.Contains(key)) return; _targets[key] = value; } } } }
これをどこかのEditor
フォルダに入れれば、Tools -> UsedCharEnumerator
という欄が出てくるはずです。
仕組み
仕組みはいたってシンプルで以下のようなステップで文字を列挙します。
- 指定したフォルダ以下の対象ファイルを取得
StreamReader
を使ってファイルを読み込む- 文字を
HashSet
に溜める - 全て読み込み終わったら出力
こだわりポイントとしては,ファイル読み込みのときに遅延評価を利用してメモリの使用を抑えた箇所と進捗バーを出したところでしょうか。
ただ進歩バーを出したかったがためにToArray
をした箇所が一つありますが、さほどメモリに影響はないと信じたいです。(色んな種類のファイルがめちゃくちゃあれば少し怪しそう?)
後はキャンセルも実装したかったのですが、今回実装しませんでした。もしかしたら追加するかもしれません。
要注意な箇所
ScriptableObject
に書かれた文字も列挙したいとの思いで、ファイル選択の箇所に.asset
があります。
ただ日本語がエンコード?されたみたく数字の羅列になってしまっているので,StreamReader
でテキストとして読み込んだときに反映されない可能性が非常に高いです。
m_EditorClassIdentifier: _gnosicon: - _word: "\u3069\u3053" _gnosemes: - place - _word: "\u5929\u6C17" _gnosemes: - weather - _word: "\u6559\u3048\u308B" _gnosemes: - tell
現状の実装では.asset
はほぼ使えないと思ってもらっても良いかもしれません。
さいごに
めんどくさいのでコードをブログに貼っちゃいましたが、需要があるようならGitHub
に乗せたりを考えようと思います。
もし何かあればコメント等で教えていただけると幸いです。
ではまた。