199 lines
5.0 KiB
C#
199 lines
5.0 KiB
C#
|
|
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<NavMeshObstacle>();
|
||
|
|
}
|
||
|
|
|
||
|
|
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));
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Update NavMesh obstacle when gate opens/closes
|
||
|
|
/// </summary>
|
||
|
|
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;
|
||
|
|
}
|