243 lines
7.7 KiB
C#
243 lines
7.7 KiB
C#
#nullable enable
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.Serialization;
|
|
|
|
public class AIEntity : Entity
|
|
{
|
|
[FormerlySerializedAs("stats")] [SerializeField]
|
|
public AIStats AIStats = null!;
|
|
BaseState currentState = null!;
|
|
public string[] enemies {get; protected set; }
|
|
public bool facingRight { get; private set; } = true;
|
|
|
|
override protected void Start(){
|
|
base.Start();
|
|
currentState = CreateInitialState();
|
|
currentState.EnterState();
|
|
}
|
|
|
|
override protected void Update() {
|
|
base.Update();
|
|
if (currentState.UpdateState() is {} newState)
|
|
SwitchState(newState);
|
|
}
|
|
|
|
override protected void FixedUpdate() {
|
|
base.FixedUpdate();
|
|
if (currentState.FixedUpdateState() is {} newState)
|
|
SwitchState(newState);
|
|
FlipAccordingToInput();
|
|
}
|
|
|
|
void OnDrawGizmos() => currentState?.OnDrawGizmos();
|
|
|
|
void SwitchState(BaseState newState) {
|
|
currentState.LeaveState();
|
|
currentState = newState;
|
|
newState.EnterState();
|
|
}
|
|
|
|
protected virtual BaseState CreateInitialState() => new FindTargetState(this);
|
|
|
|
//Looks into enemy name list to see if the other is targetable
|
|
virtual protected bool IsTargetable(Entity other){
|
|
foreach (string name in enemies){
|
|
if(other.entityName == name && other.IsAlive()){
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
override public bool TakeDamage(float amount, Entity other){
|
|
Entity currTargetEntity = GetTarget().GetComponent<Entity>();
|
|
if(!(currTargetEntity is null)){
|
|
if(currTargetEntity.entityName == "Vampire")
|
|
SetTarget(other.transform);
|
|
}
|
|
return base.TakeDamage(amount, other);
|
|
}
|
|
|
|
#region Flip
|
|
|
|
public void Flip() {
|
|
facingRight = !facingRight;
|
|
Vector3 scaler = transform.localScale;
|
|
scaler.x *= -1;
|
|
transform.localScale = scaler;
|
|
}
|
|
|
|
public void FlipAccordingToInput() {
|
|
if ((!facingRight && rb.velocity.x > 0) || (facingRight && rb.velocity.x < 0)) {
|
|
Flip();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
protected abstract class BaseStateAI : BaseState{
|
|
protected AIEntity entity;
|
|
public BaseStateAI(AIEntity entity){
|
|
this.entity = entity;
|
|
}
|
|
}
|
|
|
|
protected class SeekState : BaseStateAI{
|
|
public SeekState(AIEntity entity) : base(entity){
|
|
|
|
}
|
|
|
|
public override void EnterState() {
|
|
if(! entity.animator.GetCurrentAnimatorStateInfo(0).IsName("Attack")) {
|
|
entity.animator.Play("Running");
|
|
}
|
|
}
|
|
|
|
public override BaseState? UpdateState(){
|
|
if(!entity.IsAlive()){
|
|
return new DeadState(entity);
|
|
}
|
|
Entity targetEntity = entity.GetTarget().GetComponent<Entity>();
|
|
if(targetEntity != null){
|
|
if(targetEntity.IsAlive()){//target is alive, keep chasing it
|
|
return null;
|
|
}else{//target is dead, go to findTargetState
|
|
return new FindTargetState(entity);;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public override BaseState? FixedUpdateState(){
|
|
Transform target = entity.GetTarget();
|
|
entity.direction = Vector3.RotateTowards(entity.direction, (target.position - entity.transform.position), entity.rotSpeed*Time.fixedDeltaTime, 0.0f);
|
|
if(entity.IsTargetable(entity.GetTarget().GetComponent<Entity>())){
|
|
if(!entity.IsInAttackRange()){
|
|
entity.rb.MovePosition(entity.transform.position + entity.direction * entity.movementSpeed * Time.fixedDeltaTime);
|
|
// entity.animator.Play("Running");
|
|
}else{
|
|
return new AttackState(entity);
|
|
}
|
|
}else{
|
|
return new FindTargetState(entity);
|
|
}
|
|
// entity.animator.Play("Idle");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
protected class FindTargetState : BaseStateAI{
|
|
float closeEnough;
|
|
Vector3 roamPosition;
|
|
public FindTargetState(AIEntity entity) : base(entity){
|
|
}
|
|
|
|
public override void EnterState() {
|
|
if (!entity.animator.GetCurrentAnimatorStateInfo(0).IsName("Attack")) {
|
|
entity.animator.Play("Running");
|
|
}
|
|
}
|
|
|
|
public override BaseState? UpdateState(){
|
|
if(!entity.IsAlive()){
|
|
return new DeadState(entity);
|
|
}
|
|
Entity[] entities = FindObjectsOfType<Entity>();
|
|
float lastDist = float.MaxValue;
|
|
Entity chosenEntity = null!;
|
|
foreach (Entity other in entities){// Find the closest entity
|
|
if(entity.IsTargetable(other)){
|
|
float distance = Vector2.Distance(other.transform.position, entity.transform.position);
|
|
if(distance < lastDist){
|
|
lastDist = distance;
|
|
chosenEntity = other;
|
|
if(lastDist <= entity.AIStats.closeEnough)break;
|
|
}
|
|
}
|
|
}
|
|
if(chosenEntity != null){
|
|
entity.SetTarget(chosenEntity.transform);
|
|
return new SeekState(entity);
|
|
}else{
|
|
if(roamPosition == new Vector3()) roamPosition = entity.AIStats.getRandomRoamPositon();
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public override BaseState? FixedUpdateState(){
|
|
if(roamPosition == new Vector3()){
|
|
// entity.animator.Play("Idle");
|
|
return null;
|
|
}
|
|
entity.direction = Vector3.RotateTowards(entity.direction, (roamPosition - entity.transform.position), entity.rotSpeed*Time.fixedDeltaTime, 0.0f);
|
|
if(Vector2.Distance(entity.transform.position, roamPosition) >= entity.attackRange){
|
|
entity.rb.MovePosition(entity.transform.position + entity.direction * entity.movementSpeed * Time.fixedDeltaTime);
|
|
}else{
|
|
roamPosition = entity.AIStats.getRandomRoamPositon();
|
|
}
|
|
// entity.animator.Play("Running");
|
|
return null;
|
|
}
|
|
|
|
}
|
|
|
|
protected class AttackState : BaseStateAI{
|
|
public AttackState(AIEntity entity) : base(entity){
|
|
}
|
|
|
|
public override void EnterState() {
|
|
entity.animator.Play("Attack");
|
|
}
|
|
|
|
public override BaseState? UpdateState(){
|
|
if(!entity.IsAlive()){
|
|
return new DeadState(entity);
|
|
}
|
|
if(entity.IsInAttackRange()){
|
|
if(entity.attackTimer >= entity.attackCooldown){
|
|
entity.attackTimer = 0;
|
|
return Attack();
|
|
}else{
|
|
entity.attackTimer += Time.deltaTime;
|
|
}
|
|
|
|
}else{
|
|
return new SeekState(entity);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private BaseState? Attack(){
|
|
Entity targetEntity = entity.GetTarget().GetComponent<Entity>();
|
|
if(targetEntity != null){
|
|
targetEntity.TakeDamage(entity.attackDmg, entity);
|
|
bool isTargetAlive = targetEntity.IsAlive();
|
|
if(!isTargetAlive){
|
|
return new FindTargetState(entity);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
protected class DeadState : BaseStateAI{
|
|
public DeadState(AIEntity entity) : base(entity){
|
|
|
|
}
|
|
|
|
public override void EnterState() {
|
|
entity.animator.Play("Death");
|
|
}
|
|
|
|
public override BaseState? UpdateState(){
|
|
|
|
|
|
return null;
|
|
}
|
|
}
|
|
}
|