355 lines
13 KiB
C#
355 lines
13 KiB
C#
using System.Threading;
|
|
using Unity.VisualScripting;
|
|
using UnityEngine;
|
|
|
|
public class PlayerCharacterController : MonoBehaviour
|
|
{
|
|
private CharacterController _cController;
|
|
private Animator _anim;
|
|
private PlayerStateMachine _stateMachine;
|
|
|
|
//환경
|
|
private float _gravityValue = Physics.gravity.y; //중력
|
|
|
|
//이동
|
|
private Vector2 _moveInput; // 입력받은 이동 방향
|
|
private Vector3 _velocityY = new Vector3(0,-2f,0); // 플레이어Y의 속도 //Y축만 물리법칙의 지배를 일부 받기 때문에 필요
|
|
private Vector3 _moveTargetDir; // 실제 가고자하는 이동방향
|
|
private float _moveCutTimer = 0f; //이동금지 타이머
|
|
private Vector3 _rootMotionVelocity; //루트모션 사용시
|
|
|
|
//캐릭터 스피드
|
|
[SerializeField] private float _spdCoefficient = 4f; //스피드 계수
|
|
[SerializeField] private float _acceleration = 4f; // 가속도
|
|
[SerializeField] private float _deceleration = 20f; // 감속도
|
|
private float _walkSpdRatio = 0.3f; // 걷기 속도 블렌드 비율 //여기를 수정하면 에디터의 블렌드도 수정해야함
|
|
private float _runSpdRatio = 1f; // 달리기 속도 블렌드 비율 //여기를 수정하면 에디터의 블렌드도 수정해야함
|
|
private float _currentSpd; //현재 스피드
|
|
|
|
//점프
|
|
[SerializeField] private float _jumpPower = 3.0f; //점프 힘
|
|
private float _jumpReadyCool = 0.8f; //점프 쿨타임
|
|
private float _jumpReadyCoolTimer = 0f; //점프 쿨타이머
|
|
private int _jumpCount = 0;
|
|
private int _maxJumpCount = 2;
|
|
|
|
//대쉬
|
|
[SerializeField] private AnimationCurve _dodgeCurve; // 변속용 커브
|
|
[SerializeField] private float _dodgeDistance = 5f; // 총 대쉬 거리
|
|
[SerializeField] private float _dodgeDuration = 0.5f; // 대쉬 지속 시간 (무적시간)
|
|
private float _dodgeTimer = 0f; // 대쉬 진행 측정용
|
|
private Vector3 _dodgeDir; // 대쉬 시작 시점의 방향 고정
|
|
|
|
//카메라 전환
|
|
[SerializeField] private PlayerCameraRig _playerCameraRig; //카메라 집합체
|
|
private CameraMode _cameraMode = CameraMode.FreeLook; //카메라 모드
|
|
private CancellationTokenSource _cameraDelayChangeCts; //지연전환 취소 토큰
|
|
|
|
//캐릭터 관련
|
|
public CharacterIdentity PlayerCharacterIdentity { get; set; }
|
|
public PlayerStat PlayerCharacterStat{ get; set; }
|
|
|
|
//무기
|
|
//[SerializeField] private Weapon _weapon;
|
|
|
|
/*
|
|
* 웨폰 모듈로 따로 빼볼것
|
|
*
|
|
//차지공격
|
|
//private float _chargePowerAdded = 0f; //차지 기본값에 더해줄 힘 //weapon 모듈로 뺄것
|
|
//private float _chargePowerFirst = 20f; //차지 기본값 (바로 시전해도 값이 있어야 되기 때문) //weapon 모듈로 뺄것
|
|
//private float _chargePowerDelta = 0.2f; //차지할때 힘이 더해질 단위시간 //weapon 모듈로 뺄것
|
|
private event Action _chargeAttackCallback; //차지공격시 실행될 함수
|
|
|
|
//private AimingModule _aimingModule; //이건 weapon 모듈로 빼볼것
|
|
|
|
//private float _bowShootCoolTime = 0.5f; // weapon(Bow)로 뺄것
|
|
//private float _bowShootCoolTimer = 0f; // weapon(Bow)로 뺄것
|
|
|
|
//[SerializeField] private LineRenderer _arrowTraceRenderer; // weapon(Bow)로 뺄것
|
|
//private int _arrowTraceResolution = 30; // 화살 궤적 선을 구성하는 점의 개수 (정밀도) // weapon(Bow)로 뺄것
|
|
//private float _arrowTraceStepTime = 0.1f; // 화살 궤적 점 사이의 시간 간격 // weapon(Bow)로 뺄것
|
|
*/
|
|
|
|
private void Awake()
|
|
{
|
|
_cController = GetComponent<CharacterController>();
|
|
_anim = GetComponent<Animator>();
|
|
_stateMachine = GetComponent<PlayerStateMachine>();
|
|
PlayerCharacterIdentity = GetComponent<CharacterIdentity>();
|
|
PlayerCharacterStat = GetComponent<PlayerStat>();
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
_stateMachine.SetMaxJumpCount(_maxJumpCount);
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
//캐릭터 컨트롤러는 FixedUpdate보다 Update에서 하는게 권장됨
|
|
CheckGround();
|
|
|
|
StateUpdate();
|
|
|
|
Movement();
|
|
WorkGravity();
|
|
ApplyMove();
|
|
|
|
TickTimer();
|
|
//PlayerDebug();
|
|
}
|
|
|
|
private void FixedUpdate()
|
|
{
|
|
|
|
}
|
|
|
|
private void PlayerDebug()
|
|
{
|
|
Debug.Log($"isGrounded : {_stateMachine.IsGrounded}");
|
|
}
|
|
|
|
#region 상태 업데이트
|
|
private void StateUpdate()
|
|
{
|
|
// 땅에서 추락할때
|
|
if (!_stateMachine.IsGrounded && _velocityY.y < 0f && _stateMachine.CurrentState != PlayerState.Attack && _stateMachine.CurrentState != PlayerState.Charge)
|
|
{
|
|
_stateMachine.ChangeState(PlayerState.Fall);
|
|
}
|
|
//점프에서 바닥에 닿을때
|
|
else if (_stateMachine.IsGrounded && _stateMachine.CurrentState == PlayerState.Fall)
|
|
{
|
|
_stateMachine.ChangeState(PlayerState.Idle);
|
|
_moveCutTimer = 0.1f;
|
|
}
|
|
|
|
if (_moveInput.sqrMagnitude < Mathf.Epsilon && _stateMachine.CanMove() && _stateMachine.CurrentState != PlayerState.Attack) // 지상 이동이 가능하고 공격중도 아닌데 이동입력이 없는상태 - 거의 0
|
|
{
|
|
_stateMachine.ChangeState(PlayerState.Idle);
|
|
}
|
|
else if(_stateMachine.CanMove() && _stateMachine.CurrentState != PlayerState.Attack) //지상이동이 가능하고 공격중이 아닌데 이동입력이 있다
|
|
{
|
|
// 런키를 누른 상태면 뛰는 상태, 아니면 걷는 상태
|
|
_stateMachine.ChangeState(_stateMachine.IsRunInputPressed ? PlayerState.Run : PlayerState.Walk);
|
|
}
|
|
|
|
Debug.Log($"_moveCutTimer : {_moveCutTimer}");
|
|
|
|
if (_moveCutTimer > 0)
|
|
_stateMachine.IsMoveCut = true;
|
|
else
|
|
_stateMachine.IsMoveCut = false;
|
|
|
|
Debug.Log($"_moveCut : {_stateMachine.IsMoveCut}");
|
|
Debug.Log($"CurentState : {_stateMachine.CurrentState}");
|
|
|
|
if (_jumpReadyCoolTimer > 0)
|
|
_stateMachine.IsJumpCool = true;
|
|
else
|
|
_stateMachine.IsJumpCool = false;
|
|
|
|
|
|
if(_stateMachine.IsGrounded)
|
|
{
|
|
_jumpCount = 0;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region 체크
|
|
private bool IsCloseToGround(float checkDistance)
|
|
{
|
|
return Physics.Raycast(transform.position, Vector3.down, checkDistance);
|
|
}
|
|
private void CheckGround()
|
|
{
|
|
_stateMachine.IsGrounded = _cController.isGrounded;
|
|
_stateMachine.IsCloseToGround = IsCloseToGround(0.9f);
|
|
|
|
_anim.SetBool("IsGrounded", _stateMachine.IsGrounded);
|
|
_anim.SetBool("IsCloseToGround", _stateMachine.IsCloseToGround);
|
|
}
|
|
#endregion
|
|
|
|
#region 중력
|
|
private void WorkGravity()
|
|
{
|
|
if (_stateMachine.IsGrounded && _velocityY.y < 0)
|
|
{
|
|
_velocityY.y = -2f;
|
|
}
|
|
|
|
_velocityY.y += _gravityValue * Time.deltaTime;
|
|
_anim.SetFloat("velocityY", _velocityY.y);
|
|
}
|
|
#endregion
|
|
|
|
#region 이동
|
|
private void Movement()
|
|
{
|
|
//구르기중일때 전용 로직
|
|
if (_stateMachine.CurrentState == PlayerState.Dodge)
|
|
{
|
|
_dodgeTimer = _dodgeDuration;
|
|
|
|
float normalizedTime = (_dodgeDuration - _dodgeTimer) / _dodgeDuration; // 0 ~ 1 사이의 진행률
|
|
|
|
if (normalizedTime >= 1.0f)
|
|
{
|
|
// 구르기 종료 -> 원래상태로 복귀 로직
|
|
//_stateMachine.ChangeState(PlayerState.Idle);
|
|
//_currentSpd = 0f;
|
|
|
|
return;
|
|
}
|
|
|
|
// 커브에서 현재 시점의 '속도 배율'을 가져옴
|
|
// 그래프의 전체 넓이는 1
|
|
float curveValue = _dodgeCurve.Evaluate(normalizedTime);
|
|
|
|
// 최종 속도 = (전체 거리 / 시간) * 커브 배율 ??
|
|
_currentSpd = (_dodgeDistance / _dodgeDuration) * curveValue;
|
|
_moveTargetDir = _dodgeDir * _currentSpd;
|
|
|
|
// 구르기 중엔 즉시 회전 (선택 사항)
|
|
transform.rotation = Quaternion.LookRotation(_dodgeDir);
|
|
|
|
return;
|
|
}
|
|
|
|
float targetSpd = 0;
|
|
|
|
if(_moveInput.sqrMagnitude > Mathf.Epsilon) //입력이 있다
|
|
{
|
|
targetSpd = _stateMachine.IsRunInputPressed ? _runSpdRatio * _spdCoefficient : _walkSpdRatio * _spdCoefficient;
|
|
}
|
|
|
|
// _currentSpd가 targetSpd로 서서히 접근
|
|
float accelWeight = _stateMachine.CurrentState == PlayerState.Run ? 2 : 1; //런일때 가속도 가중치
|
|
float currentStepSpeed = (targetSpd > _currentSpd) ? _acceleration : _deceleration;
|
|
_currentSpd = Mathf.MoveTowards(_currentSpd, targetSpd, currentStepSpeed * accelWeight * Time.deltaTime);
|
|
|
|
_anim.SetFloat("speedRatio", _currentSpd / _spdCoefficient);
|
|
|
|
Vector3 camForward = Camera.main.transform.forward; //메인카메라의 로컬기준 앞쪽방향
|
|
camForward.y = 0; //높이를 무시하기 위해 0으로
|
|
camForward.Normalize();
|
|
|
|
Vector3 camRight = Camera.main.transform.right; //메인카메라의 로컬기준 x축, 오른쪽 방향
|
|
camRight.y = 0; //높이를 무시하기 위해 0으로
|
|
camRight.Normalize();
|
|
|
|
Vector3 moveDir = new Vector3(_moveInput.x, 0, _moveInput.y);
|
|
|
|
_moveTargetDir = (moveDir.x * camRight + moveDir.z * camForward) * _currentSpd;//오른쪽을 눌렀다면 camRight 방향으로 그만큼, 위쪽을 눌렀다면 camForward방향으로 그만큼 가라
|
|
|
|
RotationByMove();
|
|
}
|
|
private void RotationByMove()
|
|
{
|
|
if(_moveTargetDir.magnitude > Mathf.Epsilon)
|
|
{
|
|
Quaternion lookTarget = Quaternion.LookRotation(_moveTargetDir); // 방향 벡터를 쿼터니언 회전값으로 전환
|
|
transform.rotation = Quaternion.Slerp(transform.rotation, lookTarget, Time.deltaTime * 10);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region 속도적용
|
|
private void OnAnimatorMove()
|
|
{
|
|
// 애니메이션이 가고 싶은 값
|
|
/*
|
|
if(_stateMachine.CurrentState == PlayerState.Roll)
|
|
{
|
|
_rootMotionVelocity = _anim.deltaPosition / Time.deltaTime;
|
|
}
|
|
*/
|
|
}
|
|
private void ApplyMove()
|
|
{
|
|
//지상이동이 가능한 상황이 아니고, 공중이동도 가능한 상황이 아니라면
|
|
if (!_stateMachine.CanMove() && !_stateMachine.CanControlInAir())
|
|
{
|
|
_moveTargetDir.x = 0f;
|
|
_moveTargetDir.z = 0f;
|
|
}
|
|
|
|
Vector3 finalVector = new Vector3(_moveTargetDir.x, _velocityY.y, _moveTargetDir.z);
|
|
|
|
_cController.Move(finalVector * Time.deltaTime); // xz 이동벡터를 적용
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
#region 점프
|
|
private void JumpAction()
|
|
{
|
|
_velocityY.y = Mathf.Sqrt(_jumpPower * -2f * _gravityValue);
|
|
}
|
|
#endregion
|
|
|
|
#region 타미어
|
|
private void TickTimer()
|
|
{
|
|
if (_jumpReadyCoolTimer > 0) _jumpReadyCoolTimer -= Time.deltaTime; if (_jumpReadyCoolTimer < 0) _jumpReadyCoolTimer = 0f;
|
|
if (_moveCutTimer > 0) _moveCutTimer -= Time.deltaTime; if (_moveCutTimer < 0) _moveCutTimer = 0f;
|
|
}
|
|
#endregion
|
|
|
|
#region 입력
|
|
public void MoveInput(Vector2 moveInput)
|
|
{
|
|
_moveInput = moveInput;
|
|
}
|
|
public void SprintInput(InputState inputState)
|
|
{
|
|
if (inputState == InputState.Performing)
|
|
{
|
|
_stateMachine.IsRunInputPressed = true;
|
|
}
|
|
else if (inputState == InputState.Canceled)
|
|
{
|
|
_stateMachine.IsRunInputPressed = false;
|
|
}
|
|
}
|
|
public void JumpInput(InputState inputState)
|
|
{
|
|
if (_stateMachine.CanJump())
|
|
{
|
|
if (inputState == InputState.Started)
|
|
{
|
|
_anim.SetTrigger("jumpTrigger");
|
|
_stateMachine.ChangeState(PlayerState.Jump);
|
|
_jumpReadyCoolTimer = _jumpReadyCool;
|
|
_ = Util.RunDelayed(0.1f, JumpAction, default);
|
|
|
|
_jumpCount++;
|
|
_stateMachine.RecordJump();
|
|
|
|
}
|
|
}
|
|
}
|
|
public void DodgeInput(InputState inputState)
|
|
{
|
|
// 구르기 가능한 상태일 때만
|
|
if (inputState == InputState.Started && _stateMachine.CanDodge())
|
|
{
|
|
Vector3 camForward = Camera.main.transform.forward;
|
|
camForward.y = 0;
|
|
camForward.Normalize();
|
|
|
|
// 입력이 없으면 카메라가 보는 앞방향, 있으면 입력 방향
|
|
_dodgeDir = _moveInput.sqrMagnitude > Mathf.Epsilon ? _moveTargetDir.normalized : camForward;
|
|
|
|
// 상태 전환 및 타이머 초기화
|
|
_stateMachine.ChangeState(PlayerState.Dodge);
|
|
_dodgeTimer = 0f;
|
|
}
|
|
}
|
|
#endregion
|
|
}
|