|
|
|
using System.Collections;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
public class EnemyAI : EnemyActions, IDamagable<DamageObject>{
|
|
|
|
|
|
|
|
[Space(10)]
|
|
|
|
public bool enableAI;
|
|
|
|
public float chargeTime = 0f;
|
|
|
|
public bool ranged = false;
|
|
|
|
public DamageObject rangedDamage;
|
|
|
|
float _timeToShoot = 4f;
|
|
|
|
|
|
|
|
//a list of states where the AI is executed
|
|
|
|
private List<UNITSTATE> ActiveAIStates = new List<UNITSTATE> {
|
|
|
|
UNITSTATE.IDLE,
|
|
|
|
UNITSTATE.WALK,
|
|
|
|
UNITSTATE.CHARGE
|
|
|
|
};
|
|
|
|
|
|
|
|
void Start(){
|
|
|
|
|
|
|
|
//add this enemy to the enemylist
|
|
|
|
EnemyManager.enemyList.Add(gameObject);
|
|
|
|
|
|
|
|
//set z spread (zspread is used to keep space between the enemies)
|
|
|
|
ZSpread = (EnemyManager.enemyList.Count-1);
|
|
|
|
Invoke ("SetZSpread", .1f);
|
|
|
|
|
|
|
|
//randomize values to avoid synchronous movement
|
|
|
|
if(randomizeValues) SetRandomValues();
|
|
|
|
|
|
|
|
OnStart();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FixedUpdate(){
|
|
|
|
OnFixedUpdate();
|
|
|
|
}
|
|
|
|
|
|
|
|
void LateUpdate(){
|
|
|
|
OnLateUpdate();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Shoot()
|
|
|
|
{
|
|
|
|
_timeToShoot -= Time.deltaTime;
|
|
|
|
if (ranged && _timeToShoot < 0 && enemyState != UNITSTATE.KNOCKDOWNGROUNDED)
|
|
|
|
{
|
|
|
|
//spawn projectile at spawn position
|
|
|
|
GameObject projectilePrefab = GameObject.Instantiate(Resources.Load("ProjectileEnemyKnife"), this.transform.position + new Vector3(0f, 1f, 0f), Quaternion.identity) as GameObject;
|
|
|
|
if (!projectilePrefab) return;
|
|
|
|
|
|
|
|
//set projectile to current direction
|
|
|
|
Projectile projectileComponent = projectilePrefab.GetComponent<Projectile>();
|
|
|
|
if (projectileComponent)
|
|
|
|
{
|
|
|
|
projectileComponent.firedByEnemy = true;
|
|
|
|
projectileComponent.direction = this.currentDirection;
|
|
|
|
rangedDamage.inflictor = this.gameObject;
|
|
|
|
projectileComponent.SetDamage(rangedDamage);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
_timeToShoot = 3f;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Update(){
|
|
|
|
|
|
|
|
Shoot();
|
|
|
|
|
|
|
|
chargeTime -= Time.deltaTime;
|
|
|
|
//do nothing when there is no target or when AI is disabled
|
|
|
|
if (target == null || !enableAI) {
|
|
|
|
Ready ();
|
|
|
|
return;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
//get range to target
|
|
|
|
range = GetDistanceToTarget ();
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!isDead && enableAI){
|
|
|
|
if(ActiveAIStates.Contains(enemyState) && targetSpotted) {
|
|
|
|
|
|
|
|
//AI active
|
|
|
|
AI();
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
//try to spot the player
|
|
|
|
if(distanceToTarget.magnitude < sightDistance) targetSpotted = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AI(){
|
|
|
|
LookAtTarget(target.transform);
|
|
|
|
|
|
|
|
if (enemyTactic == ENEMYTACTIC.CHARGE)
|
|
|
|
{
|
|
|
|
if (chargeTime < 0)
|
|
|
|
{
|
|
|
|
WalkTo(closeRangeDistance, 0f);
|
|
|
|
chargeTime = 2f;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Charge();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (range == RANGE.ATTACKRANGE){
|
|
|
|
|
|
|
|
//attack the target
|
|
|
|
if (!cliffSpotted){
|
|
|
|
if (Time.time - lastAttackTime > attackInterval) {
|
|
|
|
ATTACK();
|
|
|
|
} else {
|
|
|
|
Ready();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//actions for ATTACKRANGE distance
|
|
|
|
if (enemyTactic == ENEMYTACTIC.KEEPCLOSEDISTANCE) WalkTo(closeRangeDistance, 0f);
|
|
|
|
if (enemyTactic == ENEMYTACTIC.KEEPMEDIUMDISTANCE) WalkTo(midRangeDistance, RangeMarging);
|
|
|
|
if (enemyTactic == ENEMYTACTIC.KEEPFARDISTANCE) WalkTo(farRangeDistance, RangeMarging);
|
|
|
|
if (enemyTactic == ENEMYTACTIC.STANDSTILL) Ready ();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
//actions for CLOSERANGE, MIDRANGE & FARRANGE distances
|
|
|
|
if (enemyTactic == ENEMYTACTIC.ENGAGE) WalkTo (attackRangeDistance, 0f);
|
|
|
|
if (enemyTactic == ENEMYTACTIC.KEEPCLOSEDISTANCE) WalkTo(closeRangeDistance, RangeMarging);
|
|
|
|
if (enemyTactic == ENEMYTACTIC.KEEPMEDIUMDISTANCE) WalkTo(midRangeDistance, RangeMarging);
|
|
|
|
if (enemyTactic == ENEMYTACTIC.KEEPFARDISTANCE) WalkTo(farRangeDistance, RangeMarging);
|
|
|
|
if (enemyTactic == ENEMYTACTIC.STANDSTILL) Ready();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//update the current range
|
|
|
|
private RANGE GetDistanceToTarget(){
|
|
|
|
if (target != null) {
|
|
|
|
|
|
|
|
//get distance from the target
|
|
|
|
distanceToTarget = target.transform.position - transform.position;
|
|
|
|
distance = Vector3.Distance (target.transform.position, transform.position);
|
|
|
|
|
|
|
|
float distX = Mathf.Abs(distanceToTarget.x);
|
|
|
|
float distZ = Mathf.Abs(distanceToTarget.z);
|
|
|
|
|
|
|
|
//AttackRange
|
|
|
|
if(distX <= attackRangeDistance){
|
|
|
|
if(distZ < (hitZRange/2f))
|
|
|
|
return RANGE.ATTACKRANGE;
|
|
|
|
else
|
|
|
|
return RANGE.CLOSERANGE;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Close Range
|
|
|
|
if (distX > attackRangeDistance && distX < midRangeDistance) return RANGE.CLOSERANGE;
|
|
|
|
|
|
|
|
//Mid range
|
|
|
|
if(distX > closeRangeDistance && distance < farRangeDistance) return RANGE.MIDRANGE;
|
|
|
|
|
|
|
|
//Far range
|
|
|
|
if(distX > farRangeDistance) return RANGE.FARRANGE;
|
|
|
|
|
|
|
|
}
|
|
|
|
return RANGE.FARRANGE;
|
|
|
|
}
|
|
|
|
|
|
|
|
//set an enemy tactic
|
|
|
|
public void SetEnemyTactic(ENEMYTACTIC tactic){
|
|
|
|
|
|
|
|
if (enemyTactic != ENEMYTACTIC.CHARGE)
|
|
|
|
enemyTactic = tactic;
|
|
|
|
}
|
|
|
|
|
|
|
|
//spread enemies out in z distance
|
|
|
|
void SetZSpread(){
|
|
|
|
ZSpread = (ZSpread - (float)(EnemyManager.enemyList.Count - 1) / 2f) * (capsule.radius*2) * zSpreadMultiplier;
|
|
|
|
if (ZSpread > attackRangeDistance) ZSpread = attackRangeDistance - 0.1f;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Unit has died
|
|
|
|
void Death(){
|
|
|
|
StopAllCoroutines();
|
|
|
|
CancelInvoke();
|
|
|
|
|
|
|
|
enableAI = false;
|
|
|
|
isDead = true;
|
|
|
|
animator.SetAnimatorBool("isDead", true);
|
|
|
|
Move(Vector3.zero, 0);
|
|
|
|
EnemyManager.RemoveEnemyFromList(gameObject);
|
|
|
|
gameObject.layer = LayerMask.NameToLayer ("Default");
|
|
|
|
|
|
|
|
//ground death
|
|
|
|
if(enemyState == UNITSTATE.KNOCKDOWNGROUNDED) {
|
|
|
|
StartCoroutine(GroundHit());
|
|
|
|
} else {
|
|
|
|
|
|
|
|
//normal death
|
|
|
|
animator.SetAnimatorTrigger("Death");
|
|
|
|
}
|
|
|
|
|
|
|
|
GlobalAudioPlayer.PlaySFXAtPosition("EnemyDeath", transform.position);
|
|
|
|
StartCoroutine (animator.FlickerCoroutine(2));
|
|
|
|
enemyState = UNITSTATE.DEATH;
|
|
|
|
DestroyUnit();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public enum ENEMYTACTIC {
|
|
|
|
ENGAGE = 0,
|
|
|
|
KEEPCLOSEDISTANCE = 1,
|
|
|
|
KEEPMEDIUMDISTANCE = 2,
|
|
|
|
KEEPFARDISTANCE = 3,
|
|
|
|
STANDSTILL = 4,
|
|
|
|
CHARGE = 5
|
|
|
|
}
|
|
|
|
|
|
|
|
public enum RANGE {
|
|
|
|
ATTACKRANGE,
|
|
|
|
CLOSERANGE,
|
|
|
|
MIDRANGE,
|
|
|
|
FARRANGE,
|
|
|
|
}
|