Files
beyond/Assets/Scripts/DestinationPoint.cs
2026-02-13 14:40:53 +01:00

216 lines
7.4 KiB
C#

using System.Collections;
using UnityEngine;
using UnityEngine.Events;
namespace Beyond
{
[RequireComponent(typeof(Collider))]
public class DestinationPoint : MonoBehaviour
{
[Header("References")]
[SerializeField] private DestinationPoint nextPoint; // Następny punkt
[SerializeField] private Collider triggerCollider;
[SerializeField] private AudioSource audioSource;
[SerializeField] private AudioClip destinationSound;
[Header("Events")]
[SerializeField] private UnityEvent onPointReached;
[Header("Detection Settings")]
[SerializeField] private LayerMask playerLayer; // Lepsze niż Tag
[Tooltip("Jeśli true: zalicza punkt natychmiast, jeśli gracz już w nim stoi w momencie aktywacji.")]
[SerializeField] private bool autoSkipIfInside = false;
[Tooltip("Jeśli true: punkt nie włączy się, dopóki gracz nie oddali się na bezpieczną odległość.")]
[SerializeField] private bool waitForExitIfInside = true;
[SerializeField] private float minActivationDistance = 5.0f; // Dystans wymagany do uzbrojenia punktu
[Header("General Settings")]
[SerializeField] private bool activateOnStart = true;
private bool isActive = false; // Czy punkt logicznie jest "tym aktualnym"
private bool isArmed = false; // Czy punkt jest fizycznie włączony (collider + marker)
private Transform playerTransform;
private Coroutine activationCoroutine;
private void Awake()
{
if (triggerCollider == null) triggerCollider = GetComponent<Collider>();
if (triggerCollider != null) triggerCollider.isTrigger = true;
if (audioSource == null) audioSource = GetComponent<AudioSource>();
if (audioSource == null && destinationSound != null) audioSource = gameObject.AddComponent<AudioSource>();
}
private void Start()
{
GameObject playerObj = GameObject.FindGameObjectWithTag("Player");
if (playerObj) playerTransform = playerObj.transform;
if (activateOnStart)
{
Activate();
}
else
{
// Upewnij się, że na starcie jest wyłączony, jeśli nie ma być aktywny
UpdateVisualState(false);
}
}
// --- Logika Włączania/Wyłączania (Unity Lifecycle) ---
private void OnEnable()
{
// Jeśli obiekt został włączony ponownie (np. po dezaktywacji rodzica),
// a logicznie ten punkt nadal powinien być aktywny -> wznów działanie.
if (isActive)
{
TryArmPoint();
}
}
private void OnDisable()
{
// Kiedy obiekt jest wyłączany, zawsze chowamy marker i wyłączamy logikę
// Ale NIE zmieniamy 'isActive', bo quest nadal jest na tym etapie.
if (activationCoroutine != null) StopCoroutine(activationCoroutine);
UpdateVisualState(false);
}
// --- Główna Logika ---
public void Activate()
{
if (isActive) return; // Już jest aktywny
isActive = true;
TryArmPoint();
}
private void TryArmPoint()
{
// 1. Sprawdź, czy gracz jest w środku
if (IsPlayerInsideOrTooClose())
{
if (autoSkipIfInside)
{
Debug.Log($"[DestinationPoint] Gracz wewnątrz {name}, automatyczne zaliczenie.");
HandleDestinationReached();
return;
}
else if (waitForExitIfInside)
{
Debug.Log($"[DestinationPoint] Gracz za blisko {name}, czekam na dystans...");
if (activationCoroutine != null) StopCoroutine(activationCoroutine);
activationCoroutine = StartCoroutine(WaitForSafeDistance());
return;
}
}
// 2. Normalna aktywacja (Marker + Collider)
UpdateVisualState(true);
}
public void Deactivate()
{
isActive = false;
UpdateVisualState(false);
if (activationCoroutine != null) StopCoroutine(activationCoroutine);
}
// Metoda pomocnicza do zarządzania stanem wizualnym/fizycznym
private void UpdateVisualState(bool state)
{
isArmed = state;
if (triggerCollider != null)
triggerCollider.enabled = state;
if (Compass.Instance != null)
{
if (state)
Compass.Instance.AddQuestMarker(transform, null);
else
Compass.Instance.RemoveQuestMarker(transform);
}
}
private void OnTriggerEnter(Collider other)
{
if (!isActive || !isArmed) return;
// Sprawdzamy LayerMask lub Tag
if (IsInLayerMask(other.gameObject, playerLayer) || other.CompareTag("Player"))
{
HandleDestinationReached();
}
}
private void HandleDestinationReached()
{
// Dźwięk
if (destinationSound != null && audioSource != null)
audioSource.PlayOneShot(destinationSound);
// Eventy
onPointReached?.Invoke();
// Logika następnego punktu
// Najpierw wyłączamy obecny, żeby nie wywołał się dwa razy
Deactivate();
if (nextPoint != null)
{
nextPoint.Activate();
}
}
// --- Pomocnicze (Dystans i Coroutine) ---
private bool IsPlayerInsideOrTooClose()
{
if (playerTransform == null) return false;
// Sprawdzenie 1: Czy jest fizycznie w Triggerze
if (triggerCollider != null && triggerCollider.bounds.Contains(playerTransform.position))
return true;
// Sprawdzenie 2: Czy jest za blisko (zabezpieczenie jeśli collider jest mały)
return Vector3.Distance(transform.position, playerTransform.position) < 2.0f;
}
private IEnumerator WaitForSafeDistance()
{
// Upewnij się, że marker i collider są wyłączone podczas czekania
UpdateVisualState(false);
while (playerTransform != null &&
Vector3.Distance(transform.position, playerTransform.position) < minActivationDistance)
{
yield return new WaitForSeconds(0.5f);
}
// Gracz odszedł wystarczająco daleko -> włącz punkt
UpdateVisualState(true);
activationCoroutine = null;
}
private bool IsInLayerMask(GameObject obj, LayerMask layerMask)
{
return (layerMask.value & (1 << obj.layer)) > 0;
}
// --- Debug w Edytorze ---
private void OnDrawGizmos()
{
Gizmos.color = isActive ? (isArmed ? Color.green : Color.yellow) : Color.gray;
Gizmos.DrawWireSphere(transform.position, 0.5f);
if (nextPoint != null)
{
Gizmos.color = Color.white;
Gizmos.DrawLine(transform.position, nextPoint.transform.position);
}
}
}
}