Files
Northbound/Assets/Scripts/Worker.cs

714 lines
22 KiB
C#

using Unity.Netcode;
using UnityEngine;
namespace Northbound
{
public enum WorkerState
{
Idle = 0,
Following = 1,
Mining = 2,
Returning = 3,
WaitingForResource = 4
}
[RequireComponent(typeof(Collider))]
public class Worker : NetworkBehaviour, IInteractable, ITeamMember
{
[Header("Worker Settings")]
public int maxBagCapacity = 50;
public float miningSpeed = 5f;
public float followDistance = 3f;
public float movementSpeed = 3.5f;
public int resourcesPerMining = 5;
public int recruitmentCost = 10;
[Header("Interaction")]
public string interactionAnimationTrigger = "Recruit";
[Header("Visual")]
public GameObject miningEffectPrefab;
public GameObject depositEffectPrefab;
public Transform effectSpawnPoint;
[Header("Team")]
public TeamType teamType = TeamType.Player;
private NetworkVariable<int> _currentBagResources = new NetworkVariable<int>(
0,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server
);
private NetworkVariable<ulong> _ownerPlayerId = new NetworkVariable<ulong>(
ulong.MaxValue,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server
);
private NetworkVariable<WorkerState> _currentState = new NetworkVariable<WorkerState>(
WorkerState.Idle,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server
);
private NetworkVariable<ulong> _targetResourceId = new NetworkVariable<ulong>(
ulong.MaxValue,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server
);
private Resource _targetResource;
private Transform _playerTransform;
private Core _core;
private float _lastMiningTime;
private Rigidbody _rb;
private Collider _collider;
private Animator _animator;
[Header("Animation")]
public string moveAnimation = "Walk";
public string idleAnimation = "Idle";
public string mineAnimation = "Mine";
public bool useAnimations = true;
public bool playAnimationsByName = false; // true = play by name, false = use bools
public int CurrentBagResources => _currentBagResources.Value;
public WorkerState CurrentState => _currentState.Value;
public bool IsBagFull => _currentBagResources.Value >= maxBagCapacity;
public ulong OwnerPlayerId => _ownerPlayerId.Value;
public override void OnNetworkSpawn()
{
base.OnNetworkSpawn();
_rb = GetComponent<Rigidbody>();
_collider = GetComponent<Collider>();
_animator = GetComponent<Animator>();
if (IsServer)
{
_rb.constraints = RigidbodyConstraints.FreezeRotation;
FindCore();
}
_currentState.OnValueChanged += OnStateChanged;
_ownerPlayerId.OnValueChanged += OnOwnerChanged;
_targetResourceId.OnValueChanged += OnTargetResourceChanged;
PlayIdleAnimation();
}
public override void OnNetworkDespawn()
{
_currentState.OnValueChanged -= OnStateChanged;
_ownerPlayerId.OnValueChanged -= OnOwnerChanged;
_targetResourceId.OnValueChanged -= OnTargetResourceChanged;
base.OnNetworkDespawn();
}
private void FindCore()
{
Core[] cores = FindObjectsOfType<Core>();
if (cores.Length > 0)
{
_core = cores[0];
Debug.Log($"[Worker] 코어 찾음: {_core.name}");
}
}
private void Update()
{
if (!IsServer) return;
if (_playerTransform == null && _ownerPlayerId.Value != ulong.MaxValue)
{
UpdatePlayerTransform();
}
switch (_currentState.Value)
{
case WorkerState.Following:
HandleFollowing();
break;
case WorkerState.Mining:
HandleMining();
break;
case WorkerState.Returning:
HandleReturning();
break;
case WorkerState.WaitingForResource:
HandleWaitingForResource();
break;
}
}
private void HandleFollowing()
{
if (_ownerPlayerId.Value == ulong.MaxValue)
{
SetState(WorkerState.Idle);
PlayIdleAnimation();
return;
}
if (_playerTransform == null)
{
UpdatePlayerTransform();
}
if (_playerTransform == null)
{
PlayIdleAnimation();
return;
}
float distance = Vector3.Distance(transform.position, _playerTransform.position);
if (distance > followDistance)
{
MoveTowards(_playerTransform.position);
}
else
{
PlayIdleAnimation();
}
}
private void HandleMining()
{
if (_targetResource == null)
{
SetState(WorkerState.Following);
PlayIdleAnimation();
return;
}
if (IsBagFull)
{
SetState(WorkerState.Returning);
PlayIdleAnimation();
return;
}
if (!_targetResource.HasResourcesAvailable())
{
SetState(WorkerState.WaitingForResource);
PlayIdleAnimation();
return;
}
float distance = GetDistanceToCollider(_targetResource.GetComponent<Collider>());
if (distance > 2f)
{
MoveTowards(_targetResource.transform.position);
}
else
{
PlayMineAnimation();
MineResource();
}
}
private void HandleReturning()
{
if (_core == null)
{
FindCore();
if (_core == null)
{
SetState(WorkerState.Idle);
PlayIdleAnimation();
return;
}
}
float distance = GetDistanceToCollider(_core.GetComponent<Collider>());
if (distance > 2f)
{
MoveTowards(_core.transform.position);
}
else
{
PlayIdleAnimation();
DepositToCore();
}
}
private void HandleWaitingForResource()
{
if (_targetResource == null)
{
SetState(WorkerState.Following);
PlayIdleAnimation();
return;
}
if (IsBagFull)
{
SetState(WorkerState.Returning);
PlayIdleAnimation();
return;
}
if (_targetResource.HasResourcesAvailable())
{
SetState(WorkerState.Mining);
PlayMoveAnimation();
return;
}
float distance = GetDistanceToCollider(_targetResource.GetComponent<Collider>());
if (distance > 3f)
{
MoveTowards(_targetResource.transform.position);
}
else
{
PlayIdleAnimation();
}
}
private float GetDistanceToCollider(Collider targetCollider)
{
if (targetCollider == null)
return float.MaxValue;
Vector3 closestPoint = targetCollider.ClosestPoint(transform.position);
float distance = Vector3.Distance(transform.position, closestPoint);
return distance;
}
private void MoveTowards(Vector3 targetPosition)
{
float distance = Vector3.Distance(transform.position, targetPosition);
if (distance <= 0.1f)
{
PlayIdleAnimation();
return;
}
Vector3 direction = (targetPosition - transform.position).normalized;
direction.y = 0;
float moveDistance = Mathf.Min(movementSpeed * Time.deltaTime, distance);
transform.position += direction * moveDistance;
transform.rotation = Quaternion.LookRotation(direction);
PlayMoveAnimation();
}
private void MineResource()
{
if (Time.time - _lastMiningTime < miningSpeed)
{
return;
}
if (!_targetResource.CanWorkerMineResource(NetworkObject.NetworkObjectId))
{
return;
}
int availableSpace = maxBagCapacity - _currentBagResources.Value;
int mineAmount = Mathf.Min(resourcesPerMining, availableSpace);
if (mineAmount > 0)
{
int actualAmount = _targetResource.TakeResourcesForWorker(mineAmount, NetworkObject.NetworkObjectId);
_currentBagResources.Value += actualAmount;
_lastMiningTime = Time.time;
ShowMiningEffectClientRpc();
if (IsBagFull)
{
SetState(WorkerState.Returning);
}
}
}
private void DepositToCore()
{
if (_currentBagResources.Value > 0 && _core != null)
{
_core.AddResource(_currentBagResources.Value);
_currentBagResources.Value = 0;
ShowDepositEffectClientRpc();
}
bool shouldReturnToMining = _targetResource != null && _targetResource.HasResourcesAvailable();
if (shouldReturnToMining)
{
SetState(WorkerState.Mining);
}
else if (_targetResource != null)
{
SetState(WorkerState.WaitingForResource);
}
else
{
SetState(WorkerState.Following);
}
}
private void SetState(WorkerState newState)
{
if (IsServer)
{
_currentState.Value = newState;
}
}
#region IInteractable Implementation
public bool CanInteract(ulong playerId)
{
if (_ownerPlayerId.Value != ulong.MaxValue && _ownerPlayerId.Value != playerId)
return false;
if (_ownerPlayerId.Value == ulong.MaxValue)
{
var coreResourceManager = CoreResourceManager.Instance;
if (coreResourceManager != null && !coreResourceManager.CanAfford(recruitmentCost))
return false;
}
return true;
}
public void Interact(ulong playerId)
{
if (!CanInteract(playerId)) return;
if (_ownerPlayerId.Value == ulong.MaxValue)
{
RecruitWorkerServerRpc(playerId, NetworkObject.NetworkObjectId);
}
else if (_ownerPlayerId.Value == playerId)
{
StopWorkerServerRpc();
}
}
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
private void StopWorkerServerRpc()
{
_targetResourceId.Value = ulong.MaxValue;
_targetResource = null;
SetState(WorkerState.Following);
}
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
private void RecruitWorkerServerRpc(ulong playerId, ulong workerNetObjectId)
{
var coreResourceManager = CoreResourceManager.Instance;
if (coreResourceManager == null)
{
Debug.LogWarning("[Worker] CoreResourceManager 인스턴스를 찾을 수 없습니다.");
return;
}
if (!coreResourceManager.CanAfford(recruitmentCost))
{
Debug.LogWarning($"[Worker] 코어 자원이 부족합니다. 필요: {recruitmentCost}");
return;
}
coreResourceManager.SpendResources(recruitmentCost);
_ownerPlayerId.Value = playerId;
SetState(WorkerState.Following);
UpdatePlayerTransform();
}
[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;
}
}
}
}
}
public string GetInteractionPrompt()
{
if (_ownerPlayerId.Value == ulong.MaxValue)
{
var coreResourceManager = CoreResourceManager.Instance;
if (coreResourceManager != null && !coreResourceManager.CanAfford(recruitmentCost))
return $"자원 부족 (필요: {recruitmentCost})";
return $"[E] 워커 채용 - 비용: {recruitmentCost}";
}
else if (NetworkManager.Singleton != null && _ownerPlayerId.Value == NetworkManager.Singleton.LocalClientId)
{
string stateText = _currentState.Value switch
{
WorkerState.Following => "따라가는 중",
WorkerState.Mining => "채굴 중",
WorkerState.Returning => "반환 중",
WorkerState.WaitingForResource => "자원 대기 중",
_ => "대기 중"
};
return $"워커 (가방: {_currentBagResources.Value}/{maxBagCapacity}) - {stateText}";
}
return "다른 플레이어의 워커";
}
public string GetInteractionAnimation()
{
return interactionAnimationTrigger;
}
public EquipmentData GetEquipmentData()
{
return null;
}
public Transform GetTransform()
{
return transform;
}
#endregion
#region ITeamMember Implementation
public TeamType GetTeam() => teamType;
public void SetTeam(TeamType team)
{
if (!IsServer) return;
teamType = team;
}
#endregion
#region ClientRPC Effects
[Rpc(SendTo.ClientsAndHost)]
private void ShowMiningEffectClientRpc()
{
if (miningEffectPrefab != null && effectSpawnPoint != null)
{
GameObject effect = Instantiate(miningEffectPrefab, effectSpawnPoint.position, effectSpawnPoint.rotation);
Destroy(effect, 2f);
}
}
[Rpc(SendTo.ClientsAndHost)]
private void ShowDepositEffectClientRpc()
{
if (depositEffectPrefab != null && effectSpawnPoint != null)
{
GameObject effect = Instantiate(depositEffectPrefab, effectSpawnPoint.position, effectSpawnPoint.rotation);
Destroy(effect, 2f);
}
}
#endregion
#region Event Handlers
private void OnStateChanged(WorkerState previousState, WorkerState newState)
{
if (!useAnimations || _animator == null) return;
switch (newState)
{
case WorkerState.Following:
PlayIdleAnimation();
break;
case WorkerState.Idle:
PlayIdleAnimation();
break;
case WorkerState.Mining:
PlayMoveAnimation();
break;
case WorkerState.Returning:
PlayMoveAnimation();
break;
case WorkerState.WaitingForResource:
PlayIdleAnimation();
break;
}
}
private void OnOwnerChanged(ulong previousOwner, ulong newOwner)
{
if (IsServer && newOwner != ulong.MaxValue)
{
UpdatePlayerTransform();
}
}
private bool UpdatePlayerTransform()
{
if (!IsServer || _ownerPlayerId.Value == ulong.MaxValue)
return false;
if (NetworkManager.Singleton != null &&
NetworkManager.Singleton.SpawnManager != null)
{
var spawnedObjects = NetworkManager.Singleton.SpawnManager.SpawnedObjects;
foreach (var kvp in spawnedObjects)
{
var networkObj = kvp.Value;
if (networkObj != null && networkObj.OwnerClientId == _ownerPlayerId.Value)
{
var playerInteraction = networkObj.GetComponent<PlayerInteraction>();
if (playerInteraction != null && networkObj.gameObject != null)
{
_playerTransform = networkObj.transform;
return true;
}
}
}
}
return false;
}
private void OnTargetResourceChanged(ulong previousResource, ulong newResource)
{
if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(newResource, out var resourceObj))
{
_targetResource = resourceObj.GetComponent<Resource>();
}
else
{
_targetResource = null;
}
}
#endregion
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
public void AssignMiningTargetServerRpc(ulong resourceId)
{
if (!NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(resourceId, out var resourceObj))
{
return;
}
var resource = resourceObj.GetComponent<Resource>();
if (resource == null)
{
return;
}
_targetResourceId.Value = resourceId;
_targetResource = resource;
if (_ownerPlayerId.Value != ulong.MaxValue)
{
SetState(WorkerState.Mining);
}
}
private void PlayIdleAnimation()
{
if (!useAnimations || _animator == null) return;
if (playAnimationsByName)
{
_animator.Play(idleAnimation);
}
else
{
_animator.SetBool(moveAnimation, false);
_animator.SetBool(mineAnimation, false);
_animator.SetBool(idleAnimation, true);
}
}
private void PlayMoveAnimation()
{
if (!useAnimations || _animator == null) return;
if (playAnimationsByName)
{
_animator.Play(moveAnimation);
}
else
{
_animator.SetBool(idleAnimation, false);
_animator.SetBool(mineAnimation, false);
_animator.SetBool(moveAnimation, true);
}
}
private void PlayMineAnimation()
{
if (!useAnimations || _animator == null) return;
if (playAnimationsByName)
{
_animator.Play(mineAnimation);
}
else
{
_animator.SetBool(idleAnimation, false);
_animator.SetBool(moveAnimation, false);
_animator.SetBool(mineAnimation, true);
}
}
private void OnDrawGizmosSelected()
{
#if UNITY_EDITOR
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(transform.position, followDistance);
if (_currentState.Value == WorkerState.Mining && _targetResource != null)
{
Gizmos.color = Color.red;
Gizmos.DrawLine(transform.position, _targetResource.transform.position);
}
if (_currentState.Value == WorkerState.WaitingForResource && _targetResource != null)
{
Gizmos.color = Color.blue;
Gizmos.DrawLine(transform.position, _targetResource.transform.position);
}
if (_currentState.Value == WorkerState.Returning && _core != null)
{
Gizmos.color = Color.green;
Gizmos.DrawLine(transform.position, _core.transform.position);
}
#endif
}
}
}