From 24b9ece93dcd4c63388e841adfce74b8a64703e1 Mon Sep 17 00:00:00 2001 From: Nico Date: Fri, 25 Jul 2025 15:40:43 -0700 Subject: [PATCH] Create interfaces for IAttacker and IDamageable Utilize interfaces to create new Enemy class inheriting them and further developing on them also encapsulating more enemy type functions --- .../Scripts/Runtime/AI/EnemyManager/Gobler.cs | 239 ++++-------------- .../AI/EnemyManager/SpawnableEnemyInfo.cs | 8 +- Assets/Scripts/Runtime/Core.meta | 8 + Assets/Scripts/Runtime/Core/Combat.meta | 8 + .../Scripts/Runtime/Core/Combat/Attacker.cs | 33 +++ .../Runtime/Core/Combat/Attacker.cs.meta | 2 + .../Core/Combat/AttackerAndDamageable.cs | 25 ++ .../Core/Combat/AttackerAndDamageable.cs.meta | 2 + .../Scripts/Runtime/Core/Combat/Damageable.cs | 90 +++++++ .../Runtime/Core/Combat/Damageable.cs.meta | 2 + .../Runtime/Core/Combat/EnemyCombat.cs | 128 ++++++++++ .../Runtime/Core/Combat/EnemyCombat.cs.meta | 2 + Assets/Scripts/Runtime/Core/Utils.meta | 8 + 13 files changed, 355 insertions(+), 200 deletions(-) create mode 100644 Assets/Scripts/Runtime/Core.meta create mode 100644 Assets/Scripts/Runtime/Core/Combat.meta create mode 100644 Assets/Scripts/Runtime/Core/Combat/Attacker.cs create mode 100644 Assets/Scripts/Runtime/Core/Combat/Attacker.cs.meta create mode 100644 Assets/Scripts/Runtime/Core/Combat/AttackerAndDamageable.cs create mode 100644 Assets/Scripts/Runtime/Core/Combat/AttackerAndDamageable.cs.meta create mode 100644 Assets/Scripts/Runtime/Core/Combat/Damageable.cs create mode 100644 Assets/Scripts/Runtime/Core/Combat/Damageable.cs.meta create mode 100644 Assets/Scripts/Runtime/Core/Combat/EnemyCombat.cs create mode 100644 Assets/Scripts/Runtime/Core/Combat/EnemyCombat.cs.meta create mode 100644 Assets/Scripts/Runtime/Core/Utils.meta diff --git a/Assets/Scripts/Runtime/AI/EnemyManager/Gobler.cs b/Assets/Scripts/Runtime/AI/EnemyManager/Gobler.cs index cc14b1b..bdcfea5 100644 --- a/Assets/Scripts/Runtime/AI/EnemyManager/Gobler.cs +++ b/Assets/Scripts/Runtime/AI/EnemyManager/Gobler.cs @@ -4,109 +4,33 @@ using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; +using UnityEditor.Build; using UnityEngine; using UnityEngine.AI; using static EnemySpawnerData; +using static UnityEngine.RuleTile.TilingRuleOutput; -public class Gobler : Alive { - [Header("Mechanics Attributes")] - [SerializeField] public float ChaseDistance = 10f; - [SerializeField] public float ChaseDistanceBuffer = 1f; - [SerializeField] public float AttackDistance = 1.5f; - [SerializeField] public float AttackMaskDiameter = 1f; +public class Gobler : Enemy { - [Header("Enemy Attributes")] - [SerializeField] public float Attack = 10f; - [SerializeField] public float Speed = 10f; - [SerializeField] public float Health = 10f; - [SerializeField] public float Energy = 10f; - - protected FloatingTextSpawner TextPopUp; - - - private Player Player { get { return Player.Instance; } } - private Transform PlayerTransform { get { return Player.transform; } } - private Vector2 PlayerPos { get { return PlayerTransform.position; } } - private Vector2 CrystalPos { get { return GameManager.Crystal.transform.position; } } - - private GameObject Owner; - private Rigidbody2D Rigidbody; - private Transform MyTransform { get { return Owner.transform; } } - private Vector2 MyPos { get { return MyTransform.position; } set { MyTransform.position = value; } } - - private NavMeshAgent PathAgent; - - //private List Materials; - private Material MaterialColorOverlay; - - - public Gobler(GameObject owner) { - Owner = owner; - PathAgent = Owner.GetComponent(); - PathAgent.updatePosition = false; - PathAgent.updateRotation = false; - PathAgent.updateUpAxis = false; - PathAgent.speed = Speed; - IsUpdating = false; - - MaxHealth = 100; - CurrentHealth = MaxHealth; - OnTakeDamage += (damage, direction) => SetState(State.TakeDamage); - OnDeath += () => SetState(State.Die); - - Rigidbody = Owner.GetComponent(); - foreach (var material in Owner.GetComponentsInChildren().Select(x => x.material).ToList()) { - switch (material.name.Split(' ')[0]) { - case "DamageFlashMat": MaterialColorOverlay = material; break; - } - } - - TextPopUp = new FloatingTextSpawner(Owner.transform, 1); + public Gobler(GameObject owner) : base(owner) { + BaseDamage = 10; + AttackPreparationTime = 1; + AttackCooldownDuration = 1; + BaseHealth = 100; + CurrentHealth = BaseHealth; + IsKnockable = true; SetState(State.GoToCrystal); SubscribeToEvent(); - //EnemyManagers[Enemies.Gobler].UpdateTick += () => PathAgentHandler.UpdatePositionIso(PathAgent, owner.transform); - //EnemyManagers[Enemies.Gobler].UpdateTick += Update; - } - - private Action cachedUpdate; - private void SubscribeToEvent() { - cachedUpdate = () => { - if (PathAgent == null || Owner == null) return; - PathAgentHandler.UpdatePositionIso(PathAgent, Owner.transform); - Update(); - }; - - EnemyManagers[Enemies.Gobler].UpdateTick += cachedUpdate; - EnemyManagers[Enemies.Gobler].GizmoTick += OnDrawGizmos; - } - - private void UnsubscribeToEvent() { - EnemyManagers[Enemies.Gobler].UpdateTick -= cachedUpdate; - EnemyManagers[Enemies.Gobler].GizmoTick -= OnDrawGizmos; - } - - public float DistFromPlayer { get; private set; } - public float DistFromCrystal { get; private set; } - - - public State CurrentState; - public enum State { - None, - GoToCrystal, - AttackCrystal, - ChasePlayer, - AttackPlayer, - PrepareAttack, - FinalizeAttack, - TakeDamage, - Die } - protected void SetPriorityState() { + + + + protected override void SetPriorityState() { if (CurrentState == State.PrepareAttack) return; if (CurrentState == State.FinalizeAttack) return; if (CurrentState == State.ChasePlayer) return; @@ -116,17 +40,7 @@ public class Gobler : Alive { SetState(State.ChasePlayer); } - private bool IsUpdating; - protected void Update() { - if (Owner == null) return; - if (GameManager.Crystal == null) return; - if (IsUpdating) return; - IsUpdating = true; - - DistFromPlayer = Vector2.Distance(MyPos, PlayerPos); - DistFromCrystal = Vector2.Distance(MyPos, CrystalPos); - SetPriorityState(); - + protected override void DoUpdate() { switch (CurrentState) { case State.None: SetState(State.GoToCrystal); @@ -134,33 +48,27 @@ public class Gobler : Alive { case State.GoToCrystal: - if (DistFromCrystal <= AttackDistance) + if (CanAttackCrystal) SetState(State.PrepareAttack); break; - case State.AttackCrystal: - break; - - case State.ChasePlayer: - if (DistFromPlayer <= AttackDistance) + if (CanAttackPlayer) SetState(State.PrepareAttack); else if (DistFromPlayer > ChaseDistance + ChaseDistanceBuffer) - SetState(State.GoToCrystal); + SetState(State.None); else PathAgent.SetDestination(PlayerPos); break; - case State.AttackPlayer: - - break; - - case State.TakeDamage: if (IsInvincible) { - Rigidbody.linearVelocity = DirectionOfDamage * Knockback * 10 * InvincibilityLeft; + var velocity = DirectionOfDamage * Knockback * 10 * InvincibilityLeft; + //var isWalkable = NavMeshUtils.IsWalkable(MyPos, DirectionOfDamage); + //Rigidbody.linearVelocity = isWalkable ? velocity : Vector2.zero; + Rigidbody.linearVelocity = velocity; MaterialColorOverlay.SetFloat("_FlashAmount", InvincibilityLeft); } if (!IsFrameFrozen) SetState(State.None); @@ -168,42 +76,34 @@ public class Gobler : Alive { case State.PrepareAttack: - MaterialColorOverlay.SetFloat("_FlashAmount", 1 - FrameFreezeLeft); + MaterialColorOverlay.SetFloat("_FlashAmount", AttackPreparationTimeElapsedNormalized); if (!IsFrameFrozen) SetState(State.FinalizeAttack); break; - - - - } - - IsUpdating = false; } - protected void SetState(State newState) { + protected override void SetState(State newState) { CurrentState = newState; switch (CurrentState) { case State.None: + if (AggroOnPlayer) TextPopUp.SpawnFloatingText("?", Color.red); PathAgent.isStopped = false; + AggroOnCrystal = false; + AggroOnPlayer = false; break; case State.GoToCrystal: + AggroOnCrystal = true; PathAgent.SetDestination(CrystalPos); break; - case State.AttackCrystal: - break; - - case State.ChasePlayer: - break; - - - case State.AttackPlayer: + AggroOnPlayer = true; + TextPopUp.SpawnFloatingText("!", Color.red); break; @@ -216,7 +116,7 @@ public class Gobler : Alive { case State.PrepareAttack: PathAgent.isStopped = true; - FrameFrozenUntil = Time.time + 1; + StartAttackPreparation(); MaterialColorOverlay.SetColor("_FlashColor", Color.purple); break; @@ -225,28 +125,29 @@ public class Gobler : Alive { Collider2D[] hits = Physics2D.OverlapCircleAll(MyPos + Vector2.down, 4); foreach (var hit in hits) { if (!hit.CompareTag("PlayerHurtBox") || !hit.CompareTag("SuperSpecialGem")) continue; - Debug.Log("Player hit"); + Debug.Log($"{hit.tag} hit"); //GameObject parent = hit.transform.parent?.gameObject; //Vector2 knockbakDirection = ActiveClass.HitBoxDraw.Directional ? PrevDirection : parent.transform.position - transform.position; //EnemySpawnerData.EnemyMap[parent].Damage(ActiveClass.HitBoxDraw.Damage, knockbakDirection, ActiveClass.HitBoxDraw.Knockback); } - SetState(State.None); + + StartAttackCooldown(); + SetState(AggroOnPlayer ? State.ChasePlayer : State.GoToCrystal); break; case State.Die: - UnsubscribeToEvent(); SpawnableEnemyInfo.DestroyEnemy(Owner, Enemies.Gobler); SetState(State.None); break; - - } + + if (PrevState != CurrentState) PrevState = CurrentState; } - protected void OnDrawGizmos() { + protected override void OnDrawGizmos() { if (Owner == null) return; DrawChaseDistance(); @@ -262,9 +163,11 @@ public class Gobler : Alive { } private void DrawAttackDistance() { - //Gizmos.color = CurrentState == (IState)AttackState ? Color.red : Color.green; - //Vector2 center = this.transform.position; - //Gizmos.DrawWireSphere(center, AttackDistance); + if (CurrentState != State.PrepareAttack) return; + Collider2D[] hits = Physics2D.OverlapCircleAll(MyPos + Vector2.down, 4); + + Vector2 center = MyPos + Vector2.down; + DrawWireSphere(Color.salmon, center, 4); } private void DrawAttackReach() { @@ -276,60 +179,4 @@ public class Gobler : Alive { //Gizmos.DrawWireSphere(point2D, AttackMaskDiameter); DrawWireSphere(Color.red, point2D, AttackMaskDiameter); } -} - - -public class Alive { - public int MaxHealth { get; protected set; } - public int CurrentHealth { get; protected set; } - public bool IsDead => CurrentHealth <= 0; - - public bool IsInvincible => Time.time < InvincibleUntil; - protected float InvincibleUntil = 0f; - protected float InvincibilityLeft => Math.Max(0, InvincibleUntil - Time.time); - protected float InvincibilityDuration = 0.1f; - - public bool IsFrameFrozen => Time.time < FrameFrozenUntil; - protected float FrameFrozenUntil = 0f; - protected float FrameFreezeLeft => Math.Max(0, FrameFrozenUntil - Time.time); - protected float FrameFreezeDuration = 0.3f; - - public event Action OnTakeDamage; - public event Action OnDeath; - public event Action OnHeal; - - protected int DamageTaken; - protected Vector2 DirectionOfDamage; - protected int Knockback; - - public void Damage(int amount, Vector2 hitDirection, int knockback = 1) { - if (IsDead || IsInvincible) return; - - CurrentHealth -= amount; - CurrentHealth = Mathf.Max(CurrentHealth, 0); - InvincibleUntil = Time.time + InvincibilityDuration; - FrameFrozenUntil = Time.time + FrameFreezeDuration; - DamageTaken = amount; - Knockback = knockback; - - hitDirection = hitDirection.normalized; - if (hitDirection == Vector2.zero) hitDirection = Vector2.up; - DirectionOfDamage = hitDirection; - OnTakeDamage?.Invoke(amount, hitDirection); - - if (CurrentHealth == 0) OnDeath?.Invoke(); - } - - public void Heal(int amount) { - if (IsDead) return; - - CurrentHealth += amount; - CurrentHealth = Mathf.Min(CurrentHealth, MaxHealth); - OnHeal?.Invoke(amount); - } - - public void Reset() { - CurrentHealth = MaxHealth; - InvincibleUntil = 0f; - } } \ No newline at end of file diff --git a/Assets/Scripts/Runtime/AI/EnemyManager/SpawnableEnemyInfo.cs b/Assets/Scripts/Runtime/AI/EnemyManager/SpawnableEnemyInfo.cs index f22a990..230b254 100644 --- a/Assets/Scripts/Runtime/AI/EnemyManager/SpawnableEnemyInfo.cs +++ b/Assets/Scripts/Runtime/AI/EnemyManager/SpawnableEnemyInfo.cs @@ -54,7 +54,7 @@ public class SpawnableEnemyInfo : MonoBehaviour { if (NextSpawnableTime > Time.time) return; NextSpawnableTime = Time.time + SpawnIntervalInSeconds; - foreach (var enemy in SpawnableEnemies){ + foreach (var enemy in SpawnableEnemies) { int currentSpawnCount = 0; while (enemy.CanStartSpawning && enemy.CurrentSpawnCount < enemy.MaxSpawnCount && currentSpawnCount++ < enemy.MaxCountPerSpawn) { var enemyObject = EnemyGameObjectPools[enemy.Type].Get(transform.position, Quaternion.identity); @@ -112,7 +112,7 @@ public static class EnemySpawnerData { public static Dictionary EnemyManagers = new Dictionary(); public static Dictionary MaxSpawnCount = new Dictionary(); public static Dictionary CurrentSpawnCount = new Dictionary(); - public static Dictionary EnemyMap = new Dictionary(); + public static Dictionary EnemyMap = new Dictionary(); public static void RestartData() { ObjectPrefabs = new Dictionary(); @@ -120,7 +120,7 @@ public static class EnemySpawnerData { EnemyManagers = new Dictionary(); MaxSpawnCount = new Dictionary(); CurrentSpawnCount = new Dictionary(); - EnemyMap = new Dictionary(); + EnemyMap = new Dictionary(); } public static void InitializeEnemyData(EnemySpawnInfo enemy) { @@ -130,5 +130,5 @@ public static class EnemySpawnerData { MaxSpawnCount.Add(enemy.Type, 0); CurrentSpawnCount.Add(enemy.Type, 0); } - } + diff --git a/Assets/Scripts/Runtime/Core.meta b/Assets/Scripts/Runtime/Core.meta new file mode 100644 index 0000000..57dd6b9 --- /dev/null +++ b/Assets/Scripts/Runtime/Core.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a644663d39e949c44875ee37c76c1be6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Runtime/Core/Combat.meta b/Assets/Scripts/Runtime/Core/Combat.meta new file mode 100644 index 0000000..a4803c3 --- /dev/null +++ b/Assets/Scripts/Runtime/Core/Combat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5a1493ac8b3de2142a355b4e24cc4ed8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Runtime/Core/Combat/Attacker.cs b/Assets/Scripts/Runtime/Core/Combat/Attacker.cs new file mode 100644 index 0000000..46b1722 --- /dev/null +++ b/Assets/Scripts/Runtime/Core/Combat/Attacker.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + + +public class Attacker { + public int BaseDamage = 0; + public bool CanAttack => Time.time < AttackCooldown; + public float AttackCooldown { get; protected set; } = 0f; + public float AttackCooldownLeft => Math.Max(0, AttackCooldown - Time.time); + public float AttackCooldownDuration { get; protected set; } = 1f; + public void StartAttackCooldown() => AttackCooldown = Time.time; +} + + +public interface IAttacker { + int BaseDamage { get; } + bool CanAttack => Time.time < AttackCooldown; + + void StartAttackPreparation(); + float AttackPreparationTime { get; } + float AttackPreparationTimeLeft { get; } + bool AttackIsReady { get; } + + void StartAttackCooldown(); + float AttackCooldown { get; } + float AttackCooldownLeft { get; } + float AttackCooldownDuration { get; } +} + diff --git a/Assets/Scripts/Runtime/Core/Combat/Attacker.cs.meta b/Assets/Scripts/Runtime/Core/Combat/Attacker.cs.meta new file mode 100644 index 0000000..7981491 --- /dev/null +++ b/Assets/Scripts/Runtime/Core/Combat/Attacker.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 90443507b543fbb45bd3a9f5c354596d \ No newline at end of file diff --git a/Assets/Scripts/Runtime/Core/Combat/AttackerAndDamageable.cs b/Assets/Scripts/Runtime/Core/Combat/AttackerAndDamageable.cs new file mode 100644 index 0000000..5cedbe3 --- /dev/null +++ b/Assets/Scripts/Runtime/Core/Combat/AttackerAndDamageable.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +public class AttackerAndDamageable : Damageable, IAttacker { + public int BaseDamage { get; protected set; } = 0; + public bool CanAttack => Time.time > AttackCooldown; + + public void StartAttackPreparation() => AttackReadyAt = Time.time + AttackPreparationTime; + public float AttackReadyAt { get; protected set; } + public float AttackPreparationTime { get; protected set; } = 0; + public float AttackPreparationTimeLeft => Math.Max(0, AttackReadyAt - Time.time); + public float AttackPreparationTimeLeftNormalized => Math.Max(0, (AttackReadyAt - Time.time) / AttackReadyAt); + public float AttackPreparationTimeElapsed => AttackPreparationTime - AttackPreparationTimeLeft; + public float AttackPreparationTimeElapsedNormalized => 1 - AttackPreparationTimeLeftNormalized; + public bool AttackIsReady => AttackPreparationTimeLeft == 0; + + public void StartAttackCooldown() => AttackCooldown = Time.time + AttackCooldownDuration; + public float AttackCooldown { get; protected set; } = 0f; + public float AttackCooldownLeft => Math.Max(0, AttackCooldown - Time.time); + public float AttackCooldownDuration { get; protected set; } = 1f; +} diff --git a/Assets/Scripts/Runtime/Core/Combat/AttackerAndDamageable.cs.meta b/Assets/Scripts/Runtime/Core/Combat/AttackerAndDamageable.cs.meta new file mode 100644 index 0000000..7dc0f4a --- /dev/null +++ b/Assets/Scripts/Runtime/Core/Combat/AttackerAndDamageable.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ed839425f86bc15489191258ded58af7 \ No newline at end of file diff --git a/Assets/Scripts/Runtime/Core/Combat/Damageable.cs b/Assets/Scripts/Runtime/Core/Combat/Damageable.cs new file mode 100644 index 0000000..e1d88c8 --- /dev/null +++ b/Assets/Scripts/Runtime/Core/Combat/Damageable.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + + +public class Damageable : IDamageable { + public event Action OnTakeDamage; + public event Action OnDeath; + public event Action OnHeal; + + public int BaseHealth { get; protected set; } + public int CurrentHealth { get; protected set; } + public bool IsDead => CurrentHealth <= 0; + + public bool IsInvincible => Time.time < InvincibleUntil; + public float InvincibleUntil { get; protected set; } = 0f; + public float InvincibilityLeft => Math.Max(0, InvincibleUntil - Time.time); + public float InvincibilityDuration { get; protected set; } = 0.1f; + + public bool IsFrameFrozen => Time.time < FrameFrozenUntil; + public float FrameFrozenUntil { get; protected set; } = 0f; + public float FrameFreezeLeft => Math.Max(0, FrameFrozenUntil - Time.time); + public float FrameFreezeDuration { get; protected set; } = 0.3f; + + public int DamageTaken { get; protected set; } + public Vector2 DirectionOfDamage { get; protected set; } + [SerializeField] public bool IsKnockable { get; protected set; } + public int Knockback { get; protected set; } + + public void Damage(int amount, Vector2 hitDirection, int knockback = 1) { + if (IsDead || IsInvincible) return; + + CurrentHealth -= amount; + CurrentHealth = Mathf.Max(CurrentHealth, 0); + InvincibleUntil = Time.time + InvincibilityDuration; + FrameFrozenUntil = Time.time + FrameFreezeDuration; + DamageTaken = amount; + if (IsKnockable) Knockback = knockback; + + hitDirection = hitDirection.normalized; + if (hitDirection == Vector2.zero) hitDirection = Vector2.up; + DirectionOfDamage = hitDirection; + OnTakeDamage?.Invoke(amount, hitDirection); + if (CurrentHealth == 0) OnDeath?.Invoke(); + } + + public void Heal(int amount) { + if (IsDead) return; + + CurrentHealth += amount; + CurrentHealth = Mathf.Min(CurrentHealth, BaseHealth); + OnHeal?.Invoke(amount); + } + + public void Reset() { + CurrentHealth = BaseHealth; + InvincibleUntil = Time.time; + FrameFrozenUntil = Time.time; + } +} + + + +public interface IDamageable { + int BaseHealth { get; } + int CurrentHealth { get; } + bool IsDead { get; } + + bool IsInvincible { get; } + float InvincibleUntil { get; } + float InvincibilityLeft { get; } + float InvincibilityDuration { get; } + + bool IsFrameFrozen { get; } + float FrameFrozenUntil { get; } + float FrameFreezeLeft { get; } + float FrameFreezeDuration { get; } + + int DamageTaken { get; } + Vector2 DirectionOfDamage { get; } + bool IsKnockable { get; } + int Knockback { get; } + + public void Damage(int amount, Vector2 hitDirection, int knockback = 1); + public void Heal(int amount); + public void Reset(); +} diff --git a/Assets/Scripts/Runtime/Core/Combat/Damageable.cs.meta b/Assets/Scripts/Runtime/Core/Combat/Damageable.cs.meta new file mode 100644 index 0000000..add71df --- /dev/null +++ b/Assets/Scripts/Runtime/Core/Combat/Damageable.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 54a08dfa4b27c3d40bed84ba3e471ebb \ No newline at end of file diff --git a/Assets/Scripts/Runtime/Core/Combat/EnemyCombat.cs b/Assets/Scripts/Runtime/Core/Combat/EnemyCombat.cs new file mode 100644 index 0000000..70e24e2 --- /dev/null +++ b/Assets/Scripts/Runtime/Core/Combat/EnemyCombat.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Unity.VisualScripting; +using UnityEngine; +using UnityEngine.AI; +using static EnemySpawnerData; + + +[SerializeField] +public class Enemy: AttackerAndDamageable { + [Header("Mechanics Attributes")] + [SerializeField] public float ChaseDistance = 10f; + [SerializeField] public float ChaseDistanceBuffer = 1f; + [SerializeField] public float AttackDistance = 1.5f; + [SerializeField] public float AttackMaskDiameter = 1f; + + [Header("Enemy Attributes")] + [SerializeField] public float Attack = 10f; + [SerializeField] public float Speed = 5f; + [SerializeField] public float Health = 10f; + [SerializeField] public float Energy = 10f; + + protected FloatingTextSpawner TextPopUp; + + + protected Player Player { get { return Player.Instance; } } + protected Transform PlayerTransform { get { return Player.transform; } } + protected Vector2 PlayerPos { get { return PlayerTransform.position; } } + protected Vector2 CrystalPos { get { return GameManager.Crystal.transform.position; } } + + protected GameObject Owner; + protected Rigidbody2D Rigidbody; + protected Transform MyTransform { get { return Owner.transform; } } + protected Vector2 MyPos { get { return MyTransform.position; } set { MyTransform.position = value; } } + + protected NavMeshAgent PathAgent; + + protected Material MaterialColorOverlay; + + protected bool IsUpdating; + + public float DistFromPlayer { get; protected set; } + public float DistFromCrystal { get; protected set; } + protected bool CanAttackPlayer => DistFromPlayer <= AttackDistance && CanAttack; + protected bool CanAttackCrystal => DistFromCrystal <= AttackDistance && CanAttack; + public bool AggroOnPlayer { get; protected set; } + public bool AggroOnCrystal{ get; protected set; } + + public State CurrentState; + public State PrevState; + public enum State { + None, + GoToCrystal, + AttackCrystal, + ChasePlayer, + AttackPlayer, + PrepareAttack, + FinalizeAttack, + TakeDamage, + Die + } + + public Enemy(GameObject owner) { + Owner = owner; + + PathAgent = Owner.GetComponent(); + PathAgent.updatePosition = false; + PathAgent.updateRotation = false; + PathAgent.updateUpAxis = false; + PathAgent.speed = Speed; + IsUpdating = false; + + TextPopUp = new FloatingTextSpawner(Owner.transform, 1); + + OnTakeDamage += (damage, direction) => SetState(State.TakeDamage); + OnDeath += () => SetState(State.Die); + OnDeath += () => UnsubscribeToEvent(); + + Rigidbody = Owner.GetComponent(); + foreach (var material in Owner.GetComponentsInChildren().Select(x => x.material).ToList()) { + switch (material.name.Split(' ')[0]) { + case "DamageFlashMat": MaterialColorOverlay = material; break; + } + } + } + + private Action cachedUpdate; + protected void SubscribeToEvent() { + cachedUpdate = () => { + if (PathAgent == null || Owner == null) return; + PathAgentHandler.UpdatePositionIso(PathAgent, Owner.transform); + Update(); + }; + + EnemyManagers[Enemies.Gobler].UpdateTick += cachedUpdate; + EnemyManagers[Enemies.Gobler].GizmoTick += OnDrawGizmos; + } + + protected void UnsubscribeToEvent() { + EnemyManagers[Enemies.Gobler].UpdateTick -= cachedUpdate; + EnemyManagers[Enemies.Gobler].GizmoTick -= OnDrawGizmos; + } + + protected virtual void SetPriorityState() { } + + protected virtual void DoUpdate() { } + protected void Update() { + if (Owner == null) return; + if (GameManager.Crystal == null) return; + if (IsUpdating) return; + IsUpdating = true; + + DistFromPlayer = Vector2.Distance(MyPos, PlayerPos); + DistFromCrystal = Vector2.Distance(MyPos, CrystalPos); + SetPriorityState(); + + DoUpdate(); + + IsUpdating = false; + } + + protected virtual void SetState(State newState) { } + + protected virtual void OnDrawGizmos() { } + } diff --git a/Assets/Scripts/Runtime/Core/Combat/EnemyCombat.cs.meta b/Assets/Scripts/Runtime/Core/Combat/EnemyCombat.cs.meta new file mode 100644 index 0000000..cf993c1 --- /dev/null +++ b/Assets/Scripts/Runtime/Core/Combat/EnemyCombat.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 85104d36b352039479f65ed0b24a4768 \ No newline at end of file diff --git a/Assets/Scripts/Runtime/Core/Utils.meta b/Assets/Scripts/Runtime/Core/Utils.meta new file mode 100644 index 0000000..eb7e229 --- /dev/null +++ b/Assets/Scripts/Runtime/Core/Utils.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4ad93b11b89e04e40ab9a73f32005ec9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: