Enemy movement

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:

  1. Create a new script named 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.

  2. Add the following code to the top of the 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.

  3. Next, we need to add in some additional variables we will use. Take a look at the following code:
       // 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;
  4. Now, we need to add in a coroutine function for each of the possible states, starting with the 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.

  5. Continue with the 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.

  6. Finish off by adding in the 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.

  7. Now, we need to add in those functions we talked about earlier. First, let's add in 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);
      }

    Note

    The Vector3 class also has a Distance function you can use. Vector3.Distance(transform.position, target.transform.position); will do the same thing as our GetDistance function does, but knowing the math behind things can be extremely helpful!

  8. Now, we need to add in the ability to go to another state, as follows:
       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!

    Note

    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.

  9. Then, we need to start this whole state machine up with the following code:
    void Start () 
    {
      GoToNextState();
    }
  10. Finally, we need to save our file and exit back to the Unity Editor. Attach the Enemy Behaviour script to our Ghost_mesh object and set the Target property to our FPSController object. Take a look at the following screenshot:
    Enemy movement
  11. Save the scene and play the game. Take a look at the following screenshot:
    Enemy movement

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!

Advanced FSMs

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/.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset