using Animancer; using BrewMonster; using UnityEngine; using UnityEngine.UI; namespace BrewMonster.Scripts.UI.Inventory { /// /// Clones the current host-player visual model into a dedicated preview rig for the inventory UI. /// [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); } } /// Allows manual binding from external UI scripts. public void BindPlayer(CECHostPlayer player) { if (player == null || player == hostPlayer) { return; } hostPlayer = player; QueueRefresh(); } /// Forces an immediate rebuild of the preview model. public void ForceRefreshNow() { _refreshQueued = false; BuildPreviewModel(); } /// Marks the clone dirty so it gets recreated next LateUpdate. public void QueueRefresh() { _refreshQueued = true; } private void TryBindPreviewSystem() { if (_playerModelPreview == null) { _playerModelPreview = PlayerModelPreview.Instance; } if (_playerModelPreview == null) { return; } if (_previewCamera == null) { _previewCamera = _playerModelPreview.GetComponent(); } } private void TryBindHostPlayer() { if (!autoBindHostPlayer || hostPlayer != null) { return; } hostPlayer = FindFirstObjectByType(); 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(GameObject root) where T : Behaviour //{ // if (root == null) // { // return; // } // var components = root.GetComponentsInChildren(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(true); // for (int i = 0; i < monoBehaviours.Length; i++) // { // var behaviour = monoBehaviours[i]; // if (behaviour == null) // continue; // Destroy(behaviour); // } // var colliders = cloneRoot.GetComponentsInChildren(true); // for (int i = 0; i < colliders.Length; i++) // { // var collider = colliders[i]; // if (collider == null) // continue; // Destroy(collider); // } // var rigidbodies = cloneRoot.GetComponentsInChildren(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(true); // for (int i = 0; i < animators.Length; i++) // { // var animator = animators[i]; // if (animator == null) // continue; // animator.speed = 0f; // } //} /// /// Counts all children recursively in the transform hierarchy. /// Used to detect when equipment (child objects) are added or removed from the origin model. /// 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; } } }