294 lines
8.0 KiB
C#
294 lines
8.0 KiB
C#
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<GameObject>
|
|
{
|
|
// 외부 접근 가능 변수
|
|
[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<PlayerState, IPlayerState> _playerStates;
|
|
public Animator PlayerAnimator { get; private set; }
|
|
public CharacterController CharacterController => _characterController;
|
|
|
|
private void Awake()
|
|
{
|
|
PlayerAnimator = GetComponent<Animator>();
|
|
_characterController = GetComponent<CharacterController>();
|
|
if (Joystick == null)
|
|
{
|
|
Joystick = FindObjectOfType<FixedJoystick>();
|
|
}
|
|
}
|
|
|
|
protected override void Start()
|
|
{
|
|
base.Start();
|
|
|
|
hitEffectController = GetComponentInChildren<PlayerHitEffectController>();
|
|
|
|
PlayerInit();
|
|
SwitchBattleMode();
|
|
}
|
|
|
|
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, IPlayerState>
|
|
{
|
|
{ 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<GameObject>("Player/Weapon/Chopstick");
|
|
weapon = Instantiate(weaponObject, rightHandTransform);
|
|
_weaponController = weapon?.GetComponent<WeaponController>();
|
|
_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<EnemyController>();
|
|
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
|
|
}
|