using System; using System.Collections; using BlueWater.Audios; using BlueWater.Interfaces; using BlueWater.Utility; using UnityEngine; using UnityEngine.InputSystem; namespace BlueWater.Players.Combat { public class CombatAttacker : MonoBehaviour { // Variables #region Variables // Components private Rigidbody _rigidbody; private AnimationController _animationController; // Interfaces private IPhysicMovable _iPhysicMovable; // ComboAttack [field: SerializeField, Range(1, 21), Tooltip("한 번에 공격 가능한 개체 수")] public int MaxHitCount { get; set; } = 10; [field: SerializeField] public ComboAttack[] ComboAttacks { get; set; } = new ComboAttack[2]; private int _currentComboAttackCount; public int CurrentComboAttackCount { get => _currentComboAttackCount; set { _currentComboAttackCount = value; _animationController.SetAnimationParameter("comboAttackCount", CurrentComboAttackCount); } } public bool IsComboAttackPossible { get; set; } public bool IsComboAttacking { get; set; } public Collider[] HitColliders { get; set; } private bool _enableAttack = true; [field: SerializeField] public LayerMask TargetLayer { get; private set; } [SerializeField] private LayerMask _mouseClickLayer; // Particles [SerializeField] private ParticleSystem _swordAttackParticle; // Camera effects [SerializeField] private float _comboAttackHitStopDuration = 0.07f; // Variables private Coroutine _firstComboAttackCoroutine; private Coroutine _secondComboAttackCoroutine; // Events public event Action OnStartAttack; public event Action OnEndAttack; #endregion // Unity events #region Unity events private void Start() { HitColliders = new Collider[MaxHitCount]; } #endregion // Initialize #region Initialize public void InitializeComponents(Rigidbody rigidbody, AnimationController animationController, IPhysicMovable iPhysicMovable) { _rigidbody = rigidbody; _animationController = animationController; _iPhysicMovable = iPhysicMovable; } #endregion // Methods #region Methods // Event methods public void HandleEnableAttack() => _enableAttack = true; public void HandleDisableAttack() => _enableAttack = false; public void HandleDashInAttack() { if (CurrentComboAttackCount > 0) { EndComboAttack(); } } public void HandleAttack(bool usedMouseAttack) { if (!_enableAttack || CurrentComboAttackCount == 2) return; if (CurrentComboAttackCount == 1 && IsComboAttackPossible) { IsComboAttacking = true; if (usedMouseAttack) { UseMouseAttack(); } return; } if (usedMouseAttack) { UseMouseAttack(); } Utils.StartUniqueCoroutine(this, ref _firstComboAttackCoroutine, FirstComboAttackCoroutine()); } // Methods 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)) { EndComboAttack(); return; } var attackDirection = (hit.point - _rigidbody.position).normalized; attackDirection.y = 0f; _iPhysicMovable.CurrentDirection = attackDirection; } private IEnumerator FirstComboAttackCoroutine() { OnStartAttack?.Invoke(); CurrentComboAttackCount = 1; var animationStarted = false; yield return StartCoroutine(_animationController.WaitForAnimationToRun("ComboAttack1", success => animationStarted = success)); if (!animationStarted) { EndComboAttack(); yield break; } _animationController.SetCurrentAnimationSpeed(ComboAttacks[CurrentComboAttackCount - 1].Speed); AudioManager.Instance.PlaySfx("FirstComboAttack"); IsComboAttackPossible = true; var doDamage = false; while (_animationController.IsComparingCurrentAnimation("ComboAttack1") && _animationController.GetCurrentAnimationNormalizedTime() < 1f) { if (!doDamage && _animationController.GetCurrentAnimationNormalizedTime() >= 0.28f) { var moveSpeed = ComboAttacks[CurrentComboAttackCount - 1].MovePower; var finalVelocity = _iPhysicMovable.CurrentDirection * moveSpeed; _rigidbody.MovePosition(transform.position + finalVelocity * moveSpeed * Time.deltaTime); doDamage = true; DoDamage(CurrentComboAttackCount, _iPhysicMovable.CurrentDirection); } yield return new WaitForFixedUpdate(); } if (IsComboAttacking) { Utils.StartUniqueCoroutine(this, ref _secondComboAttackCoroutine, SecondComboAttackCoroutine()); } else { EndComboAttack(); } } private IEnumerator SecondComboAttackCoroutine() { _animationController.ResetAnimationSpeed(); IsComboAttackPossible = false; CurrentComboAttackCount = 2; var animationStarted = false; yield return StartCoroutine(_animationController.WaitForAnimationToRun("ComboAttack2", success => animationStarted = success)); if (!animationStarted) { EndComboAttack(); yield break; } _animationController.SetCurrentAnimationSpeed(ComboAttacks[CurrentComboAttackCount - 1].Speed); AudioManager.Instance.PlaySfx("SecondComboAttack"); var doDamage = false; while (_animationController.IsComparingCurrentAnimation("ComboAttack2") && _animationController.GetCurrentAnimationNormalizedTime() < 1f) { if (!doDamage && _animationController.GetCurrentAnimationNormalizedTime() >= 0.3f) { var moveSpeed = ComboAttacks[CurrentComboAttackCount - 1].MovePower; var finalVelocity = _iPhysicMovable.CurrentDirection * moveSpeed; _rigidbody.MovePosition(transform.position + finalVelocity * moveSpeed * Time.deltaTime); doDamage = true; DoDamage(CurrentComboAttackCount, _iPhysicMovable.CurrentDirection); } yield return new WaitForFixedUpdate(); } EndComboAttack(); } 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.TakeDamage(ComboAttacks[comboAttackCount - 1].Damage); var closestPoint = hitCollider.ClosestPoint(transform.position); //var spawnPosition = closestPoint + Random.insideUnitSphere * 0.2f; Instantiate(_swordAttackParticle, closestPoint, Quaternion.identity); } if (comboAttackCount == 2) { VisualFeedbackManager.Instance.TriggerHitStop(_comboAttackHitStopDuration); } } } public void EndComboAttack() { Utils.EndUniqueCoroutine(this, ref _firstComboAttackCoroutine); Utils.EndUniqueCoroutine(this, ref _secondComboAttackCoroutine); CurrentComboAttackCount = 0; IsComboAttacking = false; IsComboAttackPossible = false; _animationController.ResetAnimationSpeed(); OnEndAttack?.Invoke(); } #endregion } }