Practice_Unity/Assets/Scripts/Controllers/PlayerController.cs
Seonkyu.kim 886822a545 작업
1. 적 <-> 플레이어 공격해서 값 체력깎기 기능 추가
2. 적 죽으면(explore)시 정해진 양만큼의 경험치 큐브 생성
  - but 아직 1과 연동해서 공격해서 피 깎아서 죽는건 아직 아님
3. 경험치 큐브 범위내에 있으면 당기기 기능 추가
4. 경험치 큐브 캐릭터 최근접 범위에 닿으면 흡수하기 기능 추가
5. 경험치 획득시 레벨업 기능 추가
6. UI 경험치 바 화면에 구현
7. UI 경험치 바 플레이어 정보의 경험치와 연계

Todo
1. UI 다른 요소들 죄다 UIManaer로 이동
2. 몬스터 공격해서 에너지 덩어리로 바꾸는 동작 추가
3. 레벨업시 레벨 화면 표기
4. 몬스터가 캐릭터에게 다가오는 동작 추가
5. 투사체 공격 기능 추가
2025-10-01 17:56:13 +09:00

386 lines
11 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using Animation;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.InputSystem;
public partial class PlayerController : MonoBehaviour, IDamageable
{
#region Enum
enum AnimatorParam
{
speed,
atk_speed,
attack,
run,
}
enum PlayerBehavior
{
Idle,
Move,
Attack,
Die
}
// rigidbody와 NavMeshAgent를 함께 사용할 때의 물리엔진 상 문제가 생겨서 그거를 구분하기 위한 부분
// NavMeshAgent가 뇌, rigidbody가 몸 역할을 하게 됨
public enum ControlMode
{
Player,
AI
}
#endregion
// 이벤트
public event Action<float, float> OnExpChanged;
// 상태 정보
private Status_Player _status;
public Status_Player Status
{
get { return _status; }
private set { _status = value;}
}
// 획득
private MagnetComponent _magnet;
// 이동 관련
private float _runTriggerRatio = 0.6f;
private float _rotationSpeed = 10f;
private ControlMode _controlMode = ControlMode.Player;
// 공격 관련
private NavMeshAgent _agent;
private Rigidbody _rigidbody;
public bool _isAttack = false;
private Damage _weaponDamage;
[SerializeField] private Transform _weaponSocket;
[SerializeField] private Transform _shieldSocket;
// 애니메이터 관련
private AnimatorManager<AnimatorParam> _mAnimator;
private PlayerBehavior _behavior = PlayerBehavior.Idle;
private PlayerBehavior Behavior
{
get { return _behavior; }
set
{
_behavior = value;
switch (_behavior)
{
case PlayerBehavior.Die:
case PlayerBehavior.Idle:
_mAnimator.SetValue(AnimatorParam.speed, 0);
break;
case PlayerBehavior.Move:
_mAnimator.SetValue(AnimatorParam.attack, false);
break;
case PlayerBehavior.Attack:
_mAnimator.SetValue(AnimatorParam.attack, true);
break;
}
}
}
private void Awake()
{
_mAnimator = new AnimatorManager<AnimatorParam>(GetComponent<Animator>());
_agent = gameObject.GetComponent<NavMeshAgent>();
_rigidbody = gameObject.GetComponent<Rigidbody>();
_status = gameObject.GetOrAddComponent<Status_Player>();
_magnet = gameObject.GetComponentInChildren<MagnetComponent>();
if(_agent != null) _agent.updateRotation = false;
}
void Start()
{
_status.Data = Manager.Data.LoadSingle<PlayerDataLoader, Data_Status_Player>("Data/PlayerData");
SetAnimator();
EquipWeapon(_weaponSocket, "Prefabs/Equipment/Weapon");
// EquipWeapon(_shieldSocket, "Prefabs/Equipment/Shield");
SubscribeAction();
}
void SubscribeAction()
{
Manager.Input.MoveAction -= OnMove;
Manager.Input.MoveAction += OnMove;
_magnet.OnGetExp -= OnGetExp;
_magnet.OnGetExp += OnGetExp;
// UI가 아직 생성되지 않은 경우, 코루틴을 통해 버튼이 생성될 때까지 대기
StartCoroutine(WaitForButtonsAndSubscribe());
}
private IEnumerator WaitForButtonsAndSubscribe()
{
while (Manager.Input.Buttons == null) yield return null; // 버튼이 생성될 때까지 한 프레임 대기
// 버튼이 생성되면 이벤트 등록
Manager.Input.Buttons.OnAttackButtonClicked -= OnAttack;
Manager.Input.Buttons.OnAttackButtonClicked += OnAttack;
Manager.Input.Buttons.OnSkillButtonClicked -= OnSkill;
Manager.Input.Buttons.OnSkillButtonClicked += OnSkill;
}
// 애니메이터 세팅 (지금은 한 줄 뿐이지만 후에 더 추가될 수도)
void SetAnimator()
{
if (_status != null)
{
_mAnimator.SetValue(AnimatorParam.atk_speed, _status.AtkSpeed);
}
}
// --- 매 프레임 실행 ---
void Update()
{
PlayerMovement();
}
private void PlayerMovement()
{
if (_controlMode == ControlMode.AI)
{
if(_agent.pathPending || !_agent.hasPath) return;
_rigidbody.linearVelocity = Vector3.zero;
// 회전
if (_agent.desiredVelocity.sqrMagnitude > 0.1f)
{
Quaternion targetRotation = Quaternion.LookRotation(_agent.desiredVelocity.normalized);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * _rotationSpeed);
}
if (_agent.remainingDistance <= _agent.stoppingDistance)
{
_rigidbody.linearVelocity = Vector3.zero;
SetControlMode(ControlMode.Player);
}
}
}
public void SetControlMode(ControlMode mode)
{
_controlMode = mode;
switch (_controlMode)
{
case ControlMode.Player:
if(_agent != null) _agent.enabled = false;
_rigidbody.isKinematic = false;
_rigidbody.linearVelocity = Vector3.zero;
break;
case ControlMode.AI:
_rigidbody.isKinematic = true;
_rigidbody.linearVelocity = Vector3.zero;
if (_agent != null) _agent.enabled = true;
break;
}
}
#region Navi Event
void OnInput(Define.InputEvent evt)
{
switch (evt)
{
case Define.InputEvent.Click:
break;
case Define.InputEvent.Down:
break;
case Define.InputEvent.Up:
break;
case Define.InputEvent.Press:
break;
}
}
public void NavigateTo(Vector3 target)
{
SetControlMode(ControlMode.AI);
if (_agent != null)
{
_agent.isStopped = false; // 목적지 설정 전 에이전트가 멈춰있지 않도록 합니다.
_agent.SetDestination(target);
_agent.speed = _status.MoveSpeed;
}
}
#endregion
void EquipWeapon(Transform transform, string path)
{
GameObject newParts = Manager.Resource.Instantiate($"{path}");
Damage damageScript = newParts.GetComponent<Damage>();
// damageScript._targetlayer = (1<< 11) | (1 << 12);
newParts.transform.SetParent(transform);
newParts.transform.localPosition = Vector3.zero;
newParts.transform.localRotation = Quaternion.identity;
newParts.transform.localScale = Vector3.one;
_weaponDamage = newParts.GetComponentInChildren<Damage>();
if (_weaponDamage != null)
{
_weaponDamage.OnHit -= OnWeaponHit;
_weaponDamage.OnHit += OnWeaponHit;
}
}
#region Action Event
private void OnMove(Vector2 direction)
{
SetControlMode(ControlMode.Player);
float magnitude = direction.magnitude;
bool isRun = direction.magnitude > _runTriggerRatio ? true : false;
float speed = 0f;
// 입력이 거의 없을 때
if (magnitude < 0.1f) speed = 0f;
// 걷기 구간일 때
else if (magnitude <= _runTriggerRatio) speed = Mathf.Lerp(0, _status.MoveSpeed, (magnitude/_runTriggerRatio));
// 달리기 구간일 때
else
{
float runPercentage = (magnitude - _runTriggerRatio) / (1.0f - _runTriggerRatio);
speed = Mathf.Lerp(_status.MoveSpeed, _status.MoveSpeed * 2.0f, runPercentage);
}
_agent.speed = speed;
_mAnimator.SetValue(AnimatorParam.run, isRun);
_mAnimator.SetValue(AnimatorParam.speed , speed);
if (magnitude < 0.1f)
{
_rigidbody.linearVelocity = Vector3.zero;
return;
}
Vector3 camForward = Camera.main.transform.forward;
Vector3 camRight = Camera.main.transform.right;
camForward.y = 0;
camRight.y = 0;
camForward.Normalize();
camRight.Normalize();
Vector3 moveDir = (camForward * direction.y + camRight * direction.x).normalized;
if (moveDir != Vector3.zero)
{
Quaternion targetRotation = Quaternion.LookRotation(moveDir);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, _rotationSpeed * Time.deltaTime);
}
_rigidbody.linearVelocity = transform.forward * speed;
}
private void OnWeaponHit(IDamageable target, Collider collider)
{
// 적 캐릭터인지 확인
// var enemy = target.GetComponent<EnemyController>();
if (collider.gameObject.layer == LayerMask.NameToLayer("Enemy"))
{
Debug.Log($"Enemy Hit! {collider.name}");
}
else
{
Debug.Log($"other Hit! {collider.name}");
}
}
private void OnAttack()
{
if (!_isAttack)
{
_isAttack = true;
Behavior = PlayerBehavior.Attack;
}
}
private void OnSkill(int index)
{
}
private void OnGetExp(float amount)
{
if (_status != null)
{
_status.ApplyExp(amount);
OnExpChanged?.Invoke(_status.Exp, _status.MaxExp);
Debug.Log($"Player Exp: {amount} / {_status.Exp}");
}
}
#endregion
#region Interface
public void TakeDamage(float damage)
{
float hp = Status.ApplyDamage(damage);
Debug.Log($"적에게 피격! 데미지: {damage} / 남은 체력: {hp}");
}
#endregion
private void OnDestroy()
{
}
}
// 애니메이션의 이벤트 함수 부분
public partial class PlayerController {
public void Hit()
{
_mAnimator.SetValue(AnimatorParam.attack, false);
Debug.Log("Knight Hit!");
}
public void FootR()
{
}
public void FootL()
{
}
public void EnableCollider()
{
_weaponDamage?.SetTriggerActive(true);
}
public void DisableCollider()
{
_weaponDamage?.SetTriggerActive(false);
}
public void EndAttack()
{
if (_isAttack)
{
_isAttack = false;
_mAnimator.SetValue(AnimatorParam.attack, _isAttack);
_weaponDamage?.SetTriggerActive(false);
Behavior = PlayerBehavior.Idle;
}
}
}