Create Vfx for multiple jumps while on air Finalize jumping ability with up to 3 multi-jump implementing a hacky bezier curve
321 lines
8.7 KiB
C#
321 lines
8.7 KiB
C#
using AI.Base;
|
|
using NUnit.Framework.Internal.Execution;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Xml.Linq;
|
|
using UnityEngine;
|
|
using UnityEngine.AI;
|
|
using static EnemySpawnerData;
|
|
|
|
|
|
public class Gobler : Alive {
|
|
[Header("Mechanics Attributes")]
|
|
[SerializeField] public float ChaseDistance = 10f;
|
|
[SerializeField] public float ChaseDistanceBuffer = 1f;
|
|
[SerializeField] public float AttackDistance = 1f;
|
|
[SerializeField] public float AttackMaskDiameter = 1f;
|
|
|
|
|
|
[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<Material> Materials;
|
|
private Material MaterialColorOverlay;
|
|
|
|
|
|
public Gobler(GameObject owner) {
|
|
Owner = owner;
|
|
PathAgent = Owner.GetComponent<NavMeshAgent>();
|
|
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<Rigidbody2D>();
|
|
foreach (var material in Owner.GetComponentsInChildren<SpriteRenderer>().Select(x => x.material).ToList()) {
|
|
switch (material.name.Split(' ')[0]) {
|
|
case "DamageFlashMat": MaterialColorOverlay = material; break;
|
|
}
|
|
}
|
|
|
|
TextPopUp = new FloatingTextSpawner(Owner.transform, 1);
|
|
|
|
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() {
|
|
if (CurrentState == State.AttackPlayer) return;
|
|
if (CurrentState == State.ChasePlayer) return;
|
|
if (CurrentState == State.TakeDamage) return;
|
|
if (CurrentState == State.Die) return;
|
|
if (DistFromPlayer > ChaseDistance) return;
|
|
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();
|
|
|
|
switch (CurrentState) {
|
|
case State.None:
|
|
SetState(State.GoToCrystal);
|
|
break;
|
|
|
|
|
|
case State.GoToCrystal:
|
|
PathAgent.SetDestination(CrystalPos);
|
|
|
|
break;
|
|
|
|
|
|
case State.AttackCrystal:
|
|
break;
|
|
|
|
|
|
case State.ChasePlayer:
|
|
if (DistFromPlayer > ChaseDistance + ChaseDistanceBuffer)
|
|
SetState(State.GoToCrystal);
|
|
if (DistFromPlayer > ChaseDistance + ChaseDistanceBuffer)
|
|
SetState(State.GoToCrystal);
|
|
else
|
|
PathAgent.SetDestination(PlayerPos);
|
|
break;
|
|
|
|
|
|
case State.AttackPlayer:
|
|
|
|
break;
|
|
|
|
|
|
case State.TakeDamage:
|
|
Rigidbody.linearVelocity = DirectionOfDamage * Knockback * 10 * InvincibilityLeft;
|
|
MaterialColorOverlay.SetFloat("_FlashAmount", InvincibilityLeft);
|
|
if (!IsInvincible) SetState(State.None);
|
|
break;
|
|
|
|
|
|
case State.PrepareAttack:
|
|
MaterialColorOverlay.SetFloat("_FlashColor", 1 - InvincibilityLeft);
|
|
if (!IsInvincible) SetState(State.FinalizeAttack);
|
|
break;
|
|
|
|
|
|
case State.FinalizeAttack:
|
|
Collider2D[] hits = Physics2D.OverlapCircleAll(MyPos + Vector2.down, 4);
|
|
foreach (var hit in hits) {
|
|
if (!hit.CompareTag("PlayerHurtBox")) continue;
|
|
|
|
//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);
|
|
}
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
IsUpdating = false;
|
|
}
|
|
|
|
|
|
|
|
protected void SetState(State newState) {
|
|
CurrentState = newState;
|
|
switch (CurrentState) {
|
|
case State.None:
|
|
PathAgent.isStopped = false;
|
|
break;
|
|
|
|
|
|
case State.GoToCrystal:
|
|
break;
|
|
|
|
|
|
case State.AttackCrystal:
|
|
break;
|
|
|
|
|
|
case State.ChasePlayer:
|
|
break;
|
|
|
|
|
|
case State.AttackPlayer:
|
|
break;
|
|
|
|
|
|
case State.TakeDamage:
|
|
PathAgent.isStopped = true;
|
|
TextPopUp.SpawnFloatingText(DamageTaken.ToString(), Color.red, 3);
|
|
MaterialColorOverlay.SetColor("_FlashColor", Color.white);
|
|
break;
|
|
|
|
|
|
case State.PrepareAttack:
|
|
PathAgent.isStopped = true;
|
|
MaterialColorOverlay.SetColor("_FlashColor", Color.purple);
|
|
break;
|
|
|
|
|
|
case State.Die:
|
|
UnsubscribeToEvent();
|
|
SpawnableEnemyInfo.DestroyEnemy(Owner, Enemies.Gobler);
|
|
SetState(State.None);
|
|
break;
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
protected void OnDrawGizmos() {
|
|
if (Owner == null) return;
|
|
|
|
DrawChaseDistance();
|
|
DrawAttackDistance();
|
|
DrawAttackReach();
|
|
}
|
|
|
|
private void DrawWireSphere(Color color, Vector2 center, float radius) => EnemyManagers[Enemies.Gobler].DrawWireSphere(color, center, radius);
|
|
|
|
|
|
private void DrawChaseDistance() {
|
|
DrawWireSphere(Color.green, MyPos, ChaseDistance);
|
|
}
|
|
|
|
private void DrawAttackDistance() {
|
|
//Gizmos.color = CurrentState == (IState)AttackState ? Color.red : Color.green;
|
|
//Vector2 center = this.transform.position;
|
|
//Gizmos.DrawWireSphere(center, AttackDistance);
|
|
}
|
|
|
|
private void DrawAttackReach() {
|
|
//if (CurrentState != (IState)AttackState) return;
|
|
//Gizmos.color = Color.red;
|
|
|
|
Vector2 direction2D = (PlayerPos - MyPos).normalized;
|
|
Vector2 point2D = MyPos + direction2D * AttackDistance;
|
|
//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 event Action<int, Vector2> OnTakeDamage;
|
|
public event Action OnDeath;
|
|
public event Action<int> 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;
|
|
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;
|
|
}
|
|
} |