Add knockbock gaurd against being pushed out of bounds
Further develop on attacking and taking damage states
This commit is contained in:
parent
24b9ece93d
commit
62b981f440
@ -16,10 +16,13 @@ public class Gobler : Enemy {
|
||||
|
||||
public Gobler(GameObject owner) : base(owner) {
|
||||
BaseDamage = 10;
|
||||
AttackPreparationTime = 1;
|
||||
AttackPreparationTime = 0.5f;
|
||||
AttackCooldownDuration = 1;
|
||||
|
||||
BaseHealth = 100;
|
||||
CurrentHealth = BaseHealth;
|
||||
InvincibilityDuration = 0.11f;
|
||||
FrameFreezeDuration = 0.3f;
|
||||
IsKnockable = true;
|
||||
|
||||
SetState(State.GoToCrystal);
|
||||
@ -56,9 +59,11 @@ public class Gobler : Enemy {
|
||||
case State.ChasePlayer:
|
||||
if (CanAttackPlayer)
|
||||
SetState(State.PrepareAttack);
|
||||
else if (DistFromPlayer > ChaseDistance + ChaseDistanceBuffer)
|
||||
else if (DistFromPlayer > ChaseDistance + ChaseDistanceBuffer) {
|
||||
if (AggroOnPlayer) TextPopUp.SpawnFloatingText("?", Color.red);
|
||||
AggroOnPlayer = false;
|
||||
SetState(State.None);
|
||||
else
|
||||
} else
|
||||
PathAgent.SetDestination(PlayerPos);
|
||||
break;
|
||||
|
||||
@ -66,48 +71,59 @@ public class Gobler : Enemy {
|
||||
case State.TakeDamage:
|
||||
if (IsInvincible) {
|
||||
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);
|
||||
var isWalkable = NavMeshUtils.IsWalkable(MyPos, DirectionOfDamage);
|
||||
Rigidbody.linearVelocity = isWalkable ? velocity : Vector2.zero;
|
||||
//Rigidbody.linearVelocity = velocity;
|
||||
MaterialColorOverlay.SetFloat("_FlashAmount", InvincibilityLeft / InvincibilityDuration);
|
||||
} else {
|
||||
MaterialColorOverlay.SetFloat("_FlashAmount", 0);
|
||||
Rigidbody.linearVelocity = Vector2.zero;
|
||||
}
|
||||
|
||||
if (!IsFrameFrozen) SetState(State.None);
|
||||
break;
|
||||
|
||||
|
||||
case State.PrepareAttack:
|
||||
MaterialColorOverlay.SetFloat("_FlashAmount", AttackPreparationTimeElapsedNormalized);
|
||||
if (!IsFrameFrozen) SetState(State.FinalizeAttack);
|
||||
if (AttackIsReady) SetState(State.FinalizeAttack);
|
||||
break;
|
||||
|
||||
|
||||
case State.FinalizeAttack:
|
||||
SetState(AggroOnPlayer ? State.ChasePlayer : State.GoToCrystal);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected override void SetState(State newState) {
|
||||
protected override void DoSetState(State newState) {
|
||||
Debug.Log($"New State: {newState.ToString()}");
|
||||
CurrentState = newState;
|
||||
switch (CurrentState) {
|
||||
switch (newState) {
|
||||
case State.None:
|
||||
if (AggroOnPlayer) TextPopUp.SpawnFloatingText("?", Color.red);
|
||||
PathAgent.isStopped = false;
|
||||
AggroOnCrystal = false;
|
||||
AggroOnPlayer = false;
|
||||
PathAgent.isStopped = true;
|
||||
break;
|
||||
|
||||
|
||||
case State.GoToCrystal:
|
||||
AggroOnCrystal = true;
|
||||
PathAgent.isStopped = false;
|
||||
PathAgent.SetDestination(CrystalPos);
|
||||
break;
|
||||
|
||||
|
||||
case State.ChasePlayer:
|
||||
AggroOnPlayer = true;
|
||||
PathAgent.isStopped = false;
|
||||
TextPopUp.SpawnFloatingText("!", Color.red);
|
||||
break;
|
||||
|
||||
|
||||
case State.TakeDamage:
|
||||
AggroOnCrystal = false;
|
||||
AggroOnPlayer = false;
|
||||
PathAgent.isStopped = true;
|
||||
TextPopUp.SpawnFloatingText(DamageTaken.ToString(), Color.red, 3);
|
||||
MaterialColorOverlay.SetColor("_FlashColor", Color.white);
|
||||
@ -131,8 +147,8 @@ public class Gobler : Enemy {
|
||||
//EnemySpawnerData.EnemyMap[parent].Damage(ActiveClass.HitBoxDraw.Damage, knockbakDirection, ActiveClass.HitBoxDraw.Knockback);
|
||||
}
|
||||
|
||||
MaterialColorOverlay.SetFloat("_FlashAmount", 0);
|
||||
StartAttackCooldown();
|
||||
SetState(AggroOnPlayer ? State.ChasePlayer : State.GoToCrystal);
|
||||
break;
|
||||
|
||||
|
||||
@ -143,6 +159,7 @@ public class Gobler : Enemy {
|
||||
}
|
||||
|
||||
if (PrevState != CurrentState) PrevState = CurrentState;
|
||||
Debug.Log($"State Done: {newState.ToString()}");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -5,29 +5,43 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
[SerializeField]
|
||||
public class Attacker {
|
||||
public int BaseDamage = 0;
|
||||
public bool CanAttack => Time.time < AttackCooldown;
|
||||
public float AttackCooldown { get; protected set; } = 0f;
|
||||
[SerializeField] public int BaseDamage { get; protected set; } = 0;
|
||||
|
||||
public void StartAttackPreparation() => AttackReadyAt = Time.time + AttackPreparationTime;
|
||||
public bool AttackIsReady => AttackPreparationTimeLeft == 0;
|
||||
public float AttackReadyAt { get; protected set; }
|
||||
[SerializeField] 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) / AttackPreparationTime);
|
||||
public float AttackPreparationTimeElapsed => AttackPreparationTime - AttackPreparationTimeLeft;
|
||||
public float AttackPreparationTimeElapsedNormalized => 1 - AttackPreparationTimeLeftNormalized;
|
||||
|
||||
public void StartAttackCooldown() => AttackCooldown = Time.time + AttackCooldownDuration;
|
||||
public bool CanAttack => Time.time > AttackCooldown;
|
||||
public float AttackCooldown { get; protected set; }
|
||||
public float AttackCooldownLeft => Math.Max(0, AttackCooldown - Time.time);
|
||||
public float AttackCooldownDuration { get; protected set; } = 1f;
|
||||
public void StartAttackCooldown() => AttackCooldown = Time.time;
|
||||
[SerializeField] public float AttackCooldownDuration { get; protected set; } = 1f;
|
||||
}
|
||||
|
||||
|
||||
public interface IAttacker {
|
||||
int BaseDamage { get; }
|
||||
bool CanAttack => Time.time < AttackCooldown;
|
||||
public int BaseDamage { get;}
|
||||
|
||||
void StartAttackPreparation();
|
||||
float AttackPreparationTime { get; }
|
||||
float AttackPreparationTimeLeft { get; }
|
||||
bool AttackIsReady { get; }
|
||||
public void StartAttackPreparation() ;
|
||||
public float AttackReadyAt { get; }
|
||||
public float AttackPreparationTime { get; }
|
||||
public float AttackPreparationTimeLeft { get; }
|
||||
public float AttackPreparationTimeLeftNormalized { get; }
|
||||
public float AttackPreparationTimeElapsed { get; }
|
||||
public float AttackPreparationTimeElapsedNormalized { get; }
|
||||
public bool AttackIsReady => AttackPreparationTimeLeft == 0;
|
||||
|
||||
void StartAttackCooldown();
|
||||
float AttackCooldown { get; }
|
||||
float AttackCooldownLeft { get; }
|
||||
float AttackCooldownDuration { get; }
|
||||
public void StartAttackCooldown() ;
|
||||
public bool CanAttack => Time.time > AttackCooldown;
|
||||
public float AttackCooldown { get; }
|
||||
public float AttackCooldownLeft { get; }
|
||||
public float AttackCooldownDuration { get; }
|
||||
}
|
||||
|
||||
|
||||
@ -5,21 +5,22 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
[SerializeField]
|
||||
public class AttackerAndDamageable : Damageable, IAttacker {
|
||||
public int BaseDamage { get; protected set; } = 0;
|
||||
public bool CanAttack => Time.time > AttackCooldown;
|
||||
[SerializeField] public int BaseDamage { get; protected set; } = 0;
|
||||
|
||||
public void StartAttackPreparation() => AttackReadyAt = Time.time + AttackPreparationTime;
|
||||
public bool AttackIsReady => AttackPreparationTimeLeft == 0;
|
||||
public float AttackReadyAt { get; protected set; }
|
||||
public float AttackPreparationTime { get; protected set; } = 0;
|
||||
[SerializeField] 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 AttackPreparationTimeLeftNormalized => Math.Max(0, (AttackReadyAt - Time.time) / AttackPreparationTime);
|
||||
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 bool CanAttack => Time.time > AttackCooldown;
|
||||
public float AttackCooldown { get; protected set; }
|
||||
public float AttackCooldownLeft => Math.Max(0, AttackCooldown - Time.time);
|
||||
public float AttackCooldownDuration { get; protected set; } = 1f;
|
||||
[SerializeField] public float AttackCooldownDuration { get; protected set; } = 1f;
|
||||
}
|
||||
|
||||
@ -6,24 +6,25 @@ using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
[SerializeField]
|
||||
public class Damageable : IDamageable {
|
||||
public event Action<int, Vector2> OnTakeDamage;
|
||||
public event Action OnDeath;
|
||||
public event Action<int> OnHeal;
|
||||
|
||||
public int BaseHealth { get; protected set; }
|
||||
[SerializeField] 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;
|
||||
[SerializeField] public float InvincibilityDuration { get; protected set; } = 0;
|
||||
|
||||
public bool IsFrameFrozen => Time.time < FrameFrozenUntil;
|
||||
public float FrameFrozenUntil { get; protected set; } = 0f;
|
||||
public float FrameFrozenUntil { get; protected set; }
|
||||
public float FrameFreezeLeft => Math.Max(0, FrameFrozenUntil - Time.time);
|
||||
public float FrameFreezeDuration { get; protected set; } = 0.3f;
|
||||
[SerializeField] public float FrameFreezeDuration { get; protected set; } = 0;
|
||||
|
||||
public int DamageTaken { get; protected set; }
|
||||
public Vector2 DirectionOfDamage { get; protected set; }
|
||||
|
||||
@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Unity.VisualScripting;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AI;
|
||||
using static EnemySpawnerData;
|
||||
@ -41,6 +42,7 @@ public class Enemy: AttackerAndDamageable {
|
||||
protected Material MaterialColorOverlay;
|
||||
|
||||
protected bool IsUpdating;
|
||||
protected bool IsSettingNewState;
|
||||
|
||||
public float DistFromPlayer { get; protected set; }
|
||||
public float DistFromCrystal { get; protected set; }
|
||||
@ -72,12 +74,12 @@ public class Enemy: AttackerAndDamageable {
|
||||
PathAgent.updateUpAxis = false;
|
||||
PathAgent.speed = Speed;
|
||||
IsUpdating = false;
|
||||
IsSettingNewState = false;
|
||||
|
||||
TextPopUp = new FloatingTextSpawner(Owner.transform, 1);
|
||||
|
||||
OnTakeDamage += (damage, direction) => SetState(State.TakeDamage);
|
||||
OnDeath += () => SetState(State.Die);
|
||||
OnDeath += () => UnsubscribeToEvent();
|
||||
OnTakeDamage += (damage, direction) => { QueueUpdate(); SetState(State.TakeDamage); IsUpdating = false; };
|
||||
OnDeath += () => { QueueUpdate(); SetState(State.Die); UnsubscribeToEvent(); IsUpdating = false; };
|
||||
|
||||
Rigidbody = Owner.GetComponent<Rigidbody2D>();
|
||||
foreach (var material in Owner.GetComponentsInChildren<SpriteRenderer>().Select(x => x.material).ToList()) {
|
||||
@ -106,10 +108,16 @@ public class Enemy: AttackerAndDamageable {
|
||||
|
||||
protected virtual void SetPriorityState() { }
|
||||
|
||||
protected void QueueUpdate() {
|
||||
while (IsUpdating) System.Threading.Thread.Sleep(100);
|
||||
IsUpdating = true;
|
||||
}
|
||||
|
||||
protected virtual void DoUpdate() { }
|
||||
protected void Update() {
|
||||
if (Owner == null) return;
|
||||
if (GameManager.Crystal == null) return;
|
||||
if (IsSettingNewState) return;
|
||||
if (IsUpdating) return;
|
||||
IsUpdating = true;
|
||||
|
||||
@ -122,7 +130,13 @@ public class Enemy: AttackerAndDamageable {
|
||||
IsUpdating = false;
|
||||
}
|
||||
|
||||
protected virtual void SetState(State newState) { }
|
||||
protected void SetState(State newState) {
|
||||
IsSettingNewState = true;
|
||||
DoSetState(newState);
|
||||
IsSettingNewState = false;
|
||||
}
|
||||
|
||||
protected virtual void DoSetState(State newState) { }
|
||||
|
||||
protected virtual void OnDrawGizmos() { }
|
||||
}
|
||||
|
||||
21
Assets/Scripts/Runtime/Core/Utils/NavMeshUtils.cs
Normal file
21
Assets/Scripts/Runtime/Core/Utils/NavMeshUtils.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.AI;
|
||||
using static UnityEditor.PlayerSettings;
|
||||
|
||||
public static class NavMeshUtils {
|
||||
public static bool IsWalkable(Vector3 origin, Vector3 velocity, float navMeshCheckRadius = 0.2f) {
|
||||
|
||||
Vector3 target = origin + velocity * Time.fixedDeltaTime;
|
||||
|
||||
//// 1. Physics obstacle check (optional, but good to avoid wall collisions)
|
||||
//if (Physics.Linecast(origin, target, obstacleMask))
|
||||
// return false;
|
||||
|
||||
// 2. Check if point is still on the NavMesh
|
||||
if (!NavMesh.SamplePosition(target, out var hit, navMeshCheckRadius, NavMesh.AllAreas))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
return (hit.position - target).sqrMagnitude < 0.04f;
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Runtime/Core/Utils/NavMeshUtils.cs.meta
Normal file
2
Assets/Scripts/Runtime/Core/Utils/NavMeshUtils.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 43b1282d8b6e3cf4d9b06a56faa9eed0
|
||||
@ -1325,7 +1325,7 @@ MonoBehaviour:
|
||||
m_AudioPlay: 0
|
||||
m_DebugDrawModesUseInteractiveLightBakingData: 0
|
||||
m_Position:
|
||||
m_Target: {x: -66.720314, y: -10.525324, z: -0.0061987927}
|
||||
m_Target: {x: 16.61, y: -15.860004, z: -0.14518732}
|
||||
speed: 2
|
||||
m_Value: {x: -66.720314, y: -10.525324, z: -0.0061987927}
|
||||
m_RenderMode: 0
|
||||
@ -1377,7 +1377,7 @@ MonoBehaviour:
|
||||
speed: 2
|
||||
m_Value: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_Size:
|
||||
m_Target: 7.788484
|
||||
m_Target: 1.3985817
|
||||
speed: 2
|
||||
m_Value: 7.788484
|
||||
m_Ortho:
|
||||
@ -1842,7 +1842,7 @@ MonoBehaviour:
|
||||
scrollPos: {x: 0, y: 255}
|
||||
m_SelectedIDs: 28ce0000
|
||||
m_LastClickedID: 52776
|
||||
m_ExpandedIDs: 00000000f8cd0000facd0000fccd0000fecd000000ce000002ce000004ce000006ce000008ce00000ace00000cce00000ece000010ce000012ce000014ce000016ce000018ce00001ace00001cce00001ece000020ce000022ce000024ce000026ce000028ce00002ace00002cce00002ece000030ce000032ce000034ce000036ce000038ce00003ace00003cce00003ece0000
|
||||
m_ExpandedIDs: 0000000028ce00002ace00002cce00002ece000030ce000032ce000034ce000036ce000038ce00003ace00003cce00003ece000040ce000042ce000044ce000046ce000048ce00004ace00004cce00004ece000050ce000052ce000054ce000056ce000058ce00005ace00005cce00005ece000060ce000062ce000064ce000066ce000068ce00006ace00006cce00006ece0000
|
||||
m_RenameOverlay:
|
||||
m_UserAcceptedRename: 0
|
||||
m_Name:
|
||||
@ -1871,7 +1871,7 @@ MonoBehaviour:
|
||||
scrollPos: {x: 0, y: 0}
|
||||
m_SelectedIDs:
|
||||
m_LastClickedID: 0
|
||||
m_ExpandedIDs: 00000000f8cd0000facd0000fccd0000fecd000000ce000002ce000004ce000006ce000008ce00000ace00000cce00000ece000010ce000012ce000014ce000016ce000018ce00001ace00001cce00001ece000020ce000022ce000024ce000026ce000028ce00002ace00002cce00002ece000030ce000032ce000034ce000036ce000038ce00003ace00003cce00003ece0000
|
||||
m_ExpandedIDs: 0000000028ce00002ace00002cce00002ece000030ce000032ce000034ce000036ce000038ce00003ace00003cce00003ece000040ce000042ce000044ce000046ce000048ce00004ace00004cce00004ece000050ce000052ce000054ce000056ce000058ce00005ace00005cce00005ece000060ce000062ce000064ce000066ce000068ce00006ace00006cce00006ece0000
|
||||
m_RenameOverlay:
|
||||
m_UserAcceptedRename: 0
|
||||
m_Name:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user