Paint towers with some the colors of the wind
This commit is contained in:
parent
fec4433691
commit
04ead32846
15 changed files with 584 additions and 32 deletions
|
|
@ -48,11 +48,12 @@ namespace TD.Gameplay
|
|||
// ----- Inspector --------------------------------------------------
|
||||
|
||||
[Header("Visuals")]
|
||||
[Tooltip("Mesh renderers tinted with the owner's player color. " +
|
||||
"Drag in only the tower body's renderers — exclude anything " +
|
||||
"that has its own color rules (selection rings, range " +
|
||||
"indicators, FX). If left empty, the tower is NOT tinted " +
|
||||
"and the prefab's baked materials show through.")]
|
||||
[Tooltip("Mesh renderers tinted with the owner's player color (and the Paint " +
|
||||
"tool's color). Drag in only the tower body's renderers to exclude " +
|
||||
"anything with its own color rules (selection rings, range indicators, " +
|
||||
"FX). If left EMPTY, every MeshRenderer under the tower is tinted " +
|
||||
"automatically — fine for most prefabs; populate this only when you need " +
|
||||
"to exclude specific children.")]
|
||||
[SerializeField] private MeshRenderer[] tintedRenderers;
|
||||
|
||||
// ----- Networked state ------------------------------------------------
|
||||
|
|
@ -82,6 +83,16 @@ namespace TD.Gameplay
|
|||
readPerm: NetworkVariableReadPermission.Everyone,
|
||||
writePerm: NetworkVariableWritePermission.Server);
|
||||
|
||||
// Paint color applied by the Paint tool. None means "unpainted" — the tower
|
||||
// shows its owner color. Set server-side via RequestPaintServerRpc (own-tower
|
||||
// only). Replicated so every client re-tints; later iterations will read this
|
||||
// to drive projectile behavior.
|
||||
private readonly NetworkVariable<PaintColor> paintColor =
|
||||
new NetworkVariable<PaintColor>(
|
||||
PaintColor.None,
|
||||
readPerm: NetworkVariableReadPermission.Everyone,
|
||||
writePerm: NetworkVariableWritePermission.Server);
|
||||
|
||||
// ----- Local resolved state -------------------------------------------
|
||||
|
||||
// Resolved on every client in OnNetworkSpawn from definitionName.
|
||||
|
|
@ -114,6 +125,10 @@ namespace TD.Gameplay
|
|||
/// <summary>The PlayerSlot that placed this tower.</summary>
|
||||
public PlayerSlot Owner => ownerSlot.Value;
|
||||
|
||||
/// <summary>The paint color applied to this tower, or <see cref="PaintColor.None"/>
|
||||
/// if unpainted (showing its owner color).</summary>
|
||||
public PaintColor Paint => paintColor.Value;
|
||||
|
||||
/// <summary>The footprint anchor tile (SW corner, world-tile coords).</summary>
|
||||
public Vector2Int AnchorTile => anchorTile.Value;
|
||||
|
||||
|
|
@ -209,8 +224,10 @@ namespace TD.Gameplay
|
|||
// but SetWalkable/SetOccupied are idempotent — double-stamping is safe.
|
||||
StampFootprint(walkable: false, occupied: true);
|
||||
|
||||
// Apply owner color to the mesh renderer.
|
||||
ApplyOwnerColor();
|
||||
// Apply the tower tint (paint color if painted, else owner color), and
|
||||
// re-tint on every client whenever the paint color changes.
|
||||
ApplyTint();
|
||||
paintColor.OnValueChanged += HandlePaintColorChanged;
|
||||
|
||||
// Register for minimap rendering.
|
||||
MinimapEntityRegistry.Register(this);
|
||||
|
|
@ -239,6 +256,8 @@ namespace TD.Gameplay
|
|||
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
paintColor.OnValueChanged -= HandlePaintColorChanged;
|
||||
|
||||
// Un-stamp the footprint when the tower is destroyed (sold, wave end, etc.)
|
||||
// so the tiles become walkable and buildable again.
|
||||
StampFootprint(walkable: true, occupied: false);
|
||||
|
|
@ -253,6 +272,33 @@ namespace TD.Gameplay
|
|||
SelectionState.Instance.Clear();
|
||||
}
|
||||
|
||||
// ----- Paint tool -----------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Client → server request to paint this tower. The server applies the color
|
||||
/// only if the requesting client owns this tower (matching the placement
|
||||
/// ownership rule). <see cref="PaintColor.None"/> resets the tower to its owner
|
||||
/// color. The change replicates back to every client via
|
||||
/// <see cref="paintColor"/>'s OnValueChanged.
|
||||
/// </summary>
|
||||
[Rpc(SendTo.Server)]
|
||||
public void RequestPaintServerRpc(PaintColor color, RpcParams rpcParams = default)
|
||||
{
|
||||
PlayerSlot senderSlot = PlayerMatchState.SlotForClient(rpcParams.Receive.SenderClientId);
|
||||
if (senderSlot == PlayerSlot.None || senderSlot != ownerSlot.Value)
|
||||
{
|
||||
Debug.Log($"[TowerInstance] Paint rejected: client " +
|
||||
$"{rpcParams.Receive.SenderClientId} ({senderSlot}) does not own " +
|
||||
$"tower owned by {ownerSlot.Value}.");
|
||||
return;
|
||||
}
|
||||
|
||||
paintColor.Value = color;
|
||||
}
|
||||
|
||||
// Re-tint on every client (and the server) when the replicated paint color changes.
|
||||
private void HandlePaintColorChanged(PaintColor previous, PaintColor current) => ApplyTint();
|
||||
|
||||
// ----- IMinimapEntity -------------------------------------------------
|
||||
//
|
||||
// Towers are static, so WorldPosition is cheap (no movement to track). Color reflects
|
||||
|
|
@ -343,28 +389,50 @@ namespace TD.Gameplay
|
|||
// silently ignored.
|
||||
private static readonly int BaseColorPropertyId = Shader.PropertyToID("_BaseColor");
|
||||
|
||||
private void ApplyOwnerColor()
|
||||
private void ApplyTint()
|
||||
{
|
||||
Color ownerColor = PlayerColors.Get(ownerSlot.Value);
|
||||
ownerColor.a = 1f;
|
||||
// Paint color takes precedence when set; otherwise fall back to the owner
|
||||
// color. Paint.None means "unpainted" → show owner color.
|
||||
Color tint = paintColor.Value != PaintColor.None
|
||||
? PaintColors.Get(paintColor.Value)
|
||||
: PlayerColors.Get(ownerSlot.Value);
|
||||
tint.a = 1f;
|
||||
|
||||
// MaterialPropertyBlock sets per-renderer properties without allocating
|
||||
// a new Material object. Safe to reuse across calls on the same instance.
|
||||
// All Unity standard/URP shaders expose _Color or _BaseColor, so writing
|
||||
// both lets the tint apply regardless of which shader the prefab uses.
|
||||
colorPropertyBlock ??= new MaterialPropertyBlock();
|
||||
colorPropertyBlock.SetColor(ColorPropertyId, ownerColor);
|
||||
colorPropertyBlock.SetColor(BaseColorPropertyId, ownerColor);
|
||||
colorPropertyBlock.SetColor(ColorPropertyId, tint);
|
||||
colorPropertyBlock.SetColor(BaseColorPropertyId, tint);
|
||||
|
||||
// Tint only the renderers explicitly listed in the inspector. Avoids
|
||||
// accidentally re-coloring decorative children, FX, etc. (Mirrors
|
||||
// Builder.tintedRenderers — same rationale.)
|
||||
if (tintedRenderers == null) return;
|
||||
foreach (var rend in tintedRenderers)
|
||||
foreach (var rend in ResolveTintRenderers())
|
||||
{
|
||||
if (rend == null) continue;
|
||||
rend.SetPropertyBlock(colorPropertyBlock);
|
||||
}
|
||||
}
|
||||
|
||||
// Renderers actually tinted. Prefer the inspector-assigned list (lets a prefab
|
||||
// exclude decorative children, FX, etc.). When that list is empty — the common
|
||||
// case for imported models nobody has hand-wired — fall back to every MeshRenderer
|
||||
// under the tower so owner-color and paint Just Work without per-prefab setup.
|
||||
// Cached after the first resolve.
|
||||
private MeshRenderer[] resolvedTintRenderers;
|
||||
|
||||
private MeshRenderer[] ResolveTintRenderers()
|
||||
{
|
||||
if (tintedRenderers != null && tintedRenderers.Length > 0)
|
||||
return tintedRenderers;
|
||||
|
||||
if (resolvedTintRenderers == null)
|
||||
{
|
||||
resolvedTintRenderers = GetComponentsInChildren<MeshRenderer>(includeInactive: true);
|
||||
if (resolvedTintRenderers.Length == 0)
|
||||
Debug.LogWarning($"[TowerInstance] '{name}' has no MeshRenderers to tint — " +
|
||||
$"owner color and paint will have no visible effect.");
|
||||
}
|
||||
return resolvedTintRenderers;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue