add UI layer: dialogue stream, HUD, player menu, and skills panel
Runtime UI under Scripts/UI: a UIStateController owning menu/dialogue state, a PlayerControlLock so opening a panel suppresses world input, panel visibility helpers, and the menu input binding. Views: the dialogue stream presenter and its options presenter and message row, the portrait HUD, and the player menu with character, journal, and tab-bar views. Skills gains SkillsPanelView and SkillRowView, which is why the Skills asmdef now references UnityEngine.UI. Prefabs/UI holds the authored canvases (dialogue stream, HUD, player menu) and their rows. UILayerBootstrap remains a hand-added runtime fallback for scenes where those prefabs are not instantiated; it deliberately does not auto-spawn. Editor/UILayerSetup.cs is the one-shot menu that builds and wires those prefabs into a scene, with UILayerSetupAutoRun reminding after a script reload. Art/UI/Placeholder carries the placeholder portrait the HUD falls back to. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
using System.Collections.Generic;
|
||||
using NightclubArcadia.Player;
|
||||
using StarterAssets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NightclubArcadia.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Ref-counted freeze of the player's ThirdPersonController. Multiple
|
||||
/// independent holders (dialogue, menu) can Acquire/Release; the controller
|
||||
/// stays disabled until the last holder releases.
|
||||
/// </summary>
|
||||
public sealed class PlayerControlLock : MonoBehaviour
|
||||
{
|
||||
readonly HashSet<object> holders = new HashSet<object>();
|
||||
|
||||
ThirdPersonController playerController;
|
||||
StarterAssetsInputs playerInputs;
|
||||
Animator playerAnimator;
|
||||
PlayerMotionArbiter motionArbiter;
|
||||
|
||||
static readonly int AnimIDSpeed = Animator.StringToHash("Speed");
|
||||
static readonly int AnimIDMotionSpeed = Animator.StringToHash("MotionSpeed");
|
||||
|
||||
public bool IsLocked => holders.Count > 0;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
playerController = GetComponent<ThirdPersonController>();
|
||||
playerInputs = GetComponent<StarterAssetsInputs>();
|
||||
playerAnimator = GetComponent<Animator>();
|
||||
motionArbiter = GetComponent<PlayerMotionArbiter>();
|
||||
}
|
||||
|
||||
public void Acquire(object reason)
|
||||
{
|
||||
if (reason == null)
|
||||
{
|
||||
throw new System.ArgumentNullException(nameof(reason));
|
||||
}
|
||||
|
||||
var wasLocked = IsLocked;
|
||||
holders.Add(reason);
|
||||
|
||||
if (!wasLocked && IsLocked)
|
||||
{
|
||||
ApplyLock(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void Release(object reason)
|
||||
{
|
||||
if (reason == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var wasLocked = IsLocked;
|
||||
holders.Remove(reason);
|
||||
|
||||
if (wasLocked && !IsLocked)
|
||||
{
|
||||
ApplyLock(false);
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyLock(bool locked)
|
||||
{
|
||||
if (playerController == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (locked)
|
||||
{
|
||||
// Clear pending input and the locomotion blend before freezing
|
||||
// the controller. Disabling it mid-stride otherwise leaves
|
||||
// Speed at its last value, and the character keeps running on
|
||||
// the spot for the whole conversation.
|
||||
if (playerInputs != null)
|
||||
{
|
||||
playerInputs.MoveInput(Vector2.zero);
|
||||
playerInputs.LookInput(Vector2.zero);
|
||||
playerInputs.SprintInput(false);
|
||||
playerInputs.JumpInput(false);
|
||||
}
|
||||
|
||||
if (playerAnimator != null)
|
||||
{
|
||||
playerAnimator.SetFloat(AnimIDSpeed, 0f);
|
||||
playerAnimator.SetFloat(AnimIDMotionSpeed, 0f);
|
||||
}
|
||||
|
||||
motionArbiter?.ResetState();
|
||||
}
|
||||
|
||||
// cursor state hook — see plan §3.4
|
||||
playerController.enabled = !locked;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user