173 lines
6.4 KiB
C#
173 lines
6.4 KiB
C#
using UnityEngine;
|
|
|
|
/// <summary>
|
|
/// Place this on an NPC, Item, or Trigger Zone to easily connect it to the Quest System!
|
|
/// </summary>
|
|
public class QuestInteractable : MonoBehaviour, IInteractable
|
|
{
|
|
[Header("Quest Progression")]
|
|
[Tooltip("What action does this object trigger?")]
|
|
public ObjectiveType objectiveType;
|
|
|
|
[Tooltip("The unique ID that matches the QuestObjective (e.g., 'wolf', 'ancient_relic')")]
|
|
public string targetID;
|
|
|
|
[Tooltip("How much to progress the objective (usually 1)")]
|
|
public int progressAmount = 1;
|
|
|
|
[Header("Interaction Settings")]
|
|
[Tooltip("Can the player walk up and press E on this object?")]
|
|
public bool isInteractable = false;
|
|
[Tooltip("If true, the player can ONLY interact with this if they currently have the matching quest objective active!")]
|
|
public bool requireActiveObjective = false;
|
|
[Tooltip("The action verb for the prompt (e.g., 'Open', 'Collect', 'Steal')")]
|
|
public string interactAction = "Interact";
|
|
[Tooltip("The secondary text to display (e.g. the name of the item or 'Press E')")]
|
|
public string interactTargetName = "[ E ]";
|
|
|
|
[Header("Destination/Trigger Settings")]
|
|
[Tooltip("Check this if walking into a Trigger Collider should automatically complete the objective.")]
|
|
public bool completeOnTriggerEnter = false;
|
|
[Tooltip("A visual marker GameObject to enable while active, and disable when completed.")]
|
|
public GameObject destinationMarker;
|
|
[Tooltip("A TMPro text element to show the distance counting down as the player gets closer.")]
|
|
public TMPro.TextMeshProUGUI distanceText;
|
|
|
|
[Header("Dynamic Marker Height")]
|
|
[Tooltip("If true, the marker will physically raise into the sky when far away, and lower to the ground when close.")]
|
|
public bool scaleMarkerHeight = true;
|
|
public float maxDistanceForHeight = 50f;
|
|
public float minHeight = 1.5f;
|
|
public float maxHeight = 15f;
|
|
[Tooltip("How far from the center the marker should hit its lowest point. If you have a large Box Collider, set this to half its width (e.g., 3m) so the marker bottoms out right as you touch the edge!")]
|
|
public float arrivalDistanceOffset = 3f;
|
|
|
|
[Header("Item Settings")]
|
|
[Tooltip("Check this if this is an item that should destroy itself when collected/triggered")]
|
|
public bool destroyOnTrigger;
|
|
|
|
private bool _hasBeenTriggered = false;
|
|
private Transform _playerTransform;
|
|
|
|
private void Start()
|
|
{
|
|
// Find player once at start for distance tracking
|
|
GameObject playerObj = GameObject.FindGameObjectWithTag("Player");
|
|
if (playerObj != null)
|
|
{
|
|
_playerTransform = playerObj.transform;
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
// Update distance countdown if marker is active and we haven't triggered
|
|
if (completeOnTriggerEnter && !_hasBeenTriggered && destinationMarker != null && destinationMarker.activeInHierarchy)
|
|
{
|
|
// Fallback: If the 'Player' tag is on a stationary object or missing, track the Main Camera!
|
|
// The Main Camera is guaranteed to move with you, so the distance will always update correctly.
|
|
if (_playerTransform == null || _playerTransform.position == Vector3.zero)
|
|
{
|
|
if (Camera.main != null) _playerTransform = Camera.main.transform;
|
|
}
|
|
|
|
if (_playerTransform != null)
|
|
{
|
|
float dist = Vector3.Distance(_playerTransform.position, transform.position);
|
|
|
|
if (distanceText != null)
|
|
{
|
|
distanceText.text = $"{Mathf.RoundToInt(dist)}m";
|
|
}
|
|
|
|
if (scaleMarkerHeight)
|
|
{
|
|
// Subtract the offset so it bottoms out at the edge of the collider instead of the exact center
|
|
float effectiveDist = Mathf.Max(0, dist - arrivalDistanceOffset);
|
|
|
|
// Calculate a 0 to 1 value (0 = at the edge, 1 = max distance away)
|
|
float t = Mathf.Clamp01(effectiveDist / maxDistanceForHeight);
|
|
|
|
// Lerp between our min and max height clamps
|
|
float targetY = Mathf.Lerp(minHeight, maxHeight, t);
|
|
|
|
Vector3 localPos = destinationMarker.transform.localPosition;
|
|
localPos.y = targetY;
|
|
destinationMarker.transform.localPosition = localPos;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnTriggerEnter(Collider other)
|
|
{
|
|
if (!completeOnTriggerEnter || _hasBeenTriggered) return;
|
|
|
|
// Ensure it's the player that walked into the trigger
|
|
if (other.CompareTag("Player") || other.transform.root.CompareTag("Player") || other.GetComponentInParent<PlayerStats>() != null)
|
|
{
|
|
TriggerQuestProgress();
|
|
}
|
|
}
|
|
|
|
// --- IInteractable Implementation ---
|
|
public string GetInteractText()
|
|
{
|
|
return interactAction;
|
|
}
|
|
|
|
public string GetSecondaryInteractText()
|
|
{
|
|
return interactTargetName;
|
|
}
|
|
|
|
public bool CanInteract()
|
|
{
|
|
if (!isInteractable || _hasBeenTriggered) return false;
|
|
|
|
// If this option is checked, we ask the QuestManager if this is our current objective!
|
|
if (requireActiveObjective)
|
|
{
|
|
if (QuestManager.Instance == null) return false;
|
|
return QuestManager.Instance.IsObjectiveActive(objectiveType, targetID);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public void Interact(GameObject player)
|
|
{
|
|
TriggerQuestProgress();
|
|
}
|
|
// ------------------------------------
|
|
|
|
/// <summary>
|
|
/// Call this from your existing interaction scripts, or a Unity Event when picking up an item!
|
|
/// </summary>
|
|
public void TriggerQuestProgress()
|
|
{
|
|
if (_hasBeenTriggered) return;
|
|
|
|
bool wasSuccessful = false;
|
|
if (QuestManager.Instance != null)
|
|
{
|
|
wasSuccessful = QuestManager.Instance.ProgressObjective(objectiveType, targetID, progressAmount);
|
|
}
|
|
|
|
// If the objective wasn't active (or was wrong type), we ignore it so they can try again later
|
|
if (!wasSuccessful) return;
|
|
|
|
_hasBeenTriggered = true;
|
|
|
|
if (destinationMarker != null)
|
|
{
|
|
destinationMarker.SetActive(false);
|
|
}
|
|
|
|
if (destroyOnTrigger)
|
|
{
|
|
Destroy(gameObject);
|
|
}
|
|
}
|
|
}
|