1. 공격 스크립트를 만듬 - 플레이어 -> 적 공격 테스트 (성공) - 적 -> 플레이어 공격 테스트 (성공) Todo 1. 카메라 컨트롤러는 이거 방법 아예 따로 찾아야 할거 같음 2. 공격 스크립트 (시도) - 투사체 공격 (시작해야 함) - 공격 관련 무기 스테이터스 같은거 생각해야 함 - 몬스터 AI라던가 설정 또는 이동과 관련된것도 생각해야 함 - 공격 받았을 경우 처리는 어떻게 할건지 고민을 해야 함
372 lines
10 KiB
C#
372 lines
10 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
|
|
{
|
|
private AnimatorManager<AnimatorParam> _mAnimator;
|
|
|
|
private NavMeshAgent _agent;
|
|
private Rigidbody _rigidbody;
|
|
|
|
private Status_Player _status;
|
|
|
|
public Status_Player Status
|
|
{
|
|
get { return _status; }
|
|
private set { _status = value;}
|
|
}
|
|
|
|
private Vector2 _screenPos;
|
|
|
|
private Vector3 _currentVelocity;
|
|
private Vector3 _velocitySmoothDampRef;
|
|
|
|
private float _runTriggerRatio = 0.6f;
|
|
|
|
// 공격 관련
|
|
public bool _isAttack = false;
|
|
private Damage _weaponDamage;
|
|
|
|
|
|
[SerializeField] private float _rotationSpeed = 10f;
|
|
|
|
[SerializeField] private Transform _weaponSocket;
|
|
[SerializeField] private Transform _shieldSocket;
|
|
|
|
|
|
// --- Enum 정의 ---
|
|
enum AnimatorParam
|
|
{
|
|
speed,
|
|
atk_speed,
|
|
attack,
|
|
run,
|
|
}
|
|
|
|
enum PlayerBehavior
|
|
{
|
|
Idle,
|
|
Move,
|
|
Attack,
|
|
Die
|
|
}
|
|
|
|
// rigidbody와 NavMeshAgent를 함께 사용할 때의 물리엔진 상 문제가 생겨서 그거를 구분하기 위한 부분
|
|
// NavMeshAgent가 뇌, rigidbody가 몸 역할을 하게 됨
|
|
public enum ControlMode
|
|
{
|
|
Player,
|
|
AI
|
|
}
|
|
|
|
private ControlMode _controlMode = ControlMode.Player;
|
|
|
|
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>();
|
|
_currentVelocity = Vector3.zero;
|
|
|
|
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");
|
|
|
|
SubmitAction();
|
|
|
|
}
|
|
|
|
void SubmitAction()
|
|
{
|
|
|
|
Manager.Input.MoveAction -= OnMove;
|
|
Manager.Input.MoveAction += OnMove;
|
|
|
|
if (Manager.Input.Buttons != null)
|
|
{
|
|
Manager.Input.Buttons.OnAttackButtonClicked -= OnAttack;
|
|
Manager.Input.Buttons.OnAttackButtonClicked += OnAttack;
|
|
Manager.Input.Buttons.OnSkillButtonClicked -= OnSkill;
|
|
Manager.Input.Buttons.OnSkillButtonClicked += OnSkill;
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
|
|
Debug.Log("버튼 이벤트 등록 완료");
|
|
}
|
|
|
|
void SetAnimator()
|
|
{
|
|
if (_status != null)
|
|
{
|
|
_mAnimator.SetValue(AnimatorParam.atk_speed, _status.AtkSpeed);
|
|
}
|
|
}
|
|
|
|
// --- 매 프레임 실행 ---
|
|
void Update()
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void EquipWeapon(Transform transform, string path)
|
|
{
|
|
GameObject newParts = Manager.Resource.Instantiate($"{path}");
|
|
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;
|
|
}
|
|
}
|
|
|
|
private void OnWeaponHit(IDamageable target, Collider collider)
|
|
{
|
|
// 적 캐릭터인지 확인
|
|
// var enemy = target.GetComponent<EnemyController>();
|
|
// if (enemy != null)
|
|
// {
|
|
Debug.Log($"Enemy Hit! {collider.name}");
|
|
// }
|
|
}
|
|
|
|
private void OnAttack()
|
|
{
|
|
if (!_isAttack)
|
|
{
|
|
|
|
_isAttack = true;
|
|
Behavior = PlayerBehavior.Attack;
|
|
}
|
|
|
|
}
|
|
|
|
private void OnSkill(int index)
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
private void OnDestroy()
|
|
{
|
|
|
|
}
|
|
|
|
public void TakeDamage(float damage)
|
|
{
|
|
Debug.Log($"적에게 피격! 데미지: {damage}");
|
|
}
|
|
}
|
|
|
|
// 애니메이션의 이벤트 함수 확인 부분 - 후에 따로 뺄 것
|
|
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;
|
|
}
|
|
}
|
|
|
|
}
|