using System.Collections; using BlueWater.Audios; using BlueWater.Interfaces; using BlueWater.Utility; using Sirenix.OdinInspector; using UnityEngine; using UnityEngine.InputSystem; namespace BlueWater.Players.Combat { public class CombatAttacker : MonoBehaviour, IComboAttackable { // Variables #region Variables // Components [SerializeField] private Rigidbody _rigidbody; [SerializeField] private CapsuleCollider _capsuleCollider; [SerializeField] private AnimationController _animationController; private IPhysicMovable _iPhysicMovable; private IDashable _dashable; private ISkillHandler _skillHandler; private IStunnable _stunnable; // ComboAttack [field: SerializeField, Range(1, 21), Tooltip("한 번에 공격 가능한 개체 수")] public int MaxHitCount { get; private set; } = 10; [field: SerializeField] public ComboAttack[] ComboAttacks { get; private set; } = new ComboAttack[2]; public bool IsAttackEnabled { get; private set; } = true; public bool IsComboAttackPossible { get; private set; } public bool IsComboAttacking { get; private set; } private int _currentComboAttackCount; public int CurrentComboAttackCount { get => _currentComboAttackCount; set { _currentComboAttackCount = value; _animationController.SetAnimationParameter("comboAttackCount", CurrentComboAttackCount); } } public Collider[] HitColliders { get; private set; } [field: SerializeField] public LayerMask TargetLayer { get; private set; } [field: SerializeField] public LayerMask MouseClickLayer { get; private set; } // Particles [SerializeField] private ParticleSystem _swordAttackParticle; // Camera effects [SerializeField] private float _comboAttackHitStopDuration = 0.07f; // Variables private Coroutine _firstComboAttackCoroutine; private Coroutine _secondComboAttackCoroutine; #endregion // Unity events #region Unity events private void Awake() { InitializeComponents(); } private void Start() { HitColliders = new Collider[MaxHitCount]; } #endregion // Initialize #region Initialize [Button("컴포넌트 초기화")] private void InitializeComponents() { _rigidbody = GetComponent(); _capsuleCollider = GetComponent(); _animationController = GetComponent(); _iPhysicMovable = GetComponent(); _dashable = GetComponent(); _skillHandler = GetComponent(); _stunnable = GetComponent(); } #endregion // Methods #region Methods public bool CanAttack() { if (!IsAttackEnabled || CurrentComboAttackCount == 2) return false; var isActivatingSkill = _skillHandler?.IsActivatingSkill ?? false; var isStunned = _stunnable?.IsStunned ?? false; if (isActivatingSkill || isStunned) return false; var isDashing = _dashable?.IsDashing ?? false; if (isDashing) { _dashable.EndDash(); } return true; } public void Attack(bool usedMouseAttack) { if (!CanAttack()) return; if (CurrentComboAttackCount == 1 && IsComboAttackPossible) { IsComboAttacking = true; if (usedMouseAttack) { UseMouseAttack(); } return; } if (usedMouseAttack) { UseMouseAttack(); } Utils.StartUniqueCoroutine(this, ref _firstComboAttackCoroutine, FirstComboAttackCoroutine()); } private void UseMouseAttack() { var mousePos = Mouse.current.position.ReadValue(); var ray = CombatCameraManager.Instance.MainCamera.ScreenPointToRay(mousePos); if (!Physics.Raycast(ray, out var hit, float.MaxValue, MouseClickLayer)) { EndAttack(); return; } var attackDirection = (hit.point - _rigidbody.position).normalized; attackDirection.y = 0f; _iPhysicMovable.SetCurrentDirection(attackDirection); } private IEnumerator FirstComboAttackCoroutine() { CurrentComboAttackCount = 1; var animationStarted = false; yield return StartCoroutine(_animationController.WaitForAnimationToRun("ComboAttack1", success => animationStarted = success)); if (!animationStarted) { EndAttack(); yield break; } _animationController.SetCurrentAnimationSpeed(ComboAttacks[CurrentComboAttackCount - 1].Speed); IsComboAttackPossible = true; var doDamage = false; while (_animationController.IsComparingCurrentAnimation("ComboAttack1") && _animationController.GetCurrentAnimationNormalizedTime() < 1f) { if (!doDamage && _animationController.GetCurrentAnimationNormalizedTime() >= 0.28f) { AudioManager.Instance.PlaySfx("FirstComboAttack"); var moveSpeed = ComboAttacks[CurrentComboAttackCount - 1].MovePower; var finalVelocity = _iPhysicMovable.CurrentDirection * moveSpeed; //_rigidbody.MovePosition(transform.position + finalVelocity * moveSpeed * Time.deltaTime); _rigidbody.linearVelocity = finalVelocity; doDamage = true; DoDamage(CurrentComboAttackCount, _iPhysicMovable.CurrentDirection); } yield return new WaitForFixedUpdate(); } if (IsComboAttacking) { Utils.StartUniqueCoroutine(this, ref _secondComboAttackCoroutine, SecondComboAttackCoroutine()); } else { EndAttack(); } } private IEnumerator SecondComboAttackCoroutine() { _animationController.ResetAnimationSpeed(); IsComboAttackPossible = false; CurrentComboAttackCount = 2; var animationStarted = false; yield return StartCoroutine(_animationController.WaitForAnimationToRun("ComboAttack2", success => animationStarted = success)); if (!animationStarted) { EndAttack(); yield break; } _animationController.SetCurrentAnimationSpeed(ComboAttacks[CurrentComboAttackCount - 1].Speed); var doDamage = false; while (_animationController.IsComparingCurrentAnimation("ComboAttack2") && _animationController.GetCurrentAnimationNormalizedTime() < 1f) { if (!doDamage && _animationController.GetCurrentAnimationNormalizedTime() >= 0.3f) { AudioManager.Instance.PlaySfx("SecondComboAttack"); var moveSpeed = ComboAttacks[CurrentComboAttackCount - 1].MovePower; var finalVelocity = _iPhysicMovable.CurrentDirection * moveSpeed; //_rigidbody.MovePosition(transform.position + finalVelocity * moveSpeed * Time.deltaTime); _rigidbody.linearVelocity = finalVelocity; doDamage = true; DoDamage(CurrentComboAttackCount, _iPhysicMovable.CurrentDirection); } yield return new WaitForFixedUpdate(); } EndAttack(); } private void DoDamage(int comboAttackCount, Vector3 attackDirection) { var hitCount = Physics.OverlapSphereNonAlloc(transform.position, ComboAttacks[comboAttackCount - 1].Range, HitColliders, TargetLayer, QueryTriggerInteraction.Collide); for (var i = 0; i < hitCount; i++) { var hitCollider = HitColliders[i]; var targetDirection = (hitCollider.transform.position - transform.position).normalized; var angleBetween = Vector3.Angle(attackDirection, targetDirection); if (angleBetween >= ComboAttacks[comboAttackCount - 1].Angle * 0.5f) continue; var iDamageable = hitCollider.transform.GetComponentInParent(); if (iDamageable != null && iDamageable.CanDamage()) { iDamageable.TakeDamage(ComboAttacks[comboAttackCount - 1].Damage); // TODO : Collider에 따라 잘 안보이는 경우가 있음 var spawnPosition = _capsuleCollider.bounds.center + targetDirection * 1.5f; //var closestPoint = hitCollider.ClosestPoint(_capsuleCollider.bounds.center); //var spawnPosition = closestPoint + Random.insideUnitSphere * 0.2f; Instantiate(_swordAttackParticle, spawnPosition, Quaternion.identity); } if (comboAttackCount == 2) { VisualFeedbackManager.Instance.TriggerHitStop(_comboAttackHitStopDuration); } } } public void EndAttack() { Utils.EndUniqueCoroutine(this, ref _firstComboAttackCoroutine); Utils.EndUniqueCoroutine(this, ref _secondComboAttackCoroutine); CurrentComboAttackCount = 0; IsComboAttacking = false; IsComboAttackPossible = false; _animationController.ResetAnimationSpeed(); } #endregion } }