purchase buffs menu works
This commit is contained in:
parent
3737ad517c
commit
79cd331141
6 changed files with 222 additions and 7 deletions
|
|
@ -48,7 +48,7 @@ MonoBehaviour:
|
||||||
m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3}
|
m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject
|
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject
|
||||||
GlobalObjectIdHash: 121878297
|
GlobalObjectIdHash: 1552073510
|
||||||
InScenePlacedSourceGlobalObjectIdHash: 0
|
InScenePlacedSourceGlobalObjectIdHash: 0
|
||||||
DeferredDespawnTick: 0
|
DeferredDespawnTick: 0
|
||||||
Ownership: 1
|
Ownership: 1
|
||||||
|
|
@ -116,5 +116,4 @@ MonoBehaviour:
|
||||||
m_EditorClassIdentifier: Assembly-CSharp::TD.Gameplay.PlayerBuffManager
|
m_EditorClassIdentifier: Assembly-CSharp::TD.Gameplay.PlayerBuffManager
|
||||||
ShowTopMostFoldoutHeaderGroup: 1
|
ShowTopMostFoldoutHeaderGroup: 1
|
||||||
categories:
|
categories:
|
||||||
- {fileID: 0}
|
- {fileID: 11400000, guid: d8ed3b9535538f7fc82be878cc307d26, type: 2}
|
||||||
- {fileID: 0}
|
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,14 @@ namespace TD.Gameplay
|
||||||
// Right-click. Suppressed entirely during a modal mode (placement/paint
|
// Right-click. Suppressed entirely during a modal mode (placement/paint
|
||||||
// controllers handle right-click as cancel there) and when over HUD.
|
// controllers handle right-click as cancel there) and when over HUD.
|
||||||
if (isModal) return;
|
if (isModal) return;
|
||||||
|
|
||||||
|
// B: toggle buff menu.
|
||||||
|
if (keyboard != null && keyboard.bKey.wasPressedThisFrame
|
||||||
|
&& !HUDController.IsTextInputActive)
|
||||||
|
{
|
||||||
|
HUDController.Instance?.ToggleBuffMenu();
|
||||||
|
}
|
||||||
|
|
||||||
if (pointerOverHud) return;
|
if (pointerOverHud) return;
|
||||||
if (!mouse.rightButton.wasPressedThisFrame) return;
|
if (!mouse.rightButton.wasPressedThisFrame) return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Assets/_Project/Scripts/Gameplay/PlayerBuffManager.cs
|
// Assets/_Project/Scripts/Gameplay/PlayerBuffManager.cs
|
||||||
|
using System.Collections.Generic;
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
using Unity.Netcode;
|
using Unity.Netcode;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
@ -27,6 +28,31 @@ namespace TD.Gameplay
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class PlayerBuffManager : NetworkBehaviour
|
public class PlayerBuffManager : NetworkBehaviour
|
||||||
{
|
{
|
||||||
|
// ----- Static registry (mirrors PlayerGoldManager pattern) -----------
|
||||||
|
|
||||||
|
private static readonly Dictionary<ulong, PlayerBuffManager> s_byClientId
|
||||||
|
= new Dictionary<ulong, PlayerBuffManager>();
|
||||||
|
|
||||||
|
/// <summary>Returns the PlayerBuffManager owned by the given client, or null.</summary>
|
||||||
|
public static PlayerBuffManager GetForClient(ulong clientId)
|
||||||
|
{
|
||||||
|
s_byClientId.TryGetValue(clientId, out var mgr);
|
||||||
|
return mgr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Convenience: the local client's own buff manager.</summary>
|
||||||
|
public static PlayerBuffManager Local
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var nm = NetworkManager.Singleton;
|
||||||
|
if (nm == null) return null;
|
||||||
|
return GetForClient(nm.LocalClientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Inspector ------------------------------------------------------
|
||||||
|
|
||||||
[Tooltip("Purchasable buff categories shown in the buff menu. Index in this " +
|
[Tooltip("Purchasable buff categories shown in the buff menu. Index in this " +
|
||||||
"array is the categoryIndex passed to RequestPurchaseBuffRpc.")]
|
"array is the categoryIndex passed to RequestPurchaseBuffRpc.")]
|
||||||
[SerializeField] private BuffCategory[] categories;
|
[SerializeField] private BuffCategory[] categories;
|
||||||
|
|
@ -38,6 +64,19 @@ namespace TD.Gameplay
|
||||||
buffs = new NetworkList<ActiveBuff>();
|
buffs = new NetworkList<ActiveBuff>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----- NGO lifecycle ----------------------------------------------
|
||||||
|
|
||||||
|
public override void OnNetworkSpawn()
|
||||||
|
{
|
||||||
|
s_byClientId[OwnerClientId] = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnNetworkDespawn()
|
||||||
|
{
|
||||||
|
if (s_byClientId.TryGetValue(OwnerClientId, out var registered) && registered == this)
|
||||||
|
s_byClientId.Remove(OwnerClientId);
|
||||||
|
}
|
||||||
|
|
||||||
// ----- Public API -------------------------------------------------
|
// ----- Public API -------------------------------------------------
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ namespace TD.UI
|
||||||
[RequireComponent(typeof(UIDocument))]
|
[RequireComponent(typeof(UIDocument))]
|
||||||
public class HUDController : MonoBehaviour
|
public class HUDController : MonoBehaviour
|
||||||
{
|
{
|
||||||
|
public static HUDController Instance { get; private set; }
|
||||||
|
|
||||||
// ----- Inspector --------------------------------------------------
|
// ----- Inspector --------------------------------------------------
|
||||||
|
|
||||||
[Header("Scene References")]
|
[Header("Scene References")]
|
||||||
|
|
@ -93,6 +95,10 @@ namespace TD.UI
|
||||||
|
|
||||||
// Match-end overlay — built once on Start and toggled on Phase changes.
|
// Match-end overlay — built once on Start and toggled on Phase changes.
|
||||||
private VisualElement matchEndOverlay;
|
private VisualElement matchEndOverlay;
|
||||||
|
|
||||||
|
// Buff menu overlay — toggled by the B key via ToggleBuffMenu().
|
||||||
|
private VisualElement buffMenuOverlay;
|
||||||
|
private VisualElement buffMenuContent;
|
||||||
private Label matchEndTitle;
|
private Label matchEndTitle;
|
||||||
|
|
||||||
// Chat panel (bottom-left, above portrait) — programmatic. The container
|
// Chat panel (bottom-left, above portrait) — programmatic. The container
|
||||||
|
|
@ -309,6 +315,9 @@ namespace TD.UI
|
||||||
// MatchState.OnPhaseChanged fires Victory or Defeat.
|
// MatchState.OnPhaseChanged fires Victory or Defeat.
|
||||||
BuildMatchEndOverlay(root);
|
BuildMatchEndOverlay(root);
|
||||||
|
|
||||||
|
// Build the buff menu overlay. Hidden until the player presses B.
|
||||||
|
BuildBuffMenuOverlay(root);
|
||||||
|
|
||||||
// Chat feed + input. Anchored bottom-left, just above the portrait/bottom-ui bar.
|
// Chat feed + input. Anchored bottom-left, just above the portrait/bottom-ui bar.
|
||||||
// Player typing toggled with Enter; system messages (e.g. life lost) post via
|
// Player typing toggled with Enter; system messages (e.g. life lost) post via
|
||||||
// ChatService.PostLocalSystem on every peer.
|
// ChatService.PostLocalSystem on every peer.
|
||||||
|
|
@ -323,6 +332,11 @@ namespace TD.UI
|
||||||
uiInitialized = true;
|
uiInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
Instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
TowerPlacementController.OnRejectionMessageReady += ShowRejectionMessage;
|
TowerPlacementController.OnRejectionMessageReady += ShowRejectionMessage;
|
||||||
|
|
@ -437,6 +451,8 @@ namespace TD.UI
|
||||||
|
|
||||||
private void OnDestroy()
|
private void OnDestroy()
|
||||||
{
|
{
|
||||||
|
if (Instance == this) Instance = null;
|
||||||
|
|
||||||
minimapView?.Dispose();
|
minimapView?.Dispose();
|
||||||
minimapView = null;
|
minimapView = null;
|
||||||
|
|
||||||
|
|
@ -1208,6 +1224,149 @@ namespace TD.UI
|
||||||
|
|
||||||
// ----- Chat feed + input -----------------------------------------
|
// ----- Chat feed + input -----------------------------------------
|
||||||
|
|
||||||
|
// ----- Buff menu overlay ------------------------------------------
|
||||||
|
|
||||||
|
private void BuildBuffMenuOverlay(VisualElement root)
|
||||||
|
{
|
||||||
|
buffMenuOverlay = new VisualElement();
|
||||||
|
buffMenuOverlay.style.position = Position.Absolute;
|
||||||
|
buffMenuOverlay.style.left = 0;
|
||||||
|
buffMenuOverlay.style.right = 0;
|
||||||
|
buffMenuOverlay.style.top = 0;
|
||||||
|
buffMenuOverlay.style.bottom = 0;
|
||||||
|
buffMenuOverlay.style.alignItems = Align.Center;
|
||||||
|
buffMenuOverlay.style.justifyContent = Justify.Center;
|
||||||
|
buffMenuOverlay.style.backgroundColor = new Color(0f, 0f, 0f, 0.5f);
|
||||||
|
buffMenuOverlay.style.display = DisplayStyle.None;
|
||||||
|
buffMenuOverlay.pickingMode = PickingMode.Position;
|
||||||
|
|
||||||
|
var panel = new VisualElement();
|
||||||
|
panel.style.minWidth = 340;
|
||||||
|
panel.style.paddingTop = 20;
|
||||||
|
panel.style.paddingBottom = 20;
|
||||||
|
panel.style.paddingLeft = 28;
|
||||||
|
panel.style.paddingRight = 28;
|
||||||
|
panel.style.backgroundColor = new Color(0.08f, 0.08f, 0.10f, 0.95f);
|
||||||
|
panel.style.borderTopWidth = panel.style.borderBottomWidth =
|
||||||
|
panel.style.borderLeftWidth = panel.style.borderRightWidth = 2;
|
||||||
|
var border = new Color(0.4f, 0.4f, 0.45f);
|
||||||
|
panel.style.borderTopColor = panel.style.borderBottomColor =
|
||||||
|
panel.style.borderLeftColor = panel.style.borderRightColor = border;
|
||||||
|
|
||||||
|
var title = new Label("Buffs");
|
||||||
|
title.style.fontSize = 24;
|
||||||
|
title.style.color = Color.white;
|
||||||
|
title.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||||
|
title.style.marginBottom = 12;
|
||||||
|
panel.Add(title);
|
||||||
|
|
||||||
|
// Scrollable content area: current buffs + purchase buttons.
|
||||||
|
// Rebuilt each time the menu is shown via RefreshBuffMenuContent().
|
||||||
|
buffMenuContent = new VisualElement();
|
||||||
|
panel.Add(buffMenuContent);
|
||||||
|
|
||||||
|
var closeBtn = new Button(() => SetBuffMenuVisible(false)) { text = "Close [B]" };
|
||||||
|
closeBtn.style.marginTop = 16;
|
||||||
|
closeBtn.style.height = 32;
|
||||||
|
panel.Add(closeBtn);
|
||||||
|
|
||||||
|
buffMenuOverlay.Add(panel);
|
||||||
|
root.Add(buffMenuOverlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Toggles the buff menu overlay. Called by <see cref="TD.Gameplay.BuilderInputController"/>
|
||||||
|
/// when the player presses B.
|
||||||
|
/// </summary>
|
||||||
|
public void ToggleBuffMenu()
|
||||||
|
{
|
||||||
|
bool nowVisible = buffMenuOverlay?.style.display == DisplayStyle.None;
|
||||||
|
SetBuffMenuVisible(nowVisible);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetBuffMenuVisible(bool visible)
|
||||||
|
{
|
||||||
|
if (buffMenuOverlay == null) return;
|
||||||
|
buffMenuOverlay.style.display = visible ? DisplayStyle.Flex : DisplayStyle.None;
|
||||||
|
if (visible) RefreshBuffMenuContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshBuffMenuContent()
|
||||||
|
{
|
||||||
|
if (buffMenuContent == null) return;
|
||||||
|
buffMenuContent.Clear();
|
||||||
|
|
||||||
|
var buffManager = TD.Gameplay.PlayerBuffManager.Local;
|
||||||
|
|
||||||
|
// Current buffs section.
|
||||||
|
var buffsHeader = new Label("Active Buffs");
|
||||||
|
buffsHeader.style.color = new Color(0.7f, 0.9f, 0.7f);
|
||||||
|
buffsHeader.style.fontSize = 14;
|
||||||
|
buffsHeader.style.marginBottom = 4;
|
||||||
|
buffMenuContent.Add(buffsHeader);
|
||||||
|
|
||||||
|
if (buffManager == null || buffManager.Buffs.Count == 0)
|
||||||
|
{
|
||||||
|
var none = new Label("None");
|
||||||
|
none.style.color = new Color(0.55f, 0.55f, 0.55f);
|
||||||
|
none.style.marginBottom = 8;
|
||||||
|
buffMenuContent.Add(none);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < buffManager.Buffs.Count; i++)
|
||||||
|
{
|
||||||
|
var buff = buffManager.Buffs[i];
|
||||||
|
string statName = buff.Stat == TD.Core.BuffStat.Damage ? "Damage" : "Attack Speed";
|
||||||
|
string activeTag = buff.IsActive ? "" : " [DISABLED]";
|
||||||
|
var row = new Label($"• {buff.DisplayName} ({statName} ×{buff.Multiplier:F2}){activeTag}");
|
||||||
|
row.style.color = buff.IsActive ? Color.white : new Color(0.5f, 0.5f, 0.5f);
|
||||||
|
row.style.fontSize = 13;
|
||||||
|
buffMenuContent.Add(row);
|
||||||
|
}
|
||||||
|
buffMenuContent.style.marginBottom = 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purchase buttons section.
|
||||||
|
var buyHeader = new Label("Purchase");
|
||||||
|
buyHeader.style.color = new Color(0.9f, 0.8f, 0.5f);
|
||||||
|
buyHeader.style.fontSize = 14;
|
||||||
|
buyHeader.style.marginTop = 8;
|
||||||
|
buyHeader.style.marginBottom = 4;
|
||||||
|
buffMenuContent.Add(buyHeader);
|
||||||
|
|
||||||
|
int categoryCount = buffManager?.CategoryCount ?? 0;
|
||||||
|
if (categoryCount == 0)
|
||||||
|
{
|
||||||
|
var none = new Label("No categories available.");
|
||||||
|
none.style.color = new Color(0.55f, 0.55f, 0.55f);
|
||||||
|
buffMenuContent.Add(none);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int localGold = TD.Gameplay.PlayerGoldManager.Local?.CurrentGold ?? 0;
|
||||||
|
for (int i = 0; i < categoryCount; i++)
|
||||||
|
{
|
||||||
|
var category = buffManager.GetCategory(i);
|
||||||
|
if (category == null) continue;
|
||||||
|
|
||||||
|
int idx = i; // capture for lambda
|
||||||
|
var btn = new Button(() =>
|
||||||
|
{
|
||||||
|
TD.Gameplay.PlayerBuffManager.Local?.RequestPurchaseBuffRpc(idx);
|
||||||
|
RefreshBuffMenuContent();
|
||||||
|
})
|
||||||
|
{
|
||||||
|
text = $"{category.DisplayName} — {category.Cost}g"
|
||||||
|
};
|
||||||
|
btn.style.height = 34;
|
||||||
|
btn.style.fontSize = 13;
|
||||||
|
btn.style.marginBottom = 4;
|
||||||
|
btn.SetEnabled(localGold >= category.Cost);
|
||||||
|
buffMenuContent.Add(btn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Bottom-left chat panel. Anchored 12px from the left edge, with the
|
// Bottom-left chat panel. Anchored 12px from the left edge, with the
|
||||||
// bottom edge sitting above the 220px bottom-ui. Layout uses a flex
|
// bottom edge sitting above the 220px bottom-ui. Layout uses a flex
|
||||||
// column: scrollable feed on top, input below. The feed clips at
|
// column: scrollable feed on top, input below. The feed clips at
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,12 @@
|
||||||
"com.unity.netcode.gameobjects": "2.11.0",
|
"com.unity.netcode.gameobjects": "2.11.0",
|
||||||
"com.unity.probuilder": "6.0.9",
|
"com.unity.probuilder": "6.0.9",
|
||||||
"com.unity.render-pipelines.universal": "17.4.0",
|
"com.unity.render-pipelines.universal": "17.4.0",
|
||||||
"com.unity.sdk.linux-x86_64": "1.0.2",
|
"com.unity.sdk.linux-x86_64": "1.1.0",
|
||||||
"com.unity.services.multiplayer": "2.1.3",
|
"com.unity.services.multiplayer": "2.1.3",
|
||||||
"com.unity.terrain-tools": "5.3.2",
|
"com.unity.terrain-tools": "5.3.2",
|
||||||
"com.unity.test-framework": "1.6.0",
|
"com.unity.test-framework": "1.6.0",
|
||||||
"com.unity.timeline": "1.8.12",
|
"com.unity.timeline": "1.8.12",
|
||||||
|
"com.unity.toolchain.linux-x86_64-linux": "1.1.0",
|
||||||
"com.unity.toolchain.win-x86_64-linux": "1.0.2",
|
"com.unity.toolchain.win-x86_64-linux": "1.0.2",
|
||||||
"com.unity.ugui": "2.0.0",
|
"com.unity.ugui": "2.0.0",
|
||||||
"com.unity.visualscripting": "1.9.11",
|
"com.unity.visualscripting": "1.9.11",
|
||||||
|
|
|
||||||
|
|
@ -221,11 +221,11 @@
|
||||||
"url": "https://packages.unity.com"
|
"url": "https://packages.unity.com"
|
||||||
},
|
},
|
||||||
"com.unity.sdk.linux-x86_64": {
|
"com.unity.sdk.linux-x86_64": {
|
||||||
"version": "1.0.2",
|
"version": "1.1.0",
|
||||||
"depth": 0,
|
"depth": 0,
|
||||||
"source": "registry",
|
"source": "registry",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"com.unity.sysroot.base": "1.0.2"
|
"com.unity.sysroot.base": "1.1.0"
|
||||||
},
|
},
|
||||||
"url": "https://packages.unity.com"
|
"url": "https://packages.unity.com"
|
||||||
},
|
},
|
||||||
|
|
@ -355,7 +355,7 @@
|
||||||
"url": "https://packages.unity.com"
|
"url": "https://packages.unity.com"
|
||||||
},
|
},
|
||||||
"com.unity.sysroot.base": {
|
"com.unity.sysroot.base": {
|
||||||
"version": "1.0.2",
|
"version": "1.1.0",
|
||||||
"depth": 1,
|
"depth": 1,
|
||||||
"source": "registry",
|
"source": "registry",
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
|
|
@ -403,6 +403,15 @@
|
||||||
},
|
},
|
||||||
"url": "https://packages.unity.com"
|
"url": "https://packages.unity.com"
|
||||||
},
|
},
|
||||||
|
"com.unity.toolchain.linux-x86_64-linux": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"depth": 0,
|
||||||
|
"source": "registry",
|
||||||
|
"dependencies": {
|
||||||
|
"com.unity.sysroot.base": "1.1.0"
|
||||||
|
},
|
||||||
|
"url": "https://packages.unity.com"
|
||||||
|
},
|
||||||
"com.unity.toolchain.win-x86_64-linux": {
|
"com.unity.toolchain.win-x86_64-linux": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"depth": 0,
|
"depth": 0,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue