using OctoberStudio.Easing; using OctoberStudio.Extensions; using OctoberStudio.Pool; using System.Collections; using System.Collections.Generic; using UnityEngine; namespace OctoberStudio.Enemy { public class EnemyBellBehavior : EnemyBehavior { public static readonly int IS_ATTACKING_BOOL = Animator.StringToHash("Is Attacking"); public static readonly int IS_CHARGING_BOOL = Animator.StringToHash("Is Charging"); [SerializeField] float attackCooldown = 5f; [SerializeField] Animator animator; private PoolComponent projectilePool; private PoolComponent smallProjectilePool; private PoolComponent spikeProjectilePool; private PoolComponent explosionParticlePool; private List projectiles = new List(); private Coroutine attackCoroutine; private Coroutine movementCoroutine; [SerializeField] List projectilePositions; private List explosionCoroutines = new List(); protected override void Awake() { base.Awake(); projectilePool = new PoolComponent("Bell Enemy Projectile", projectilePrefab, 5); smallProjectilePool = new PoolComponent("Bell Enemy Small Projectile", smallProjectilePrefab, 30); spikeProjectilePool = new PoolComponent("Bell Enemy Spike Projectile", spikeProjectilePrefab, 10); explosionParticlePool = new PoolComponent("Bell Enemy Projectile Explosion", explosionParticlePrefab, 5); } public override void Play() { base.Play(); attackCoroutine = StartCoroutine(AttackCoroutine()); movementCoroutine = StartCoroutine(MovementCoroutine()); } [SerializeField] float movementCooldown = 0.5f; private IEnumerator MovementCoroutine() { while (true) { IsMoving = true; var randomPoint = StageController.FieldManager.Fence.GetRandomPointInside(1); IsMovingToCustomPoint = true; CustomPoint = randomPoint; yield return new WaitUntil(() => Vector2.Distance(transform.position, randomPoint) < 0.2f); IsMovingToCustomPoint = false; IsMoving = false; yield return new WaitForSeconds(movementCooldown); } } private IEnumerator AttackCoroutine() { while (true) { yield return new WaitForSeconds(attackCooldown); animator.SetBool(IS_CHARGING_BOOL, true); yield return SimpleAttackCoroutine(); animator.SetBool(IS_CHARGING_BOOL, false); yield return new WaitForSeconds(attackCooldown); animator.SetBool(IS_CHARGING_BOOL, true); yield return SlowAttackCoroutine(); animator.SetBool(IS_CHARGING_BOOL, false); yield return new WaitForSeconds(attackCooldown); animator.SetBool(IS_CHARGING_BOOL, true); yield return SmallAttackCoroutine(); animator.SetBool(IS_CHARGING_BOOL, false); } } [Header("Simple Attack")] [SerializeField] int simpleAttackWavesCount = 3; [SerializeField] float simpleAttackDamage = 1; [SerializeField] float simpleAttackProjectileCooldown = 0.2f; [SerializeField] float simpleAttackStayDuration = 1f; [SerializeField] GameObject projectilePrefab; [SerializeField] GameObject explosionParticlePrefab; private IEnumerator SimpleAttackCoroutine() { for(int i = 0; i < simpleAttackWavesCount; i++) { var simpleProjectiles = new List(); for (int j = 0; j < projectilePositions.Count; j++) { var projectile = projectilePool.GetEntity(); projectile.transform.position = projectilePositions[j].transform.position; projectile.StickToTransform(projectilePositions[j]); projectile.onFinished += OnProjectileFinished; projectiles.Add(projectile); simpleProjectiles.Add(projectile); yield return new WaitForSeconds(simpleAttackProjectileCooldown); } yield return new WaitForSeconds(simpleAttackStayDuration); foreach (var projectile in simpleProjectiles) { var distanceToPlayer = PlayerBehavior.CenterPosition - projectile.transform.position.XY(); var playerDirection = distanceToPlayer.normalized; projectile.LifeTime = distanceToPlayer.magnitude / projectile.Speed; projectile.Damage = StageController.Stage.EnemyDamage * simpleAttackDamage; projectile.Init(projectile.transform.position, playerDirection); yield return new WaitForSeconds(simpleAttackProjectileCooldown); } } } [Header("Slow Attack")] [SerializeField] int slowAttackRadialAmount = 10; [SerializeField] int slowAttackWavesAmount = 3; [SerializeField] float slowAttackDamage = 1; [SerializeField] float slowAttackWaveCooldown = 1; [SerializeField] GameObject spikeProjectilePrefab; private IEnumerator SlowAttackCoroutine() { for (int j = 0; j < slowAttackWavesAmount; j++) { for (int i = 0; i < slowAttackRadialAmount; i++) { var angle = 360f / 10f * i; var direction = Quaternion.Euler(0, 0, angle) * Vector2.up; var projectile = spikeProjectilePool.GetEntity(); projectile.Init(transform.position, direction); projectile.Damage = StageController.Stage.EnemyDamage * slowAttackDamage; projectile.onFinished += OnProjectileFinished; projectiles.Add(projectile); } yield return new WaitForSeconds(slowAttackWaveCooldown); } } [Header("Burst Attack")] [SerializeField] int burstAttackRadialAmount = 10; [SerializeField] int burstAttackSingleBurstAmount = 3; [SerializeField] float burstAttackDamage = 1; [SerializeField] float burstAttackProjectileCooldown = 0.05f; [SerializeField] GameObject smallProjectilePrefab; private IEnumerator SmallAttackCoroutine() { for (int i = 0; i < burstAttackRadialAmount; i++) { var angle = 360f / 10f * i; var direction = Quaternion.Euler(0, 0, angle) * Vector2.up; for (int j = 0; j < burstAttackSingleBurstAmount; j++) { var projectile = smallProjectilePool.GetEntity(); projectile.Init(transform.position, direction); projectile.Damage = StageController.Stage.EnemyDamage * burstAttackDamage; projectile.onFinished += OnProjectileFinished; projectiles.Add(projectile); yield return new WaitForSeconds(burstAttackProjectileCooldown); } } } private void OnProjectileFinished(SimpleEnemyProjectileBehavior projectile) { projectile.onFinished -= OnProjectileFinished; projectiles.Remove(projectile); if(projectile is EnemyBellProjectile bellProjectile) { var particle = explosionParticlePool.GetEntity(); if (bellProjectile.Successful) { particle.transform.position = PlayerBehavior.Player.transform.position; } else { particle.transform.position = bellProjectile.transform.position; } particle.Play(); IEasingCoroutine coroutine = null; coroutine = EasingManager.DoAfter(5f, () => { particle.gameObject.SetActive(false); explosionCoroutines.Remove(coroutine); }); explosionCoroutines.Add(coroutine); } } protected override void Die(bool flash) { base.Die(flash); if (attackCoroutine != null) { StopCoroutine(attackCoroutine); attackCoroutine = null; } if(movementCoroutine != null) { StopCoroutine(movementCoroutine); movementCoroutine = null; } foreach(var projectile in projectiles) { projectile.onFinished -= OnProjectileFinished; projectile.Disable(); } projectiles.Clear(); projectilePool.Destroy(); smallProjectilePool.Destroy(); spikeProjectilePool.Destroy(); foreach(var coroutine in explosionCoroutines) { coroutine.StopIfExists(); } explosionCoroutines.Clear(); explosionParticlePool.DisableAllEntities(); explosionParticlePool.Destroy(); } } }