com.hidano.missing-reference-finder 0.0.0-stage → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Editor/Hidano.MissingReferenceFinder.asmdef +16 -0
- package/Editor/Hidano.MissingReferenceFinder.asmdef.meta +7 -0
- package/Editor/MissingReferenceBuildCheck.cs +1506 -0
- package/Editor/MissingReferenceBuildCheck.cs.meta +11 -0
- package/Editor/MissingReferenceFinderWindow.cs +79 -0
- package/Editor/MissingReferenceFinderWindow.cs.meta +11 -0
- package/Editor/MissingReferenceScanner.cs +256 -0
- package/Editor/MissingReferenceScanner.cs.meta +11 -0
- package/Editor/TimelineBindings/TimelineBindingEntryFiller.cs +274 -0
- package/Editor/TimelineBindings/TimelineBindingEntryFiller.cs.meta +11 -0
- package/Editor/TimelineBindings.meta +8 -0
- package/Editor.meta +8 -0
- package/package.json +11 -4
- package/package.json.meta +7 -0
- package/README.md +0 -3
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
using UnityEditor;
|
|
2
|
+
using UnityEngine;
|
|
3
|
+
|
|
4
|
+
namespace Hidano.MissingReferenceFinder.Editor
|
|
5
|
+
{
|
|
6
|
+
/// <summary>
|
|
7
|
+
/// <see cref="MissingReferenceScanner"/> を手動実行するためのウィンドウ。
|
|
8
|
+
/// 現在開いている全シーンを対象に Missing / None の参照を Console に出力する。
|
|
9
|
+
/// </summary>
|
|
10
|
+
public sealed class MissingReferenceFinderWindow : EditorWindow
|
|
11
|
+
{
|
|
12
|
+
private const string MenuPath = "Tools/Hidano/Missing Reference Finder";
|
|
13
|
+
|
|
14
|
+
[SerializeField] private bool _reportNone = true;
|
|
15
|
+
[SerializeField] private bool _monoBehaviourOnly = true;
|
|
16
|
+
[SerializeField] private bool _includeInactive = true;
|
|
17
|
+
|
|
18
|
+
[MenuItem(MenuPath)]
|
|
19
|
+
private static void Open()
|
|
20
|
+
{
|
|
21
|
+
var window = GetWindow<MissingReferenceFinderWindow>();
|
|
22
|
+
window.titleContent = new GUIContent("Missing Ref Finder");
|
|
23
|
+
window.minSize = new Vector2(320f, 200f);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private void OnGUI()
|
|
27
|
+
{
|
|
28
|
+
EditorGUILayout.HelpBox(
|
|
29
|
+
"現在開いている全シーンのコンポーネントを走査し、Missing / None の参照を Console に出力します。\n" +
|
|
30
|
+
"Console のログをクリックすると該当 GameObject が Hierarchy 上でハイライトされます。",
|
|
31
|
+
MessageType.Info);
|
|
32
|
+
|
|
33
|
+
EditorGUILayout.Space();
|
|
34
|
+
|
|
35
|
+
_reportNone = EditorGUILayout.ToggleLeft(
|
|
36
|
+
new GUIContent("None も報告する", "OFF の場合は Missing (参照先が消失したもの) のみ報告します。"),
|
|
37
|
+
_reportNone);
|
|
38
|
+
_monoBehaviourOnly = EditorGUILayout.ToggleLeft(
|
|
39
|
+
new GUIContent("MonoBehaviour のみ", "OFF の場合は Renderer や Camera などの組み込みコンポーネントも検査します。"),
|
|
40
|
+
_monoBehaviourOnly);
|
|
41
|
+
_includeInactive = EditorGUILayout.ToggleLeft(
|
|
42
|
+
new GUIContent("非アクティブな GameObject も含める"),
|
|
43
|
+
_includeInactive);
|
|
44
|
+
|
|
45
|
+
EditorGUILayout.Space();
|
|
46
|
+
|
|
47
|
+
if (GUILayout.Button("検索してConsoleに出力", GUILayout.Height(32f)))
|
|
48
|
+
{
|
|
49
|
+
Scan();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
EditorGUILayout.Space();
|
|
53
|
+
|
|
54
|
+
var buildCheckEnabled = EditorGUILayout.ToggleLeft(
|
|
55
|
+
new GUIContent(
|
|
56
|
+
"ビルド時に Missing をチェックしてビルドを中止する",
|
|
57
|
+
"ビルド開始前に Build Settings の各シーンファイルを読み、存在しないアセットへの参照 (Missing) があればビルドを失敗させます。"),
|
|
58
|
+
MissingReferenceBuildCheck.Enabled);
|
|
59
|
+
if (buildCheckEnabled != MissingReferenceBuildCheck.Enabled)
|
|
60
|
+
{
|
|
61
|
+
MissingReferenceBuildCheck.Enabled = buildCheckEnabled;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private void Scan()
|
|
66
|
+
{
|
|
67
|
+
var options = new MissingReferenceScanner.Options
|
|
68
|
+
{
|
|
69
|
+
ReportNone = _reportNone,
|
|
70
|
+
MonoBehaviourOnly = _monoBehaviourOnly,
|
|
71
|
+
IncludeInactive = _includeInactive,
|
|
72
|
+
ShowProgressBar = true,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
var result = MissingReferenceScanner.ScanLoadedScenes(options);
|
|
76
|
+
Debug.Log(MissingReferenceScanner.FormatSummary("[MissingReferenceFinder]", result, _reportNone));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
using System.Collections.Generic;
|
|
2
|
+
using System.Text;
|
|
3
|
+
using UnityEditor;
|
|
4
|
+
using UnityEngine;
|
|
5
|
+
using UnityEngine.SceneManagement;
|
|
6
|
+
|
|
7
|
+
namespace Hidano.MissingReferenceFinder.Editor
|
|
8
|
+
{
|
|
9
|
+
/// <summary>
|
|
10
|
+
/// シーン内のコンポーネントを走査し、Serialize されたオブジェクト参照の Missing / None を Console に出力する。
|
|
11
|
+
/// ログは 1 パラメータ 1 行で出力し、context に GameObject を渡すので Console でクリックすると Hierarchy 上でハイライトされる。
|
|
12
|
+
/// <see cref="MissingReferenceFinderWindow"/> から呼び出す。
|
|
13
|
+
/// ビルド時のチェックはシーンファイルを直接読む <see cref="MissingReferenceBuildCheck"/> が担当する。
|
|
14
|
+
/// </summary>
|
|
15
|
+
public static class MissingReferenceScanner
|
|
16
|
+
{
|
|
17
|
+
public struct Options
|
|
18
|
+
{
|
|
19
|
+
/// <summary>None (未設定) も報告するか。</summary>
|
|
20
|
+
public bool ReportNone;
|
|
21
|
+
|
|
22
|
+
/// <summary>MonoBehaviour のみを対象にするか。false の場合は組み込みコンポーネントも検査する。</summary>
|
|
23
|
+
public bool MonoBehaviourOnly;
|
|
24
|
+
|
|
25
|
+
/// <summary>非アクティブな GameObject も対象にするか。</summary>
|
|
26
|
+
public bool IncludeInactive;
|
|
27
|
+
|
|
28
|
+
/// <summary>進捗バーを表示するか。</summary>
|
|
29
|
+
public bool ShowProgressBar;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public struct Result
|
|
33
|
+
{
|
|
34
|
+
public int ComponentCount;
|
|
35
|
+
public int MissingCount;
|
|
36
|
+
public int MissingScriptCount;
|
|
37
|
+
public int NoneCount;
|
|
38
|
+
|
|
39
|
+
/// <summary>Missing 参照または Missing Script が 1 件でもあれば true。</summary>
|
|
40
|
+
public bool HasMissing => MissingCount > 0 || MissingScriptCount > 0;
|
|
41
|
+
|
|
42
|
+
public void Add(in Result other)
|
|
43
|
+
{
|
|
44
|
+
ComponentCount += other.ComponentCount;
|
|
45
|
+
MissingCount += other.MissingCount;
|
|
46
|
+
MissingScriptCount += other.MissingScriptCount;
|
|
47
|
+
NoneCount += other.NoneCount;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// <summary>
|
|
52
|
+
/// Unity が内部管理用に持つ参照フィールド。ユーザーが設定するものではないので検査対象から除外する。
|
|
53
|
+
/// </summary>
|
|
54
|
+
private static readonly HashSet<string> IgnoredPropertyPaths = new HashSet<string>
|
|
55
|
+
{
|
|
56
|
+
"m_Script",
|
|
57
|
+
"m_GameObject",
|
|
58
|
+
"m_CorrespondingSourceObject",
|
|
59
|
+
"m_PrefabInstance",
|
|
60
|
+
"m_PrefabAsset",
|
|
61
|
+
"m_EditorClassIdentifier",
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/// <summary>現在ロードされている全シーンを走査する。</summary>
|
|
65
|
+
public static Result ScanLoadedScenes(in Options options)
|
|
66
|
+
{
|
|
67
|
+
var total = new Result();
|
|
68
|
+
try
|
|
69
|
+
{
|
|
70
|
+
for (var sceneIndex = 0; sceneIndex < SceneManager.sceneCount; sceneIndex++)
|
|
71
|
+
{
|
|
72
|
+
var scene = SceneManager.GetSceneAt(sceneIndex);
|
|
73
|
+
if (!scene.isLoaded)
|
|
74
|
+
{
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
total.Add(ScanScene(scene, options));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
finally
|
|
82
|
+
{
|
|
83
|
+
if (options.ShowProgressBar)
|
|
84
|
+
{
|
|
85
|
+
EditorUtility.ClearProgressBar();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return total;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// <summary>指定したシーンを走査する。</summary>
|
|
93
|
+
public static Result ScanScene(Scene scene, in Options options)
|
|
94
|
+
{
|
|
95
|
+
var result = new Result();
|
|
96
|
+
var roots = scene.GetRootGameObjects();
|
|
97
|
+
|
|
98
|
+
try
|
|
99
|
+
{
|
|
100
|
+
for (var rootIndex = 0; rootIndex < roots.Length; rootIndex++)
|
|
101
|
+
{
|
|
102
|
+
var root = roots[rootIndex];
|
|
103
|
+
if (options.ShowProgressBar)
|
|
104
|
+
{
|
|
105
|
+
EditorUtility.DisplayProgressBar(
|
|
106
|
+
"Missing Reference Finder",
|
|
107
|
+
$"{scene.name} : {root.name}",
|
|
108
|
+
(float)rootIndex / roots.Length);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
var components = root.GetComponentsInChildren<Component>(true);
|
|
112
|
+
foreach (var component in components)
|
|
113
|
+
{
|
|
114
|
+
// component が null なのはスクリプト自体が Missing になっているケース。
|
|
115
|
+
// GetComponentsInChildren では所属 GameObject を辿れないため、GameObject 単位で別途検出する。
|
|
116
|
+
if (component == null)
|
|
117
|
+
{
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!options.IncludeInactive && !component.gameObject.activeInHierarchy)
|
|
122
|
+
{
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (options.MonoBehaviourOnly && !(component is MonoBehaviour))
|
|
127
|
+
{
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
result.ComponentCount++;
|
|
132
|
+
ScanComponent(component, options.ReportNone, ref result);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
result.MissingScriptCount += ReportMissingScripts(root);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
finally
|
|
139
|
+
{
|
|
140
|
+
if (options.ShowProgressBar)
|
|
141
|
+
{
|
|
142
|
+
EditorUtility.ClearProgressBar();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
public static string FormatSummary(string prefix, in Result result, bool includeNone)
|
|
150
|
+
{
|
|
151
|
+
var summary = new StringBuilder();
|
|
152
|
+
summary.Append(prefix).Append(" 検査完了: ");
|
|
153
|
+
summary.Append(result.ComponentCount).Append(" コンポーネント / Missing ");
|
|
154
|
+
summary.Append(result.MissingCount).Append(" 件 / Missing Script ");
|
|
155
|
+
summary.Append(result.MissingScriptCount).Append(" 件");
|
|
156
|
+
if (includeNone)
|
|
157
|
+
{
|
|
158
|
+
summary.Append(" / None ").Append(result.NoneCount).Append(" 件");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return summary.ToString();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private static void ScanComponent(Component component, bool reportNone, ref Result result)
|
|
165
|
+
{
|
|
166
|
+
var serializedObject = new SerializedObject(component);
|
|
167
|
+
var property = serializedObject.GetIterator();
|
|
168
|
+
|
|
169
|
+
var enterChildren = true;
|
|
170
|
+
while (property.Next(enterChildren))
|
|
171
|
+
{
|
|
172
|
+
enterChildren = true;
|
|
173
|
+
|
|
174
|
+
// 文字列の子要素へは潜らない
|
|
175
|
+
if (property.propertyType == SerializedPropertyType.String)
|
|
176
|
+
{
|
|
177
|
+
enterChildren = false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (property.propertyType != SerializedPropertyType.ObjectReference)
|
|
181
|
+
{
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (IgnoredPropertyPaths.Contains(property.propertyPath))
|
|
186
|
+
{
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (property.objectReferenceValue != null)
|
|
191
|
+
{
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 参照先の instanceID が残っているのに解決できない = Missing
|
|
196
|
+
var isMissing = property.objectReferenceInstanceIDValue != 0;
|
|
197
|
+
if (isMissing)
|
|
198
|
+
{
|
|
199
|
+
result.MissingCount++;
|
|
200
|
+
Debug.LogError(FormatMessage("Missing", component, property), component.gameObject);
|
|
201
|
+
}
|
|
202
|
+
else if (reportNone)
|
|
203
|
+
{
|
|
204
|
+
result.NoneCount++;
|
|
205
|
+
Debug.LogWarning(FormatMessage("None", component, property), component.gameObject);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private static int ReportMissingScripts(GameObject root)
|
|
211
|
+
{
|
|
212
|
+
var count = 0;
|
|
213
|
+
var transforms = root.GetComponentsInChildren<Transform>(true);
|
|
214
|
+
foreach (var transform in transforms)
|
|
215
|
+
{
|
|
216
|
+
var gameObject = transform.gameObject;
|
|
217
|
+
var missing = GameObjectUtility.GetMonoBehavioursWithMissingScriptCount(gameObject);
|
|
218
|
+
for (var i = 0; i < missing; i++)
|
|
219
|
+
{
|
|
220
|
+
count++;
|
|
221
|
+
Debug.LogError(
|
|
222
|
+
$"[Missing Script] {GetHierarchyPath(gameObject)} にスクリプトが見つからないコンポーネントがあります。",
|
|
223
|
+
gameObject);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return count;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private static string FormatMessage(string kind, Component component, SerializedProperty property)
|
|
231
|
+
{
|
|
232
|
+
var typeName = property.type;
|
|
233
|
+
// "PPtr<$Transform>" のような表記から型名だけを取り出す
|
|
234
|
+
if (typeName.StartsWith("PPtr<$") && typeName.EndsWith(">"))
|
|
235
|
+
{
|
|
236
|
+
typeName = typeName.Substring(6, typeName.Length - 7);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return $"[{kind}] {GetHierarchyPath(component.gameObject)} <{component.GetType().Name}> " +
|
|
240
|
+
$"{property.propertyPath} ({typeName})";
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private static string GetHierarchyPath(GameObject gameObject)
|
|
244
|
+
{
|
|
245
|
+
var path = new StringBuilder(gameObject.name);
|
|
246
|
+
var parent = gameObject.transform.parent;
|
|
247
|
+
while (parent != null)
|
|
248
|
+
{
|
|
249
|
+
path.Insert(0, '/').Insert(0, parent.name);
|
|
250
|
+
parent = parent.parent;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return $"{gameObject.scene.name}:{path}";
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections.Generic;
|
|
3
|
+
using System.Linq;
|
|
4
|
+
using System.Text;
|
|
5
|
+
using UnityEditor;
|
|
6
|
+
using UnityEditor.SceneManagement;
|
|
7
|
+
using UnityEngine;
|
|
8
|
+
using UnityEngine.Playables;
|
|
9
|
+
using UnityEngine.SceneManagement;
|
|
10
|
+
using Object = UnityEngine.Object;
|
|
11
|
+
|
|
12
|
+
namespace Hidano.MissingReferenceFinder.Editor.TimelineBindings
|
|
13
|
+
{
|
|
14
|
+
/// <summary>
|
|
15
|
+
/// Timeline (.playable) の保存時に、その Timeline を再生する PlayableDirector の m_SceneBindings へ
|
|
16
|
+
/// 未登録トラックの空 Binding (value: None) を追加する。
|
|
17
|
+
///
|
|
18
|
+
/// PlayableDirector の Inspector は Bindings 欄を表示した時点で、未登録の出力に空の Binding を作る
|
|
19
|
+
/// (UnityEditor.DirectorEditor.UpdatePlayableBindingsIfRequired)。トラックを追加した人がその Prefab の
|
|
20
|
+
/// Inspector を表示していないと、後で別の人が表示したときに意図しない差分として現れる。その差分をコミットせずに
|
|
21
|
+
/// 抱えたまま pull すると、コンフリクト解決時に他の人が設定した Binding を上書きして消す事故につながる。
|
|
22
|
+
/// トラック追加と同じタイミングで空 Binding を作っておき、同じコミットに含めることでこれを防ぐ。
|
|
23
|
+
///
|
|
24
|
+
/// 対象は Inspector と同じく、playableAsset.outputs のうち出力先の型を持つもの。Inspector は出力先の型を持たない
|
|
25
|
+
/// 出力の Binding を削除もするが、要素の位置がずれて Prefab インスタンスのオーバーライド (配列番号で記録される) を
|
|
26
|
+
/// 崩しうるため、ここでは末尾への追加のみを行う。Prefab インスタンス上の PlayableDirector は
|
|
27
|
+
/// 元 Prefab 側で処理されるので対象外 (インスタンス側に追加すると不要なオーバーライドになる)。
|
|
28
|
+
///
|
|
29
|
+
/// 有効/無効はメニュー (Tools/Hidano/Fill Timeline Binding Entries On Save) で切り替えられる。
|
|
30
|
+
/// </summary>
|
|
31
|
+
public sealed class TimelineBindingEntryFiller : AssetPostprocessor
|
|
32
|
+
{
|
|
33
|
+
private const string ToggleMenuPath = "Tools/Hidano/Fill Timeline Binding Entries On Save";
|
|
34
|
+
private const string RunMenuPath = "Tools/Hidano/Fill Timeline Binding Entries In All Prefabs";
|
|
35
|
+
private const string EditorPrefsKey = "Hidano.TimelineBindingEntryFiller.Enabled";
|
|
36
|
+
private const string LogPrefix = "[TimelineBindingEntryFiller]";
|
|
37
|
+
|
|
38
|
+
private const string PlayableExtension = ".playable";
|
|
39
|
+
private const string AssetsFolder = "Assets";
|
|
40
|
+
private const string SceneBindingsProperty = "m_SceneBindings";
|
|
41
|
+
private const string BindingKeyProperty = "key";
|
|
42
|
+
private const string BindingValueProperty = "value";
|
|
43
|
+
|
|
44
|
+
/// <summary>delayCall 待ちの間に保存された Timeline のパス。</summary>
|
|
45
|
+
private static readonly HashSet<string> PendingTimelinePaths = new HashSet<string>();
|
|
46
|
+
|
|
47
|
+
public static bool Enabled
|
|
48
|
+
{
|
|
49
|
+
get => EditorPrefs.GetBool(EditorPrefsKey, true);
|
|
50
|
+
set => EditorPrefs.SetBool(EditorPrefsKey, value);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
[MenuItem(ToggleMenuPath)]
|
|
54
|
+
private static void ToggleEnabled()
|
|
55
|
+
{
|
|
56
|
+
Enabled = !Enabled;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
[MenuItem(ToggleMenuPath, true)]
|
|
60
|
+
private static bool ToggleEnabledValidate()
|
|
61
|
+
{
|
|
62
|
+
Menu.SetChecked(ToggleMenuPath, Enabled);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// <summary>保存を待たずに、Timeline を参照する全 Prefab と開いているシーンに同じ処理を行う。</summary>
|
|
67
|
+
[MenuItem(RunMenuPath)]
|
|
68
|
+
private static void RunManually()
|
|
69
|
+
{
|
|
70
|
+
var addedCount = Fill(_ => true);
|
|
71
|
+
if (addedCount == 0)
|
|
72
|
+
{
|
|
73
|
+
Debug.Log($"{LogPrefix} 追加が必要な Binding はありませんでした。");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private static void OnPostprocessAllAssets(
|
|
78
|
+
string[] importedAssets,
|
|
79
|
+
string[] deletedAssets,
|
|
80
|
+
string[] movedAssets,
|
|
81
|
+
string[] movedFromAssetPaths)
|
|
82
|
+
{
|
|
83
|
+
if (!Enabled)
|
|
84
|
+
{
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
var added = false;
|
|
89
|
+
foreach (var path in importedAssets)
|
|
90
|
+
{
|
|
91
|
+
if (IsProjectTimelinePath(path))
|
|
92
|
+
{
|
|
93
|
+
added |= PendingTimelinePaths.Add(path);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (added)
|
|
98
|
+
{
|
|
99
|
+
// インポート処理中に他のアセットを書き換えないよう、インポート完了後に回す
|
|
100
|
+
EditorApplication.delayCall -= ProcessPending;
|
|
101
|
+
EditorApplication.delayCall += ProcessPending;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private static bool IsProjectTimelinePath(string path)
|
|
106
|
+
{
|
|
107
|
+
return path.StartsWith(AssetsFolder + "/", StringComparison.Ordinal) &&
|
|
108
|
+
path.EndsWith(PlayableExtension, StringComparison.OrdinalIgnoreCase);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private static void ProcessPending()
|
|
112
|
+
{
|
|
113
|
+
if (PendingTimelinePaths.Count == 0)
|
|
114
|
+
{
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
var timelinePaths = new HashSet<string>(PendingTimelinePaths);
|
|
119
|
+
PendingTimelinePaths.Clear();
|
|
120
|
+
Fill(timelinePaths.Contains);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// <summary>
|
|
124
|
+
/// <paramref name="isTargetTimeline"/> に該当する Timeline を参照する Prefab と、開いているシーンを処理する。
|
|
125
|
+
/// </summary>
|
|
126
|
+
/// <returns>追加した Binding の総数。</returns>
|
|
127
|
+
private static int Fill(Func<string, bool> isTargetTimeline)
|
|
128
|
+
{
|
|
129
|
+
var addedCount = 0;
|
|
130
|
+
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
|
|
131
|
+
|
|
132
|
+
foreach (var guid in AssetDatabase.FindAssets("t:Prefab", new[] { AssetsFolder }))
|
|
133
|
+
{
|
|
134
|
+
var prefabPath = AssetDatabase.GUIDToAssetPath(guid);
|
|
135
|
+
var referencesTarget = AssetDatabase.GetDependencies(prefabPath, false)
|
|
136
|
+
.Any(path => IsProjectTimelinePath(path) && isTargetTimeline(path));
|
|
137
|
+
if (!referencesTarget)
|
|
138
|
+
{
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Prefab モードで開いている Prefab をファイル側から書き換えると編集中の内容と食い違うので、ステージ上で処理する
|
|
143
|
+
if (prefabStage != null && prefabStage.assetPath == prefabPath)
|
|
144
|
+
{
|
|
145
|
+
var stageAdded = FillDirectors(prefabStage.prefabContentsRoot, isTargetTimeline, prefabPath);
|
|
146
|
+
if (stageAdded > 0)
|
|
147
|
+
{
|
|
148
|
+
EditorSceneManager.MarkSceneDirty(prefabStage.scene);
|
|
149
|
+
addedCount += stageAdded;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
var root = PrefabUtility.LoadPrefabContents(prefabPath);
|
|
156
|
+
try
|
|
157
|
+
{
|
|
158
|
+
var prefabAdded = FillDirectors(root, isTargetTimeline, prefabPath);
|
|
159
|
+
if (prefabAdded > 0)
|
|
160
|
+
{
|
|
161
|
+
PrefabUtility.SaveAsPrefabAsset(root, prefabPath);
|
|
162
|
+
addedCount += prefabAdded;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
finally
|
|
166
|
+
{
|
|
167
|
+
PrefabUtility.UnloadPrefabContents(root);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
for (var i = 0; i < SceneManager.sceneCount; i++)
|
|
172
|
+
{
|
|
173
|
+
var scene = SceneManager.GetSceneAt(i);
|
|
174
|
+
if (!scene.isLoaded)
|
|
175
|
+
{
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
var sceneAdded = scene.GetRootGameObjects()
|
|
180
|
+
.Sum(root => FillDirectors(root, isTargetTimeline, scene.path));
|
|
181
|
+
if (sceneAdded > 0)
|
|
182
|
+
{
|
|
183
|
+
EditorSceneManager.MarkSceneDirty(scene);
|
|
184
|
+
addedCount += sceneAdded;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return addedCount;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private static int FillDirectors(GameObject root, Func<string, bool> isTargetTimeline, string context)
|
|
192
|
+
{
|
|
193
|
+
var addedCount = 0;
|
|
194
|
+
foreach (var director in root.GetComponentsInChildren<PlayableDirector>(true))
|
|
195
|
+
{
|
|
196
|
+
if (PrefabUtility.IsPartOfPrefabInstance(director))
|
|
197
|
+
{
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
var playableAsset = director.playableAsset;
|
|
202
|
+
if (playableAsset == null || !isTargetTimeline(AssetDatabase.GetAssetPath(playableAsset)))
|
|
203
|
+
{
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
var addedKeys = FillDirector(director, playableAsset);
|
|
208
|
+
if (addedKeys.Count == 0)
|
|
209
|
+
{
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
addedCount += addedKeys.Count;
|
|
214
|
+
Debug.Log(
|
|
215
|
+
$"{LogPrefix} {context} の {GetHierarchyPath(director.transform)} に空の Binding を {addedKeys.Count} 件追加しました " +
|
|
216
|
+
$"({playableAsset.name}: {string.Join(", ", addedKeys.Select(key => key.name))})。" +
|
|
217
|
+
"Timeline の変更と一緒にコミットしてください。");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return addedCount;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/// <summary>
|
|
224
|
+
/// 出力先の型を持つ出力のうち m_SceneBindings に無いものを、value が None の要素として末尾に追加する。
|
|
225
|
+
/// UnityEditor.DirectorEditor.UpdatePlayableBindingsIfRequired と同じ対象・同じ順序になる。
|
|
226
|
+
/// </summary>
|
|
227
|
+
private static List<Object> FillDirector(PlayableDirector director, PlayableAsset playableAsset)
|
|
228
|
+
{
|
|
229
|
+
var serializedDirector = new SerializedObject(director);
|
|
230
|
+
var bindings = serializedDirector.FindProperty(SceneBindingsProperty);
|
|
231
|
+
|
|
232
|
+
var boundKeys = new HashSet<Object>();
|
|
233
|
+
for (var i = 0; i < bindings.arraySize; i++)
|
|
234
|
+
{
|
|
235
|
+
boundKeys.Add(bindings.GetArrayElementAtIndex(i).FindPropertyRelative(BindingKeyProperty).objectReferenceValue);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
var addedKeys = new List<Object>();
|
|
239
|
+
foreach (var output in playableAsset.outputs)
|
|
240
|
+
{
|
|
241
|
+
var key = output.sourceObject;
|
|
242
|
+
if (key == null || output.outputTargetType == null || !boundKeys.Add(key))
|
|
243
|
+
{
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
var index = bindings.arraySize;
|
|
248
|
+
bindings.InsertArrayElementAtIndex(index);
|
|
249
|
+
var element = bindings.GetArrayElementAtIndex(index);
|
|
250
|
+
element.FindPropertyRelative(BindingKeyProperty).objectReferenceValue = key;
|
|
251
|
+
element.FindPropertyRelative(BindingValueProperty).objectReferenceValue = null;
|
|
252
|
+
addedKeys.Add(key);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (addedKeys.Count > 0)
|
|
256
|
+
{
|
|
257
|
+
serializedDirector.ApplyModifiedPropertiesWithoutUndo();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return addedKeys;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private static string GetHierarchyPath(Transform transform)
|
|
264
|
+
{
|
|
265
|
+
var builder = new StringBuilder(transform.name);
|
|
266
|
+
for (var parent = transform.parent; parent != null; parent = parent.parent)
|
|
267
|
+
{
|
|
268
|
+
builder.Insert(0, parent.name + "/");
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return builder.ToString();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|