일꾼(Worker) 개발 및 Kaykit Adventurer 애셋 추가
코어 오른쪽 건물에서 상호작용 하면 Worker 생성 Worker와 상호작용하면 Worker가 플레이어를 따라옴 그 상태에서 자원과 상호작용하면 Worker를 자원에 배치할 수 있음.
This commit is contained in:
623
Assets/Scripts/Worker.cs
Normal file
623
Assets/Scripts/Worker.cs
Normal file
@@ -0,0 +1,623 @@
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Northbound
|
||||
{
|
||||
public enum WorkerState
|
||||
{
|
||||
Idle = 0,
|
||||
Following = 1,
|
||||
Mining = 2,
|
||||
Returning = 3
|
||||
}
|
||||
|
||||
[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;
|
||||
|
||||
[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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 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)
|
||||
{
|
||||
_targetResource.TakeResourcesForWorker(mineAmount, NetworkObject.NetworkObjectId);
|
||||
_currentBagResources.Value += mineAmount;
|
||||
_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
|
||||
{
|
||||
SetState(WorkerState.Following);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetState(WorkerState newState)
|
||||
{
|
||||
if (IsServer)
|
||||
{
|
||||
_currentState.Value = newState;
|
||||
}
|
||||
}
|
||||
|
||||
#region IInteractable Implementation
|
||||
|
||||
public bool CanInteract(ulong playerId)
|
||||
{
|
||||
return _ownerPlayerId.Value == ulong.MaxValue || _ownerPlayerId.Value == playerId;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
_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)
|
||||
{
|
||||
return "[E] 워커 채용";
|
||||
}
|
||||
else if (NetworkManager.Singleton != null && _ownerPlayerId.Value == NetworkManager.Singleton.LocalClientId)
|
||||
{
|
||||
string stateText = _currentState.Value switch
|
||||
{
|
||||
WorkerState.Following => "따라가는 중",
|
||||
WorkerState.Mining => "채굴 중",
|
||||
WorkerState.Returning => "반환 중",
|
||||
_ => "대기 중"
|
||||
};
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (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.Returning && _core != null)
|
||||
{
|
||||
Gizmos.color = Color.green;
|
||||
Gizmos.DrawLine(transform.position, _core.transform.position);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user