Adding tons of new functionality
Decals, ghost textures, placement functionality, builder stub ins, a new camera system, and more.
This commit is contained in:
parent
56dc775c68
commit
a63cce53e2
54 changed files with 4817 additions and 238 deletions
141
Assets/_Project/Scripts/Gameplay/PlayerBuilderSpawner.cs
Normal file
141
Assets/_Project/Scripts/Gameplay/PlayerBuilderSpawner.cs
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
// Assets/_Project/Scripts/Gameplay/PlayerBuilderSpawner.cs
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using TD.Core;
|
||||
using TD.Levels;
|
||||
|
||||
namespace TD.Gameplay
|
||||
{
|
||||
/// <summary>
|
||||
/// Lives on the Player Prefab. On the server, when the player NetworkObject spawns,
|
||||
/// instantiates and spawns a separate <see cref="Builder"/> NetworkObject owned by that
|
||||
/// player. The builder is positioned at the centroid of the player's zone before spawn.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>Why a separate NetworkObject?</b> Multi-builder races (Path E) become "spawn
|
||||
/// N builder NetworkObjects" without restructuring the Player Prefab. See the design
|
||||
/// discussion in Path D scoping.</para>
|
||||
///
|
||||
/// <para><b>Server-only.</b> Spawning is server-authoritative. Non-server peers are
|
||||
/// no-ops; they just receive the resulting Builder NetworkObject like any other
|
||||
/// replicated spawn.</para>
|
||||
///
|
||||
/// <para><b>Lifetime.</b> The spawned builder is destroyed when the player NetworkObject
|
||||
/// despawns (e.g., disconnect). NGO does this automatically because we set
|
||||
/// <c>destroyWithScene</c> and store no other references — the builder's despawn cleans
|
||||
/// up the static registry in <see cref="Builder.OnNetworkDespawn"/>.</para>
|
||||
/// </remarks>
|
||||
public class PlayerBuilderSpawner : NetworkBehaviour
|
||||
{
|
||||
[Tooltip("Builder prefab to instantiate. Must be registered with the NetworkManager " +
|
||||
"as a network prefab.")]
|
||||
[SerializeField] private GameObject builderPrefab;
|
||||
|
||||
// Cached reference so we can despawn the builder if needed (e.g., player disconnects).
|
||||
private NetworkObject spawnedBuilder;
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
if (!IsServer) return;
|
||||
|
||||
if (builderPrefab == null)
|
||||
{
|
||||
Debug.LogError("[PlayerBuilderSpawner] No Builder prefab assigned. " +
|
||||
"Cannot spawn builder for client " + OwnerClientId + ".");
|
||||
return;
|
||||
}
|
||||
|
||||
SpawnBuilderForOwner();
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
// When the player despawns (disconnect), also despawn their builder if it still exists.
|
||||
if (IsServer && spawnedBuilder != null && spawnedBuilder.IsSpawned)
|
||||
{
|
||||
spawnedBuilder.Despawn(destroy: true);
|
||||
}
|
||||
spawnedBuilder = null;
|
||||
}
|
||||
|
||||
private void SpawnBuilderForOwner()
|
||||
{
|
||||
// Compute initial position: centroid of this player's zone.
|
||||
// Falls back to origin if loader/zone data isn't available.
|
||||
Vector3 spawnPos = ComputeZoneCentroid(OwnerToSlot(OwnerClientId));
|
||||
|
||||
var go = Instantiate(builderPrefab, spawnPos, Quaternion.identity);
|
||||
var netObj = go.GetComponent<NetworkObject>();
|
||||
if (netObj == null)
|
||||
{
|
||||
Debug.LogError("[PlayerBuilderSpawner] Builder prefab is missing a " +
|
||||
"NetworkObject component. Cannot spawn.");
|
||||
Destroy(go);
|
||||
return;
|
||||
}
|
||||
|
||||
netObj.SpawnWithOwnership(OwnerClientId, destroyWithScene: true);
|
||||
spawnedBuilder = netObj;
|
||||
|
||||
// Tell NetworkTransform to teleport to the spawn position, so clients don't
|
||||
// interpolate from the prefab's authoring position (typically the origin) to
|
||||
// the spawn position over the first few sync ticks. Without this, clients see
|
||||
// the builder smoothly drift from world origin to its spawn point — which is
|
||||
// exactly what we don't want for a brand-new spawn.
|
||||
//
|
||||
// Teleport sets a one-frame "no interpolation" flag that NetworkTransform
|
||||
// honors on its next sync, so clients snap to the position instead.
|
||||
var netTransform = go.GetComponent<Unity.Netcode.Components.NetworkTransform>();
|
||||
if (netTransform != null)
|
||||
{
|
||||
netTransform.Teleport(spawnPos, Quaternion.identity, go.transform.localScale);
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Helpers ----------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Stub mapping: client 0 = Player1, client 1 = Player2, etc.
|
||||
/// Replaced by MatchState's authoritative assignment when that lands.
|
||||
/// </summary>
|
||||
private static PlayerSlot OwnerToSlot(ulong clientId)
|
||||
{
|
||||
byte slotByte = (byte)(clientId + 1);
|
||||
if (slotByte < 1 || slotByte > 9) return PlayerSlot.None;
|
||||
return (PlayerSlot)slotByte;
|
||||
}
|
||||
|
||||
private static Vector3 ComputeZoneCentroid(PlayerSlot slot)
|
||||
{
|
||||
var loader = LevelLoader.Instance;
|
||||
if (loader == null || !loader.IsLoaded) return Vector3.zero;
|
||||
if (slot == PlayerSlot.None) return Vector3.zero;
|
||||
|
||||
var levelData = loader.LevelData;
|
||||
if (levelData == null || levelData.OwnerGrid == null) return Vector3.zero;
|
||||
|
||||
Vector2 sum = Vector2.zero;
|
||||
int count = 0;
|
||||
|
||||
for (int y = 0; y < levelData.GridSize.y; y++)
|
||||
{
|
||||
for (int x = 0; x < levelData.GridSize.x; x++)
|
||||
{
|
||||
int idx = y * levelData.GridSize.x + x;
|
||||
if (levelData.OwnerGrid[idx] != slot) continue;
|
||||
|
||||
Vector2Int tile = new Vector2Int(
|
||||
levelData.GridOriginTile.x + x,
|
||||
levelData.GridOriginTile.y + y);
|
||||
Vector3 world = GridCoordinates.GridToWorld(tile);
|
||||
sum.x += world.x;
|
||||
sum.y += world.z;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0) return Vector3.zero;
|
||||
return new Vector3(sum.x / count, GridCoordinates.BUILDABLE_PLANE_Y, sum.y / count);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue