mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-01-31 02:32:53 +08:00
fix: improved Scale Adjuster implementation (#730)
This new implementation avoids creating any internal objects within the avatar itself, instead creating them as hidden scene root objects. We also move all update processing to be driven by camera callbacks as well. These changes help improve compatibility with tools such as FaceEmo, as well as being less likely to break when other tools manipulate the avatar's hierarchy directly.
This commit is contained in:
parent
e39a77855a
commit
f077d24d48
@ -18,8 +18,9 @@ namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
|
||||
var m_orig = AccessTools.Method(t_HandleUtility, "FilterInstanceIDs");
|
||||
|
||||
var m_prefix = AccessTools.Method(typeof(HandleUtilityPatches), "Prefix_FilterInstanceIDs");
|
||||
var m_postfix = AccessTools.Method(typeof(HandleUtilityPatches), "Postfix_FilterInstanceIDs");
|
||||
|
||||
h.Patch(original: m_orig, prefix: new HarmonyMethod(m_prefix));
|
||||
h.Patch(original: m_orig, prefix: new HarmonyMethod(m_prefix), postfix: new HarmonyMethod(m_postfix));
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
@ -34,13 +35,41 @@ namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void Postfix_FilterInstanceIDs(
|
||||
ref IEnumerable<GameObject> gameObjects,
|
||||
ref int[] parentInstanceIDs,
|
||||
ref int[] childInstanceIDs
|
||||
)
|
||||
{
|
||||
HashSet<int> newChildInstanceIDs = null;
|
||||
|
||||
foreach (var parent in gameObjects)
|
||||
{
|
||||
foreach (var renderer in parent.GetComponentsInChildren<Renderer>())
|
||||
{
|
||||
if (renderer is SkinnedMeshRenderer smr &&
|
||||
ProxyManager.OriginalToProxyRenderer.TryGetValue(smr, out var proxy) &&
|
||||
proxy != null)
|
||||
{
|
||||
if (newChildInstanceIDs == null) newChildInstanceIDs = new HashSet<int>(childInstanceIDs);
|
||||
newChildInstanceIDs.Add(proxy.GetInstanceID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newChildInstanceIDs != null)
|
||||
{
|
||||
childInstanceIDs = newChildInstanceIDs.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<GameObject> RemapObjects(IEnumerable<GameObject> objs)
|
||||
{
|
||||
return objs.Select(
|
||||
obj =>
|
||||
{
|
||||
if (obj == null) return obj;
|
||||
if (ScaleAdjusterRenderer.originalObjects.TryGetValue(obj, out var proxy) && proxy != null)
|
||||
if (ProxyManager.OriginalToProxyObject.TryGetValue(obj, out var proxy) && proxy != null)
|
||||
{
|
||||
return proxy.gameObject;
|
||||
}
|
||||
|
@ -1,56 +0,0 @@
|
||||
#region
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
|
||||
{
|
||||
/// <summary>
|
||||
/// Try to prevent various internal objects from showing up in the Prefab Overrides window...
|
||||
/// </summary>
|
||||
internal class HideScaleAdjusterFromPrefabOverrideView
|
||||
{
|
||||
internal static Type t_PrefabOverrides;
|
||||
internal static PropertyInfo p_AddedGameObjects, p_ObjectOverrides;
|
||||
|
||||
internal static void Patch(Harmony harmony)
|
||||
{
|
||||
var t_PrefabOverridesTreeView = AccessTools.TypeByName("UnityEditor.PrefabOverridesTreeView");
|
||||
var m_GetPrefabOverrides = AccessTools.Method(t_PrefabOverridesTreeView, "GetPrefabOverrides");
|
||||
|
||||
var m_postfix = AccessTools.Method(typeof(HideScaleAdjusterFromPrefabOverrideView), "Postfix");
|
||||
|
||||
t_PrefabOverrides = AccessTools.TypeByName("UnityEditor.PrefabOverridesTreeView+PrefabOverrides");
|
||||
p_AddedGameObjects = AccessTools.Property(t_PrefabOverrides, "addedGameObjects");
|
||||
p_ObjectOverrides = AccessTools.Property(t_PrefabOverrides, "objectOverrides");
|
||||
|
||||
harmony.Patch(original: m_GetPrefabOverrides, postfix: new HarmonyMethod(m_postfix));
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
private static void Postfix(GameObject prefabInstance, object __result)
|
||||
{
|
||||
List<AddedGameObject> added = p_AddedGameObjects.GetValue(__result) as List<AddedGameObject>;
|
||||
|
||||
if (added == null) return;
|
||||
added.RemoveAll(obj => ScaleAdjusterRenderer.proxyObjects.ContainsKey(obj.instanceGameObject));
|
||||
|
||||
List<ObjectOverride> objectOverrides = p_ObjectOverrides.GetValue(__result) as List<ObjectOverride>;
|
||||
if (objectOverrides == null) return;
|
||||
objectOverrides.RemoveAll(oo =>
|
||||
{
|
||||
var c = oo.instanceObject as Component;
|
||||
return c != null && ScaleAdjusterRenderer.proxyObjects.ContainsKey(c.gameObject);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0893522c012a46358e5ecf1df6628b2e
|
||||
timeCreated: 1708237029
|
@ -143,7 +143,7 @@ namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
|
||||
var pptrValue = p_pptrValue.GetValue(hierarchyProperty);
|
||||
if (pptrValue == null) return false;
|
||||
|
||||
var skip = ScaleAdjusterRenderer.proxyObjects.ContainsKey((GameObject)pptrValue);
|
||||
var skip = ProxyManager.ProxyToOriginalObject.ContainsKey((GameObject)pptrValue);
|
||||
if (skip) skipped++;
|
||||
|
||||
return skip;
|
||||
@ -163,7 +163,7 @@ namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
|
||||
{
|
||||
if (pptrObject == null || isSceneHeader) return true;
|
||||
|
||||
if (hasChildren && ScaleAdjusterRenderer.originalObjects.ContainsKey((GameObject)pptrObject))
|
||||
if (hasChildren && ProxyManager.ProxyToOriginalObject.ContainsKey((GameObject)pptrObject))
|
||||
{
|
||||
// See if there are any other children...
|
||||
hasChildren = ((GameObject)pptrObject).transform.childCount > 1;
|
||||
|
@ -13,7 +13,6 @@ namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
|
||||
{
|
||||
private static readonly Action<Harmony>[] patches = new Action<Harmony>[]
|
||||
{
|
||||
HideScaleAdjusterFromPrefabOverrideView.Patch,
|
||||
HierarchyViewPatches.Patch,
|
||||
#if UNITY_2022_3_OR_NEWER
|
||||
HandleUtilityPatches.Patch_FilterInstanceIDs,
|
||||
|
@ -58,11 +58,11 @@ namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
|
||||
if ((bool)m_TryGetGameObject.Invoke(obj, args))
|
||||
{
|
||||
var go = args[0] as GameObject;
|
||||
if (go != null && ScaleAdjusterRenderer.proxyObjects.ContainsKey(go))
|
||||
if (go != null && ProxyManager.ProxyToOriginalObject.TryGetValue(go, out var original))
|
||||
{
|
||||
list.Add(ctor_PickingObject.Invoke(new[]
|
||||
{
|
||||
go.transform.parent.gameObject,
|
||||
original,
|
||||
p_materialIndex.GetValue(obj)
|
||||
}));
|
||||
continue;
|
||||
|
@ -1,9 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
#region
|
||||
|
||||
using System.Collections.Generic;
|
||||
using nadena.dev.ndmf;
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEngine;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
internal class ScaleAdjusterPass : Pass<ScaleAdjusterPass>
|
||||
@ -11,25 +13,30 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
protected override void Execute(ndmf.BuildContext context)
|
||||
{
|
||||
Dictionary<Transform, Transform> boneMappings = new Dictionary<Transform, Transform>();
|
||||
foreach (var component in context.AvatarRootObject.GetComponentsInChildren<ScaleProxy>())
|
||||
{
|
||||
var proxyTransform = component.transform;
|
||||
var parentAdjuster = component.transform.parent?.GetComponent<ModularAvatarScaleAdjuster>();
|
||||
if (parentAdjuster != null)
|
||||
{
|
||||
UnityEngine.Object.DestroyImmediate(component);
|
||||
|
||||
proxyTransform.localScale = parentAdjuster.Scale;
|
||||
parentAdjuster.scaleProxy = null; // prevent destruction of the ScaleProxy itself
|
||||
UnityEngine.Object.DestroyImmediate(parentAdjuster);
|
||||
foreach (var adjuster in context.AvatarRootObject.GetComponentsInChildren<ModularAvatarScaleAdjuster>(true))
|
||||
{
|
||||
var proxyObject = new GameObject("ScaleProxy");
|
||||
var proxyTransform = proxyObject.transform;
|
||||
|
||||
boneMappings.Add(proxyTransform.parent, proxyTransform);
|
||||
}
|
||||
proxyTransform.SetParent(adjuster.transform, false);
|
||||
proxyTransform.localPosition = Vector3.zero;
|
||||
proxyTransform.localRotation = Quaternion.identity;
|
||||
proxyTransform.localScale = adjuster.Scale;
|
||||
|
||||
boneMappings.Add(adjuster.transform, proxyTransform);
|
||||
|
||||
Object.DestroyImmediate(adjuster);
|
||||
}
|
||||
|
||||
// Legacy cleanup
|
||||
foreach (var sar in context.AvatarRootObject.GetComponentsInChildren<ScaleAdjusterRenderer>())
|
||||
{
|
||||
UnityEngine.Object.DestroyImmediate(sar.gameObject);
|
||||
Object.DestroyImmediate(sar.gameObject);
|
||||
}
|
||||
foreach (var sar in context.AvatarRootObject.GetComponentsInChildren<ScaleProxy>())
|
||||
{
|
||||
Object.DestroyImmediate(sar.gameObject);
|
||||
}
|
||||
|
||||
if (boneMappings.Count == 0)
|
||||
|
@ -1,86 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using nadena.dev.modular_avatar.JacksonDunstan.NativeCollections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
internal static class CameraHooks
|
||||
{
|
||||
private static Dictionary<SkinnedMeshRenderer, SkinnedMeshRenderer> originalToProxy
|
||||
= new Dictionary<SkinnedMeshRenderer, SkinnedMeshRenderer>(
|
||||
new ObjectIdentityComparer<SkinnedMeshRenderer>()
|
||||
);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[UnityEditor.InitializeOnLoadMethod]
|
||||
private static void Init()
|
||||
{
|
||||
Camera.onPreCull += OnPreCull;
|
||||
Camera.onPostRender += OnPostRender;
|
||||
UnityEditor.AssemblyReloadEvents.beforeAssemblyReload += ClearStates;
|
||||
UnityEditor.SceneManagement.EditorSceneManager.sceneSaving += (scene, path) => ClearStates();
|
||||
}
|
||||
#endif
|
||||
|
||||
internal static void RegisterProxy(SkinnedMeshRenderer original, SkinnedMeshRenderer proxy)
|
||||
{
|
||||
originalToProxy[original] = proxy;
|
||||
}
|
||||
|
||||
internal static void UnregisterProxy(SkinnedMeshRenderer original)
|
||||
{
|
||||
originalToProxy.Remove(original);
|
||||
}
|
||||
|
||||
private static List<(SkinnedMeshRenderer, bool)> statesToRestore = new List<(SkinnedMeshRenderer, bool)>();
|
||||
|
||||
private static List<SkinnedMeshRenderer> toDeregister = new List<SkinnedMeshRenderer>();
|
||||
|
||||
|
||||
private static void OnPreCull(Camera camera)
|
||||
{
|
||||
ClearStates();
|
||||
toDeregister.Clear();
|
||||
|
||||
foreach (var kvp in originalToProxy)
|
||||
{
|
||||
var original = kvp.Key;
|
||||
var proxy = kvp.Value;
|
||||
|
||||
if (original == null || proxy == null)
|
||||
{
|
||||
toDeregister.Add(original);
|
||||
continue;
|
||||
}
|
||||
|
||||
proxy.enabled = original.enabled;
|
||||
if (original.enabled && original.gameObject.activeInHierarchy)
|
||||
{
|
||||
statesToRestore.Add((original, original.enabled));
|
||||
original.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var original in toDeregister)
|
||||
{
|
||||
originalToProxy.Remove(original);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnPostRender(Camera camera)
|
||||
{
|
||||
ClearStates();
|
||||
}
|
||||
|
||||
|
||||
private static void ClearStates()
|
||||
{
|
||||
foreach (var (original, state) in statesToRestore)
|
||||
{
|
||||
original.enabled = state;
|
||||
}
|
||||
|
||||
statesToRestore.Clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
#region
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.Serialization;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
@ -25,105 +27,100 @@ namespace nadena.dev.modular_avatar.core
|
||||
set
|
||||
{
|
||||
m_Scale = value;
|
||||
Update();
|
||||
PreCull();
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] [FormerlySerializedAs("scaleProxy")]
|
||||
internal Transform legacyScaleProxy;
|
||||
|
||||
internal Transform scaleProxy;
|
||||
internal Transform scaleProxyParent, scaleProxyChild;
|
||||
|
||||
[NonSerialized]
|
||||
private bool initialized = false;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void OnValidate()
|
||||
void Awake()
|
||||
{
|
||||
base.OnValidate();
|
||||
ProxyManager.RegisterAdjuster(this);
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
void OnValidate()
|
||||
{
|
||||
if (scaleProxy == null || initialized == false)
|
||||
ProxyManager.RegisterAdjuster(this);
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
internal void PreCull()
|
||||
{
|
||||
if (PrefabUtility.IsPartOfPrefabAsset(this)) return;
|
||||
|
||||
if (scaleProxyParent == null || initialized == false)
|
||||
{
|
||||
InitializeProxy();
|
||||
}
|
||||
|
||||
var xform = transform;
|
||||
scaleProxyParent.position = transform.position;
|
||||
scaleProxyParent.rotation = transform.rotation;
|
||||
scaleProxyParent.localScale = transform.localScale;
|
||||
scaleProxyChild.localScale = m_Scale;
|
||||
|
||||
ProxyManager.RegisterBone(xform, scaleProxyChild);
|
||||
|
||||
if (legacyScaleProxy != null && !PrefabUtility.IsPartOfPrefabAsset(legacyScaleProxy))
|
||||
{
|
||||
DestroyImmediate(legacyScaleProxy.gameObject);
|
||||
legacyScaleProxy = null;
|
||||
}
|
||||
|
||||
scaleProxy.localScale = m_Scale;
|
||||
}
|
||||
|
||||
private void InitializeProxy()
|
||||
{
|
||||
if (scaleProxy == null)
|
||||
if (scaleProxyParent == null)
|
||||
{
|
||||
scaleProxy = new GameObject(gameObject.name + " (Scale Proxy)").transform;
|
||||
scaleProxy.SetParent(transform, false);
|
||||
scaleProxy.localPosition = Vector3.zero;
|
||||
scaleProxy.localRotation = Quaternion.identity;
|
||||
scaleProxy.localScale = m_Scale;
|
||||
scaleProxy.gameObject.AddComponent<ScaleProxy>();
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(this);
|
||||
}
|
||||
scaleProxyParent = new GameObject(gameObject.name + " (Scale Proxy)").transform;
|
||||
scaleProxyChild = new GameObject("Child").transform;
|
||||
|
||||
ConfigureRenderers();
|
||||
scaleProxyChild.transform.SetParent(scaleProxyParent, false);
|
||||
|
||||
#if MODULAR_AVATAR_DEBUG_HIDDEN
|
||||
scaleProxyParent.gameObject.hideFlags = HideFlags.DontSave;
|
||||
scaleProxyChild.gameObject.hideFlags = HideFlags.DontSave;
|
||||
#else
|
||||
scaleProxyParent.gameObject.hideFlags = HideFlags.HideAndDontSave;
|
||||
scaleProxyChild.gameObject.hideFlags = HideFlags.HideAndDontSave;
|
||||
#endif
|
||||
|
||||
if (scaleProxyParent.gameObject.scene != gameObject.scene && gameObject.scene.IsValid())
|
||||
{
|
||||
SceneManager.MoveGameObjectToScene(scaleProxyParent.gameObject, gameObject.scene);
|
||||
}
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (scaleProxy != null)
|
||||
ProxyManager.UnregisterAdjuster(this);
|
||||
|
||||
if (scaleProxyParent != null)
|
||||
{
|
||||
DestroyImmediate(scaleProxy.gameObject);
|
||||
DestroyImmediate(scaleProxyParent.gameObject);
|
||||
}
|
||||
|
||||
if (transform != null)
|
||||
{
|
||||
ProxyManager.UnregisterBone(transform);
|
||||
}
|
||||
|
||||
ScaleAdjusterRenderer.InvalidateAll();
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
|
||||
private void ConfigureRenderers()
|
||||
{
|
||||
var avatar = RuntimeUtil.FindAvatarInParents(transform);
|
||||
if (avatar == null) return;
|
||||
foreach (var smr in avatar.GetComponentsInChildren<SkinnedMeshRenderer>(true))
|
||||
{
|
||||
if (smr.GetComponent<ScaleAdjusterRenderer>() != null) continue;
|
||||
|
||||
var child = smr.transform.Find(ADJUSTER_OBJECT)?.GetComponent<ScaleAdjusterRenderer>();
|
||||
if (child == null)
|
||||
{
|
||||
var childObj = new GameObject(ADJUSTER_OBJECT);
|
||||
Undo.RegisterCreatedObjectUndo(childObj, "");
|
||||
|
||||
var childSmr = childObj.AddComponent<SkinnedMeshRenderer>();
|
||||
EditorUtility.CopySerialized(smr, childSmr);
|
||||
|
||||
childObj.transform.SetParent(smr.transform, false);
|
||||
childObj.transform.localPosition = Vector3.zero;
|
||||
childObj.transform.localRotation = Quaternion.identity;
|
||||
childObj.transform.localScale = Vector3.one;
|
||||
|
||||
child = childObj.AddComponent<ScaleAdjusterRenderer>();
|
||||
}
|
||||
|
||||
child.BoneMappings[transform] = scaleProxy;
|
||||
child.ClearBoneCache();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !UNITY_EDITOR
|
||||
private void Update()
|
||||
{
|
||||
// placeholder to make builds work
|
||||
#else
|
||||
internal void PreCull() {
|
||||
// build time stub
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
338
Runtime/ScaleAdjuster/ProxyManager.cs
Normal file
338
Runtime/ScaleAdjuster/ProxyManager.cs
Normal file
@ -0,0 +1,338 @@
|
||||
#region
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using nadena.dev.modular_avatar.JacksonDunstan.NativeCollections;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
#endif
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
internal static class ProxyManager
|
||||
{
|
||||
#region Accessible from multiple threads
|
||||
|
||||
private static bool _dirty = false;
|
||||
private static readonly object _lock = new object();
|
||||
|
||||
private static ImmutableHashSet<ModularAvatarScaleAdjuster> _adjusters
|
||||
= ImmutableHashSet<ModularAvatarScaleAdjuster>.Empty;
|
||||
|
||||
private static ImmutableDictionary<Transform, Transform> _originalToReplacementBone
|
||||
= ImmutableDictionary<Transform, Transform>.Empty.WithComparers(new ObjectIdentityComparer<Transform>());
|
||||
|
||||
internal static void RegisterAdjuster(ModularAvatarScaleAdjuster adjuster)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_adjusters = _adjusters.Add(adjuster);
|
||||
_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void UnregisterAdjuster(ModularAvatarScaleAdjuster adjuster)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_adjusters = _adjusters.Remove(adjuster);
|
||||
_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RegisterBone(Transform original, Transform replacement)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_originalToReplacementBone.TryGetValue(original, out var val) && val == replacement)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_originalToReplacementBone = _originalToReplacementBone.Add(original, replacement);
|
||||
_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void UnregisterBone(Transform original)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_originalToReplacementBone = _originalToReplacementBone.Remove(original);
|
||||
_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static ImmutableHashSet<ModularAvatarScaleAdjuster> _capturedAdjusters =
|
||||
ImmutableHashSet<ModularAvatarScaleAdjuster>.Empty;
|
||||
|
||||
private static ImmutableDictionary<Transform, Transform> _capturedBones =
|
||||
ImmutableDictionary<Transform, Transform>.Empty;
|
||||
|
||||
private static ImmutableDictionary<SkinnedMeshRenderer, SkinnedMeshRenderer> _originalToReplacementRenderer
|
||||
= ImmutableDictionary<SkinnedMeshRenderer, SkinnedMeshRenderer>.Empty.WithComparers(
|
||||
new ObjectIdentityComparer<SkinnedMeshRenderer>());
|
||||
|
||||
internal static ImmutableDictionary<GameObject, GameObject> ProxyToOriginalObject { get; private set; } =
|
||||
ImmutableDictionary<GameObject, GameObject>.Empty;
|
||||
|
||||
internal static ImmutableDictionary<GameObject, GameObject> OriginalToProxyObject { get; private set; } =
|
||||
ImmutableDictionary<GameObject, GameObject>.Empty;
|
||||
|
||||
internal static ImmutableDictionary<SkinnedMeshRenderer, SkinnedMeshRenderer> OriginalToProxyRenderer =>
|
||||
_originalToReplacementRenderer;
|
||||
|
||||
internal static ImmutableHashSet<GameObject> RetainedObjects;
|
||||
|
||||
internal static bool ShouldRetain(GameObject obj) => RetainedObjects.Contains(obj);
|
||||
|
||||
private static void BuildRenderers()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_capturedAdjusters = _adjusters;
|
||||
|
||||
// Give each adjuster a chance to initialize the bone mappings first
|
||||
foreach (var adj in _capturedAdjusters)
|
||||
{
|
||||
adj.PreCull();
|
||||
}
|
||||
|
||||
foreach (var kvp in _originalToReplacementBone)
|
||||
{
|
||||
if (kvp.Key == null || kvp.Value == null)
|
||||
{
|
||||
_originalToReplacementBone = _originalToReplacementBone.Remove(kvp.Key);
|
||||
}
|
||||
}
|
||||
|
||||
_capturedBones = _originalToReplacementBone;
|
||||
_dirty = false;
|
||||
}
|
||||
|
||||
var avatarRoots = _capturedBones.Keys.Select(RuntimeUtil.FindAvatarTransformInParents).ToImmutableHashSet();
|
||||
var potentialRenderers = avatarRoots.SelectMany(r => r.GetComponentsInChildren<SkinnedMeshRenderer>(true))
|
||||
.ToList();
|
||||
|
||||
ImmutableDictionary<SkinnedMeshRenderer, SkinnedMeshRenderer>.Builder renderers =
|
||||
ImmutableDictionary.CreateBuilder<SkinnedMeshRenderer, SkinnedMeshRenderer>(
|
||||
new ObjectIdentityComparer<SkinnedMeshRenderer>()
|
||||
);
|
||||
|
||||
foreach (var originalRenderer in potentialRenderers)
|
||||
{
|
||||
SkinnedMeshRenderer replacement;
|
||||
|
||||
if (!NeedsReplacement(originalRenderer))
|
||||
{
|
||||
if (_originalToReplacementRenderer.TryGetValue(originalRenderer, out replacement) &&
|
||||
replacement != null)
|
||||
{
|
||||
Object.DestroyImmediate(replacement.gameObject);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_originalToReplacementRenderer.TryGetValue(originalRenderer, out replacement) ||
|
||||
replacement == null)
|
||||
{
|
||||
replacement = CreateReplacement(originalRenderer);
|
||||
}
|
||||
|
||||
SetupBoneMappings(originalRenderer, replacement);
|
||||
|
||||
renderers.Add(originalRenderer, replacement);
|
||||
}
|
||||
|
||||
foreach (var kvp in _originalToReplacementRenderer)
|
||||
{
|
||||
if (!renderers.ContainsKey(kvp.Key))
|
||||
{
|
||||
if (kvp.Value != null)
|
||||
{
|
||||
Object.DestroyImmediate(kvp.Value.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_originalToReplacementRenderer = renderers.ToImmutable();
|
||||
ProxyToOriginalObject = _originalToReplacementRenderer.ToImmutableDictionary(
|
||||
kvp => kvp.Value.gameObject,
|
||||
kvp => kvp.Key.gameObject
|
||||
);
|
||||
|
||||
OriginalToProxyObject = _originalToReplacementRenderer.ToImmutableDictionary(
|
||||
kvp => kvp.Key.gameObject,
|
||||
kvp => kvp.Value.gameObject
|
||||
);
|
||||
|
||||
RetainedObjects = ProxyToOriginalObject.Keys.Concat(
|
||||
_capturedBones.Values.Where(b => b != null).Select(b => b.gameObject)
|
||||
).ToImmutableHashSet(new ObjectIdentityComparer<GameObject>());
|
||||
}
|
||||
|
||||
private static void SetupBoneMappings(SkinnedMeshRenderer originalRenderer, SkinnedMeshRenderer replacement)
|
||||
{
|
||||
replacement.sharedMesh = originalRenderer.sharedMesh;
|
||||
replacement.bones = originalRenderer.bones.Select(MapBone).ToArray();
|
||||
}
|
||||
|
||||
private static Transform MapBone(Transform srcBone)
|
||||
{
|
||||
if (_capturedBones.TryGetValue(srcBone, out var newBone) && newBone != null)
|
||||
{
|
||||
return newBone;
|
||||
}
|
||||
else
|
||||
{
|
||||
return srcBone;
|
||||
}
|
||||
}
|
||||
|
||||
private static SkinnedMeshRenderer CreateReplacement(SkinnedMeshRenderer originalRenderer)
|
||||
{
|
||||
var obj = new GameObject("MA Proxy Renderer for " + originalRenderer.gameObject.name);
|
||||
// We can't use HideAndDontSave as this would break scene view click-to-pick handling
|
||||
// (so instead this is hidden via the HierarchyViewPatches harmony hack)
|
||||
obj.hideFlags = HideFlags.DontSave;
|
||||
|
||||
var renderer = obj.AddComponent<SkinnedMeshRenderer>();
|
||||
|
||||
return renderer;
|
||||
}
|
||||
|
||||
private static bool NeedsReplacement(SkinnedMeshRenderer originalRenderer)
|
||||
{
|
||||
if (originalRenderer.sharedMesh == null) return false;
|
||||
|
||||
var bones = originalRenderer.bones;
|
||||
var weights = originalRenderer.sharedMesh.GetAllBoneWeights();
|
||||
|
||||
for (var i = 0; i < weights.Length; i++)
|
||||
{
|
||||
var bone = bones[weights[i].boneIndex];
|
||||
if (_capturedBones.ContainsKey(bone)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[InitializeOnLoadMethod]
|
||||
private static void Init()
|
||||
{
|
||||
Camera.onPreCull += OnPreCull;
|
||||
Camera.onPostRender += OnPostRender;
|
||||
AssemblyReloadEvents.beforeAssemblyReload += () =>
|
||||
{
|
||||
ClearStates();
|
||||
foreach (var renderer in _originalToReplacementRenderer.Values)
|
||||
{
|
||||
Object.DestroyImmediate(renderer.gameObject);
|
||||
}
|
||||
};
|
||||
EditorSceneManager.sceneSaving += (scene, path) => ClearStates();
|
||||
}
|
||||
|
||||
private static List<(SkinnedMeshRenderer, bool)> statesToRestore = new List<(SkinnedMeshRenderer, bool)>();
|
||||
|
||||
private static void OnPreCull(Camera camera)
|
||||
{
|
||||
if (_dirty)
|
||||
{
|
||||
BuildRenderers();
|
||||
}
|
||||
|
||||
ClearStates();
|
||||
|
||||
foreach (var adj in _capturedAdjusters)
|
||||
{
|
||||
adj.PreCull(); // update scale
|
||||
}
|
||||
|
||||
foreach (var kvp in _originalToReplacementRenderer)
|
||||
{
|
||||
var original = kvp.Key;
|
||||
var proxy = kvp.Value;
|
||||
|
||||
if (original == null || proxy == null)
|
||||
{
|
||||
_dirty = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
var originalGameObject = original.gameObject;
|
||||
var proxyActive = original.enabled && originalGameObject.activeInHierarchy &&
|
||||
!SceneVisibilityManager.instance.IsHidden(originalGameObject, false);
|
||||
|
||||
proxy.enabled = proxyActive;
|
||||
if (original.enabled && originalGameObject.activeInHierarchy)
|
||||
{
|
||||
CopyRendererStates(original, proxy);
|
||||
|
||||
statesToRestore.Add((original, original.enabled));
|
||||
original.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void CopyRendererStates(SkinnedMeshRenderer parentRenderer, SkinnedMeshRenderer myRenderer)
|
||||
{
|
||||
myRenderer.transform.position = parentRenderer.transform.position;
|
||||
myRenderer.transform.rotation = parentRenderer.transform.rotation;
|
||||
|
||||
myRenderer.sharedMaterials = parentRenderer.sharedMaterials;
|
||||
myRenderer.localBounds = parentRenderer.localBounds;
|
||||
myRenderer.rootBone = MapBone(parentRenderer.rootBone);
|
||||
myRenderer.quality = parentRenderer.quality;
|
||||
myRenderer.shadowCastingMode = parentRenderer.shadowCastingMode;
|
||||
myRenderer.receiveShadows = parentRenderer.receiveShadows;
|
||||
myRenderer.lightProbeUsage = parentRenderer.lightProbeUsage;
|
||||
myRenderer.reflectionProbeUsage = parentRenderer.reflectionProbeUsage;
|
||||
myRenderer.probeAnchor = parentRenderer.probeAnchor;
|
||||
myRenderer.motionVectorGenerationMode = parentRenderer.motionVectorGenerationMode;
|
||||
myRenderer.allowOcclusionWhenDynamic = parentRenderer.allowOcclusionWhenDynamic;
|
||||
|
||||
if (myRenderer.gameObject.scene != parentRenderer.gameObject.scene &&
|
||||
parentRenderer.gameObject.scene.IsValid())
|
||||
{
|
||||
SceneManager.MoveGameObjectToScene(myRenderer.gameObject, parentRenderer.gameObject.scene);
|
||||
}
|
||||
|
||||
if (myRenderer.sharedMesh != null)
|
||||
{
|
||||
var blendShapeCount = myRenderer.sharedMesh.blendShapeCount;
|
||||
|
||||
for (int i = 0; i < blendShapeCount; i++)
|
||||
{
|
||||
myRenderer.SetBlendShapeWeight(i, parentRenderer.GetBlendShapeWeight(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnPostRender(Camera camera)
|
||||
{
|
||||
ClearStates();
|
||||
}
|
||||
|
||||
private static void ClearStates()
|
||||
{
|
||||
foreach (var (original, state) in statesToRestore)
|
||||
{
|
||||
original.enabled = state;
|
||||
}
|
||||
|
||||
statesToRestore.Clear();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
@ -1,300 +1,33 @@
|
||||
#region
|
||||
|
||||
using UnityEngine;
|
||||
using VRC.SDKBase;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using nadena.dev.modular_avatar.JacksonDunstan.NativeCollections;
|
||||
using UnityEngine;
|
||||
using VRC.SDKBase;
|
||||
using Object = System.Object;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
/// <summary>
|
||||
/// Legacy component from early 1.9.x builds.
|
||||
/// </summary>
|
||||
[ExecuteInEditMode]
|
||||
[AddComponentMenu("")]
|
||||
[RequireComponent(typeof(SkinnedMeshRenderer))]
|
||||
internal class ScaleAdjusterRenderer : MonoBehaviour, IEditorOnly
|
||||
{
|
||||
internal static Dictionary<ScaleAdjusterRenderer, GameObject> originalParent =
|
||||
new Dictionary<ScaleAdjusterRenderer, GameObject>(new ObjectIdentityComparer<ScaleAdjusterRenderer>());
|
||||
|
||||
internal static Dictionary<GameObject, GameObject> proxyObjects = new Dictionary<GameObject, GameObject>(
|
||||
new ObjectIdentityComparer<GameObject>());
|
||||
|
||||
internal static Dictionary<GameObject, ScaleAdjusterRenderer> originalObjects =
|
||||
new Dictionary<GameObject, ScaleAdjusterRenderer>(
|
||||
new ObjectIdentityComparer<GameObject>()
|
||||
);
|
||||
|
||||
private static int RecreateHierarchyIndexCount = 0;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[InitializeOnLoadMethod]
|
||||
static void Setup()
|
||||
{
|
||||
EditorApplication.hierarchyChanged += InvalidateAll;
|
||||
}
|
||||
#endif
|
||||
|
||||
internal static void InvalidateAll()
|
||||
{
|
||||
RecreateHierarchyIndexCount++;
|
||||
}
|
||||
|
||||
private SkinnedMeshRenderer myRenderer;
|
||||
private SkinnedMeshRenderer parentRenderer;
|
||||
|
||||
private bool wasActive = false;
|
||||
private bool redoBoneMappings = true;
|
||||
private bool hasRelevantBones = false;
|
||||
private int lastRecreateHierarchyIndex = -1;
|
||||
|
||||
internal Dictionary<Transform, Transform> BoneMappings = new Dictionary<Transform, Transform>(
|
||||
new ObjectIdentityComparer<Transform>()
|
||||
);
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnValidate()
|
||||
{
|
||||
if (PrefabUtility.IsPartOfPrefabAsset(this)) return;
|
||||
redoBoneMappings = true;
|
||||
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
if (this == null) return;
|
||||
|
||||
// We hide this in Harmony, not here, so it is eligible for click-to-select.
|
||||
gameObject.hideFlags = HideFlags.DontSaveInBuild;
|
||||
|
||||
if (BoneMappings == null)
|
||||
{
|
||||
BoneMappings = new Dictionary<Transform, Transform>();
|
||||
}
|
||||
|
||||
Configure();
|
||||
if (this != null) DestroyImmediate(gameObject);
|
||||
};
|
||||
|
||||
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
|
||||
redoBoneMappings = true;
|
||||
}
|
||||
|
||||
private void OnPlayModeStateChanged(PlayModeStateChange change)
|
||||
{
|
||||
if (change == PlayModeStateChange.ExitingEditMode)
|
||||
{
|
||||
ClearHooks();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private Transform MapBone(Transform bone)
|
||||
{
|
||||
if (bone == null) return null;
|
||||
if (BoneMappings.TryGetValue(bone, out var newBone) && newBone != null) return newBone;
|
||||
return bone;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
ClearHooks();
|
||||
#if UNITY_EDITOR
|
||||
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
|
||||
#endif
|
||||
}
|
||||
|
||||
private void Configure()
|
||||
{
|
||||
if (originalParent.TryGetValue(this, out var prevParent) && transform.parent?.gameObject == prevParent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (prevParent != null)
|
||||
{
|
||||
ClearHooks();
|
||||
}
|
||||
|
||||
if (!hasRelevantBones)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (transform.parent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
AssemblyReloadEvents.beforeAssemblyReload += ClearHooks;
|
||||
#endif
|
||||
|
||||
var parent = transform.parent.gameObject;
|
||||
|
||||
proxyObjects[gameObject] = parent;
|
||||
originalObjects[parent] = this;
|
||||
originalParent[this] = parent;
|
||||
|
||||
CheckBoneUsage(); // register the proxy if needed
|
||||
}
|
||||
|
||||
private void ClearHooks()
|
||||
{
|
||||
if (originalParent.TryGetValue(this, out var prevParent))
|
||||
{
|
||||
if (parentRenderer != null)
|
||||
{
|
||||
CameraHooks.UnregisterProxy(parentRenderer);
|
||||
}
|
||||
|
||||
if ((Object)prevParent != null)
|
||||
{
|
||||
originalObjects.Remove(prevParent);
|
||||
}
|
||||
|
||||
originalParent.Remove(this);
|
||||
if (gameObject != null)
|
||||
{
|
||||
proxyObjects.Remove(gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
CleanDeadObjects();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void Update()
|
||||
{
|
||||
if (EditorApplication.isPlayingOrWillChangePlaymode) return;
|
||||
if (transform.parent == null)
|
||||
{
|
||||
DestroyImmediate(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
if (myRenderer == null)
|
||||
{
|
||||
myRenderer = GetComponent<SkinnedMeshRenderer>();
|
||||
}
|
||||
|
||||
if (parentRenderer == null)
|
||||
{
|
||||
parentRenderer = transform.parent.GetComponent<SkinnedMeshRenderer>();
|
||||
}
|
||||
|
||||
Configure();
|
||||
|
||||
myRenderer.sharedMaterials = parentRenderer.sharedMaterials;
|
||||
myRenderer.localBounds = parentRenderer.localBounds;
|
||||
if (redoBoneMappings || lastRecreateHierarchyIndex != RecreateHierarchyIndexCount
|
||||
|| myRenderer.sharedMesh != parentRenderer.sharedMesh)
|
||||
{
|
||||
CleanDeadObjects(BoneMappings);
|
||||
|
||||
if (BoneMappings.Count == 0)
|
||||
{
|
||||
#if UNITY_2022_3_OR_NEWER
|
||||
DestroyImmediate(gameObject);
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
myRenderer.sharedMesh = parentRenderer.sharedMesh;
|
||||
myRenderer.rootBone = MapBone(parentRenderer.rootBone);
|
||||
myRenderer.bones = parentRenderer.bones.Select(MapBone).ToArray();
|
||||
redoBoneMappings = false;
|
||||
lastRecreateHierarchyIndex = RecreateHierarchyIndexCount;
|
||||
|
||||
CheckBoneUsage();
|
||||
}
|
||||
|
||||
if (!hasRelevantBones) return;
|
||||
|
||||
myRenderer.quality = parentRenderer.quality;
|
||||
myRenderer.shadowCastingMode = parentRenderer.shadowCastingMode;
|
||||
myRenderer.receiveShadows = parentRenderer.receiveShadows;
|
||||
myRenderer.lightProbeUsage = parentRenderer.lightProbeUsage;
|
||||
myRenderer.reflectionProbeUsage = parentRenderer.reflectionProbeUsage;
|
||||
myRenderer.probeAnchor = parentRenderer.probeAnchor;
|
||||
myRenderer.motionVectorGenerationMode = parentRenderer.motionVectorGenerationMode;
|
||||
myRenderer.allowOcclusionWhenDynamic = parentRenderer.allowOcclusionWhenDynamic;
|
||||
|
||||
if (myRenderer.sharedMesh != null)
|
||||
{
|
||||
var blendShapeCount = myRenderer.sharedMesh.blendShapeCount;
|
||||
|
||||
for (int i = 0; i < blendShapeCount; i++)
|
||||
{
|
||||
myRenderer.SetBlendShapeWeight(i, parentRenderer.GetBlendShapeWeight(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
private void CheckBoneUsage()
|
||||
{
|
||||
hasRelevantBones = false;
|
||||
if (myRenderer.sharedMesh != null)
|
||||
{
|
||||
var weights = myRenderer.sharedMesh.GetAllBoneWeights();
|
||||
var parentBones = parentRenderer.bones;
|
||||
foreach (var weight in weights)
|
||||
{
|
||||
if (weight.weight < 0.0001f) continue;
|
||||
if (weight.boneIndex < 0 || weight.boneIndex >= parentBones.Length) continue;
|
||||
|
||||
var bone = parentBones[weight.boneIndex];
|
||||
if (BoneMappings.ContainsKey(bone))
|
||||
{
|
||||
hasRelevantBones = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasRelevantBones)
|
||||
{
|
||||
CameraHooks.RegisterProxy(parentRenderer, myRenderer);
|
||||
}
|
||||
else
|
||||
{
|
||||
CameraHooks.UnregisterProxy(parentRenderer);
|
||||
myRenderer.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearBoneCache()
|
||||
{
|
||||
redoBoneMappings = true;
|
||||
}
|
||||
|
||||
private static void CleanDeadObjects()
|
||||
{
|
||||
CleanDeadObjects(originalParent);
|
||||
CleanDeadObjects(originalObjects);
|
||||
CleanDeadObjects(proxyObjects);
|
||||
}
|
||||
|
||||
private static int lastCleanedFrame = 0;
|
||||
private static void CleanDeadObjects<K, V>(IDictionary<K, V> dict)
|
||||
where K: UnityEngine.Object
|
||||
where V: UnityEngine.Object
|
||||
{
|
||||
// Avoid any O(n^2) behavior if we have lots of cleanup calls happening at the same instant
|
||||
if (Time.frameCount == lastCleanedFrame) return;
|
||||
lastCleanedFrame = Time.frameCount;
|
||||
|
||||
var dead = dict.Where(kvp => kvp.Key == null || kvp.Value == null).ToList();
|
||||
|
||||
foreach (var kvp in dead)
|
||||
{
|
||||
dict.Remove(kvp.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -16,13 +16,12 @@ namespace nadena.dev.modular_avatar.core
|
||||
#if UNITY_EDITOR
|
||||
void OnValidate()
|
||||
{
|
||||
base.OnDestroy();
|
||||
base.OnValidate();
|
||||
EditorApplication.delayCall += DeferredValidate;
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
ScaleAdjusterRenderer.InvalidateAll();
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
@ -30,46 +29,14 @@ namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
if (this == null) return;
|
||||
|
||||
if (GetComponent<ModularAvatarPBBlocker>() == null)
|
||||
{
|
||||
gameObject.AddComponent<ModularAvatarPBBlocker>();
|
||||
}
|
||||
// Avoid logspam on Unity 2019
|
||||
if (PrefabUtility.IsPartOfPrefabInstance(gameObject)) return;
|
||||
|
||||
var avatar = ndmf.runtime.RuntimeUtil.FindAvatarInParents(transform);
|
||||
ClearOverrides(avatar);
|
||||
|
||||
gameObject.hideFlags = HideFlags.HideInHierarchy;
|
||||
|
||||
#if MODULAR_AVATAR_DEBUG_HIDDEN
|
||||
gameObject.hideFlags = HideFlags.None;
|
||||
#endif
|
||||
hideFlags = HideFlags.None;
|
||||
|
||||
var parentObject = transform.parent;
|
||||
var parentScaleAdjuster =
|
||||
parentObject != null ? parentObject.GetComponent<ModularAvatarScaleAdjuster>() : null;
|
||||
|
||||
if (parentScaleAdjuster == null || parentScaleAdjuster.scaleProxy != transform)
|
||||
{
|
||||
if (PrefabUtility.IsPartOfPrefabAsset(this))
|
||||
{
|
||||
var path = AssetDatabase.GetAssetPath(this);
|
||||
var root = PrefabUtility.LoadPrefabContents(path);
|
||||
|
||||
foreach (var obj in root.GetComponentsInChildren<ScaleProxy>())
|
||||
{
|
||||
obj.DeferredValidate();
|
||||
}
|
||||
|
||||
PrefabUtility.SaveAsPrefabAsset(root, path);
|
||||
PrefabUtility.UnloadPrefabContents(root);
|
||||
}
|
||||
else
|
||||
if (!ProxyManager.ShouldRetain(gameObject))
|
||||
{
|
||||
SelfDestruct();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SelfDestruct()
|
||||
{
|
||||
@ -82,7 +49,6 @@ namespace nadena.dev.modular_avatar.core
|
||||
|
||||
ClearOverrides(root);
|
||||
|
||||
// Avoid logspam on Unity 2019
|
||||
if (PrefabUtility.IsPartOfPrefabInstance(gameObject)) return;
|
||||
|
||||
DestroyImmediate(gameObject);
|
||||
|
Loading…
Reference in New Issue
Block a user