using UnityEngine; using DG.Tweening; [RequireComponent(typeof(Collider))] public class SmoothChildRotation : MonoBehaviour { [Header("Door Parts")] public Transform targetChild; // door mesh child to rotate public string playerTag = "Player"; [Header("Door Rotation")] public float rotationDuration = 0.5f; public Ease rotationEase = Ease.OutQuad; public Vector3 targetRotationOnEnter = new Vector3(0, 0, -90); public Vector3 targetRotationOnExit = new Vector3(0, 0, 0); [Header("Cutscene: Exit Path")] [Tooltip("If not set, we use (this.transform.position + this.transform.forward * exitForwardDistance).")] public Transform exitPoint; public float exitForwardDistance = 3f; // used when exitPoint is null public float faceDuration = 0.2f; // how quickly the player faces exit [Header("Cutscene: Anim States")] public string celebrationStateName = "Celebration"; public string walkStateName = "Run"; // or your locomotion state/BT name [Header("Cutscene: Timing")] public float celebrationDuration = 1.2f; // seconds to hold Celebration public float walkMoveDuration = 1.5f; // seconds to walk to exit public Ease walkMoveEase = Ease.Linear; [Header("Behavior")] public bool disableTriggerAfterUse = true; // prevents re-entrant triggers private Tween _doorTween; private bool _cutsceneRunning; void Reset() { var col = GetComponent(); if (col) col.isTrigger = true; } void Start() { if (!targetChild) { Debug.LogError("[SmoothChildRotation] Target child not assigned!", this); enabled = false; } } void OnTriggerEnter(Collider other) { if (_cutsceneRunning) return; if (!other.CompareTag(playerTag)) return; // Open the door RotateTo(targetRotationOnEnter); // Start the cutscene StartCoroutine(RunDoorCutscene(other)); } void OnTriggerExit(Collider other) { if (!other.CompareTag(playerTag)) return; if (_cutsceneRunning) return; // don’t close while cutscene is ongoing RotateTo(targetRotationOnExit); } void RotateTo(Vector3 targetRot) { _doorTween?.Kill(); _doorTween = targetChild .DOLocalRotate(targetRot, rotationDuration) .SetEase(rotationEase); } System.Collections.IEnumerator RunDoorCutscene(Collider hit) { _cutsceneRunning = true; if (disableTriggerAfterUse) { var c = GetComponent(); if (c) c.enabled = false; } // Find player components Transform root = hit.attachedRigidbody ? hit.attachedRigidbody.transform : hit.transform.root; var controller = root.GetComponentInChildren(); var anim = root.GetComponentInChildren(); var rb = root.GetComponentInChildren(); // 1) Freeze player control & velocity if (controller) controller.LockControls(true); if (rb) { rb.velocity = Vector3.zero; rb.angularVelocity = Vector3.zero; } // 2) Work out exit position / facing Vector3 dst = exitPoint ? exitPoint.position : (transform.position + transform.forward * exitForwardDistance); if (rb) dst.y = rb.position.y; else dst.y = root.position.y; Vector3 faceDir = dst - root.position; faceDir.y = 0f; if (faceDir.sqrMagnitude > 0.0001f) { Quaternion look = Quaternion.LookRotation(faceDir.normalized, Vector3.up); // quick face toward the door exit root.DORotateQuaternion(look, faceDuration).SetEase(Ease.OutQuad); } // 3) Celebration anim if (anim && !string.IsNullOrEmpty(celebrationStateName)) anim.PlayState(celebrationStateName, 0.1f); yield return new WaitForSeconds(celebrationDuration); // 4) Walk/Run anim if (anim && !string.IsNullOrEmpty(walkStateName)) anim.PlayState(walkStateName, 0.1f); // 5) Move player outside the door Tween moveT; if (rb != null) moveT = rb.DOMove(dst, walkMoveDuration).SetEase(walkMoveEase).SetUpdate(UpdateType.Fixed); else moveT = root.DOMove(dst, walkMoveDuration).SetEase(walkMoveEase); yield return moveT.WaitForCompletion(); // 6) Level complete signal CrateEscapeGameManager.Instance?.OnLevelCompleteTriggered(); // (Optional) keep controls locked while level completes. Remove next line if you want to give control back: // if (controller) controller.LockControls(false); _cutsceneRunning = false; } }