ShareDataPrefabExtension.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. // Magica Cloth.
  2. // Copyright (c) MagicaSoft, 2020-2022.
  3. // https://magicasoft.jp
  4. using System.Collections.Generic;
  5. using UnityEditor;
  6. using UnityEngine;
  7. #if UNITY_2021_2_OR_NEWER
  8. using UnityEditor.SceneManagement;
  9. #else
  10. using UnityEditor.Experimental.SceneManagement;
  11. #endif
  12. namespace MagicaCloth
  13. {
  14. /// <summary>
  15. /// 共有データオブジェクトのプレハブ化処理
  16. /// プレハブがApplyされた場合に、自動でスクリプタブルオブジェクをプレハブのサブアセットとして保存します。
  17. /// 該当するコンポーネントにIShareDataObjectを継承し、GetAllShareDataObject()で該当する共有データを返す必要があります。
  18. /// </summary>
  19. [InitializeOnLoad]
  20. internal class ShareDataPrefabExtension
  21. {
  22. private enum Mode
  23. {
  24. Saving = 1,
  25. Update = 2,
  26. }
  27. static List<GameObject> prefabInstanceList = new List<GameObject>();
  28. static List<Mode> prefabModeList = new List<Mode>();
  29. /// <summary>
  30. /// プレハブ更新コールバック登録
  31. /// </summary>
  32. static ShareDataPrefabExtension()
  33. {
  34. PrefabUtility.prefabInstanceUpdated += OnPrefabInstanceUpdate;
  35. PrefabStage.prefabStageClosing += OnPrefabStageClosing;
  36. PrefabStage.prefabSaving += OnPrefabSaving;
  37. }
  38. /// <summary>
  39. /// プレハブステージが閉じる時
  40. /// </summary>
  41. /// <param name="obj"></param>
  42. static void OnPrefabStageClosing(PrefabStage pstage)
  43. {
  44. //#if UNITY_2020_1_OR_NEWER
  45. // Debug.Log($"OnPrefabStageClosing() root:[{pstage.prefabContentsRoot.name}] id:{pstage.prefabContentsRoot.GetInstanceID()} path:{pstage.assetPath}");
  46. //#else
  47. // Debug.Log($"OnPrefabStageClosing() root:[{pstage.prefabContentsRoot.name}] id:{pstage.prefabContentsRoot.GetInstanceID()} path:{pstage.prefabAssetPath}");
  48. //#endif
  49. if (prefabInstanceList.Count > 0)
  50. {
  51. DelayAnalyze();
  52. }
  53. }
  54. /// <summary>
  55. /// プレハブモードでプレハブが保存される直前
  56. /// </summary>
  57. /// <param name="instance"></param>
  58. static void OnPrefabSaving(GameObject instance)
  59. {
  60. //Debug.Log($"OnPrefabSaving() instance:[{instance.name}] id:{instance.GetInstanceID()}");
  61. if (prefabInstanceList.Contains(instance) == false)
  62. {
  63. prefabInstanceList.Add(instance);
  64. prefabModeList.Add(Mode.Saving);
  65. DelayAnalyze();
  66. }
  67. }
  68. /// <summary>
  69. /// プレハブがApplyされた場合に呼ばれる
  70. /// instanceはヒエラルキーにあるゲームオブジェクト
  71. /// プレハブが更新された場合、スクリプタブルオブジェクをプレハブのサブアセットとして自動保存する
  72. /// </summary>
  73. /// <param name="instance"></param>
  74. static void OnPrefabInstanceUpdate(GameObject instance)
  75. {
  76. //Debug.Log($"OnPrefabInstanceUpdate() instance:{instance.name} id:{instance.GetInstanceID()}");
  77. if (prefabInstanceList.Contains(instance))
  78. return;
  79. prefabInstanceList.Add(instance);
  80. prefabModeList.Add(Mode.Update);
  81. EditorApplication.delayCall += DelayAnalyze;
  82. }
  83. static void DelayAnalyze()
  84. {
  85. //Debug.Log($"DelayAnalyze.start:{prefabInstanceList.Count}");
  86. EditorApplication.delayCall -= DelayAnalyze;
  87. for (int i = 0; i < prefabInstanceList.Count; i++)
  88. {
  89. var instance = prefabInstanceList[i];
  90. var mode = prefabModeList[i];
  91. if (instance)
  92. {
  93. Analyze(instance, mode);
  94. }
  95. }
  96. prefabInstanceList.Clear();
  97. prefabModeList.Clear();
  98. //Debug.Log("DelayAnalyze.end.");
  99. }
  100. static void Analyze(GameObject instance, Mode mode)
  101. {
  102. var pstage = PrefabStageUtility.GetCurrentPrefabStage();
  103. bool isVariant = PrefabUtility.IsPartOfVariantPrefab(instance);
  104. bool onStage = pstage != null ? pstage.IsPartOfPrefabContents(instance) : false;
  105. //Debug.Log($"Analyze instance:{instance.name} id:{instance.GetInstanceID()} IsVariant:{isVariant} Mode:{mode} PStage:{pstage != null} OnStage:{onStage}");
  106. string pstageAssetPath = string.Empty;
  107. if (pstage != null)
  108. {
  109. #if UNITY_2020_1_OR_NEWER
  110. pstageAssetPath = pstage.assetPath;
  111. #else
  112. pstageAssetPath = pstage.prefabAssetPath;
  113. #endif
  114. //Debug.Log($"pstage root:{pstage.prefabContentsRoot.name} id:{pstage.prefabContentsRoot.GetInstanceID()} path:{pstageAssetPath}");
  115. }
  116. else
  117. {
  118. //Debug.Log($"pstage = (null)");
  119. }
  120. string prefabAssetPath = string.Empty;
  121. string baseAssetPath = string.Empty;
  122. if (mode == Mode.Saving)
  123. {
  124. // 自身のプレハブアセット
  125. prefabAssetPath = pstageAssetPath;
  126. var prefabAsset = AssetDatabase.LoadAssetAtPath<GameObject>(prefabAssetPath);
  127. // 派生元のプレハブアセット
  128. var baseAsset = PrefabUtility.GetCorrespondingObjectFromSource(prefabAsset);
  129. baseAssetPath = AssetDatabase.GetAssetPath(baseAsset);
  130. }
  131. else
  132. {
  133. if (pstage != null)
  134. {
  135. if (pstage.prefabContentsRoot == instance)
  136. {
  137. // 自身のプレハブアセット
  138. prefabAssetPath = pstageAssetPath;
  139. var prefabAsset = AssetDatabase.LoadAssetAtPath<GameObject>(prefabAssetPath);
  140. // 派生元のプレハブアセット
  141. var baseAsset = PrefabUtility.GetCorrespondingObjectFromSource(prefabAsset);
  142. baseAssetPath = AssetDatabase.GetAssetPath(baseAsset);
  143. }
  144. else
  145. {
  146. // 自身のプレハブアセット
  147. var prefabAsset = PrefabUtility.GetCorrespondingObjectFromSource(instance);
  148. prefabAssetPath = AssetDatabase.GetAssetPath(prefabAsset);
  149. // 派生元のプレハブアセット
  150. var baseAsset = PrefabUtility.GetCorrespondingObjectFromOriginalSource(prefabAsset);
  151. baseAssetPath = AssetDatabase.GetAssetPath(baseAsset);
  152. }
  153. }
  154. else
  155. {
  156. // 自身のプレハブアセット
  157. var prefabAsset = PrefabUtility.GetCorrespondingObjectFromSource(instance);
  158. prefabAssetPath = AssetDatabase.GetAssetPath(prefabAsset);
  159. // 派生元のプレハブアセット
  160. if (prefabAsset)
  161. {
  162. var baseAsset = PrefabUtility.GetCorrespondingObjectFromSource(prefabAsset);
  163. baseAssetPath = AssetDatabase.GetAssetPath(baseAsset);
  164. }
  165. }
  166. }
  167. //Debug.Log($"prefabPath:{prefabAssetPath}");
  168. //Debug.Log($"basePath:{baseAssetPath}");
  169. // 判定
  170. string saveAssetPath = prefabAssetPath;
  171. if (pstage == null && isVariant)
  172. {
  173. //Debug.Log($"instance1[{instance.name}]の変更を->[{prefabAssetPath}]に反映する.");
  174. }
  175. else if (string.IsNullOrEmpty(prefabAssetPath))
  176. {
  177. //Debug.Log($"Skip1");
  178. return;
  179. }
  180. else if (mode == Mode.Saving)
  181. {
  182. //Debug.Log($"instance2[{instance.name}]の変更を->[{prefabAssetPath}]に反映する.");
  183. }
  184. else
  185. {
  186. if (isVariant)
  187. {
  188. //Debug.Log("Skip2");
  189. return;
  190. }
  191. else if (string.IsNullOrEmpty(baseAssetPath))
  192. {
  193. //Debug.Log($"instance4[{instance.name}]の変更を->[{prefabAssetPath}]に反映する.");
  194. }
  195. else
  196. {
  197. //Debug.Log($"instance5[{instance.name}]の変更を->[{baseAssetPath}]に反映する.");
  198. saveAssetPath = baseAssetPath;
  199. }
  200. }
  201. // 強制保存判定
  202. bool forceCopy = false;
  203. if (isVariant && pstage != null && instance != pstage.prefabContentsRoot)
  204. forceCopy = true;
  205. if (pstage != null && prefabAssetPath == saveAssetPath && saveAssetPath != pstageAssetPath && onStage)
  206. forceCopy = true;
  207. // 保存実行
  208. SavePrefab(instance, prefabAssetPath, saveAssetPath, isVariant, forceCopy, mode);
  209. }
  210. static void SavePrefab(GameObject instance, string prefabPath, string savePrefabPath, bool isVariant, bool forceCopy, Mode mode)
  211. {
  212. //Debug.Log($"SavePrefab instance:{instance.name} forceCopy:{forceCopy} isVariant:{isVariant}\npath:{prefabPath}\nsavePath:{savePrefabPath} ");
  213. // 保存先プレハブアセット
  214. var savePrefab = AssetDatabase.LoadAssetAtPath<GameObject>(savePrefabPath);
  215. // 編集不可のプレハブならば保存できないため処理を行わない
  216. if (PrefabUtility.IsPartOfImmutablePrefab(savePrefab))
  217. {
  218. //Debug.Log("Skip3");
  219. return;
  220. }
  221. // 不要な共有データを削除するためのリスト
  222. bool change = false;
  223. List<ShareDataObject> removeDatas = new List<ShareDataObject>();
  224. // 現在アセットとして保存されているすべてのShareDataObjectサブアセットを削除対象としてリスト化する
  225. List<Object> subassets = new List<Object>(AssetDatabase.LoadAllAssetRepresentationsAtPath(savePrefabPath));
  226. if (subassets != null)
  227. {
  228. foreach (var obj in subassets)
  229. {
  230. // ShareDataObjectのみ
  231. ShareDataObject sdata = obj as ShareDataObject;
  232. if (sdata && removeDatas.Contains(sdata) == false)
  233. {
  234. //Debug.Log("remove reserve sub asset:" + obj.name + " type:" + obj + " test:" + AssetDatabase.IsSubAsset(sdata));
  235. // 削除対象として一旦追加
  236. removeDatas.Add(sdata);
  237. }
  238. }
  239. }
  240. // データコンポーネント収集
  241. var coreList = instance.GetComponentsInChildren<CoreComponent>(true);
  242. if (coreList != null)
  243. {
  244. foreach (var core in coreList)
  245. {
  246. // 共有データ収集
  247. var shareDataInterfaces = core.GetComponentsInChildren<IShareDataObject>(true);
  248. if (shareDataInterfaces != null)
  249. {
  250. foreach (var sdataInterface in shareDataInterfaces)
  251. {
  252. List<ShareDataObject> shareDatas = sdataInterface.GetAllShareDataObject();
  253. if (shareDatas != null)
  254. {
  255. foreach (var sdata in shareDatas)
  256. {
  257. if (sdata)
  258. {
  259. //Debug.Log($"target shareData:{sdata.name}");
  260. if (removeDatas.Contains(sdata))
  261. {
  262. //Debug.Log($"Ignore:{sdata.name}");
  263. removeDatas.Remove(sdata);
  264. }
  265. else if (AssetDatabase.Contains(sdata))
  266. {
  267. // アセットのプレハブパスを取得
  268. var sdataPrefabPath = AssetDatabase.GetAssetPath(sdata);
  269. //Debug.Log($"sdataPrefabPath:{sdataPrefabPath}");
  270. if (forceCopy || prefabPath != savePrefabPath)
  271. {
  272. var newdata = sdataInterface.DuplicateShareDataObject(sdata);
  273. if (newdata != null)
  274. {
  275. //Debug.Log($"+Duplicate sub asset:{newdata.name} -> [{savePrefab.name}]");
  276. AssetDatabase.AddObjectToAsset(newdata, savePrefab);
  277. change = true;
  278. }
  279. }
  280. else
  281. {
  282. removeDatas.Remove(sdata);
  283. }
  284. }
  285. else
  286. {
  287. //Debug.Log($"+Add sub asset:{sdata.name} -> [{savePrefab.name}]");
  288. AssetDatabase.AddObjectToAsset(sdata, savePrefab);
  289. change = true;
  290. }
  291. }
  292. }
  293. }
  294. }
  295. }
  296. }
  297. }
  298. // 不要な共有データは削除する
  299. foreach (var sdata in removeDatas)
  300. {
  301. //Debug.Log($"-Remove sub asset:{sdata.name} path:{AssetDatabase.GetAssetPath(sdata)}");
  302. UnityEngine.Object.DestroyImmediate(sdata, true);
  303. change = true;
  304. }
  305. // 変更を反映する
  306. if (change)
  307. {
  308. //Debug.Log("save!");
  309. // どうもこの手順を踏まないと保存した共有データが正しくアタッチされない
  310. if (mode == Mode.Saving)
  311. {
  312. PrefabUtility.SaveAsPrefabAsset(instance, savePrefabPath);
  313. }
  314. else
  315. {
  316. PrefabUtility.SaveAsPrefabAssetAndConnect(instance, savePrefabPath, InteractionMode.AutomatedAction);
  317. }
  318. }
  319. }
  320. //=========================================================================================
  321. public static bool CleanUpSubAssets(GameObject savePrefab, bool log = true)
  322. {
  323. // 編集不可のプレハブならば保存できないため処理を行わない
  324. if (PrefabUtility.IsPartOfImmutablePrefab(savePrefab))
  325. {
  326. return false;
  327. }
  328. string savePrefabPath = AssetDatabase.GetAssetPath(savePrefab);
  329. //Debug.Log($"PrefabPath:{savePrefabPath}");
  330. if (string.IsNullOrEmpty(savePrefabPath))
  331. return false;
  332. // 不要な共有データを削除するためのリスト
  333. List<ShareDataObject> removeDatas = new List<ShareDataObject>();
  334. // 現在アセットとして保存されているすべてのShareDataObjectサブアセットを削除対象としてリスト化する
  335. List<Object> subassets = new List<Object>(AssetDatabase.LoadAllAssetRepresentationsAtPath(savePrefabPath));
  336. if (subassets != null)
  337. {
  338. foreach (var obj in subassets)
  339. {
  340. // ShareDataObjectのみ
  341. ShareDataObject sdata = obj as ShareDataObject;
  342. if (sdata && removeDatas.Contains(sdata) == false)
  343. {
  344. //Debug.Log("remove reserve sub asset:" + obj.name + " type:" + obj + " test:" + AssetDatabase.IsSubAsset(sdata));
  345. // 削除対象として一旦追加
  346. removeDatas.Add(sdata);
  347. }
  348. }
  349. }
  350. // データコンポーネント収集
  351. var coreList = savePrefab.GetComponentsInChildren<CoreComponent>(true);
  352. if (coreList != null)
  353. {
  354. foreach (var core in coreList)
  355. {
  356. // 共有データ収集
  357. var shareDataInterfaces = core.GetComponentsInChildren<IShareDataObject>(true);
  358. if (shareDataInterfaces != null)
  359. {
  360. foreach (var sdataInterface in shareDataInterfaces)
  361. {
  362. List<ShareDataObject> shareDatas = sdataInterface.GetAllShareDataObject();
  363. if (shareDatas != null)
  364. {
  365. foreach (var sdata in shareDatas)
  366. {
  367. if (sdata)
  368. {
  369. //Debug.Log($"target shareData:{sdata.name}");
  370. if (removeDatas.Contains(sdata))
  371. {
  372. //Debug.Log($"Ignore:{sdata.name}");
  373. removeDatas.Remove(sdata);
  374. }
  375. }
  376. }
  377. }
  378. }
  379. }
  380. }
  381. }
  382. // 不要な共有データは削除する
  383. if (removeDatas.Count > 0)
  384. {
  385. foreach (var sdata in removeDatas)
  386. {
  387. //Debug.Log($"-Remove sub asset:{sdata.name} path:{AssetDatabase.GetAssetPath(sdata)}");
  388. if (log)
  389. Debug.Log($"Remove sub-asset : {sdata.name}");
  390. UnityEngine.Object.DestroyImmediate(sdata, true);
  391. }
  392. AssetDatabase.SaveAssets();
  393. }
  394. if (log)
  395. Debug.Log($"Remove Count : {removeDatas.Count}");
  396. return true;
  397. }
  398. }
  399. }