Files
Northbound/Assets/Scripts/WorkerSpawner.cs
2026-02-03 21:32:16 +09:00

249 lines
8.1 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;
public int recruitmentCost = 20;
[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)
{
}
#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;
var coreResourceManager = CoreResourceManager.Instance;
if (coreResourceManager != null && !coreResourceManager.CanAfford(recruitmentCost))
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;
}
var coreResourceManager = CoreResourceManager.Instance;
if (coreResourceManager == null)
{
Debug.LogWarning("[WorkerSpawner] CoreResourceManager 인스턴스를 찾을 수 없습니다.");
return;
}
if (!coreResourceManager.CanAfford(recruitmentCost))
{
Debug.LogWarning($"[WorkerSpawner] 코어 자원이 부족합니다. 필요: {recruitmentCost}");
return;
}
coreResourceManager.SpendResources(recruitmentCost);
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);
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;
}
}
}
}
}
[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 $"Worker ({_workerCount.Value}/{maxWorkers})";
float cooldownRemaining = Mathf.Max(0, spawnCooldown - (Time.time - _lastSpawnTime));
if (cooldownRemaining > 0)
return $"Waiting Worker... ({cooldownRemaining:F1}s)";
var coreResourceManager = CoreResourceManager.Instance;
if (coreResourceManager != null && !coreResourceManager.CanAfford(recruitmentCost))
return $"Resource Required: {recruitmentCost})";
return $"{interactionPrompt} ({_workerCount.Value}/{maxWorkers}) - 비용: {recruitmentCost}";
}
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
}
}
}