using UnityEngine; using System.Collections; using UnityEngine.AI; public class Gate : MonoBehaviour, IInteractiveObject { [Header("Gate Settings")] public bool startsOpen = false; public bool isLocked = false; public string requiredKeyId = ""; // empty = no key required public string gateName = "Gate"; [Header("Animation")] public Transform hinge; // pivot for rotation public float openAngle = 90f; public float closedAngle = 0f; public float swingSpeed = 180f; // degrees per second public bool rotateAroundLocalY = true; [Header("Interaction")] public Transform interactionPoint; // Where player should stand to interact (defaults to this transform) [Header("NavMesh")] public NavMeshObstacle navMeshObstacle; bool targetOpen; Coroutine swingRoutine; void Awake() { if (navMeshObstacle == null) navMeshObstacle = GetComponent(); } void Start() { if (hinge == null) { Debug.LogError("Gate: Hinge not assigned!", gameObject); return; } targetOpen = startsOpen; SetImmediateRotation(targetOpen ? openAngle : closedAngle); // Initialize NavMesh obstacle state based on gate state if (navMeshObstacle != null) navMeshObstacle.enabled = !startsOpen; // Disable obstacle if gate starts open } // Public API public void Interact() { if (isLocked) { OnLockedInteract(); return; } Toggle(); } public bool TryUnlock(string keyId) { if (!isLocked) return true; if (string.IsNullOrEmpty(requiredKeyId) || requiredKeyId != keyId) return false; isLocked = false; return true; } public void Lock(string keyId) { isLocked = true; requiredKeyId = keyId; } public void Unlock() { isLocked = false; } public void Open() { if (isLocked) return; SetOpen(true); UpdateNavMesh(false); // Disable obstacle when opening } public void Close() { SetOpen(false); UpdateNavMesh(true); // Enable obstacle when closing } public void Toggle() { SetOpen(!targetOpen); } public bool IsOpen() => targetOpen; void SetOpen(bool open) { if (targetOpen == open) return; // Already in target state targetOpen = open; if (swingRoutine != null) StopCoroutine(swingRoutine); swingRoutine = StartCoroutine(SwingToTarget(open ? openAngle : closedAngle)); } /// /// Update NavMesh obstacle when gate opens/closes /// void UpdateNavMesh(bool enableObstacle) { // Update the obstacle to carve/uncarve the NavMesh if (navMeshObstacle != null) { navMeshObstacle.enabled = enableObstacle; Debug.Log($"Gate {gateName}: NavMesh obstacle {(enableObstacle ? "enabled" : "disabled")}", gameObject); } } IEnumerator SwingToTarget(float targetAngle) { if (hinge == null) yield break; float current = GetHingeAngle(); float difference = Mathf.DeltaAngle(current, targetAngle); if (Mathf.Abs(difference) < 0.5f) yield break; // Already at target float sign = Mathf.Sign(difference); while (Mathf.Abs(Mathf.DeltaAngle(current, targetAngle)) > 0.5f) { float step = swingSpeed * Time.deltaTime * sign; current += step; SetHingeAngle(current); yield return null; } SetHingeAngle(targetAngle); swingRoutine = null; } void SetImmediateRotation(float angle) { if (hinge == null) return; SetHingeAngle(angle); } float GetHingeAngle() { if (hinge == null) return 0f; return rotateAroundLocalY ? hinge.localEulerAngles.y : hinge.localEulerAngles.z; } void SetHingeAngle(float angle) { if (hinge == null) return; Vector3 e = hinge.localEulerAngles; if (rotateAroundLocalY) e.y = angle; else e.z = angle; hinge.localEulerAngles = e; } void OnLockedInteract() { Debug.Log($"Gate is locked. Requires key: {(string.IsNullOrEmpty(requiredKeyId) ? "None" : requiredKeyId)}", gameObject); } void OnDestroy() { if (swingRoutine != null) { StopCoroutine(swingRoutine); swingRoutine = null; } } // IInteractiveObject implementation public Vector3 GetInteractionPoint() { if (interactionPoint != null) return interactionPoint.position; return transform.position; } public bool CanInteract() { return !isLocked; } public void Interact(GameObject player) { Open(); Debug.Log($"Gate {gateName} opened by {player.name}"); } public string GetDisplayName() => gateName; }