As spooky as the character is, right now, it is just a static mesh. It will not come to us to damage us, and we cannot damage it. Let's fix that next using the state machines you've just learned about! Perform the following steps:
EnemyBehaviour
.We want our enemy to follow the player if they get too close to them; however, they will stay where they are if the player gets far enough away. Finally, if, for some reason, we defeat the enemy, they should no longer run this behavior, and we should kill them. The first step to creating a state machine is to extract the states that the object can be in. In this case, we have three states: Idle
, Following
, and Death
. Just as we discussed in Chapter 2, Creating GUIs, using an enumeration is the best tool for the job here as well.
EnemyBehaviour
class:public enum State { Idle, Follow, Die, } // The current state the player is in public State state;
Now, depending on the value that the state is currently in, we can do different things. We could use something like the following code:
void Update() { if(state == State.Idle) { //And so on } else if(state == State.Follow) { //And so on } //etc... }
But, as I'm sure you can already see, this is incredibly messy. Also, what if we want to do something when we first enter the state? What about when you leave? To fix this issue, let's use a tool we covered earlier, the coroutine function, which will contain the contents for each of our states.
// The object the enemy wants to follow public Transform target; // How fast should the enemy move? public float moveSpeed = 3.0f; public float rotateSpeed = 3.0f; // How close should the enemy be before they follow? public float followRange = 10.0f; // How far should the target be before the enemy gives up // following? // Note: Needs to be >= followRange public float idleRange = 10.0f;
Idle
state. Take a look at the following code:IEnumerator IdleState () { //OnEnter Debug.Log("Idle: Enter"); while (state == State.Idle) { //OnUpdate if(GetDistance() < followRange) { state = State.Follow; } yield return 0; } //OnEnd Debug.Log("Idle: Exit"); GoToNextState(); }
This state will continuously check whether the players are close enough to the target to start following it until its state is no longer State.Idle
. You'll note the two functions we'll need to create later, GetDistance
and GoToNextState
, which we will implement after we finish the other states.
Following
state, as follows:IEnumerator FollowState () { Debug.Log("Follow: Enter"); while (state == State.Follow) { transform.position = Vector3.MoveTowards(transform.position, target.position, Time.deltaTime * moveSpeed); RotateTowardsTarget(); if(GetDistance() > idleRange) { state = State.Idle; } yield return 0; } Debug.Log("Follow: Exit"); GoToNextState(); }
This state will move the enemy closer to the player while continuously checking if the target is far enough to go back to Idle
. In addition to the other functions we talked about earlier, we also have a new function named RotateTowardsTarget
, which we will also need to add in.
Die
state, as follows:IEnumerator DieState () { Debug.Log("Die: Enter"); Destroy (this.gameObject); yield return 0; }
This state just destroys the object attached to it. Right now, there is no way to get here aside from us setting it in the Inspector tab, but it will be useful when we add in damage.
GetDistance
and RotateTowardsTarget
, which are self-explanatory in terms of what they do. Take a look at the following code:public float GetDistance() { return (transform.position - target.transform.position).magnitude; } private void RotateTowardsTarget() { transform.rotation = Quaternion.Slerp(transform.rotation,Quaternion.LookRotation(target.position - transform.position), rotateSpeed * Time.deltaTime); }
void GoToNextState () { // Find out the name of the function we want to call string methodName = state.ToString() + "State"; // Searches this class for a function with the name of // state + State (for example: idleState) System.Reflection.MethodInfo info = GetType().GetMethod(methodName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); StartCoroutine((IEnumerator)info.Invoke(this, null)); }
The preceding code is fairly advanced stuff, so it's okay if you do not fully understand it at a glance. For the preceding code, I could have written something like the Update
example I wrote previously, calling the appropriate coroutine based on the state to go to.
Instead, this code will call the appropriate function with the name of the state plus the word State
. The nice thing about this is that you can now write as many additional states as you want without having to modify this function. All you have to do is add an item to the State
enumerator and then write a function for it with a proper name!
For information on the GetMethod
function and the different kinds of BindingFlags
, you can visit http://msdn.microsoft.com/en-us/library/05eey4y9(v=vs.110).aspx.
void Start () { GoToNextState(); }
As you can see now, you can follow the enemy's current state in the Inspector tab, and they will turn and move towards you whenever you get too close!
This is a good introduction to state machines and what you can use them for, but there is a lot of additional information out there on their uses, such as an abstract version of a state machine at http://playmedusa.com/blog/a-finite-state-machine-in-c-for-unity3d/.
The Asset Store also features Playmaker, which is a fairly popular commercial add-on that creates state machines with a visual editor, making it very easy to add in states. For more information on Playmaker, check out http://www.hutonggames.com/.