using System.Collections; using UnityEngine; using UnityEngine.InputSystem; // ReSharper disable once CheckNamespace namespace BlueWaterProject { public class CombatAttacker : MonoBehaviour, IMeleeComboAttackable { // Components private Rigidbody rb; // Interfaces private IAnimationStateController iAnimationStateController; private IRigidMovable iRigidMovable; // ComboAttack [field: Range(1, 21), Tooltip("한 번에 공격 가능한 개체 수")] [field: SerializeField] public int MaxHitCount { get; set; } = 10; [field: SerializeField] public ComboAttack[] ComboAttacks { get; set; } = new ComboAttack[2]; public bool UsedMouseAttack { get; set; } private int currentComboAttackCount; public int CurrentComboAttackCount { get => currentComboAttackCount; set { currentComboAttackCount = value; iAnimationStateController.SetAnimationParameter(CombatPlayerAnimatorParameter.COMBO_ATTACK_COUNT, 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; set; } [SerializeField] private LayerMask groundLayer; // Particles [SerializeField] private ParticleSystem swordHit; // Camera effects [SerializeField] private float comboAttackHitStopDuration = 0.15f; // Variables private Coroutine firstComboAttackCoroutine; private Coroutine secondComboAttackCoroutine; // Events public delegate void AttackInDashEvent(); public event AttackInDashEvent OnAttackInDashing; public delegate void StartAttackEvent(); public event StartAttackEvent OnStartAttack; public delegate void EndAttackEvent(); public event EndAttackEvent OnEndAttack; // Unity events private void Start() { iAnimationStateController = GetComponent(); iRigidMovable = GetComponent(); HitColliders = new Collider[MaxHitCount]; } // Init public void InitComponent(Rigidbody rigidbody) { rb = rigidbody; } // Event methods public void HandleEnableAttack() => enableAttack = true; public void HandleDisableAttack() => enableAttack = false; public void HandleAttack(bool usedMouseAttack) { if (!enableAttack) return; if (CurrentComboAttackCount == 1 && IsComboAttackPossible) { IsComboAttacking = true; if (usedMouseAttack) { UseMouseAttack(); } return; } if (CurrentComboAttackCount >= 1) return; OnAttackInDashing?.Invoke(); if (usedMouseAttack) { UseMouseAttack(); } firstComboAttackCoroutine = StartCoroutine(nameof(FirstComboAttackCoroutine)); } // Methods private void UseMouseAttack() { var mousePos = Mouse.current.position.ReadValue(); var ray = CameraManager.Inst.MainCam.ScreenPointToRay(mousePos); if (!Physics.Raycast(ray, out var hit, float.MaxValue, groundLayer)) { EndComboAttack(); return; } var attackDirection = (hit.point - rb.position).normalized; attackDirection.y = 0f; iRigidMovable.PreviousMoveDirection = attackDirection; } private IEnumerator FirstComboAttackCoroutine() { OnStartAttack?.Invoke(); CurrentComboAttackCount = 1; var animationStarted = false; yield return StartCoroutine(iAnimationStateController.WaitForAnimationToRun(CombatPlayerAnimationName.COMBO_ATTACK1, success => animationStarted = success)); if (!animationStarted) { EndComboAttack(); yield break; } iAnimationStateController.SetCurrentAnimationSpeed(ComboAttacks[CurrentComboAttackCount - 1].Speed); IsComboAttackPossible = true; var doDamage = false; while (iAnimationStateController.IsComparingCurrentAnimation(CombatPlayerAnimationName.COMBO_ATTACK1) && iAnimationStateController.GetCurrentAnimationNormalizedTime() < 1f) { if (!doDamage && iAnimationStateController.GetCurrentAnimationNormalizedTime() >= 0.28f) { var moveSpeed = ComboAttacks[CurrentComboAttackCount - 1].MovePower; var finalVelocity = iRigidMovable.PreviousMoveDirection * moveSpeed; //rb.velocity = finalVelocity; rb.AddForce(finalVelocity, ForceMode.Impulse); rb.velocity = Vector3.zero; doDamage = true; DoDamage(CurrentComboAttackCount, iRigidMovable.PreviousMoveDirection); } yield return new WaitForFixedUpdate(); } if (IsComboAttacking) { secondComboAttackCoroutine = StartCoroutine(nameof(SecondComboAttackCoroutine)); } else { EndComboAttack(); } } private IEnumerator SecondComboAttackCoroutine() { IsComboAttackPossible = false; CurrentComboAttackCount = 2; var animationStarted = false; yield return StartCoroutine(iAnimationStateController.WaitForAnimationToRun(CombatPlayerAnimationName.COMBO_ATTACK2, success => animationStarted = success)); if (!animationStarted) { EndComboAttack(); yield break; } iAnimationStateController.SetCurrentAnimationSpeed(ComboAttacks[CurrentComboAttackCount - 1].Speed); var doDamage = false; while (iAnimationStateController.IsComparingCurrentAnimation(CombatPlayerAnimationName.COMBO_ATTACK2) && iAnimationStateController.GetCurrentAnimationNormalizedTime() < 1f) { if (!doDamage && iAnimationStateController.GetCurrentAnimationNormalizedTime() >= 0.3f) { var moveSpeed = ComboAttacks[CurrentComboAttackCount - 1].MovePower; var finalVelocity = iRigidMovable.PreviousMoveDirection * moveSpeed; //rb.velocity = finalVelocity; rb.AddForce(finalVelocity, ForceMode.Impulse); rb.velocity = Vector3.zero; doDamage = true; DoDamage(CurrentComboAttackCount, iRigidMovable.PreviousMoveDirection); } yield return new WaitForFixedUpdate(); } EndComboAttack(); } private void DoDamage(int comboAttackCount, Vector3 attackDirection) { var size = Physics.OverlapSphereNonAlloc(transform.position, ComboAttacks[comboAttackCount - 1].Range, HitColliders, TargetLayer, QueryTriggerInteraction.Collide); for (var i = 0; i < size; i++) { var targetDirection = (HitColliders[i].transform.position - transform.position).normalized; var angleBetween = Vector3.Angle(attackDirection, targetDirection); if (angleBetween >= ComboAttacks[comboAttackCount - 1].Angle * 0.5f) continue; if (HitColliders[i].gameObject.layer == LayerMask.NameToLayer("Enemy")) { var iDamageable = HitColliders[i].transform.GetComponent(); if (iDamageable != null) { iDamageable.TakeDamage(ComboAttacks[comboAttackCount - 1].Damage); var closestPoint = HitColliders[i].ClosestPoint(transform.position); //var spawnPosition = closestPoint + Random.insideUnitSphere * 0.2f; Instantiate(swordHit, closestPoint, Quaternion.identity); } if (comboAttackCount == 2) { VisualFeedbackManager.Inst.TriggerHitStop(comboAttackHitStopDuration); } } else if (HitColliders[i].gameObject.layer == LayerMask.NameToLayer("Skill") && HitColliders[i].CompareTag("DestructiveSkill")) { var iDamageable = HitColliders[i].transform.GetComponent(); iDamageable?.TakeDamage(0); } } } public void EndComboAttack() { if (firstComboAttackCoroutine != null) { StopCoroutine(firstComboAttackCoroutine); firstComboAttackCoroutine = null; } if (secondComboAttackCoroutine != null) { StopCoroutine(secondComboAttackCoroutine); secondComboAttackCoroutine = null; } rb.velocity = Vector3.zero; IsComboAttacking = false; IsComboAttackPossible = false; iAnimationStateController.ResetAnimationSpeed(); CurrentComboAttackCount = 0; OnEndAttack?.Invoke(); } } }