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,50 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace NightclubArcadia.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// One skill row: name, numeric rank, and accent-tinted fill bar.
|
||||
/// </summary>
|
||||
public sealed class SkillRowView : MonoBehaviour
|
||||
{
|
||||
[SerializeField] TMP_Text nameLabel;
|
||||
[SerializeField] TMP_Text rankLabel;
|
||||
[SerializeField] Image barFill;
|
||||
[SerializeField] Image accentStripe;
|
||||
|
||||
public void Bind(SkillDefinition def, int rank, int displayMaxRank)
|
||||
{
|
||||
if (def == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (nameLabel != null)
|
||||
{
|
||||
nameLabel.text = string.IsNullOrEmpty(def.DisplayName)
|
||||
? def.Id.ToUpperInvariant()
|
||||
: def.DisplayName;
|
||||
nameLabel.color = def.AccentColor;
|
||||
}
|
||||
|
||||
if (rankLabel != null)
|
||||
{
|
||||
rankLabel.text = rank.ToString();
|
||||
}
|
||||
|
||||
if (barFill != null)
|
||||
{
|
||||
var max = Mathf.Max(1, displayMaxRank);
|
||||
barFill.fillAmount = Mathf.Clamp01(rank / (float)max);
|
||||
barFill.color = def.AccentColor;
|
||||
}
|
||||
|
||||
if (accentStripe != null)
|
||||
{
|
||||
accentStripe.color = def.AccentColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ccda37aee7e42b09a0628f8a5729060
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,88 @@
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NightclubArcadia.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// Skills tab content. Rebuilds the list from SkillDatabase.All +
|
||||
/// PlayerSkills.GetRank on each show. No change event on PlayerSkills —
|
||||
/// refresh on show is deliberate (menu is modal; ranks cannot change while open).
|
||||
/// </summary>
|
||||
public sealed class SkillsPanelView : MonoBehaviour
|
||||
{
|
||||
[SerializeField] RectTransform contentRoot;
|
||||
[SerializeField] SkillRowView rowPrefab;
|
||||
[SerializeField] TMP_Text axisHeaderPrefab;
|
||||
[SerializeField] int displayMaxRank = 6;
|
||||
|
||||
readonly List<GameObject> spawned = new List<GameObject>();
|
||||
|
||||
/// <summary>
|
||||
/// Wired from MenuTabBar TabEntry.onShown in the Inspector.
|
||||
/// </summary>
|
||||
public void Refresh()
|
||||
{
|
||||
Clear();
|
||||
|
||||
var runtime = SkillRuntime.Instance;
|
||||
if (runtime == null || runtime.Database == null || runtime.Skills == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (contentRoot == null || rowPrefab == null)
|
||||
{
|
||||
Debug.LogWarning($"[{nameof(SkillsPanelView)}] Missing contentRoot or rowPrefab.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
SkillAxis? currentAxis = null;
|
||||
foreach (var def in runtime.Database.All)
|
||||
{
|
||||
if (def == null || string.IsNullOrEmpty(def.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentAxis == null || currentAxis.Value != def.Axis)
|
||||
{
|
||||
currentAxis = def.Axis;
|
||||
SpawnAxisHeader(def.Axis);
|
||||
}
|
||||
|
||||
var rank = runtime.Skills.GetRank(def.Id);
|
||||
var row = Instantiate(rowPrefab, contentRoot);
|
||||
row.gameObject.SetActive(true);
|
||||
row.Bind(def, rank, displayMaxRank);
|
||||
spawned.Add(row.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
void SpawnAxisHeader(SkillAxis axis)
|
||||
{
|
||||
if (axisHeaderPrefab == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var header = Instantiate(axisHeaderPrefab, contentRoot);
|
||||
header.gameObject.SetActive(true);
|
||||
header.text = axis.ToString().ToUpperInvariant();
|
||||
spawned.Add(header.gameObject);
|
||||
}
|
||||
|
||||
void Clear()
|
||||
{
|
||||
for (var i = 0; i < spawned.Count; i++)
|
||||
{
|
||||
if (spawned[i] != null)
|
||||
{
|
||||
Destroy(spawned[i]);
|
||||
}
|
||||
}
|
||||
|
||||
spawned.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3955c87e179f42a790c048be66b69606
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user