using UnityEngine;
using System.Collections;
using System.Collections.Generic;
// Each character has two mono–behaviors,
// a generic character that responds to
// inputs and an input behavior. The character
// has no knowledge of what its input type is.
//
// Input behaviors for AI types are:
// AiGeneric, AiAlly, AiZombie.
//
// Input behaviors for player types are:
// InputLocal, InputRemote.
//
// AiGenric handles shared behaviors.
public enum Faction {
Human,
Zombie,
Count
};
public enum Behavior {
Hide,
Protect,// AiAlly controls this behavior
Idle,
MakeSpace,
Scout,
Chase,
Attack,
Eat, // AiZombie controls this behavior
TakeHit,
Die, // behaviors after this are only for non–alive characters
Sink,
ReadyToSpawn,
};
public class AiGeneric : MonoBehaviour {
static List<GameCharacter>[] ThreatList = null;
public static void AddThreat(GameCharacter character, Faction faction) {
List<GameCharacter> list = ThreatList[(int)faction];
if (!list.Contains(character))
list.Add(character);
}
public static void RemoveThreat(GameCharacter character, Faction faction) {
List<GameCharacter> list = ThreatList[(int)faction];
if (list.Contains(character))
list.Remove(character);
}
public static GameCharacter FindClosest(Vector3 position, Faction faction, float range) {
List<GameCharacter> list = ThreatList[(int)faction];
GameCharacter retval = null;
float closest = range;
foreach (GameCharacter character in list) {
float dist = Vector3.Distance(position, character.Position);
if (dist < closest) {
dist = closest;
retval = character;
if (dist < 2f) // close enough!
return retval;
}
}
return retval;
}
public static void PauseAll(bool paused) {
foreach(List<GameCharacter> list in ThreatList) {
foreach (GameCharacter character in list) {
character.Pause = paused;
}
}
}
// Behavior values
float behaviorTime;
protected float BehaviorTime { get {return Time.time – behaviorTime; }}
Behavior behavior = Behavior.ReadyToSpawn;
public Behavior Behavior { get { return behavior; } set { behavior = value; behaviorTime = Time.time; } }
// customizable values
protected float AttackRange = 4f;
protected float DetectionRange = 999f;
protected float ChaseDistance = 40f;
protected float UpdateRate = 0.1f;
protected GameCharacter character = null;
protected GameCharacter target = null;
protected Faction targeting = Faction.Count;
void Awake () {
if (ThreatList == null) {
ThreatList = new List<GameCharacter>[(int)Faction.Count];
for (int i = 0; i < (int)Faction.Count; i++) {
ThreatList[i] = new List<GameCharacter>();
}
}
}
// Use this for initialization
public virtual void Start () {
character = gameObject.GetComponent<GameHuman>();
targeting = Faction.Zombie;
if (character == null) {
character = gameObject.GetComponent<GameZombie>();
targeting = Faction.Human;
}
if (character == null) {
character = gameObject.GetComponent<GameCharacter>();
}
//StartCoroutine(UpdateBehavior());
}
void Update() {
if (GameManager.Instance.inGame
&& (BehaviorTime > UpdateRate || Behavior == Behavior.Sink))
{
DoBehavior();
}
}
// private IEnumerator UpdateBehavior() {
// for(;;) {
// if (this is AiAlly)
// Debug.Log(“Ally Behavior: “ + Behavior.ToString());
// if (GameManager.Instance.inGame && character.isAlive)
// DoBehavior();
//
// if (Behavior == Behavior.Sink)
// yield return new WaitForSeconds(0.01f);
// else
// yield return new WaitForSeconds(UpdateRate);
// }
// }
// Update is called once per frame
public virtual void DoBehavior () {
switch(behavior)
{
case Behavior.Hide: break;
case Behavior.Idle:
{
if (BehaviorTime > 1f)
{
Behavior = Behavior.Scout;
}
else
{
target = FindTarget(targeting, DetectionRange);
if (target != null)
Behavior = Behavior.Chase;
}
}break;
case Behavior.MakeSpace:
{
if (BehaviorTime > 2f)
{
Behavior = Behavior.Idle;
}
} break;
case Behavior.Scout:
{
Vector3 origin = character.Position;
if (Random.Range(0, 10) == 0)
{
target = FindTarget(targeting, DetectionRange);
}
origin.x += Random.Range(–10f, 10f);
origin.z += Random.Range(–10f, 10f);
character.Destination = origin;
Behavior = Behavior.Idle;
}break;
case Behavior.Chase:
{
if (target == null) {
Behavior = Behavior.Idle;
break;
}
float distance = Vector3.Distance(character.Position, target.Position);
if (distance < AttackRange)
{
Behavior = Behavior.Attack;
}
else if (distance > ChaseDistance)
{
Behavior = Behavior.Idle;
}
else
{
character.Destination = target.Position;
}
} break;
case Behavior.Attack:
{
if (target == null)
{
Behavior = Behavior.Idle;
break;
}
character.Destination = target.Position;
character.TryStrike();
Behavior = Behavior.Eat;
} break;
case Behavior.Eat:
{
} break;
case Behavior.Die:
{
if (BehaviorTime > 2f)
{
Behavior = Behavior.Sink;
}
} break;
case Behavior.Sink:
{
// sink into the ground
Vector3 origin = transform.position;
origin.y -= Time.deltaTime * 0.6f;
transform.position = origin;
if (BehaviorTime > 3f)
{
character.DeSpawn(new Vector3(0f, –100f, 0f));
}
} break;
case Behavior.ReadyToSpawn: break;
default: Behavior = Behavior.Idle; break;
}
}
protected GameCharacter FindTarget(Faction faction, float detectRange) {
float closest = detectRange;
GameCharacter value = null;
foreach (GameCharacter threat in ThreatList[(int)faction]) {
float dist = Vector3.Distance(threat.Position, character.Position);
if (dist < closest && threat != character) {
value = threat;
closest = dist;
}
}
return value;
}
public virtual void UpdateStats(CharacterStatistics statistics) {}
}
// AiZombie is a simple class. It just
// likes to eat brains.
public class AiZombie : AiGeneric {
Animator animator = null;
// Use this for initialization
public override void Start () {
base.Start();
animator = GetComponent<Animator>();
}
// Update is called once per frame
public override void DoBehavior () {
if (Behavior == Behavior.Eat) {
if (target == null)
{
Behavior = Behavior.Idle;
return;
}
character.Destination = target.Position;
if (BehaviorTime > 0.45f)
{
AnimatorStateInfo info = animator.GetCurrentAnimatorStateInfo(0);
if (info.IsTag(“Attack1“)
&& Vector3.Distance(character.Position, target.Position) < AttackRange)
{
target.TakeDamage(character.Power, character.Position);
Behavior = Behavior.Idle;
}
}
if (BehaviorTime > 0.5f)
Behavior = Behavior.Idle;
}
base.DoBehavior();
}
}
// AiAlly has special behaviors for killing
// zombies as well as protecting the player
public class AiAlly : AiGeneric {
float FollowDistance = 10f;
float SettleDistance = 8f;
float BreakoffDistance = 14f;
// Use this for initialization
public override void Start () {
UpdateRate = 0.02f;
Behavior = Behavior.Idle;
AttackRange = 4f;
DetectionRange = 15f;
ChaseDistance = 15f;
FollowDistance = 10f;
BreakoffDistance = 14f;
base.Start();
StartCoroutine(UpdateDialog());
}
private IEnumerator UpdateDialog() {
for(;;) {
MissionData mission = GameManager.Instance.CurrentMission;
if (mission != null && character != null) {
string dialog = Random.value < 0.25f ? mission.GameDialog1 : Random.value < 0.25 ? mission.GameDialog2 : null;
GameHuman human = character as GameHuman;
if (human != null) {
human.ShowSpeech(dialog);
}
}
yield return new WaitForSeconds(5f);
}
}
public override void DoBehavior () {
switch (Behavior)
{
case Behavior.Idle:
{
float followDist = character.isAttacking ? BreakoffDistance : FollowDistance;
GameCharacter ally = FindTarget(Faction.Human, 999f);
if (ally && Vector3.Distance(ally.Position, character.Position) > followDist)
{
Behavior = Behavior.Protect;
}
else
{
target = FindTarget(targeting, DetectionRange);
if (target != null)
Behavior = Behavior.Chase;
}
}
break;
case Behavior.Protect:
{
GameCharacter ally = FindTarget(Faction.Human, 999f);
if (ally && Vector3.Distance(ally.Position, character.Position) > SettleDistance)
{
character.Destination = ally.Position;
}
else
{
character.Destination = character.Position;
Behavior = Behavior.Idle;
}
}
break;
case Behavior.Attack:
{
if (target == null)
{
Behavior = Behavior.Idle;
break;
}
character.Destination = target.Position;
if (target.CompareTag(“Crawler“)) {
character.TryStrike(“DownStrike“);
} else {
character.TryStrike();
}
Behavior = Behavior.Idle;
} break;
default:
base.DoBehavior();
break;
}
}
public override void UpdateStats(CharacterStatistics statistics) {
AttackRange = 4f + (statistics.Reach / 5);
base.UpdateStats(statistics);
}
}