기존 시스템 재사용 프로토타입 보존
This commit is contained in:
@@ -11,6 +11,8 @@ namespace Northbound
|
||||
[Header("Attack Settings")]
|
||||
public float attackCooldown = 0.5f;
|
||||
public LayerMask attackableLayer = ~0;
|
||||
public bool useFacingArc = false;
|
||||
[Range(-1f, 1f)] public float minimumFacingDot = 0.25f;
|
||||
|
||||
[Header("Animation")]
|
||||
public string attackAnimationTrigger = "Attack";
|
||||
@@ -72,7 +74,7 @@ namespace Northbound
|
||||
PlayAttackAnimation();
|
||||
|
||||
// 애니메이션이 없으면 즉시 공격 실행
|
||||
if (_animator == null || string.IsNullOrEmpty(attackAnimationTrigger))
|
||||
if (!useAnimationEvents || _animator == null || string.IsNullOrEmpty(attackAnimationTrigger))
|
||||
{
|
||||
PerformAttack();
|
||||
_isAttacking = false;
|
||||
@@ -89,6 +91,14 @@ namespace Northbound
|
||||
if (hit.transform.root == transform.root)
|
||||
continue;
|
||||
|
||||
if (useFacingArc)
|
||||
{
|
||||
Vector3 targetDirection = hit.bounds.center - transform.position;
|
||||
targetDirection.y = 0f;
|
||||
if (targetDirection.sqrMagnitude > 0.01f && Vector3.Dot(transform.forward, targetDirection.normalized) < minimumFacingDot)
|
||||
continue;
|
||||
}
|
||||
|
||||
var targetDamageable = hit.GetComponent<IDamageable>();
|
||||
var targetTeamMember = hit.GetComponent<ITeamMember>();
|
||||
|
||||
|
||||
@@ -9,6 +9,10 @@ using UnityEngine;
|
||||
[Header("Team Settings")]
|
||||
[Tooltip("포털의 팀 (Hostile 또는 Monster)")]
|
||||
[SerializeField] private TeamType portalTeam = TeamType.Hostile;
|
||||
[Header("Lifecycle")]
|
||||
[SerializeField] private bool spawnFromGlobalTimer = true;
|
||||
[SerializeField] private bool despawnWhenDestroyed = true;
|
||||
public event System.Action<ulong> OnPortalDestroyed;
|
||||
|
||||
[Header("Health Settings")]
|
||||
[Tooltip("최대 체력")]
|
||||
@@ -67,12 +71,18 @@ using UnityEngine;
|
||||
}
|
||||
}
|
||||
|
||||
GlobalTimer.Instance.OnCycleStart += OnCycleStart;
|
||||
if (spawnFromGlobalTimer && GlobalTimer.Instance != null)
|
||||
{
|
||||
GlobalTimer.Instance.OnCycleStart += OnCycleStart;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
GlobalTimer.Instance.OnCycleStart -= OnCycleStart;
|
||||
if (spawnFromGlobalTimer && GlobalTimer.Instance != null)
|
||||
{
|
||||
GlobalTimer.Instance.OnCycleStart -= OnCycleStart;
|
||||
}
|
||||
|
||||
// 체력 변경 이벤트 정리 (나중에 추가할 경우 대비)
|
||||
|
||||
@@ -249,7 +259,7 @@ using UnityEngine;
|
||||
}
|
||||
}
|
||||
|
||||
private void DestroyPortal(ulong attackerId)
|
||||
private void DestroyPortal(ulong attackerId)
|
||||
{
|
||||
if (!IsServer) return;
|
||||
|
||||
@@ -257,10 +267,40 @@ using UnityEngine;
|
||||
ShowDestroyEffectClientRpc();
|
||||
|
||||
// 몬스터 스폰 중지 (이벤트 구독 해제)
|
||||
GlobalTimer.Instance.OnCycleStart -= OnCycleStart;
|
||||
if (spawnFromGlobalTimer && GlobalTimer.Instance != null)
|
||||
{
|
||||
GlobalTimer.Instance.OnCycleStart -= OnCycleStart;
|
||||
}
|
||||
|
||||
// 네트워크 오브젝트 파괴 (약간의 딜레이)
|
||||
Invoke(nameof(DespawnPortal), 1.0f);
|
||||
OnPortalDestroyed?.Invoke(attackerId);
|
||||
|
||||
if (despawnWhenDestroyed)
|
||||
{
|
||||
Invoke(nameof(DespawnPortal), 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public int CurrentHealth => _currentHealth.Value;
|
||||
public int MaxHealth => maxHealth;
|
||||
|
||||
public void ConfigureLifecycle(bool useGlobalTimerSpawning, bool shouldDespawn)
|
||||
{
|
||||
spawnFromGlobalTimer = useGlobalTimerSpawning;
|
||||
despawnWhenDestroyed = shouldDespawn;
|
||||
}
|
||||
|
||||
public void RestorePortal(TeamType team)
|
||||
{
|
||||
if (!IsServer) return;
|
||||
_team.Value = team;
|
||||
_currentHealth.Value = maxHealth;
|
||||
}
|
||||
|
||||
public void SetDormant()
|
||||
{
|
||||
if (!IsServer) return;
|
||||
_team.Value = TeamType.Neutral;
|
||||
_currentHealth.Value = 0;
|
||||
}
|
||||
|
||||
private void DespawnPortal()
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace Northbound
|
||||
|
||||
private Dictionary<ulong, int> _clientSpawnIndices = new Dictionary<ulong, int>();
|
||||
private int _nextSpawnIndex = 0;
|
||||
private NetworkManager _networkManager;
|
||||
private bool _callbacksRegistered;
|
||||
|
||||
// ⭐ Awake에서 스폰 포인트 먼저 찾고, 콜백 등록
|
||||
private void Awake()
|
||||
@@ -28,15 +30,25 @@ namespace Northbound
|
||||
}
|
||||
|
||||
// 2️⃣ 그 다음 콜백 등록
|
||||
if (NetworkManager.Singleton != null)
|
||||
{
|
||||
NetworkManager.Singleton.ConnectionApprovalCallback = ApprovalCheck;
|
||||
NetworkManager.Singleton.OnServerStarted += OnServerStarted;
|
||||
NetworkManager.Singleton.OnClientConnectedCallback += OnClientConnected;
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
RegisterCallbacks();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
RegisterCallbacks();
|
||||
}
|
||||
|
||||
private void RegisterCallbacks()
|
||||
{
|
||||
if (_callbacksRegistered) return;
|
||||
_networkManager = GetComponent<NetworkManager>();
|
||||
if (_networkManager == null) _networkManager = NetworkManager.Singleton;
|
||||
if (_networkManager == null) return;
|
||||
|
||||
_networkManager.ConnectionApprovalCallback = ApprovalCheck;
|
||||
_networkManager.OnServerStarted += OnServerStarted;
|
||||
_networkManager.OnClientConnectedCallback += OnClientConnected;
|
||||
_callbacksRegistered = true;
|
||||
}
|
||||
|
||||
private void FindSpawnPoints()
|
||||
@@ -192,11 +204,11 @@ namespace Northbound
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (NetworkManager.Singleton != null)
|
||||
if (_callbacksRegistered && _networkManager != null)
|
||||
{
|
||||
NetworkManager.Singleton.ConnectionApprovalCallback = null;
|
||||
NetworkManager.Singleton.OnServerStarted -= OnServerStarted;
|
||||
NetworkManager.Singleton.OnClientConnectedCallback -= OnClientConnected;
|
||||
_networkManager.ConnectionApprovalCallback = null;
|
||||
_networkManager.OnServerStarted -= OnServerStarted;
|
||||
_networkManager.OnClientConnectedCallback -= OnClientConnected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl
|
||||
{
|
||||
[Header("Movement Settings")]
|
||||
public float rotationSpeed = 10f;
|
||||
[Tooltip("공격 애니메이션 중 이동을 막을지 여부")]
|
||||
public bool blockMovementDuringAttack = true;
|
||||
|
||||
[Header("Team Settings")]
|
||||
[SerializeField] private TeamType initialTeam = TeamType.Player;
|
||||
@@ -21,6 +23,7 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl
|
||||
[Header("Death Settings")]
|
||||
[SerializeField] private GameObject resourcePickupPrefab; // 자원 드랍 프리팹
|
||||
[SerializeField] private float respawnDelay = 10f; // 부활 대기 시간 (초)
|
||||
[SerializeField] private bool dropResourcesOnDeath = true;
|
||||
|
||||
[Header("Health Bar")]
|
||||
[SerializeField] private bool showHealthBar = true;
|
||||
@@ -61,9 +64,18 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl
|
||||
// 이 플레이어가 로컬 플레이어인지 확인
|
||||
|
||||
|
||||
public new bool IsLocalPlayer => _ownerPlayerId.Value == NetworkManager.Singleton.LocalClientId;
|
||||
public new bool IsLocalPlayer => NetworkManager.Singleton != null &&
|
||||
(_ownerPlayerId.Value == ulong.MaxValue ? OwnerClientId : _ownerPlayerId.Value) == NetworkManager.Singleton.LocalClientId;
|
||||
|
||||
public ulong OwnerPlayerId => _ownerPlayerId.Value;
|
||||
public ulong OwnerPlayerId => _ownerPlayerId.Value == ulong.MaxValue ? OwnerClientId : _ownerPlayerId.Value;
|
||||
public Vector3 ServerMoveDirection => new Vector3(_serverMoveInput.x, 0f, _serverMoveInput.y).normalized;
|
||||
|
||||
public void ConfigurePrototypeMode()
|
||||
{
|
||||
blockMovementDuringAttack = false;
|
||||
dropResourcesOnDeath = false;
|
||||
respawnDelay = 3f;
|
||||
}
|
||||
|
||||
// 중앙 집중식 입력 관리 - 다른 컴포넌트에서 참조
|
||||
public PlayerInputActions InputActions => _inputActions;
|
||||
@@ -89,6 +101,10 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl
|
||||
// 서버에서 초기화
|
||||
if (IsServer)
|
||||
{
|
||||
if (_ownerPlayerId.Value == ulong.MaxValue)
|
||||
{
|
||||
_ownerPlayerId.Value = OwnerClientId;
|
||||
}
|
||||
if (_team.Value == TeamType.Neutral)
|
||||
{
|
||||
_team.Value = initialTeam;
|
||||
@@ -245,7 +261,7 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl
|
||||
var attackAction = GetComponent<AttackAction>();
|
||||
var playerInteraction = GetComponent<PlayerInteraction>();
|
||||
|
||||
bool isActionBlocked = (attackAction != null && attackAction.IsAttacking) ||
|
||||
bool isActionBlocked = (blockMovementDuringAttack && attackAction != null && attackAction.IsAttacking) ||
|
||||
(playerInteraction != null && playerInteraction.IsInteracting);
|
||||
|
||||
if (isActionBlocked)
|
||||
@@ -420,7 +436,10 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl
|
||||
if (!IsServer) return;
|
||||
|
||||
// 플레이어가 가진 자원을 ResourcePickup으로 드랍
|
||||
DropPlayerResources();
|
||||
if (dropResourcesOnDeath)
|
||||
{
|
||||
DropPlayerResources();
|
||||
}
|
||||
|
||||
// 사망 이펙트
|
||||
ShowDeathEffectClientRpc();
|
||||
|
||||
@@ -17,6 +17,10 @@ namespace Northbound
|
||||
[Header("Animation")]
|
||||
public bool playAnimations = true;
|
||||
|
||||
[Header("Secondary Action")]
|
||||
[Tooltip("우클릭으로 실행할 액션 이름. 비어 있으면 사용하지 않습니다.")]
|
||||
public string secondaryActionName = "";
|
||||
|
||||
private Dictionary<string, IAction> _actions = new Dictionary<string, IAction>();
|
||||
private Animator _animator;
|
||||
private NetworkAnimator _networkAnimator;
|
||||
@@ -103,6 +107,18 @@ namespace Northbound
|
||||
ExecuteAction("Attack");
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!_isInputInitialized || !IsLocalPlayer || string.IsNullOrEmpty(secondaryActionName)) return;
|
||||
if (Mouse.current != null && Mouse.current.rightButton.wasPressedThisFrame)
|
||||
{
|
||||
if (!UpgradeListPopup.IsOpen && (_networkPlayerController == null || !_networkPlayerController.IsDead()))
|
||||
{
|
||||
ExecuteAction(secondaryActionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ExecuteAction(string actionName)
|
||||
{
|
||||
if (_actions.TryGetValue(actionName, out IAction action))
|
||||
|
||||
@@ -11,6 +11,15 @@ public class SimpleCameraZFollow : MonoBehaviour
|
||||
|
||||
private Transform target;
|
||||
|
||||
public void Configure(float height, float forwardOffset, float followSmoothSpeed)
|
||||
{
|
||||
fixedXPosition = 0f;
|
||||
fixedYPosition = height;
|
||||
zOffset = forwardOffset;
|
||||
smoothSpeed = followSmoothSpeed;
|
||||
lockRotation = true;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
FindPlayerTarget();
|
||||
@@ -21,7 +30,7 @@ public class SimpleCameraZFollow : MonoBehaviour
|
||||
var players = FindObjectsByType<NetworkPlayerController>();
|
||||
foreach (var player in players)
|
||||
{
|
||||
if (player.IsOwner)
|
||||
if (player.IsLocalPlayer)
|
||||
{
|
||||
target = player.transform;
|
||||
Debug.Log($"<color=green>[Camera] Target found: {player.name}</color>");
|
||||
|
||||
Reference in New Issue
Block a user