Files
Northbound/Assets/Scripts/WorkerSpawner.cs
dal4segno 3e747a9d97 일꾼(Worker) 개발 및 Kaykit Adventurer 애셋 추가
코어 오른쪽 건물에서 상호작용 하면 Worker 생성
Worker와 상호작용하면 Worker가 플레이어를 따라옴
그 상태에서 자원과 상호작용하면 Worker를 자원에 배치할 수 있음.
2026-02-01 21:55:14 +09:00

228 lines
7.3 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;
}
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<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
}
}
}