working on quest and interaction system
This commit is contained in:
172
Assets/Scripts/QuestInteractable.cs
Normal file
172
Assets/Scripts/QuestInteractable.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user