using System.Threading; 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; // 실제 가고자하는 이동방향 //캐릭터 스피드 [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 = 1.5f; //점프 힘 private float _jumpReadyCool = 0.8f; //점프 쿨타임 private float _jumpReadyCoolTimer = 0f; //점프 쿨타이머 //대쉬 [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(); _anim = GetComponent(); _stateMachine = GetComponent(); PlayerCharacterIdentity = GetComponent(); PlayerCharacterStat = GetComponent(); } private void Update() { //캐릭터 컨트롤러는 FixedUpdate보다 Update에서 하는게 권장됨 Movement(); CheckGround(); WorkGravity(); ApplyVelocity(); //PlayerDebug(); } private void FixedUpdate() { } private void PlayerDebug() { Debug.Log($"isGrounded : {_stateMachine.IsGrounded}"); } #region 체크 private void CheckGround() { _stateMachine.IsGrounded = _cController.isGrounded; } #endregion #region 중력 private void WorkGravity() { if (_stateMachine.IsGrounded && _velocityY.y < 0) { _velocityY.y = -2f; } _velocityY.y += _gravityValue * Time.deltaTime; _anim.SetFloat("velocityY", _velocityY.y); // 상태 자동 전환 로직 if (!_stateMachine.IsGrounded && _stateMachine.CurrentState != PlayerState.Jump && _stateMachine.CurrentState != PlayerState.Attack && _stateMachine.CurrentState != PlayerState.Charge) { _stateMachine.ChangeState(PlayerState.Fall); } else if (_stateMachine.IsGrounded && _stateMachine.CurrentState == PlayerState.Fall) { _stateMachine.ChangeState(PlayerState.Idle); } } #endregion #region 이동 public void MoveInput(Vector2 moveInput) { if (_stateMachine.CanMove()) { _moveInput = moveInput; } } public void SprintInput(InputState inputState) { if(inputState == InputState.Performing) { _stateMachine.IsRunInputPressed = true; } else if(inputState == InputState.Canceled) { _stateMachine.IsRunInputPressed = false; } } 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) // 이동이 없는상태 - 거의 0 { targetSpd = 0f; // 완전히 멈추기 전까지는 이동 상태를 유지하다가 아주 느려지면 Idle 전환 if (_currentSpd < 0.05f) _stateMachine.ChangeState(PlayerState.Idle); //가속도 안쓸시 //_currentSpd = 0; //_stateMachine.ChangeState(PlayerState.Idle); } else //이동이 없지 않음 = 이동이 있다 { // 런키를 누른 상태면 뛰는 스피드, 아니면 걷는 스피드 적용 _stateMachine.ChangeState(_stateMachine.IsRunInputPressed ? PlayerState.Run : PlayerState.Walk); targetSpd = _stateMachine.IsRunInputPressed ? _runSpdRatio * _spdCoefficient : _walkSpdRatio * _spdCoefficient; //가속도 안쓸시 //_stateMachine.ChangeState(_stateMachine.IsRunInputPressed ? PlayerState.Run : PlayerState.Walk); //_currentSpd = _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 ApplyVelocity() { Vector3 finalVector = new Vector3(_moveTargetDir.x, _velocityY.y, _moveTargetDir.z); _cController.Move(finalVector * Time.deltaTime); // xz 이동벡터를 적용 } #endregion #region 대시(구르기,횡이동등) 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 #region 점프 public void JumpInput(InputState inputState) { if(_stateMachine.CanJump()) { if (inputState == InputState.Started && _stateMachine.IsGrounded && _stateMachine.CurrentState != PlayerState.Jump && _jumpReadyCoolTimer <= 0f) { _anim.SetTrigger("jumpTrigger"); _stateMachine.ChangeState(PlayerState.Jump); _jumpReadyCoolTimer = _jumpReadyCool; _ = Util.RunDelayed(0.7f, JumpAction, default); } } } private void JumpAction() { _velocityY.y = Mathf.Sqrt(_jumpPower * -2f * _gravityValue); } #endregion }