229 lines
7.4 KiB
C#
229 lines
7.4 KiB
C#
using Unity.Netcode;
|
|
using UnityEngine;
|
|
|
|
namespace Northbound
|
|
{
|
|
public class WorkerSpawner : NetworkBehaviour, IInteractable
|
|
{
|
|
[Header("Spawner Settings")]
|
|
public GameObject workerPrefab;
|
|
public Transform spawnPoint;
|
|
public float spawnRadius = 2f;
|
|
public int maxWorkers = 5;
|
|
public float spawnCooldown = 5f;
|
|
|
|
[Header("Interaction")]
|
|
public string interactionAnimationTrigger = "Build";
|
|
public string interactionPrompt = "[E] 워커 생성";
|
|
|
|
[Header("Visual")]
|
|
public GameObject spawnEffectPrefab;
|
|
|
|
private NetworkVariable<int> _workerCount = new NetworkVariable<int>(
|
|
0,
|
|
NetworkVariableReadPermission.Everyone,
|
|
NetworkVariableWritePermission.Server
|
|
);
|
|
|
|
private float _lastSpawnTime;
|
|
|
|
public int WorkerCount => _workerCount.Value;
|
|
|
|
public override void OnNetworkSpawn()
|
|
{
|
|
base.OnNetworkSpawn();
|
|
_workerCount.OnValueChanged += OnWorkerCountChanged;
|
|
}
|
|
|
|
public override void OnNetworkDespawn()
|
|
{
|
|
_workerCount.OnValueChanged -= OnWorkerCountChanged;
|
|
base.OnNetworkDespawn();
|
|
}
|
|
|
|
private void OnWorkerCountChanged(int previousValue, int newValue)
|
|
{
|
|
Debug.Log($"[WorkerSpawner] 워커 수 변경: {previousValue} → {newValue}");
|
|
}
|
|
|
|
#region IInteractable Implementation
|
|
|
|
public bool CanInteract(ulong playerId)
|
|
{
|
|
if (_workerCount.Value >= maxWorkers)
|
|
return false;
|
|
|
|
if (Time.time - _lastSpawnTime < spawnCooldown)
|
|
return false;
|
|
|
|
if (workerPrefab == null)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
public void Interact(ulong playerId)
|
|
{
|
|
if (!CanInteract(playerId))
|
|
return;
|
|
|
|
SpawnWorkerServerRpc(playerId);
|
|
}
|
|
|
|
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
|
private void SpawnWorkerServerRpc(ulong playerId)
|
|
{
|
|
if (!CanInteract(playerId))
|
|
return;
|
|
|
|
if (_workerCount.Value >= maxWorkers)
|
|
{
|
|
Debug.LogWarning("[WorkerSpawner] 최대 워커 수에 도달함");
|
|
return;
|
|
}
|
|
|
|
Vector3 spawnPosition = spawnPoint != null ? spawnPoint.position : transform.position;
|
|
|
|
float randomAngle = Random.Range(0f, 360f);
|
|
Vector3 offset = new Vector3(
|
|
Mathf.Cos(randomAngle * Mathf.Deg2Rad) * spawnRadius,
|
|
0f,
|
|
Mathf.Sin(randomAngle * Mathf.Deg2Rad) * spawnRadius
|
|
);
|
|
spawnPosition += offset;
|
|
|
|
GameObject workerObj = Instantiate(workerPrefab, spawnPosition, Quaternion.identity);
|
|
NetworkObject workerNetObj = workerObj.GetComponent<NetworkObject>();
|
|
|
|
if (workerNetObj == null)
|
|
{
|
|
Debug.LogError("[WorkerSpawner] Worker prefab must have NetworkObject component");
|
|
Destroy(workerObj);
|
|
return;
|
|
}
|
|
|
|
// IMPORTANT: Spawn as server-owned so server can modify worker's NetworkVariables
|
|
workerNetObj.Spawn();
|
|
_lastSpawnTime = Time.time;
|
|
_workerCount.Value++;
|
|
|
|
ShowSpawnEffectClientRpc(spawnPosition);
|
|
|
|
Debug.Log($"[WorkerSpawner] Worker spawned (server-owned) and assigned to player {playerId} (Total: {_workerCount.Value}/{maxWorkers})");
|
|
|
|
ScheduleWorkerAssignment(playerId, workerNetObj.NetworkObjectId);
|
|
}
|
|
|
|
private void ScheduleWorkerAssignment(ulong playerId, ulong workerNetObjectId)
|
|
{
|
|
StartCoroutine(AssignWorkerAfterFrame(playerId, workerNetObjectId));
|
|
}
|
|
|
|
private System.Collections.IEnumerator AssignWorkerAfterFrame(ulong playerId, ulong workerNetObjectId)
|
|
{
|
|
yield return new WaitForEndOfFrame();
|
|
|
|
if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(workerNetObjectId, out var workerObj))
|
|
{
|
|
var worker = workerObj.GetComponent<Worker>();
|
|
if (worker != null)
|
|
{
|
|
SetPlayerAssignedWorkerClientRpc(playerId, workerNetObjectId);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Rpc(SendTo.ClientsAndHost)]
|
|
private void SetPlayerAssignedWorkerClientRpc(ulong playerId, ulong workerNetObjectId)
|
|
{
|
|
var spawnedObjects = NetworkManager.Singleton.SpawnManager.SpawnedObjects;
|
|
|
|
NetworkObject playerObj = null;
|
|
foreach (var kvp in spawnedObjects)
|
|
{
|
|
if (kvp.Value != null && kvp.Value.OwnerClientId == playerId)
|
|
{
|
|
playerObj = kvp.Value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (playerObj != null)
|
|
{
|
|
var playerInteraction = playerObj.GetComponent<PlayerInteraction>();
|
|
if (playerInteraction != null && playerInteraction.IsOwner)
|
|
{
|
|
if (spawnedObjects.TryGetValue(workerNetObjectId, out var workerObj))
|
|
{
|
|
var worker = workerObj.GetComponent<Worker>();
|
|
if (worker != null)
|
|
{
|
|
playerInteraction.assignedWorker = worker;
|
|
Debug.Log($"[WorkerSpawner] Worker assigned to player {playerId}'s PlayerInteraction");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[Rpc(SendTo.ClientsAndHost)]
|
|
private void ShowSpawnEffectClientRpc(Vector3 position)
|
|
{
|
|
if (spawnEffectPrefab != null)
|
|
{
|
|
GameObject effect = Instantiate(spawnEffectPrefab, position, Quaternion.identity);
|
|
Destroy(effect, 2f);
|
|
}
|
|
}
|
|
|
|
public string GetInteractionPrompt()
|
|
{
|
|
if (_workerCount.Value >= maxWorkers)
|
|
return $"워커 수 최대 ({_workerCount.Value}/{maxWorkers})";
|
|
|
|
float cooldownRemaining = Mathf.Max(0, spawnCooldown - (Time.time - _lastSpawnTime));
|
|
if (cooldownRemaining > 0)
|
|
return $"워커 생성 대기 중 ({cooldownRemaining:F1}s)";
|
|
|
|
return $"{interactionPrompt} ({_workerCount.Value}/{maxWorkers})";
|
|
}
|
|
|
|
public string GetInteractionAnimation()
|
|
{
|
|
return interactionAnimationTrigger;
|
|
}
|
|
|
|
public EquipmentData GetEquipmentData()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public Transform GetTransform()
|
|
{
|
|
return transform;
|
|
}
|
|
|
|
#endregion
|
|
|
|
public void OnWorkerDestroyed()
|
|
{
|
|
if (IsServer)
|
|
{
|
|
_workerCount.Value = Mathf.Max(0, _workerCount.Value - 1);
|
|
}
|
|
}
|
|
|
|
private void OnDrawGizmosSelected()
|
|
{
|
|
#if UNITY_EDITOR
|
|
Gizmos.color = Color.green;
|
|
Vector3 spawnCenter = spawnPoint != null ? spawnPoint.position : transform.position;
|
|
Gizmos.DrawWireSphere(spawnCenter, spawnRadius);
|
|
|
|
UnityEditor.Handles.Label(spawnCenter + Vector3.up * 2f,
|
|
$"Worker Spawner\nWorkers: {_workerCount.Value}/{maxWorkers}");
|
|
#endif
|
|
}
|
|
}
|
|
}
|