1. UI - 조이스틱 UIManager에 추가 및 Scene에서 호출 방식 변경 2. UI - 경험치 바 앞에 레벨 아이콘 추가 3. 몬스터 죽었을때 경험치로 변경 4. 경험치 바와 레벨 아이콘 연동 Todo 1. 투사체 공격 만들기 2. 몬스터가 플레이어 쫓아오게 만들기 3. 몬스터를 카메라 외각에서 다량으로 생성하는 기능 추가하기 4. 몬스터가 캐릭터 공격시 체력 닳게 하기 5. 메뉴 UI 만들기 6. 레벨업시 획득 스킬 UI 만들기 7. 체력바 UI 만들기 8. 공격시 데미지 띄우는 UI 만들기
386 lines
11 KiB
C#
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<int, 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.Level ,_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;
|
|
}
|
|
}
|
|
|
|
}
|