Updating HUD, Gold Config, and finishing off Play flow for 9player map.
This commit is contained in:
parent
a7be12fa9b
commit
3dcc0e7edd
28 changed files with 2272 additions and 9601 deletions
|
|
@ -59,6 +59,10 @@ namespace TD.UI
|
|||
private Label goldLabel;
|
||||
private Label waveLabel;
|
||||
private Label livesLabel;
|
||||
private Label nextWaveLabel; // prep countdown ("next: 0:12")
|
||||
private Label leakedLabel; // local player's origin-leak count ("leaked: 3")
|
||||
private Label incomeLabel; // top-bar per-wave gold-earned counter ("+150 g/wave")
|
||||
private VisualElement playerListContainer; // right-panel scoreboard rows
|
||||
private Label portraitName;
|
||||
private Label levelLabel;
|
||||
private VisualElement statLines;
|
||||
|
|
@ -236,6 +240,10 @@ namespace TD.UI
|
|||
goldLabel = Require<Label>(root, "gold-label");
|
||||
waveLabel = Require<Label>(root, "wave-label");
|
||||
livesLabel = Require<Label>(root, "lives-label");
|
||||
nextWaveLabel = Require<Label>(root, "next-wave-label");
|
||||
leakedLabel = Require<Label>(root, "leaked-label");
|
||||
incomeLabel = Require<Label>(root, "income-label");
|
||||
playerListContainer = Require<VisualElement>(root, "player-list");
|
||||
portraitName = Require<Label>(root, "portrait-name");
|
||||
levelLabel = Require<Label>(root, "level-label");
|
||||
statLines = Require<VisualElement>(root, "stat-lines");
|
||||
|
|
@ -438,17 +446,195 @@ namespace TD.UI
|
|||
private void RefreshMatchStateDisplays()
|
||||
{
|
||||
var ms = MatchState.Instance;
|
||||
var wm = WaveManager.Instance;
|
||||
|
||||
if (livesLabel != null)
|
||||
livesLabel.text = ms != null ? $"lives: {ms.Lives}" : "lives: --";
|
||||
|
||||
if (waveLabel != null)
|
||||
{
|
||||
int total = WaveManager.Instance?.TotalWaves ?? 0;
|
||||
int total = wm?.TotalWaves ?? 0;
|
||||
waveLabel.text = ms != null && ms.CurrentWave > 0 && total > 0
|
||||
? $"Wave {ms.CurrentWave} / {total}"
|
||||
: "Wave --";
|
||||
}
|
||||
|
||||
// Next-wave countdown. Shows during prep ("next: 0:12") and clears the
|
||||
// moment the wave actually starts spawning. WaveManager.PrepCountdown
|
||||
// is networked so this reads the same value on every peer.
|
||||
if (nextWaveLabel != null)
|
||||
{
|
||||
float t = wm != null ? wm.PrepCountdown : 0f;
|
||||
if (t > 0f)
|
||||
{
|
||||
// Ceiling so the user sees a full "0:01" tick before "0:00".
|
||||
int seconds = Mathf.CeilToInt(t);
|
||||
int mm = seconds / 60;
|
||||
int ss = seconds % 60;
|
||||
nextWaveLabel.text = $"next: {mm}:{ss:00}";
|
||||
}
|
||||
else
|
||||
{
|
||||
nextWaveLabel.text = "next: --:--";
|
||||
}
|
||||
}
|
||||
|
||||
// Top-bar "earned this wave" counter. Reads the LOCAL player's
|
||||
// PlayerGoldManager.GoldEarnedThisWave — which the server resets to 0 at
|
||||
// wave start and increments via AwardGold on every kill / completion /
|
||||
// no-leak bonus. Spending doesn't decrement it.
|
||||
if (incomeLabel != null)
|
||||
{
|
||||
var localGold = PlayerGoldManager.Local;
|
||||
int earned = localGold != null ? localGold.GoldEarnedThisWave : 0;
|
||||
incomeLabel.text = $"+{earned} g/wave";
|
||||
}
|
||||
|
||||
// Local player's origin-leak count: how many enemies that spawned in MY
|
||||
// zone escaped my maze. Resolves the local PlayerMatchState's slot then
|
||||
// reads the per-slot counter from WaveManager (replicated NetworkList).
|
||||
if (leakedLabel != null)
|
||||
{
|
||||
var local = PlayerMatchState.Local;
|
||||
int leaks = 0;
|
||||
if (wm != null && local != null && local.Slot != PlayerSlot.None)
|
||||
leaks = wm.GetZoneLeakCount(local.Slot);
|
||||
leakedLabel.text = $"leaked: {leaks}";
|
||||
}
|
||||
|
||||
// Right-panel scoreboard rebuild — see RefreshScoreboard.
|
||||
RefreshScoreboard();
|
||||
}
|
||||
|
||||
// ----- Scoreboard --------------------------------------------------
|
||||
|
||||
// Snapshot of last-rebuilt scoreboard state so we only rebuild when something
|
||||
// changes. Without this we'd destroy and recreate every row every frame —
|
||||
// wasteful and (in line with LobbyController's player-list pattern) would
|
||||
// also break any per-row pointer interaction we might add later.
|
||||
private string lastScoreboardSignature = string.Empty;
|
||||
|
||||
private void RefreshScoreboard()
|
||||
{
|
||||
if (playerListContainer == null) return;
|
||||
|
||||
// Sort by slot for stable ordering. Counter-intuitively the underlying
|
||||
// collection isn't slot-ordered (it's keyed by NGO clientId).
|
||||
var players = new System.Collections.Generic.List<PlayerMatchState>();
|
||||
foreach (var pms in PlayerMatchState.AllPlayers) players.Add(pms);
|
||||
players.Sort((a, b) => ((int)a.Slot).CompareTo((int)b.Slot));
|
||||
|
||||
// Build a signature of everything the rendered rows depend on. Skip the
|
||||
// rebuild when nothing has changed.
|
||||
string sig = ComputeScoreboardSignature(players);
|
||||
if (sig == lastScoreboardSignature) return;
|
||||
lastScoreboardSignature = sig;
|
||||
|
||||
playerListContainer.Clear();
|
||||
if (players.Count == 0)
|
||||
{
|
||||
var emptyLabel = new Label("(no players)");
|
||||
emptyLabel.style.color = new Color(0.6f, 0.6f, 0.6f);
|
||||
emptyLabel.style.unityFontStyleAndWeight = FontStyle.Italic;
|
||||
playerListContainer.Add(emptyLabel);
|
||||
return;
|
||||
}
|
||||
|
||||
var wm = WaveManager.Instance;
|
||||
foreach (var pms in players)
|
||||
{
|
||||
playerListContainer.Add(BuildScoreboardRow(pms, wm));
|
||||
}
|
||||
}
|
||||
|
||||
private VisualElement BuildScoreboardRow(PlayerMatchState pms, WaveManager wm)
|
||||
{
|
||||
var row = new VisualElement();
|
||||
row.style.flexDirection = FlexDirection.Row;
|
||||
row.style.alignItems = Align.Center;
|
||||
row.style.marginBottom = 2;
|
||||
row.style.paddingLeft = 4;
|
||||
row.style.paddingRight = 4;
|
||||
|
||||
// Layout model: three columns with explicit widths/flex so all three are
|
||||
// guaranteed visible inside the right panel. Name has flexGrow:1 so it
|
||||
// takes leftover space; gold and leaks have fixed widths and right-align
|
||||
// their text so the numbers line up vertically across rows. The previous
|
||||
// layout used flexGrow on name with justify-content space-between, which
|
||||
// pushed the leaks column off the panel's right edge on narrow widths.
|
||||
string name = string.IsNullOrEmpty(pms.DisplayName)
|
||||
? $"P{(int)pms.Slot}"
|
||||
: pms.DisplayName;
|
||||
var nameLabel = new Label(name);
|
||||
// Tint with the canonical player-slot color — same palette used by
|
||||
// builders, minimap icons, and zone outlines for consistent identity.
|
||||
// Colors were tuned for gizmos on Unity's gray scene view; they're still
|
||||
// legible on the dark panel for all slots except P9 (dark gray), which is
|
||||
// intentionally subdued relative to the others.
|
||||
nameLabel.style.color = PlayerColors.Get(pms.Slot);
|
||||
nameLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||
nameLabel.style.fontSize = 10;
|
||||
nameLabel.style.whiteSpace = WhiteSpace.NoWrap; // keep name on one line
|
||||
nameLabel.style.flexGrow = 1; // takes all leftover width
|
||||
nameLabel.style.flexShrink = 1;
|
||||
nameLabel.style.overflow = Overflow.Hidden;
|
||||
nameLabel.style.textOverflow = TextOverflow.Ellipsis;
|
||||
row.Add(nameLabel);
|
||||
|
||||
// Gold column. Read from PlayerGoldManager by clientId. May be null briefly
|
||||
// during spawn races; render "--" in that case rather than crashing.
|
||||
// Width 46px: right-aligned numbers; fits 4-digit gold totals at 10px font.
|
||||
var gm = PlayerGoldManager.GetForClient(pms.OwnerClientId);
|
||||
string goldText = gm != null ? $"{gm.CurrentGold}" : "--";
|
||||
var goldLabel = new Label(goldText);
|
||||
goldLabel.style.color = new Color(1f, 0.85f, 0.35f); // gold-y
|
||||
goldLabel.style.fontSize = 10;
|
||||
goldLabel.style.width = 46;
|
||||
goldLabel.style.flexShrink = 0;
|
||||
goldLabel.style.unityTextAlign = TextAnchor.MiddleRight;
|
||||
row.Add(goldLabel);
|
||||
|
||||
// Leaks column. Same NetworkList the local player's "leaked: N" top-bar
|
||||
// label reads — keeps the two views in sync. Always rendered (including 0)
|
||||
// so designers can see at a glance whether a player has clean runs so far.
|
||||
// Width 30px: right-aligned; 2-digit leak counts fit comfortably at 10px font.
|
||||
int leaks = (wm != null && pms.Slot != PlayerSlot.None)
|
||||
? wm.GetZoneLeakCount(pms.Slot)
|
||||
: 0;
|
||||
var leaksLabel = new Label($"{leaks}");
|
||||
leaksLabel.style.color = leaks == 0
|
||||
? new Color(0.55f, 0.85f, 0.55f)
|
||||
: new Color(0.95f, 0.6f, 0.4f);
|
||||
leaksLabel.style.fontSize = 10;
|
||||
leaksLabel.style.width = 30;
|
||||
leaksLabel.style.flexShrink = 0;
|
||||
leaksLabel.style.unityTextAlign = TextAnchor.MiddleRight;
|
||||
row.Add(leaksLabel);
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
// Components: ordered slot sequence + their name / gold / leaks. Any change
|
||||
// triggers a rebuild. Slot count itself rarely changes mid-match (joins are
|
||||
// gated by the lobby), but the values do.
|
||||
private static readonly System.Text.StringBuilder s_scoreboardSigBuf =
|
||||
new System.Text.StringBuilder(64);
|
||||
private string ComputeScoreboardSignature(System.Collections.Generic.List<PlayerMatchState> players)
|
||||
{
|
||||
s_scoreboardSigBuf.Clear();
|
||||
var wm = WaveManager.Instance;
|
||||
foreach (var pms in players)
|
||||
{
|
||||
var gm = PlayerGoldManager.GetForClient(pms.OwnerClientId);
|
||||
int gold = gm != null ? gm.CurrentGold : 0;
|
||||
int leaks = (wm != null && pms.Slot != PlayerSlot.None)
|
||||
? wm.GetZoneLeakCount(pms.Slot) : 0;
|
||||
s_scoreboardSigBuf.Append((int)pms.Slot).Append(':')
|
||||
.Append(pms.DisplayName ?? string.Empty).Append(':')
|
||||
.Append(gold).Append(':')
|
||||
.Append(leaks).Append(';');
|
||||
}
|
||||
return s_scoreboardSigBuf.ToString();
|
||||
}
|
||||
|
||||
// ----- Command grid -----------------------------------------------
|
||||
|
|
@ -807,7 +993,13 @@ namespace TD.UI
|
|||
if (def != null)
|
||||
{
|
||||
AddStatLine($"Speed: {def.MoveSpeed:0.0}");
|
||||
AddStatLine($"Bounty: {def.GoldReward} g");
|
||||
// Bounty is per-wave now (GoldConfig.Waves[N].GoldPerEnemy) rather than
|
||||
// per-enemy-type. Read the current wave's value so the tooltip is accurate.
|
||||
var wm = WaveManager.Instance;
|
||||
int currentWave = MatchState.Instance != null ? MatchState.Instance.CurrentWave : 0;
|
||||
var goldEntry = wm?.GoldConfig?.GetWaveEntry(currentWave);
|
||||
if (goldEntry != null)
|
||||
AddStatLine($"Bounty: {goldEntry.GoldPerEnemy} g");
|
||||
// (Weaknesses/resistances will go here once the resistance system lands.)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue