Video Game Design 8 | Game AI Case Study
Video Game Design 8 | Game AI Case Study

- Game AI
(1) Minion with NavMeshAgent
- Utilize the main camera looking down on your scene, uncheck the Third Person Camera (Script)

- Duplicate the
Minion_noRootMotion
and rename itAI_Minion
- Remove all the components related to human character control including
CharacterInputController
,MinionBasicControlScript
,MinionScript
- Add a
NavMeshAgent
component to theAI_Minion
- Create a new script called
MinionAI
in folder Asset > GameAI. Add this script component to theAI_Minion
GameObject. - Select Window > AI > Navigation to open up the Navigation tag

- Under the Navigation tag, select Back and make sure all the settings are proper. Then click on the Back button. We are expected to see that the playground turns blue. If it is not blue, check the box
Show NavMesh
, and make sure you haveGizmos
mode on.

- Require (by
RequireComponent
) and reference (byGetComponent
) the componentUnityEngine.AI.NavMeshAgent
calledagent
in scriptMinionAI
- Grab a reference (by
GetComponent
) to the Minion’s Animator calledanim
as well in scriptMinionAI
- Add a
public
array ofGameObject
s toMinionAI
namedwaypoints
- Add an
int currWaypoint
property inMinionAI
- Make sure that
currWaypoint
is first initialized with a value that will causesetNextWaypoint()
to go to the 0th item. (This means that-1
might work in this case) - Create a private method
setNextWaypoint()
. This method should analyzecurrWaypoint
andwaypoints
to incrementcurrWaypoint
and loop back to zero ifcurrWaypoint
is too big for the array. - Additionally, the
navMeshAgent
object should be updated of the new waypoint withSetDestination()
. You will set to,
waypoints[currWaypoint].transform.position
- Add error handling in the script if the array is zero length. You may have to call
Debug.LogError
. - In
Start()
function, let’s callsetNextWaypoint()
- In
Update()
also callsetNextWaypoint()
, but only if thenavMeshAgent
has reached the target waypoint. You can figure this out withnavMeshAgent.remainingDistance
, but you will also want to check thatnavMeshAgent.pathPending
is not true so that being near a waypoint doesn’t cause rapid iteration through the waypoints before the new path can be found. - More complicated
NavMeshAgent
implementations may necessitateNavMeshAgent.hasPath
andNavMeshAgent.pathStatus
(complete
,partial
, orinvalid
). We don’t have to check these conditions in this case study. - In the editor, create 5 waypoints out of
GameObject
s of 3D > Cube calledWaypoint1
,Waypoint2
,Waypoint3
,Waypoint4
, andWaypoint5
, respectively. Uncheck the box ofBoxCollider
to cancel the colliders of them. - Create a material called
TransRed
to make these waypoints seem transparent red but visible to us. We have to set the alpha value of Albedo and change the Rending Mode toFade
rather thanOpaque
. Apply this material to the waypoints. - Go back to select the
AI_Minion
GameObject. Manually increase the size of the array as appropriate to5
. - Populate our
AI_Minion
’s waypoints (in Inspector) with theseWaypoint…
GameObjects - Before running the game, we should confirm that our
AI_Minion
is not on top of any other characters. We should also confirm that all the waypoints are navigable and reachable from each other and the Minion. - Now run the game. We are expected to see the minion translate around from waypoint to waypoint. He will likely be sunk into the ground and will not be making any step animations.
(2) Enhance Bouncy Step Animation
- Modify your
AI_Minion
’sNavMeshAgent
component Base Offset in Inspector to0.75
. This will fix the issue with the model sinking into the ground. - Modify
MinionAI
script inUpdate()
to tell the animator the forward animation parameter according to thenavMeshAgent
component’s speed. It should look something like,
anim.SetFloat(“vely”, agent.velocity.magnitude / agent.speed)
- Now test the game. You should see the minion hopping around and not sunk into the ground.
(3) Moving Waypoint with Predictions
- Next up, let’s add a moving waypoint that the Minion uses prediction to track down. Duplicate a
Waypoint
object and rename it asMovingWaypoint
. - Use Mecanim with an approach similar to the elevator from M2 to create an animation of it. Make sure the waypoint is always just above the navmesh (blue area) and not going over an obstacle or through a wall.
- Then, save and use the following component script
VelocityReporter
in Asset > Scripts > GameAI. This is a script for getting a velocity from the present object. With this script, we can get the velocity information of theMovingWaypoint
easily. Add this script as a component toMovingWaypoint
.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class VelocityReporter : MonoBehaviour
{
private Vector3 prevPos;
public Vector3 rawVelocity
{
get;
private set;
}
public Vector3 velocity
{
get;
private set;
}
public float smoothingTimeFactor = 0.5f;
private Vector3 smoothingParamVel;
// Use this for initialization
void Start () {
prevPos = this.transform.position;
}
// Update is called once per frame
void Update()
{
rawVelocity = (this.transform.position - prevPos) / Time.deltaTime;
velocity = Vector3.SmoothDamp(velocity, rawVelocity, ref smoothingParamVel, smoothingTimeFactor);
prevPos = this.transform.position;
}
}
- Then create a new script called
TargetPrediction
, and this script should implement the logic of prediction. To get the velocity of the target, we can useGetComponent()
to get theVelocityReporter
from a reference to theMovingWaypoint
.
MovingWaypoint.GetComponent<VelocityReporter>();
- For prediction, implement a simple distance function to determine lookahead time as we have discussed in class. Then use the
MovingWaypoint
’s velocity and position to extrapolate based on this look-ahead time. To calculate the length of aVector3
object, we have to callmagnitude
. Theagent.speed
is the max speed we have for the agent whereagent
is thenavMeshAgent
object we have used in scriptMinionAI
. We should callSetDestination
to make thefutureTarget
our target. Here is the formula with pseudo interface names, and you may need to implement this on your own.
Dist = (target.pos - agent.pos).Length();
lookAheadT = Dist/agent.maxSpeed;
futureTarget = target.pos + lookAheadT * target.velocity;
- Add this script as a component to the object
AI_Minion
. Test it out by chasing only theMovingWaypoint
. If everything is okay, we are able to see the future target position. We should finally combine this logic toMinionAI
script, but it will not be an easy task if we don’t have a state machine.
(4) Convert MinionAI
script into a state machine
- Don’t confuse the discussion of AI state machines with the Mecanim Animator state machine. State machines can be used for lots of things. There is one for the animation system, and you will be implementing a different one for your AI. These state machines may likely interact in some way but are independent implementations. This is to say, the AI state machine is only about the script and nothing about the GUI.
- The AI state machine we will implement should have at least two states.
State0
state can be for the 5 stationary waypoints.State1
state can be for the one moving waypoint. The states should loop forever (e.g.state0
->state1
->state0
->state1
-> …). - The recommended state machine for this AI is based on the procedural approach called a procedural state machine. We will first create a
enum
object calledAIState
. - You can refer to the following script and see how to implement this state machine.
public enum AIState {
Patrol,
GoToAmmoDepot,
AttackPlayerWithProjectile,
InterceptPlayer , AttackPlayerWithMelee,
ChasePlayer
//TODO more? states...
};
public AIState aiState;
// Use this for initialization
void Start ()
{
aiState = AIState.Patrol;
}
void Update () {
// state transitions that can happen from any state might happen here
// such as:
// if(inView(enemy) && (ammoCount == 0) &&
// closeEnoughForMeleeAttack(enemy))
// aiState = AIState.AttackPlayerWithMelee;
// Assess the current state, possibly deciding to change to a different state
switch (aiState)
{
case AIState.Patrol:
//if(ammoCount == 0)
// aiState = AIState.GoToAmmoDepot; //else
// SteerTo(nextWaypoint);
break;
case AIState.GoToAmmoDepot:
//SteerToClosestAmmoDepot()
break;
//... TODO handle other states
default:
break;
}
}
2. Submission
(1) Update the HUD
Update the HUD in both scenes to show your name. Edit the Name game object under the “FramerateCounter — NAME TEXT IS HERE”.
(2) Modification Check List
- [30 pt] AI-controlled Minion visits 5 stationary visible waypoints
- [20 pt] Minion is animated with steps and not sunk into the ground
- [30 pt] AI-controlled Minion heads off moving visible waypoint
- [20 pt] AI is controlled by a procedural state machine of at least 2 states
(3) Game Building
- Edit > Project Settings > Player > Product Name:
Last_FirstInit_m4
- File > Save to save the file
- Select File > Build Settings
- Choose MacOS, Intel 64-bit + Apple Silicon
- Create a new folder named
Last_FirstInit_m4
(replace with your name Info). This is the root folder. - Create a path
<root>/Build/OSX
. Name the buildLast_FirstInit_m4
and save it here. - Select File > Build Settings again
- Choose Windows, x86_64
- Build it in the path
<root>/Build
and name itWindows
.
(4) Create UNTESTED
file
Because one of the builds (.app
or .exe
) has not been tested, we should add a file UNTESTED
to the folder, if we don’t test it.
$ touch UNTESTED
(5) Clean Up the Root
We have to clean the root before we submit.
$ rm -rf .git
$ rm -rf Library
$ rm -rf temp
$ rm -rf Obj
$ rm -rf Logs
$ rm *.sln
$ rm *.csproj