added dialogue manager and radar

This commit is contained in:
2026-02-18 13:53:02 +00:00
parent e1df65bce2
commit 23362e7bdd
11 changed files with 915 additions and 3 deletions

View File

@@ -0,0 +1,278 @@
using UnityEngine;
/// <summary>
/// Singleton. Manages the dialogue UI box.
/// Attach to any persistent GameObject in the scene (e.g. a "Managers" object),
/// or drop it on the Player — it will survive fine either way.
///
/// Controls:
/// E or Space or Enter — advance to next page / close
/// Escape — close immediately
///
/// While dialogue is open:
/// - Cursor is unlocked and visible
/// - FirstPersonController mouse look is blocked (via IsOpen flag)
/// </summary>
public class DialogueManager : MonoBehaviour
{
// ── Singleton ─────────────────────────────────────────────────────
public static DialogueManager Instance { get; private set; }
// ── Inspector ─────────────────────────────────────────────────────
[Header("Box Layout")]
[Tooltip("Height of the dialogue box as a fraction of screen height.")]
public float boxHeightFraction = 0.22f;
public float boxMargin = 24f;
public float innerPad = 18f;
[Header("Colours — Cruelty Squad palette")]
public Color colBoxBg = new Color(0.04f, 0.06f, 0.04f, 0.94f);
public Color colBoxBorder = new Color(0.18f, 0.75f, 0.22f, 0.90f);
public Color colNameBg = new Color(0.10f, 0.28f, 0.10f, 1.00f);
public Color colNameText = new Color(0.25f, 1.00f, 0.30f, 1.00f);
public Color colBodyText = new Color(0.78f, 0.90f, 0.78f, 1.00f);
public Color colHintText = new Color(0.35f, 0.55f, 0.35f, 0.85f);
public Color colPageDots = new Color(0.28f, 0.65f, 0.30f, 0.80f);
[Header("Text")]
public int nameFontSize = 13;
public int bodyFontSize = 14;
public int hintFontSize = 10;
[Header("Typewriter Effect")]
public bool useTypewriter = true;
public float charsPerSecond = 40f;
// ── Public state ──────────────────────────────────────────────────
public bool IsOpen { get; private set; }
// ── Private ───────────────────────────────────────────────────────
private DialogueLine[] _lines;
private int _lineIndex;
private int _pageIndex;
// Typewriter
private float _charTimer;
private int _visibleChars;
private bool _pageComplete;
private Texture2D _white;
// ── Current text shortcuts ─────────────────────────────────────────
private string CurrentSpeaker => (_lines != null && _lineIndex < _lines.Length)
? _lines[_lineIndex].speakerName : "";
private string CurrentPage => (_lines != null && _lineIndex < _lines.Length
&& _pageIndex < _lines[_lineIndex].pages.Length)
? _lines[_lineIndex].pages[_pageIndex] : "";
private int TotalPages => (_lines != null && _lineIndex < _lines.Length)
? _lines[_lineIndex].pages.Length : 1;
// ─────────────────────────────────────────────────────────────────
void Awake()
{
if (Instance != null && Instance != this) { Destroy(gameObject); return; }
Instance = this;
}
void Start()
{
_white = Texture2D.whiteTexture;
}
// ─────────────────────────────────────────────────────────────────
public void StartDialogue(DialogueLine[] lines)
{
if (lines == null || lines.Length == 0) return;
_lines = lines;
_lineIndex = 0;
_pageIndex = 0;
IsOpen = true;
BeginPage();
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
}
void BeginPage()
{
_charTimer = 0f;
_visibleChars = useTypewriter ? 0 : int.MaxValue;
_pageComplete = !useTypewriter;
}
// ─────────────────────────────────────────────────────────────────
void Update()
{
if (!IsOpen) return;
// Advance typewriter
if (useTypewriter && !_pageComplete)
{
_charTimer += Time.deltaTime;
_visibleChars = Mathf.FloorToInt(_charTimer * charsPerSecond);
if (_visibleChars >= CurrentPage.Length)
{
_visibleChars = CurrentPage.Length;
_pageComplete = true;
}
}
// Advance or close on E / Space / Return
bool advance = Input.GetKeyDown(KeyCode.E)
|| Input.GetKeyDown(KeyCode.Space)
|| Input.GetKeyDown(KeyCode.Return);
bool cancel = Input.GetKeyDown(KeyCode.Escape);
if (cancel)
{
CloseDialogue();
return;
}
if (advance)
{
// If typewriter is still running, skip to end of page first
if (useTypewriter && !_pageComplete)
{
_visibleChars = CurrentPage.Length;
_pageComplete = true;
return;
}
// Try next page in current line
_pageIndex++;
if (_pageIndex < TotalPages)
{
BeginPage();
return;
}
// Try next line
_lineIndex++;
_pageIndex = 0;
if (_lineIndex < _lines.Length)
{
BeginPage();
return;
}
// All done
CloseDialogue();
}
}
void CloseDialogue()
{
IsOpen = false;
_lines = null;
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
}
// ─────────────────────────────────────────────────────────────────
void OnGUI()
{
if (!IsOpen || _lines == null) return;
float sw = Screen.width;
float sh = Screen.height;
float bh = sh * boxHeightFraction;
float bw = sw - boxMargin * 2f;
float by = sh - bh - boxMargin;
float bx = boxMargin;
// ── Outer border ─────────────────────────────────────────────
float border = 2f;
DrawTex(new Rect(bx - border, by - border, bw + border * 2f, bh + border * 2f), colBoxBorder);
// ── Main background ───────────────────────────────────────────
DrawTex(new Rect(bx, by, bw, bh), colBoxBg);
// ── Left accent stripe ────────────────────────────────────────
DrawTex(new Rect(bx, by, 4f, bh), colBoxBorder);
float cx = bx + innerPad + 6f; // content X (after the stripe + pad)
float cy = by + innerPad;
float cw = bw - innerPad * 2f - 6f;
// ── Speaker name bar ──────────────────────────────────────────
string speaker = CurrentSpeaker;
float nameH = 0f;
if (!string.IsNullOrWhiteSpace(speaker))
{
GUIStyle nameStyle = new GUIStyle();
nameStyle.fontSize = nameFontSize;
nameStyle.fontStyle = FontStyle.Bold;
nameStyle.normal.textColor = colNameText;
nameStyle.alignment = TextAnchor.MiddleLeft;
Vector2 ns = nameStyle.CalcSize(new GUIContent(speaker));
nameH = ns.y + 6f;
float namePadX = 10f;
DrawTex(new Rect(cx - 2f, cy, ns.x + namePadX * 2f + 4f, nameH), colNameBg);
GUI.Label(new Rect(cx + namePadX, cy + 3f, ns.x + 4f, ns.y), speaker, nameStyle);
cy += nameH + 8f;
}
// ── Body text (typewriter) ────────────────────────────────────
string fullText = CurrentPage;
string visibleText = useTypewriter
? fullText.Substring(0, Mathf.Min(_visibleChars, fullText.Length))
: fullText;
GUIStyle bodyStyle = new GUIStyle();
bodyStyle.fontSize = bodyFontSize;
bodyStyle.fontStyle = FontStyle.Normal;
bodyStyle.normal.textColor = colBodyText;
bodyStyle.wordWrap = true;
bodyStyle.richText = true;
float bodyH = bh - innerPad * 2f - nameH - 8f - 20f; // leave room for hint
GUI.Label(new Rect(cx, cy, cw, bodyH), visibleText, bodyStyle);
// ── Page indicator dots ───────────────────────────────────────
int total = TotalPages;
if (total > 1)
{
float dotR = 4f;
float dotGap = dotR * 2f + 4f;
float dotsW = total * dotGap;
float dotX = bx + bw * 0.5f - dotsW * 0.5f;
float dotY = by + bh - innerPad - dotR;
for (int i = 0; i < total; i++)
{
Color dc = (i == _pageIndex) ? colBoxBorder : colPageDots;
DrawTex(new Rect(dotX + i * dotGap, dotY - dotR, dotR * 2f, dotR * 2f), dc);
}
}
// ── Advance hint ──────────────────────────────────────────────
string hintStr = _pageComplete
? (_lineIndex >= _lines.Length - 1 && _pageIndex >= TotalPages - 1
? "[ E ] Close"
: "[ E ] Next")
: "[ E ] Skip";
GUIStyle hintStyle = new GUIStyle();
hintStyle.fontSize = hintFontSize;
hintStyle.fontStyle = FontStyle.Bold;
hintStyle.normal.textColor = colHintText;
hintStyle.alignment = TextAnchor.LowerRight;
GUI.Label(new Rect(bx, by, bw - innerPad, bh - 6f), hintStr, hintStyle);
}
// ─────────────────────────────────────────────────────────────────
void DrawTex(Rect r, Color c)
{
Color prev = GUI.color;
GUI.color = c;
GUI.DrawTexture(r, _white);
GUI.color = prev;
}
}