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 _workerCount = new NetworkVariable( 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(); if (workerNetObj == null) { Debug.LogError("[WorkerSpawner] Worker prefab must have NetworkObject component"); Destroy(workerObj); return; } workerNetObj.Spawn(); _lastSpawnTime = Time.time; _workerCount.Value++; ShowSpawnEffectClientRpc(spawnPosition); Debug.Log($"[WorkerSpawner] 워커 생성됨 (총 워커: {_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(); 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(); if (playerInteraction != null && playerInteraction.IsOwner) { if (spawnedObjects.TryGetValue(workerNetObjectId, out var workerObj)) { var worker = workerObj.GetComponent(); 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 } } }