136 lines
5.2 KiB
C#
136 lines
5.2 KiB
C#
using UnityEngine;
|
|
|
|
/// <summary>
|
|
/// Attach this to any world object (NPC, terminal, sign, corpse, etc.)
|
|
/// to make it interactable. The player presses E when looking at it
|
|
/// from within interactRange to start the dialogue.
|
|
///
|
|
/// The component draws a small world-space "[E]" prompt via OnGUI
|
|
/// when the player is close enough and looking at the object.
|
|
/// </summary>
|
|
public class DialogueNPC : MonoBehaviour
|
|
{
|
|
[Header("Dialogue")]
|
|
public DialogueLine[] lines = new DialogueLine[]
|
|
{
|
|
new DialogueLine { speakerName = "STRANGER", pages = new[] { "Hey." } }
|
|
};
|
|
|
|
[Header("Interaction")]
|
|
[Tooltip("Maximum distance from which the player can interact.")]
|
|
public float interactRange = 3.5f;
|
|
[Tooltip("Maximum angle (degrees) between player forward and direction to this object.")]
|
|
public float interactAngle = 45f;
|
|
[Tooltip("Layer mask used for the line-of-sight raycast.")]
|
|
public LayerMask occlusionMask = ~0;
|
|
|
|
[Header("Prompt Style")]
|
|
public string promptText = "[E] Talk";
|
|
public Color colPrompt = new Color(0.20f, 0.95f, 0.40f, 1f);
|
|
public Color colPromptBg = new Color(0f, 0f, 0f, 0.70f);
|
|
|
|
// ── Private ──────────────────────────────────────────────────────
|
|
private Transform _playerTransform;
|
|
private Camera _playerCamera;
|
|
private bool _promptVisible;
|
|
private Texture2D _white;
|
|
|
|
// ─────────────────────────────────────────────────────────────────
|
|
void Start()
|
|
{
|
|
_white = Texture2D.whiteTexture;
|
|
|
|
// Find the player by component
|
|
var player = FindObjectOfType<FirstPersonController>();
|
|
if (player != null)
|
|
{
|
|
_playerTransform = player.transform;
|
|
_playerCamera = player.GetComponentInChildren<Camera>();
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"[DialogueNPC] '{name}': Could not find FirstPersonController in scene.");
|
|
}
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
if (_playerTransform == null || DialogueManager.Instance == null) return;
|
|
if (DialogueManager.Instance.IsOpen) { _promptVisible = false; return; }
|
|
|
|
_promptVisible = CanInteract();
|
|
|
|
if (_promptVisible && Input.GetKeyDown(KeyCode.E))
|
|
DialogueManager.Instance.StartDialogue(lines);
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────
|
|
bool CanInteract()
|
|
{
|
|
if (_playerTransform == null || _playerCamera == null) return false;
|
|
|
|
// Distance check
|
|
float dist = Vector3.Distance(_playerTransform.position, transform.position);
|
|
if (dist > interactRange) return false;
|
|
|
|
// Angle check — is the player roughly facing this object?
|
|
Vector3 dir = (transform.position - _playerCamera.transform.position).normalized;
|
|
float dot = Vector3.Dot(_playerCamera.transform.forward, dir);
|
|
if (dot < Mathf.Cos(interactAngle * Mathf.Deg2Rad)) return false;
|
|
|
|
// Line-of-sight (optional — fire a ray toward us)
|
|
if (Physics.Raycast(_playerCamera.transform.position, dir, out RaycastHit hit, interactRange, occlusionMask))
|
|
{
|
|
// Allow if the hit object is us or a child of us
|
|
if (!hit.transform.IsChildOf(transform) && hit.transform != transform)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────
|
|
void OnGUI()
|
|
{
|
|
if (!_promptVisible || _playerCamera == null) return;
|
|
|
|
// Project to screen
|
|
Vector3 worldPos = transform.position + Vector3.up * 0.5f; // slightly above pivot
|
|
Vector3 screenPos = _playerCamera.WorldToScreenPoint(worldPos);
|
|
|
|
if (screenPos.z <= 0f) return; // behind camera
|
|
|
|
// Flip Y (GUI vs screen coords)
|
|
float sx = screenPos.x;
|
|
float sy = Screen.height - screenPos.y;
|
|
|
|
GUIStyle style = new GUIStyle();
|
|
style.fontSize = 13;
|
|
style.fontStyle = FontStyle.Bold;
|
|
style.normal.textColor = colPrompt;
|
|
style.alignment = TextAnchor.MiddleCenter;
|
|
|
|
Vector2 size = style.CalcSize(new GUIContent(promptText));
|
|
float padX = 8f;
|
|
float padY = 4f;
|
|
float bgW = size.x + padX * 2f;
|
|
float bgH = size.y + padY * 2f;
|
|
Rect bgRect = new Rect(sx - bgW * 0.5f, sy - bgH * 0.5f, bgW, bgH);
|
|
Rect txRect = new Rect(sx - size.x * 0.5f, sy - size.y * 0.5f, size.x, size.y);
|
|
|
|
// Background
|
|
Color prev = GUI.color;
|
|
GUI.color = colPromptBg;
|
|
GUI.DrawTexture(bgRect, _white);
|
|
GUI.color = prev;
|
|
|
|
GUI.Label(txRect, promptText, style);
|
|
}
|
|
|
|
void OnDrawGizmosSelected()
|
|
{
|
|
Gizmos.color = new Color(0f, 1f, 0.4f, 0.25f);
|
|
Gizmos.DrawWireSphere(transform.position, interactRange);
|
|
}
|
|
}
|