using System; using System.Collections; using Sirenix.OdinInspector; using UnityEngine; using UnityEngine.InputSystem; // ReSharper disable once CheckNamespace namespace BlueWaterProject { public class CombatPlayerController : MonoBehaviour, IDamageable { /*********************************************************************** * Definitions ***********************************************************************/ #region Class [Serializable] public class Components { public PlayerInput playerInput; public Transform visualLook; public Animator animator; public PhysicsMovement movement; } [Serializable] public class CharacterOption { [Range(0f, 1000f), Tooltip("최대 체력")] public float maxHp = 100f; [Title("공격")] [Range(1, 21), Tooltip("한 번에 공격 가능한 개체 수")] public int maxHitNum = 10; [Range(0f, 100f), Tooltip("공격 데미지")] public float attackDamage = 10f; [Range(0.1f, 2f), Tooltip("콤보 공격 포함 총 걸리는 시간")] public float attackTime = 0.7f; [Range(0.1f, 5f), Tooltip("공격 범위 사거리(반지름)")] public float attackRange = 1.5f; [Range(0f, 360f), Tooltip("공격 범위 각도")] public float attackAngle = 180f; [Tooltip("공격할 레이어 설정")] public LayerMask targetLayer = -1; } [Serializable] [DisableIf("@true")] public class CurrentState { public bool useMouseAttack = true; public bool isInvincibility; public bool isAttacking; public bool isComboPossible; public bool isComboAttacking; } [DisableIf("@true")] [Serializable] public class CurrentValue { [Tooltip("현재 체력")] public float currentHp; [Tooltip("최근에 공격 받은 충돌체 배열")] public Collider[] hitColliders; } #endregion /*********************************************************************** * Variables ***********************************************************************/ #region Variables [field: SerializeField] public Components MyComponents { get; private set; } [field: SerializeField] public CharacterOption MyCharacterOption { get; private set; } [field: SerializeField] public CurrentState MyCurrentState { get; set; } [field: SerializeField] public CurrentValue MyCurrentValue { get; set; } public string mainSkillName; public GameObject MainSkillObject { get; private set; } private ISkill mainSkill; public static readonly int IsMovingHash = Animator.StringToHash("isMoving"); public static readonly int XDirectionHash = Animator.StringToHash("xDirection"); public static readonly int ZDirectionHash = Animator.StringToHash("zDirection"); public static readonly int IsAttackingHash = Animator.StringToHash("isAttacking"); public static readonly int IsActivateMainSkillHash = Animator.StringToHash("isActivateMainSkill"); #endregion /*********************************************************************** * Unity Events ***********************************************************************/ #region Unity Events private void OnEnable() { MyComponents.playerInput.actions.FindAction("Attack").performed += OnAttackEvent; } private void OnDisable() { MyComponents.playerInput.actions.FindAction("Attack").performed -= OnAttackEvent; } private void Start() { InitComponents(); InitSkills(); InitStartValue(); } private void Update() { MoveAnimation(); FlipVisualLook(MyComponents.movement.GetPreviousMoveDirection().x); } #endregion /*********************************************************************** * Init Methods ***********************************************************************/ #region Unity Events private void InitComponents() { if (!TryGetComponent(out MyComponents.movement)) { MyComponents.movement = gameObject.AddComponent(); } MyComponents.movement.SetAnimator(MyComponents.animator); } private void InitSkills() { MainSkillObject = SkillManager.Inst.InstantiateSkill(mainSkillName).gameObject; mainSkill = MainSkillObject.GetComponent(); if (mainSkill != null) { var myCollider = MyComponents.movement.MyComponents.capsuleCollider; var myRb = MyComponents.movement.MyComponents.rb; var myVisualLook = MyComponents.visualLook; var ui = UiManager.Inst.CombatUi.MainSkillUi; ui.gameObject.SetActive(true); mainSkill.SkillInputData.InitInputData(myCollider, myRb, myVisualLook, MyCharacterOption.targetLayer, ui); } } private void InitStartValue() { MyCurrentValue.hitColliders = new Collider[MyCharacterOption.maxHitNum]; SetCurrentHp(MyCharacterOption.maxHp); } #endregion /*********************************************************************** * Player Input ***********************************************************************/ #region Unity Events private void OnAttackEvent(InputAction.CallbackContext context) { if (MyCurrentState.isAttacking && !MyCurrentState.isComboPossible) return; var control = context.control; if (control.name.Equals("leftButton")) { MyCurrentState.useMouseAttack = true; } else if (control.name.Equals("k")) { MyCurrentState.useMouseAttack = false; } if (MyCurrentState.isComboPossible) { MyCurrentState.isComboAttacking = true; return; } MyComponents.animator.SetBool(IsAttackingHash, true); } private void OnActivateMainSkill() { if (MyCurrentState.isAttacking || GetIsDashing()) return; mainSkill.SkillInputData.UpdateInputData(GetCurrentPosition()); if (mainSkill.EnableSkill()) { SetEnableMoving(false); MyComponents.animator.SetBool(IsActivateMainSkillHash, true); mainSkill.ActivateSkill(); } } #endregion /*********************************************************************** * Interfaces ***********************************************************************/ #region Interfaces // IDamageable public void TakeDamage(float attackerPower, Vector3? attackPos = null) { if (MyCurrentState.isInvincibility) return; var changeHp = Mathf.Max(MyCurrentValue.currentHp - attackerPower, 0); SetCurrentHp(changeHp); // if (InIslandCamera.Inst.InIslandCam) // { // VisualFeedbackManager.Inst.CameraShake(InIslandCamera.Inst.InIslandCam); // } // 죽었는지 체크 if (changeHp == 0f) { Die(); return; } } public void Die() { print("Combat Player Die"); } public float GetCurrentHp() => MyCurrentValue.currentHp; #endregion /*********************************************************************** * Methods ***********************************************************************/ #region Methods public void AttackTiming() { var attackDirection = MyComponents.movement.GetPreviousMoveDirection(); Array.Clear(MyCurrentValue.hitColliders, 0, MyCharacterOption.maxHitNum); var size = Physics.OverlapSphereNonAlloc(transform.position, MyCharacterOption.attackRange, MyCurrentValue.hitColliders, MyCharacterOption.targetLayer); for (var i = 0; i < size; i++) { var targetDirection = (MyCurrentValue.hitColliders[i].transform.position - transform.position).normalized; var angleBetween = Vector3.Angle(attackDirection, targetDirection); if (angleBetween >= MyCharacterOption.attackAngle * 0.5f) continue; if (MyCurrentValue.hitColliders[i].gameObject.layer == LayerMask.NameToLayer("Enemy")) { var iDamageable = MyCurrentValue.hitColliders[i].transform.GetComponent(); iDamageable.TakeDamage(MyCharacterOption.attackDamage); VisualFeedbackManager.Inst.TriggerHitStop(0.1f); } else if (MyCurrentValue.hitColliders[i].gameObject.layer == LayerMask.NameToLayer("Skill") && MyCurrentValue.hitColliders[i].CompareTag("DestructiveSkill")) { var iDamageable = MyCurrentValue.hitColliders[i].transform.GetComponent(); iDamageable.TakeDamage(MyCharacterOption.attackDamage); } } } private void MoveAnimation() { var isMoving = MyComponents.movement.GetIsMoving(); var previousDirection = MyComponents.movement.GetPreviousMoveDirection(); MyComponents.animator.SetBool(IsMovingHash, isMoving); if (isMoving) { MyComponents.animator.SetFloat(XDirectionHash, previousDirection.x); MyComponents.animator.SetFloat(ZDirectionHash, previousDirection.z); } } private void FlipVisualLook(float previousDirectionX) { var localScale = MyComponents.visualLook.localScale; localScale.x = previousDirectionX switch { > 0.01f => Mathf.Abs(localScale.x), < -0.01f => -Mathf.Abs(localScale.x), _ => localScale.x }; MyComponents.visualLook.localScale = localScale; } public void CoolDown(float waitTime, Action onCooldownComplete = null) { StartCoroutine(CoolDownCoroutine(waitTime, onCooldownComplete)); } private IEnumerator CoolDownCoroutine(float waitTime, Action onCooldownComplete = null) { var time = 0f; while (time <= waitTime) { time += Time.deltaTime; yield return null; } onCooldownComplete?.Invoke(); } [Button("현재 체력 변경")] private void SetCurrentHp(float value) => MyCurrentValue.currentHp = value; public void SetEnableMoving(bool value) => MyComponents.movement.SetEnableMoving(value); public void Move(Vector3 velocity) => MyComponents.movement.Move(velocity); public void MoveToCurrentDirection(float speed) => MyComponents.movement.MoveToCurrentDirection(speed); public bool GetIsAttacking() => MyCurrentState.isAttacking; public bool GetIsComboAttacking() => MyCurrentState.isComboAttacking; public bool GetIsDashing() => MyComponents.movement.GetIsDashing(); public float GetDashCooldown() => MyComponents.movement.GetDashCooldown(); public float GetDashTime() => MyComponents.movement.GetDashTime(); public float GetAttackTime() => MyCharacterOption.attackTime; public void SetIsAttacking(bool value) => MyCurrentState.isAttacking = value; public void SetIsComboAttacking(bool value) => MyCurrentState.isComboAttacking = value; public void SetIsComboPossible(bool value) => MyCurrentState.isComboPossible = value; public void SetIsDashing(bool value) => MyComponents.movement.SetIsDashing(value); public void SetEnableDashing(bool value) => MyComponents.movement.SetEnableDashing(value); public Vector3 GetCurrentPosition() => MyComponents.movement.GetCurrentPosition(); public void SetIsInvincibility(bool value) => MyCurrentState.isInvincibility = value; public void SetIsTrigger(bool value) => MyComponents.movement.SetIsTrigger(value); public void SetUseGravity(bool value) => MyComponents.movement.SetUseGravity(value); public void SetPreviousMoveDirection(Vector3 value) { MyComponents.movement.SetPreviousMoveDirection(value); if (value != Vector3.zero) { MyComponents.animator.SetFloat(XDirectionHash, value.x); MyComponents.animator.SetFloat(ZDirectionHash, value.z); } } #endregion } }