플레이어/적/몬스터 팀 시스템 생성
몬스터 및 적 AI 구현
This commit is contained in:
@@ -2,13 +2,37 @@ using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using Unity.Cinemachine;
|
||||
using Northbound;
|
||||
|
||||
public class NetworkPlayerController : NetworkBehaviour
|
||||
public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageable
|
||||
{
|
||||
[Header("Movement Settings")]
|
||||
public float moveSpeed = 5f;
|
||||
public float rotationSpeed = 10f;
|
||||
|
||||
[Header("Team Settings")]
|
||||
[SerializeField] private TeamType initialTeam = TeamType.Player;
|
||||
|
||||
[Header("Health Settings")]
|
||||
[SerializeField] private int maxHealth = 100;
|
||||
[SerializeField] private bool showHealthBar = true;
|
||||
|
||||
[Header("Visual Effects")]
|
||||
[SerializeField] private GameObject damageEffectPrefab;
|
||||
[SerializeField] private GameObject deathEffectPrefab;
|
||||
|
||||
private NetworkVariable<TeamType> _team = new NetworkVariable<TeamType>(
|
||||
TeamType.Player,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server
|
||||
);
|
||||
|
||||
private NetworkVariable<int> _currentHealth = new NetworkVariable<int>(
|
||||
100,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server
|
||||
);
|
||||
|
||||
private Vector2 _moveInput;
|
||||
private CharacterController _controller;
|
||||
private PlayerInputActions _inputActions;
|
||||
@@ -22,6 +46,27 @@ public class NetworkPlayerController : NetworkBehaviour
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
base.OnNetworkSpawn();
|
||||
|
||||
// 서버에서 초기화
|
||||
if (IsServer)
|
||||
{
|
||||
if (_team.Value == TeamType.Neutral)
|
||||
{
|
||||
_team.Value = initialTeam;
|
||||
}
|
||||
|
||||
if (_currentHealth.Value == 0)
|
||||
{
|
||||
_currentHealth.Value = maxHealth;
|
||||
}
|
||||
|
||||
Debug.Log($"<color=cyan>[Player] {gameObject.name} 스폰됨 (팀: {TeamManager.GetTeamName(_team.Value)}, 체력: {_currentHealth.Value}/{maxHealth})</color>");
|
||||
}
|
||||
|
||||
// 체력 변경 이벤트 구독
|
||||
_currentHealth.OnValueChanged += OnHealthChanged;
|
||||
|
||||
if (!IsOwner) return;
|
||||
|
||||
var vcam = GameObject.FindFirstObjectByType<CinemachineCamera>();
|
||||
@@ -39,16 +84,23 @@ public class NetworkPlayerController : NetworkBehaviour
|
||||
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
_currentHealth.OnValueChanged -= OnHealthChanged;
|
||||
|
||||
if (IsOwner && _inputActions != null)
|
||||
{
|
||||
_inputActions.Disable();
|
||||
}
|
||||
|
||||
base.OnNetworkDespawn();
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (!IsOwner) return;
|
||||
|
||||
// 죽었으면 이동 불가
|
||||
if (_currentHealth.Value <= 0) return;
|
||||
|
||||
_moveInput = _inputActions.Player.Move.ReadValue<Vector2>();
|
||||
Vector3 move = new Vector3(_moveInput.x, 0, _moveInput.y).normalized;
|
||||
|
||||
@@ -68,4 +120,201 @@ public class NetworkPlayerController : NetworkBehaviour
|
||||
_animator.SetFloat("MoveSpeed", move.magnitude);
|
||||
}
|
||||
}
|
||||
|
||||
#region ITeamMember Implementation
|
||||
|
||||
public TeamType GetTeam() => _team.Value;
|
||||
|
||||
public void SetTeam(TeamType team)
|
||||
{
|
||||
if (!IsServer) return;
|
||||
|
||||
TeamType previousTeam = _team.Value;
|
||||
_team.Value = team;
|
||||
Debug.Log($"<color=cyan>[Player] 팀 변경: {TeamManager.GetTeamName(previousTeam)} → {TeamManager.GetTeamName(team)}</color>");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDamageable Implementation
|
||||
|
||||
public void TakeDamage(int damage, ulong attackerId)
|
||||
{
|
||||
if (!IsServer) return;
|
||||
|
||||
// 이미 죽었으면 무시
|
||||
if (_currentHealth.Value <= 0) return;
|
||||
|
||||
// 공격자의 팀 확인
|
||||
if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(attackerId, out NetworkObject attackerObj))
|
||||
{
|
||||
var attackerTeamMember = attackerObj.GetComponent<ITeamMember>();
|
||||
if (attackerTeamMember != null)
|
||||
{
|
||||
if (!TeamManager.CanAttack(attackerTeamMember, this))
|
||||
{
|
||||
Debug.Log($"<color=yellow>[Player] {TeamManager.GetTeamName(attackerTeamMember.GetTeam())} 팀은 {TeamManager.GetTeamName(_team.Value)} 팀을 공격할 수 없습니다.</color>");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 데미지 적용
|
||||
int actualDamage = Mathf.Min(damage, _currentHealth.Value);
|
||||
_currentHealth.Value -= actualDamage;
|
||||
|
||||
Debug.Log($"<color=red>[Player] {gameObject.name} ({TeamManager.GetTeamName(_team.Value)})이(가) {actualDamage} 데미지를 받았습니다. 남은 체력: {_currentHealth.Value}/{maxHealth}</color>");
|
||||
|
||||
// 데미지 이펙트
|
||||
ShowDamageEffectClientRpc();
|
||||
|
||||
// 체력이 0이 되면 사망
|
||||
if (_currentHealth.Value <= 0)
|
||||
{
|
||||
Die(attackerId);
|
||||
}
|
||||
}
|
||||
|
||||
private void Die(ulong killerId)
|
||||
{
|
||||
if (!IsServer) return;
|
||||
|
||||
Debug.Log($"<color=red>[Player] {gameObject.name} ({TeamManager.GetTeamName(_team.Value)})이(가) 사망했습니다! (킬러: {killerId})</color>");
|
||||
|
||||
// 사망 이펙트
|
||||
ShowDeathEffectClientRpc();
|
||||
|
||||
// 애니메이션 (있는 경우)
|
||||
if (_animator != null)
|
||||
{
|
||||
_animator.SetTrigger("Die");
|
||||
}
|
||||
|
||||
// 일정 시간 후 리스폰 또는 디스폰
|
||||
Invoke(nameof(HandleDeath), 3f);
|
||||
}
|
||||
|
||||
private void HandleDeath()
|
||||
{
|
||||
if (!IsServer) return;
|
||||
|
||||
// 여기서 리스폰 로직을 추가하거나 게임 오버 처리
|
||||
// 예: 리스폰 위치로 이동 및 체력 회복
|
||||
Respawn();
|
||||
}
|
||||
|
||||
private void Respawn()
|
||||
{
|
||||
if (!IsServer) return;
|
||||
|
||||
// 체력 회복
|
||||
_currentHealth.Value = maxHealth;
|
||||
|
||||
// 스폰 포인트로 이동 (PlayerSpawnPoint 활용)
|
||||
var spawnPoints = FindObjectsByType<PlayerSpawnPoint>(FindObjectsSortMode.None);
|
||||
if (spawnPoints.Length > 0)
|
||||
{
|
||||
var spawnPoint = spawnPoints[Random.Range(0, spawnPoints.Length)];
|
||||
transform.position = spawnPoint.transform.position;
|
||||
transform.rotation = spawnPoint.transform.rotation;
|
||||
}
|
||||
|
||||
Debug.Log($"<color=green>[Player] {gameObject.name} 리스폰!</color>");
|
||||
}
|
||||
|
||||
[ClientRpc]
|
||||
private void ShowDamageEffectClientRpc()
|
||||
{
|
||||
if (damageEffectPrefab != null)
|
||||
{
|
||||
GameObject effect = Instantiate(damageEffectPrefab, transform.position + Vector3.up, Quaternion.identity);
|
||||
Destroy(effect, 2f);
|
||||
}
|
||||
}
|
||||
|
||||
[ClientRpc]
|
||||
private void ShowDeathEffectClientRpc()
|
||||
{
|
||||
if (deathEffectPrefab != null)
|
||||
{
|
||||
GameObject effect = Instantiate(deathEffectPrefab, transform.position, Quaternion.identity);
|
||||
Destroy(effect, 3f);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Health Management
|
||||
|
||||
/// <summary>
|
||||
/// 현재 체력
|
||||
/// </summary>
|
||||
public int GetCurrentHealth() => _currentHealth.Value;
|
||||
|
||||
/// <summary>
|
||||
/// 최대 체력
|
||||
/// </summary>
|
||||
public int GetMaxHealth() => maxHealth;
|
||||
|
||||
/// <summary>
|
||||
/// 체력 비율 (0.0 ~ 1.0)
|
||||
/// </summary>
|
||||
public float GetHealthPercentage()
|
||||
{
|
||||
return maxHealth > 0 ? (float)_currentHealth.Value / maxHealth : 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 죽었는지 여부
|
||||
/// </summary>
|
||||
public bool IsDead() => _currentHealth.Value <= 0;
|
||||
|
||||
/// <summary>
|
||||
/// 체력 회복
|
||||
/// </summary>
|
||||
public void Heal(int amount)
|
||||
{
|
||||
if (!IsServer) return;
|
||||
|
||||
int healAmount = Mathf.Min(amount, maxHealth - _currentHealth.Value);
|
||||
_currentHealth.Value += healAmount;
|
||||
|
||||
Debug.Log($"<color=green>[Player] {gameObject.name}이(가) {healAmount} 회복되었습니다. 현재 체력: {_currentHealth.Value}/{maxHealth}</color>");
|
||||
}
|
||||
|
||||
private void OnHealthChanged(int previousValue, int newValue)
|
||||
{
|
||||
// 체력바 UI 업데이트 또는 체력 변경 시각 효과
|
||||
Debug.Log($"<color=yellow>[Player] 체력 변경: {previousValue} → {newValue}</color>");
|
||||
|
||||
// 클라이언트에서도 체력 변경 인지 가능
|
||||
if (IsOwner)
|
||||
{
|
||||
// UI 업데이트 등
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Gizmos
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
string teamName = TeamManager.GetTeamName(_team.Value);
|
||||
UnityEditor.Handles.Label(transform.position + Vector3.up * 3f,
|
||||
$"Player: {gameObject.name}\nTeam: {teamName}\nHP: {_currentHealth.Value}/{maxHealth}");
|
||||
}
|
||||
else
|
||||
{
|
||||
string teamName = TeamManager.GetTeamName(initialTeam);
|
||||
UnityEditor.Handles.Label(transform.position + Vector3.up * 3f,
|
||||
$"Player: {gameObject.name}\nTeam: {teamName}\nHP: {maxHealth}/{maxHealth}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user