I’d like to start adding enemies in my project however I don’t really know where to start.
Almost all the tutorials I’ve seen and read use States, coroutines and Delegates to perform the AI however it is all extremely confusing and very hard to apply to my situation because;
-I don’t have the character models and animations which they have implemented already through code.
-My project game is 2D constrained to the X and Z axis.
-There seems to be a massive leap into states/delegates/coroutines - or at least I havn’t found a decent starting point.
So with all those of you who know about this stuff, I’d appreciate it greatly if someone could point me in the right direction, where to start, whether I need them, what I need them for etc.
Ideally if I need to use them I would like to start simple, and apply Animation among other things when I actually have the assets.
A lot of the stuff I’ve seen being done for AI seems like it could be done with lots of normal functions. For a 2D game with basic AI I can’t see how states are necessary.
States and a State Machine decouple AI agent behavior while also making it re-usable. It also provides excellent separation of duties which makes debugging your AI much easier. For example - if your agent isn’t moving around your world correctly then you know you need to look in only one place - your move state. What’s more, if you decouple steering from move state then you can use the move state for any AI agent that moves in your entire game. It also gives you a finite start and end point to a state which means attaching handlers to those events is much simpler.
A state machine is also very extensible. If you need to add more functionality to your AI it’s just a matter of creating new states and telling your agent when to use them. You don’t have to touch any existing state code.
Thanks KelsoMRK - I realise that I will most likely need to use them in my game.
However I still don’t know where to start, I’ve read about some FSM frameworks, which I believe is basically the functionality behind switching states and such. However lets say I have a framework. Can I then create all these types of states :
I execute my state logic as an IEnumerator that gets wrapped in an agent’s coroutine. This is mainly because Unity doesn’t let you execute Coroutines in classes that don’t extend MonoBehaviour. So - a very basic example to get started:
Every state implements a common interface with these methods:
public class StateMachine
{
private Agent owner;
private IState currentState;
public StateMachine(Agent owner)
{
this.owner = owner;
}
public void ChangeState(IState newState)
{
if (this.currentState != null)
{
this.currentState.Exit();
}
this.currentState = newState;
this.currentState.Enter();
}
}
The Agent that owns an instance of a state machine. It has a coroutine that wraps the current state’s execution
public class Agent : MonoBehaviour
{
public StateMachine FSM = new StateMachine(this);
public IEnumerator StateWrapper(IEnumerator state)
{
while (state.MoveNext())
yield return state.Current;
}
}
A simple state that implements our interface
public class SomeState : IState
{
protected Agent owner;
private int counter;
public SomeState(Agent owner)
{
this.owner = owner;
}
public void Enter()
{
Debug.Log("Entering Test State");
owner.StateCoroutine("StateWrapper", this.Execute());
}
public IEnumerator Execute()
{
while (counter < 10)
{
Debug.Log("Executing State");
counter++;
yield return null;
}
this.Exit();
}
public void Exit()
{
Debug.Log("Exiting Test State");
}
}
We can change state in the agent by doing
this.FSM.ChangeState(new SomeState(this));
If we call the above line before SomeState had finished counting to 10 you would still see the “Exiting State” statement in the console because Enter and Exit are always called when changing states. I chose to also call it when Execute had finished but there’s nothing saying you have to. You could switch to a different state and Exit would still run.
public enum Behavior
{
idle, search, confused, moveToPlayer, wander, attack, useAbility
}
public class EnemyAI : MonoBehaviour
{
public Behavior currentState = Behavior.idle;
private Behavior nextState = Behavior.idle;
private Transform playerTarget;
public int playerLayer = 1<<8; //Just saying the player layer is on 8
public float viewDistance = 5; //Distance they can see in front of them.
public float viewAngle = 45; //Angle they can see a player infront of them
public float awareDistance = 1; //Aware of player if they are in this distnace
public float attackDistance = 2; //Distance at which the player can attack
private bool isStateFinished = true;
//Maybe the player stunned the enemy..
private bool interuptState = false;
public bool InteruptState
{
get{return interuptState;}
set{interuptState = value;}
}
public void Update()
{
if(currentState != nextState (isStateFinished || interuptState))
{
currentState = nextState;
isStateFinished = false;
switch(currentState)
{
case Behavior.idle:
Idle();
break;
case Behavior.search:
Search();
break;
case Behavior.confused:
Confused();
break;
case Behavior.MoveToPlayer:
MoveToPlayer();
break;
case Behavior.attack:
Attack();
break;
case Behavior.useAbility:
UseAbility();
break;
}
}
}
#region Behavior methods
private void Idle()
{
interuptState = true;
List<GameObject> viewedPlayers = CheckIfPlayerInView();
if(viewedPlayers.Count > 0)
{
playerTarget = viewedPlayers[0]; //Grab the first.. Just for an example.. Maybe it would check distance and get the closest.
currentState = Behavior.Move();
}
isStateFinished = false;
}
private void Search()
{
//Randomly move around and search?
}
private void Confused()
{
//Could be enabled by player attack/ability
}
private void MoveToPlayer()
{
//Move to the player's location
//Check if the player is in range
}
private void Wander()
{
//Move to a random legal location
}
private void Attack()
{
// animate attack..
// apply damage to the player
}
private void UseAbility()
{
//Enable some particle effect and apply dmg to the player.
}
#endregion
#region Behavior Checking methods
private List<GameObject> CheckIfPlayerInView()
{
List<GameObject> resultList = new List<GameObject>();
hit = Physics.OverlapSphere(this.transform.position, viewDistance, playerLayer);
foreach (Collider c in hit)
{
if (Vector3.Angle(this.transform.forward, c.transform.position - from.position) <= viewAngle)
{
//Check if alive.. or whatever else before adding.
resultList.Add(c.gameObject);
}
}
return resultList;
}
#endregion
}
Thanks for this example Kelso its helping my understanding of all this. so if you had a state called Dead you would call it at some point like;
if(PlayerHealth <= 0)
{
// all the stuff that happens when an entity dies in the game will happen in the State Dead instead of normally being here
this.FSM.ChangeState(new Dead(this));
}
public IEnumerator Dead()
{
// something died, go through all the timed things and events that are supposed to happen when something dies
}
is something like that correct ?
and the IEnumerator Dead() … if I kept this separate from all my enemy scripts that means I could use the same script that handles death for any Enemy in the game right?
@Eiznek- Simpler - but not a state machine and if your logic got complex that code would become a maintenance disaster. You’re also polling state every frame regardless of what it is. @Ovredon- Exactly.
I’m kind of getting it, but to see it working in action with that example would be great Eiznek, atm its got 4 errors, I think 2 of them are typos but I can’t figure out the other 2. I’d like to get this in unity working to help me understand it better, I just don’t get how any of those functions or where they are called atm though :\
Unless that is the framework and the states are called from a different class?
KelsoMRK, I’m trying to use your example, however I’m getting an error that says ‘this’ is not available in the current context
public StateMachine FSM = new StateMachine(this);
using UnityEngine;
using System.Collections;
public class Agent : MonoBehaviour
{
public StateMachine FSM = new StateMachine(this);
public IEnumerator StateWrapper(IEnumerator state)
{
while (state.MoveNext())
yield return state.Current;
}
}
I appreciate the help you’ve gave me Kelso cheers.
I just want to say that I read up tons of stuff before coming asking questions on the forums, I must have read about 6 articles on States/Delegates/AI and how they all tie in, and watching about 1 and a half hours of video tutorials, but they havn’t seemed to give me the stepping stone I need.
Where should I go from here?
If I have a game object, and attach those scripts to it, do I need another script which is specific for that type of enemy (for example) ?
Ahh dam, with the change you suggested I get the error
" Type ‘Agent’ does not contain a definition for ‘StateMachine’ and no extension method ‘StateMachine’ of type ‘Agent’ could be found (are you missing a using directive or assembly reference?)"
Okay… So I need to attach the agent onto a game object, or ‘enemy’.
I have a script attached to the enemy which would communicate with the Agent and call a function to enter the enemy into the correct state he needs entering, OnCollisionEnter or something would be a good example of a way of saying put enemy in Attack state if something is colliding with it, put it in chase state if something is near it and put it in sleep / wander if nothing is near it and nothing is colliding.
those 3-4 example states above would be attached to the enemy, and would again switch between them by using this.FSM.ChangeState(new SomeState(this));
Okay so.
I think I am starting to understand now however there’s one last thing II think.
If I want to have Attack/Move/Wander/Sleep those kind of states, surely the variable of Speed or Damage the enemy inflicts will be nested inside the State, if that is the case since the states don’t inherit from Monodevelop is it easy to throw over variables for that enemy to change values such as speed/ damage.
You can choose to either hold variables in the state or on the agent (another reason for the state to know about its owner). For statistic type things like speed and damage I would hold them on the agent. Something like a calculated path for pathfinding I would hold in a move state because there is no point in the agent carrying it around when it only has a lifecycle associated with a particular state and is of no use otherwise.
That means that the Agent is going to have to hold a “lot” of variables which aren’t always going to be used. If that Agent is to be used on all enemies there will be redundant variables for some enemies as there might be a bool or statistic that only some need as they pertain to the unique functionality behind the enemy.
Doing it this way does however allow me to include things like the TakeDamge function on the Agent.
However it means that the variables have to be public and assigned through the inspector so they pertain only to that particular enemy and not globally for anything using that class. Unless I want to find out what enemy it is (via name or tag etc), and then have a SetStats() function if its this enemy set these stats, etc etc…
Does this seem like an acceptable way of doing it, do you think Kelso, I’m asking because I don’t want to do it like this and all along it wont work and if only I’d have asked, heh
I wish there was like a +reputation thing on this website Kelso, you deserve it
Oh and also, I don’t know if this is the proper way of doing it, just let me know please but if I’m in the state class and I need to get a function or variable and access its properties of that the agent is attached to I would just do
owner.gameObject
and have a GameObject variable and find the type/name of the GameObject that Agent is attached to if I needed to destroy it etc ?
Sorry for not responding sooner - I’m doing the Ludum Dare 7DRTS this week so my ability to post in the evening is pretty much nil.
There are a lot of options when it comes to how you hold your data. My state’s ‘owner’ is typically a manager class that derives from MonoBehaviour and includes “actions”- so things like TakeDamage, Heal, Move, Attack, Pursue, etc etc. A lot of these actions kick off state changes in and of themselves. Having an owner in the state also exposes that GameObject to the state so you’re free to do the things you talked about like checking tags and such.
One option if you need to maintain vital stats is to make a container class that is serialized so you can still assign via the Inspector but you don’t glob up your manager class with a bunch of members. You also don’t have to make stuff public. If you want good encapsulation you can make the members private and use [SerializeField()] to expose them to the Inspector. A good example is health, which you wouldn’t want to manipulate directly.
public class Agent : MonoBehaviour
{
[SerializeField()]
private int maxHealth; //assign via inspector but can't be changed outside this class
private int currentHealth; // hidden from inspector because we assign in Awake
void Awake()
{
this.currentHealth = this.maxHealth;
}
public void TakeDamage(int amount)
{
this.currentHealth -= amount;
if (this.currentHealth <= 0)
{
this.FSM.ChangeState(new UnitDieState(this));
}
}
public void Heal(int amount)
{
this.currentHealth += amount;
if (this.currentHealth > this.maxHealth)
this.currenHealth = this.maxHealth;
}
}
If you want to go the container class route
[System.Serializable()]
public class AgentStats
{
public int MaxHealth;
}
public class Agent : MonoBehaviour
{
public AgentStats Stats; // this gets exposed in Inspector as a collapsible prop drawer
private int currentHealth;
void Awake()
{
this.currentHealth = this.Stats.MaxHealth;
}
// etc....
}
Great thanks, that’s exactly what I needed, I am much clearer today than I was 2 days ago on all of this. I am just sorting ouit take damage functions and stuff and organising variables as we speak.
Thanks again kelso.
Though I’m probably going with the first option you showed. I’ll see if I like the serialize thing. its probably best to do it that way anyway