Adding Gold Manager with initial network setup
This commit is contained in:
parent
048e618f1e
commit
9369763df5
15 changed files with 488 additions and 100 deletions
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"name": "TD.Core"
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f62380d35dc31494f957f6b5559755f5
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
152
Assets/_Project/Scripts/Gameplay/GoldManager.cs
Normal file
152
Assets/_Project/Scripts/Gameplay/GoldManager.cs
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TD.Gameplay
|
||||
{
|
||||
/// <summary>
|
||||
/// GoldManager — canonical server-authoritative template for this project.
|
||||
///
|
||||
/// Every gameplay system (towers, enemies, waves, damage) should follow
|
||||
/// the same three-beat pattern demonstrated here:
|
||||
/// 1. State lives in NetworkVariables, which only the server can write.
|
||||
/// 2. Clients REQUEST changes via [Rpc(SendTo.Server, ...)] methods.
|
||||
/// They never change state directly.
|
||||
/// 3. The server VALIDATES the request before applying it.
|
||||
/// Never trust the client.
|
||||
///
|
||||
/// Cosmetic-only reactions (sounds, VFX, UI popups) can use
|
||||
/// [Rpc(SendTo.ClientsAndHost)] or [Rpc(SendTo.NotServer)] to broadcast.
|
||||
/// </summary>
|
||||
public class GoldManager : NetworkBehaviour
|
||||
{
|
||||
private void Awake()
|
||||
{
|
||||
Debug.Log("[GoldManager] Awake ran!");
|
||||
}
|
||||
|
||||
// --- Tunables (editable in Inspector) -----------------------------
|
||||
|
||||
[Tooltip("How much gold every player starts with when the game begins.")]
|
||||
[SerializeField] private int startingGold = 100;
|
||||
|
||||
// --- Networked state ----------------------------------------------
|
||||
|
||||
// A NetworkVariable<T> automatically syncs from server to clients.
|
||||
// readPerm = Everyone: all clients can read the current value.
|
||||
// writePerm = Server: only the server can change it.
|
||||
private readonly NetworkVariable<int> currentGold = new NetworkVariable<int>(
|
||||
value: 0,
|
||||
readPerm: NetworkVariableReadPermission.Everyone,
|
||||
writePerm: NetworkVariableWritePermission.Server
|
||||
);
|
||||
|
||||
// Public read-only accessor for other scripts (UI, tower placement).
|
||||
public int CurrentGold => currentGold.Value;
|
||||
|
||||
// --- Lifecycle ----------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// OnNetworkSpawn runs on every peer (server + all clients) when this
|
||||
/// NetworkBehaviour becomes active on the network. Replaces Start()
|
||||
/// for networked setup.
|
||||
/// </summary>
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
Debug.Log($"[GoldManager] OnNetworkSpawn ran. IsServer={IsServer}, IsClient={IsClient}, IsHost={IsHost}");
|
||||
|
||||
currentGold.OnValueChanged += HandleGoldChanged;
|
||||
Debug.Log($"[GoldManager] Subscribed to OnValueChanged. Current value before init: {currentGold.Value}");
|
||||
|
||||
if (IsServer)
|
||||
{
|
||||
currentGold.Value = startingGold;
|
||||
Debug.Log($"[GoldManager] Server initialized gold. Current value after set: {currentGold.Value}");
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
// Always unsubscribe to avoid callback leaks.
|
||||
currentGold.OnValueChanged -= HandleGoldChanged;
|
||||
}
|
||||
|
||||
private void HandleGoldChanged(int previous, int current)
|
||||
{
|
||||
// Fires on every peer whenever the value syncs. Use Log here so
|
||||
// you can see syncing in the Console during development.
|
||||
Debug.Log($"[GoldManager] Gold changed: {previous} -> {current}");
|
||||
}
|
||||
|
||||
// --- Public API (called by client-side code) ----------------------
|
||||
|
||||
/// <summary>
|
||||
/// Client-side entry point for spending gold. Called by gameplay code
|
||||
/// like TowerPlacement when the local player clicks "build tower."
|
||||
///
|
||||
/// The actual spending happens on the server via the Rpc.
|
||||
/// </summary>
|
||||
public void RequestSpendGold(int amount)
|
||||
{
|
||||
SpendGoldRpc(amount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server-side entry point for awarding gold (wave clear, enemy kill).
|
||||
/// Not Rpc-wrapped — this is called directly by server game logic in
|
||||
/// response to server-authoritative events.
|
||||
/// </summary>
|
||||
public void AwardGold(int amount)
|
||||
{
|
||||
if (!IsServer)
|
||||
{
|
||||
Debug.LogError("[GoldManager] AwardGold called on a client! " +
|
||||
"Only server code should call this directly.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (amount <= 0) return;
|
||||
currentGold.Value += amount;
|
||||
}
|
||||
|
||||
// --- Server-side RPC ----------------------------------------------
|
||||
|
||||
// [Rpc(SendTo.Server, ...)] means: a client calls this locally, but
|
||||
// NGO routes the call and executes the method on the server.
|
||||
//
|
||||
// RequireOwnership = false lets any client call it (correct for a
|
||||
// shared GoldManager). For per-player NetworkObjects you'd usually
|
||||
// leave the default ownership requirement in place.
|
||||
//
|
||||
// Naming convention: methods with [Rpc] attributes must end with "Rpc".
|
||||
// The source generator relies on this suffix.
|
||||
[Rpc(SendTo.Server, RequireOwnership = false)]
|
||||
private void SpendGoldRpc(int amount, RpcParams rpcParams = default)
|
||||
{
|
||||
// This method body runs on the server only.
|
||||
// Validate everything — do not trust the client.
|
||||
|
||||
// Validation 1: reject non-positive amounts. A negative amount
|
||||
// would let a malicious client GAIN gold if we just subtracted.
|
||||
if (amount <= 0)
|
||||
{
|
||||
Debug.LogWarning($"[GoldManager] Rejected spend of {amount} " +
|
||||
$"from client {rpcParams.Receive.SenderClientId}: " +
|
||||
$"amount must be positive.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Validation 2: can't spend more than current balance.
|
||||
if (currentGold.Value < amount)
|
||||
{
|
||||
Debug.LogWarning($"[GoldManager] Rejected spend of {amount} " +
|
||||
$"from client {rpcParams.Receive.SenderClientId}: " +
|
||||
$"insufficient funds (have {currentGold.Value}).");
|
||||
return;
|
||||
}
|
||||
|
||||
// Server applies the change. NetworkVariable syncs to clients
|
||||
// automatically at the next network tick.
|
||||
currentGold.Value -= amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/_Project/Scripts/Gameplay/GoldManager.cs.meta
Normal file
2
Assets/_Project/Scripts/Gameplay/GoldManager.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d44ebdd0b2fc4144c8f8a181a714b738
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"name": "TD.Gameplay",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:f62380d35dc31494f957f6b5559755f5"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 027bbe8f3a58f024fa5c3fd977f0371b
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"name": "TD.Networking",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:f62380d35dc31494f957f6b5559755f5",
|
||||
"GUID:027bbe8f3a58f024fa5c3fd977f0371b",
|
||||
"GUID:1491147abca9d7d4bb7105af628b223e"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 824f8ff8ddfcf9a4da4b9c38a264d73e
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"name": "TD.UI",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:027bbe8f3a58f024fa5c3fd977f0371b",
|
||||
"GUID:f62380d35dc31494f957f6b5559755f5"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: dccb24d2a739b9d46aa3fc7d50c9511e
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loading…
Add table
Add a link
Reference in a new issue