Add tower Sell action with refund + coin VFX/SFX
Owner-validated sell RPC refunds SellRefundPercent of invested gold (Wall refunds 100% while un-upgraded), despawns the tower, and broadcasts a coin-burst VFX + rustle SFX. Refunds do not count as per-wave income. Investment/upgrade tracking added as the seam for the future upgrade system. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
2429efbf6a
commit
9be31b1645
7 changed files with 361 additions and 12 deletions
83
Assets/_Project/Scripts/VFX/SellEffectSpawner.cs
Normal file
83
Assets/_Project/Scripts/VFX/SellEffectSpawner.cs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
// Assets/_Project/Scripts/VFX/SellEffectSpawner.cs
|
||||
using UnityEngine;
|
||||
using TD.Audio;
|
||||
|
||||
namespace TD.VFX
|
||||
{
|
||||
/// <summary>
|
||||
/// Scene singleton that plays the "tower sold" feedback — a burst of gold coins plus a
|
||||
/// coin-rustle sound — at a world position. Mirrors <see cref="TD.UI.FloatingTextSpawner"/>:
|
||||
/// visual-only, plain <c>MonoBehaviour</c>, invoked on every peer via a ClientRpc so all
|
||||
/// players see and hear a sale locally.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <b>Who calls this:</b> <see cref="TD.Gameplay.TowerPlacementManager.BroadcastSellEffect"/>
|
||||
/// routes a ClientRpc here when a tower is sold.
|
||||
///
|
||||
/// <b>Inspector setup:</b> drop this on a <c>SellEffectSpawner</c> GameObject in each Match
|
||||
/// scene. The VFX prefab is OPTIONAL — leave it empty to use the built-in
|
||||
/// <see cref="CoinBurstVfx"/> placeholder, or assign an authored particle/VFX-graph prefab.
|
||||
/// Assign the coin-rustle clip on <see cref="sellSound"/>.
|
||||
/// </remarks>
|
||||
public class SellEffectSpawner : MonoBehaviour
|
||||
{
|
||||
// ----- Singleton --------------------------------------------------
|
||||
|
||||
public static SellEffectSpawner Instance { get; private set; }
|
||||
|
||||
// ----- Inspector --------------------------------------------------
|
||||
|
||||
[Tooltip("Optional authored VFX prefab spawned at the sale position. Leave empty to " +
|
||||
"fall back to the built-in code-generated CoinBurstVfx placeholder.")]
|
||||
[SerializeField] private GameObject coinBurstPrefab;
|
||||
|
||||
[Tooltip("Coin-rustle sound played (2D, via AudioManager) when a tower is sold.")]
|
||||
[SerializeField] private SoundConfig sellSound;
|
||||
|
||||
[Tooltip("Vertical offset above the sale position where the coin burst originates, so " +
|
||||
"it reads as coming from the tower body rather than the ground.")]
|
||||
[SerializeField] private float verticalOffset = 0.75f;
|
||||
|
||||
// ----- Lifecycle --------------------------------------------------
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Debug.LogWarning("[SellEffectSpawner] Duplicate instance detected. " +
|
||||
"Only one should exist per scene.");
|
||||
return;
|
||||
}
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (Instance == this) Instance = null;
|
||||
}
|
||||
|
||||
// ----- Public API -------------------------------------------------
|
||||
|
||||
/// <summary>Plays the coin burst + rustle sound at <paramref name="worldPos"/>.</summary>
|
||||
public void Play(Vector3 worldPos)
|
||||
{
|
||||
Vector3 spawnPos = worldPos + Vector3.up * verticalOffset;
|
||||
|
||||
if (coinBurstPrefab != null)
|
||||
{
|
||||
Instantiate(coinBurstPrefab, spawnPos, Quaternion.identity);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Zero-art fallback: a self-configuring, self-destroying particle burst.
|
||||
var go = new GameObject("CoinBurst");
|
||||
go.transform.position = spawnPos;
|
||||
go.AddComponent<CoinBurstVfx>();
|
||||
}
|
||||
|
||||
if (sellSound.clip != null)
|
||||
AudioManager.Instance?.Play(sellSound.clip, AudioCategory.UI,
|
||||
sellSound.RandomPitch(), sellSound.volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue