using System; using System.Collections; using System.Collections.Generic; using UnityEditor.TextCore.Text; using UnityEngine; public enum PlayerState { None, Idle, Move, Win, Hit, Dead } public class PlayerController : CharacterBase, IObserver { // 외부 접근 가능 변수 [Header("Attach Points")] [SerializeField] private Transform rightHandTransform; [SerializeField] private CameraShake cameraShake; // 내부에서만 사용하는 변수 private PlayerHitEffectController hitEffectController; private CharacterController _characterController; private bool _isBattle; private GameObject weapon; private WeaponController _weaponController; private IPlayerState _currentStateClass { get; set; } private IPlayerAction _currentAction; public IPlayerAction CurrentAction => _currentAction; // 상태 관련 private PlayerStateIdle _playerStateIdle; private PlayerStateMove _playerStateMove; private PlayerStateWin _playerStateWin; private PlayerStateDead _playerStateDead; // 행동 관련 private PlayerActionAttack _attackAction; private PlayerActionDash _actionDash; // 외부에서도 사용하는 변수 public FixedJoystick Joystick { get; private set; } public PlayerState CurrentState { get; private set; } private Dictionary _playerStates; public Animator PlayerAnimator { get; private set; } public CharacterController CharacterController => _characterController; private void Awake() { PlayerAnimator = GetComponent(); _characterController = GetComponent(); if (Joystick == null) { Joystick = FindObjectOfType(); } } protected override void Start() { base.Start(); hitEffectController = GetComponentInChildren(); PlayerInit(); } private void Update() { if (CurrentState != PlayerState.None) { _playerStates[CurrentState].Update(); } // 대시 우선 입력 처리 if (Input.GetKeyDown(KeyCode.Space)) { StartDashAction(); return; } // 공격 입력 처리 if (Input.GetKeyDown(KeyCode.X) && (_currentAction == null || !_currentAction.IsActive) && (CurrentState != PlayerState.Win && CurrentState != PlayerState.Dead)) { StartAttackAction(); } // 액션 업데이트 if (_currentAction != null && _currentAction.IsActive) { _currentAction.UpdateAction(); } } private void OnDestroy() { OnGetHit -= TakeDamage; } #region 초기화 관련 private void PlayerInit() { // 상태 초기화 _playerStateIdle = new PlayerStateIdle(); _playerStateMove = new PlayerStateMove(); _playerStateWin = new PlayerStateWin(); _playerStateDead = new PlayerStateDead(); _playerStates = new Dictionary { { PlayerState.Idle, _playerStateIdle }, { PlayerState.Move, _playerStateMove }, { PlayerState.Win, _playerStateWin }, { PlayerState.Dead, _playerStateDead }, }; _attackAction = new PlayerActionAttack(); _actionDash = new PlayerActionDash(); OnGetHit -= TakeDamage; OnGetHit += TakeDamage; SetState(PlayerState.Idle); InstantiateWeapon(); } private void InstantiateWeapon() { if (weapon == null) { GameObject weaponObject = Resources.Load("Player/Weapon/Chopstick"); weapon = Instantiate(weaponObject, rightHandTransform); _weaponController = weapon?.GetComponent(); _weaponController?.Subscribe(this); weapon?.SetActive(_isBattle); } } #endregion #region 상태, 동작 변화 관련 public void SetState(PlayerState state) { if (CurrentState != PlayerState.None) { _playerStates[CurrentState].Exit(); } CurrentState = state; _currentStateClass = _playerStates[state]; _currentStateClass.Enter(this); } public void StartAttackAction() { _currentAction = _attackAction; _currentAction.StartAction(this); } public void StartDashAction() { // 만약 공격 중이면 강제로 공격 종료 if (_currentAction == _attackAction && _attackAction.IsActive) { _attackAction.EndAction(); // 애니메이션도 중단 _weaponController.AttackEnd(); } // 기존 대시 중이면 중복 실행 안 함 if (_actionDash.IsActive) return; _currentAction = _actionDash; _actionDash.StartAction(this); } public void OnActionEnded(IPlayerAction action) { if (_currentAction == action) _currentAction = null; } #endregion #region 공격 관련 public void SwitchBattleMode() { _isBattle = !_isBattle; weapon.SetActive(_isBattle); } // Animation Event에서 호출될 메서드 public void SetAttackComboTrue() { if (_weaponController.IsAttacking) return; // 이미 공격 중이면 실행 안함 if (_currentAction == _attackAction) { _attackAction.EnableCombo(); _weaponController.AttackStart(); } } public void SetAttackComboFalse() { if (_currentAction == _attackAction) { // 이벤트 중복 호출? 공격 종료 시 SetAttackComboFalse가 아니라 ~True로 끝나서 오류 발생. (공격 안하는 상태여도 공격으로 판정됨) _attackAction.DisableCombo(); _weaponController.AttackEnd(); // IsAttacking = false로 변경 } } #endregion #region 대시 관련 public Vector3 GetMoveDirectionOrForward() { Vector3 dir = new Vector3(Joystick.Horizontal, 0, Joystick.Vertical); return dir.sqrMagnitude > 0.01f ? dir.normalized : transform.forward; } public void DashButtonPressed() { if (!_actionDash.IsActive) { StartDashAction(); } } #endregion #region IObserver 관련 public void OnNext(GameObject value) { float playerAttackPower = _weaponController.AttackPower * attackPower; if (value.CompareTag("Enemy")) // 적이 Enemy일 때만 공격 처리 { var enemyController = value.transform.GetComponent(); if (enemyController != null) { enemyController.TakeDamage(playerAttackPower); } } } public void OnError(Exception error) { } public void OnCompleted() { _weaponController.Unsubscribe(this); } #endregion #region 회피 관련 // TODO: Editor에서 확인하기 위한 임시용 public void TakeDamage() { if (CurrentState == PlayerState.Dead) return; // 피격 이벤트 재생 PlayerHitEffect(); // 죽었는지 체크 if (currentHP <= 0) SetState(PlayerState.Dead); } public void TakeDamage(CharacterBase character) { if (character != this) return; // 혹시 다른 애가 맞은 경우 무시 if (CurrentState == PlayerState.Dead) return; // 피격 이벤트 재생 PlayerHitEffect(); // 죽었는지 체크 if (currentHP <= 0) SetState(PlayerState.Dead); } private void PlayerHitEffect() { if (_currentAction != _attackAction || !_attackAction.IsActive) PlayerAnimator.SetTrigger("GetHit"); hitEffectController.PlayHitEffect(); cameraShake.Shake(); } #endregion }