470 lines
15 KiB
C#
470 lines
15 KiB
C#
using Animancer;
|
|
using BrewMonster;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
|
|
namespace BrewMonster.Scripts.UI.Inventory
|
|
{
|
|
/// <summary>
|
|
/// Clones the current host-player visual model into a dedicated preview rig for the inventory UI.
|
|
/// </summary>
|
|
[DisallowMultipleComponent]
|
|
public class InventoryCharacterPreview : MonoBehaviour
|
|
{
|
|
[Header("Player Binding")]
|
|
[SerializeField] private CECHostPlayer hostPlayer;
|
|
[Tooltip("Auto locate the active host player if none is assigned.")]
|
|
[SerializeField] private bool autoBindHostPlayer = true;
|
|
[Tooltip("Optional manual override for the visual root. Leave empty to auto detect the PlayerVisual/host root.")]
|
|
[SerializeField] private Transform sourceModelRoot;
|
|
[Tooltip("Clone the entire CECHostPlayer hierarchy instead of just the PlayerVisual child.")]
|
|
[SerializeField] private bool cloneWholeHostHierarchy = true;
|
|
[Tooltip("Remove gameplay scripts/colliders from the preview clone when duplicating the full hierarchy.")]
|
|
[SerializeField] private bool stripRuntimeComponents = true;
|
|
|
|
[Header("Preview Rig")]
|
|
[SerializeField] private Transform previewRoot;
|
|
[SerializeField] private Vector3 previewLocalPosition = Vector3.zero;
|
|
[SerializeField] private Vector3 previewLocalEuler;
|
|
[SerializeField] private Vector3 previewLocalScale = Vector3.one;
|
|
[SerializeField] private float previewHeightOffset = 0.12f;
|
|
[Tooltip("Copy the preview root layer onto the cloned hierarchy so the preview camera can isolate it.")]
|
|
[SerializeField] private bool inheritPreviewLayer = true;
|
|
|
|
[Header("Camera Output")]
|
|
[SerializeField] private RawImage previewFrame;
|
|
[SerializeField] private RenderTexture previewRenderTexture;
|
|
[SerializeField] private bool autoFocusCamera = true;
|
|
[SerializeField] private Vector3 cameraFocusOffset = new Vector3(0f, 1.5f, 0f);
|
|
|
|
[Header("Camera Framing")]
|
|
[SerializeField] private float previewCameraDistance = 1.4f;
|
|
[SerializeField] private Vector3 previewCameraOffset = new Vector3(0f, 0.5f, 0f);
|
|
[SerializeField] private bool overrideFieldOfView = true;
|
|
[SerializeField][Range(10f, 60f)] private float previewFieldOfView = 38f;
|
|
|
|
private bool _cameraStateCached;
|
|
private Vector3 _cachedCameraPosition;
|
|
private Quaternion _cachedCameraRotation;
|
|
private float _cachedCameraFov;
|
|
|
|
//[Header("Behaviour")]
|
|
//[Tooltip("Disable PlayerVisual/Animancer components on the clone so it stays in a frozen idle pose.")]
|
|
//[SerializeField] private bool freezeAnimation = true;
|
|
|
|
private GameObject _previewInstance;
|
|
private bool _refreshQueued;
|
|
private int _lastOriginModelChildCount;
|
|
private Transform _lastOriginModelRoot;
|
|
private Camera _previewCamera;
|
|
private PlayerModelPreview _playerModelPreview;
|
|
private RenderTexture _cachedTargetTexture;
|
|
|
|
private void Awake()
|
|
{
|
|
|
|
if (previewRoot == null)
|
|
{
|
|
previewRoot = transform;
|
|
}
|
|
|
|
TryBindPreviewSystem();
|
|
TryBindHostPlayer();
|
|
EnsureCameraBindings();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
TryBindPreviewSystem();
|
|
EnsureCameraBindings();
|
|
QueueRefresh();
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
RestoreCameraTargetTexture();
|
|
RestoreSharedCameraState();
|
|
ReleasePreviewReference();
|
|
}
|
|
|
|
private void LateUpdate()
|
|
{
|
|
TryBindPreviewSystem();
|
|
TryBindHostPlayer();
|
|
|
|
// Check if origin model structure has changed (equipment models are child objects)
|
|
// This detects equipment changes in realtime since all equipment is attached as children
|
|
Transform currentOriginRoot = ResolveSourceModelRoot();
|
|
if (currentOriginRoot != null)
|
|
{
|
|
int currentChildCount = CountAllChildren(currentOriginRoot);
|
|
|
|
// Refresh if origin model changed or child count changed (equipment added/removed)
|
|
if (_lastOriginModelRoot != currentOriginRoot || _lastOriginModelChildCount != currentChildCount)
|
|
{
|
|
_lastOriginModelRoot = currentOriginRoot;
|
|
_lastOriginModelChildCount = currentChildCount;
|
|
QueueRefresh();
|
|
}
|
|
}
|
|
|
|
if (_refreshQueued)
|
|
{
|
|
_refreshQueued = false;
|
|
BuildPreviewModel();
|
|
}
|
|
|
|
if(_previewInstance != null)
|
|
{
|
|
EnsureCameraFocus(_previewInstance.transform);
|
|
}
|
|
}
|
|
|
|
/// <summary>Allows manual binding from external UI scripts.</summary>
|
|
public void BindPlayer(CECHostPlayer player)
|
|
{
|
|
if (player == null || player == hostPlayer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
hostPlayer = player;
|
|
QueueRefresh();
|
|
}
|
|
|
|
/// <summary>Forces an immediate rebuild of the preview model.</summary>
|
|
public void ForceRefreshNow()
|
|
{
|
|
_refreshQueued = false;
|
|
BuildPreviewModel();
|
|
}
|
|
|
|
/// <summary>Marks the clone dirty so it gets recreated next LateUpdate.</summary>
|
|
public void QueueRefresh()
|
|
{
|
|
_refreshQueued = true;
|
|
}
|
|
|
|
private void TryBindPreviewSystem()
|
|
{
|
|
if (_playerModelPreview == null)
|
|
{
|
|
_playerModelPreview = PlayerModelPreview.Instance;
|
|
}
|
|
|
|
if (_playerModelPreview == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_previewCamera == null)
|
|
{
|
|
_previewCamera = _playerModelPreview.GetComponent<Camera>();
|
|
}
|
|
}
|
|
|
|
private void TryBindHostPlayer()
|
|
{
|
|
if (!autoBindHostPlayer || hostPlayer != null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
hostPlayer = FindFirstObjectByType<CECHostPlayer>();
|
|
if (hostPlayer != null)
|
|
{
|
|
QueueRefresh();
|
|
}
|
|
}
|
|
|
|
private void EnsureCameraBindings()
|
|
{
|
|
if (_previewCamera == null)
|
|
{
|
|
TryBindPreviewSystem();
|
|
}
|
|
|
|
if (_previewCamera == null || previewFrame == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_cachedTargetTexture == null)
|
|
{
|
|
_cachedTargetTexture = _previewCamera.targetTexture;
|
|
}
|
|
|
|
if (previewRenderTexture != null)
|
|
{
|
|
_previewCamera.targetTexture = previewRenderTexture;
|
|
}
|
|
|
|
if(previewFrame != null)
|
|
previewFrame.texture = _previewCamera.targetTexture;
|
|
}
|
|
|
|
private void RestoreCameraTargetTexture()
|
|
{
|
|
if (_previewCamera == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_cachedTargetTexture != null)
|
|
{
|
|
_previewCamera.targetTexture = _cachedTargetTexture;
|
|
}
|
|
}
|
|
|
|
private void BuildPreviewModel()
|
|
{
|
|
if (previewRoot == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Transform sourceRoot = ResolveSourceModelRoot();
|
|
if (sourceRoot == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Use resolved source root, not sourceModelRoot field directly.
|
|
_previewInstance = sourceRoot.gameObject;
|
|
Transform instanceTransform = _previewInstance.transform;
|
|
|
|
if (instanceTransform.parent != previewRoot)
|
|
{
|
|
instanceTransform.SetParent(previewRoot, false);
|
|
}
|
|
|
|
instanceTransform.localPosition = previewLocalPosition + Vector3.up * previewHeightOffset;
|
|
instanceTransform.localRotation = Quaternion.Euler(previewLocalEuler);
|
|
instanceTransform.localScale = previewLocalScale;
|
|
|
|
if (inheritPreviewLayer)
|
|
{
|
|
ApplyLayerRecursive(instanceTransform, previewRoot.gameObject.layer);
|
|
}
|
|
|
|
EnsureCameraFocus(instanceTransform);
|
|
EnsureCameraBindings();
|
|
}
|
|
|
|
|
|
private Transform ResolveSourceModelRoot()
|
|
{
|
|
if (sourceModelRoot != null)
|
|
{
|
|
return sourceModelRoot;
|
|
}
|
|
|
|
if (_playerModelPreview == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Prefer active model.
|
|
for (int i = 0; i < _playerModelPreview.playerModels.Count; i++)
|
|
{
|
|
GameObject model = _playerModelPreview.playerModels[i];
|
|
if (model != null && model.activeSelf)
|
|
{
|
|
return model.transform;
|
|
}
|
|
}
|
|
|
|
// Fallback to first available model.
|
|
for (int i = 0; i < _playerModelPreview.playerModels.Count; i++)
|
|
{
|
|
GameObject model = _playerModelPreview.playerModels[i];
|
|
if (model != null)
|
|
{
|
|
return model.transform;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void ReleasePreviewReference()
|
|
{
|
|
_previewInstance = null;
|
|
}
|
|
|
|
//private void DestroyPreviewInstance()
|
|
//{
|
|
// if (_previewInstance != null)
|
|
// {
|
|
// Destroy(_previewInstance);
|
|
// _previewInstance = null;
|
|
// }
|
|
//}
|
|
|
|
//private static void DisableComponentInChildren<T>(GameObject root) where T : Behaviour
|
|
//{
|
|
// if (root == null)
|
|
// {
|
|
// return;
|
|
// }
|
|
|
|
// var components = root.GetComponentsInChildren<T>(true);
|
|
// for (int i = 0; i < components.Length; i++)
|
|
// {
|
|
// var component = components[i];
|
|
// if (component == null)
|
|
// continue;
|
|
|
|
// component.enabled = false;
|
|
|
|
// if (component is PlayerVisual)
|
|
// {
|
|
// Destroy(component);
|
|
// }
|
|
// }
|
|
//}
|
|
|
|
private void ApplyLayerRecursive(Transform root, int layer)
|
|
{
|
|
if (root == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
root.gameObject.layer = layer;
|
|
for (int i = 0; i < root.childCount; i++)
|
|
{
|
|
ApplyLayerRecursive(root.GetChild(i), layer);
|
|
}
|
|
}
|
|
|
|
private void EnsureCameraFocus(Transform modelRoot)
|
|
{
|
|
if (_previewCamera == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!_cameraStateCached)
|
|
{
|
|
_cachedCameraPosition = _previewCamera.transform.position;
|
|
_cachedCameraRotation = _previewCamera.transform.rotation;
|
|
_cachedCameraFov = _previewCamera.fieldOfView;
|
|
_cameraStateCached = true;
|
|
}
|
|
|
|
// Apply FOV override independently from auto focus.
|
|
if (overrideFieldOfView)
|
|
{
|
|
_previewCamera.fieldOfView = previewFieldOfView;
|
|
}
|
|
|
|
if (!autoFocusCamera || modelRoot == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Vector3 focusPoint = modelRoot.TransformPoint(cameraFocusOffset) + previewCameraOffset;
|
|
|
|
Vector3 viewDirection = modelRoot.forward;
|
|
viewDirection.y = 0f;
|
|
if (viewDirection.sqrMagnitude < 0.0001f)
|
|
{
|
|
viewDirection = Vector3.back;
|
|
}
|
|
|
|
viewDirection.Normalize();
|
|
|
|
_previewCamera.transform.position = focusPoint + viewDirection * previewCameraDistance;
|
|
_previewCamera.transform.LookAt(focusPoint);
|
|
}
|
|
|
|
//private void StripRuntimeScripts(GameObject cloneRoot)
|
|
//{
|
|
// if (cloneRoot == null)
|
|
// {
|
|
// return;
|
|
// }
|
|
|
|
// var monoBehaviours = cloneRoot.GetComponentsInChildren<MonoBehaviour>(true);
|
|
// for (int i = 0; i < monoBehaviours.Length; i++)
|
|
// {
|
|
// var behaviour = monoBehaviours[i];
|
|
// if (behaviour == null)
|
|
// continue;
|
|
|
|
// Destroy(behaviour);
|
|
// }
|
|
|
|
// var colliders = cloneRoot.GetComponentsInChildren<Collider>(true);
|
|
// for (int i = 0; i < colliders.Length; i++)
|
|
// {
|
|
// var collider = colliders[i];
|
|
// if (collider == null)
|
|
// continue;
|
|
|
|
// Destroy(collider);
|
|
// }
|
|
|
|
// var rigidbodies = cloneRoot.GetComponentsInChildren<Rigidbody>(true);
|
|
// for (int i = 0; i < rigidbodies.Length; i++)
|
|
// {
|
|
// var body = rigidbodies[i];
|
|
// if (body == null)
|
|
// continue;
|
|
|
|
// Destroy(body);
|
|
// }
|
|
//}
|
|
|
|
private void RestoreSharedCameraState()
|
|
{
|
|
if (!_cameraStateCached || _previewCamera == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_previewCamera.transform.position = _cachedCameraPosition;
|
|
_previewCamera.transform.rotation = _cachedCameraRotation;
|
|
_previewCamera.fieldOfView = _cachedCameraFov;
|
|
_cameraStateCached = false;
|
|
}
|
|
|
|
//private void FreezeAnimators(GameObject cloneRoot)
|
|
//{
|
|
// if (cloneRoot == null)
|
|
// {
|
|
// return;
|
|
// }
|
|
|
|
// var animators = cloneRoot.GetComponentsInChildren<Animator>(true);
|
|
// for (int i = 0; i < animators.Length; i++)
|
|
// {
|
|
// var animator = animators[i];
|
|
// if (animator == null)
|
|
// continue;
|
|
|
|
// animator.speed = 0f;
|
|
// }
|
|
//}
|
|
|
|
/// <summary>
|
|
/// Counts all children recursively in the transform hierarchy.
|
|
/// Used to detect when equipment (child objects) are added or removed from the origin model.
|
|
/// </summary>
|
|
private int CountAllChildren(Transform root)
|
|
{
|
|
if (root == null)
|
|
return 0;
|
|
|
|
int count = root.childCount;
|
|
for (int i = 0; i < root.childCount; i++)
|
|
{
|
|
count += CountAllChildren(root.GetChild(i));
|
|
}
|
|
return count;
|
|
}
|
|
}
|
|
}
|
|
|