Unity Version: 2019.3
Unity Standard Assets to get similar assets to the ones in the book.
Uncheck everything when importing, then check the box for Standard Assets > Characters > ThirdPersonCharacter. This will take up less space in your project view.
You can find a model called Ethan.fbx at Standard Assets > Characters > Models > Ethan.fbx.
You can find the animation clips at Standard Assets > Characters > Animation.
You can find the materials at Standard Assets > Characters > Materials
- Floor and Obstacles
- Lighting
- Add a floor plane
- Add an obstacle cube
- Prefab and name both
- Give obstacle a color
- Create an Empty called Environment in Hierarchy
- Parent both to environment
At Edit > Grid and Snap
- Set Grid Size to 1
- Set Move to 1
- Set Scale to 1
- Set Rotate to 90
Use the move, scale, and rotate tools to create a maze with the obstacle instances.
- Add the Ethan model to the maze.
- Change the material on EthanBody to EthanGrey
- Scale the environment up to (2,2,2)
- Add NavMeshAgent to Ethan
- Select all environment objects and set
Navigation > Object > Navigation Staticto true. - Select all obstacles and set Navigation Area to "Not Walkable"
- Open Navigation window and Bake
- Tweak Agent Radius to define NavMesh area
- Add a Sphere called "Target"
- Give the Target a material that contrasts Obstacle
- Add a script to Ethan called Movement.cs
using UnityEngine;
using UnityEngine.AI; // Need this for NavMeshAgent
public class Movement : MonoBehaviour
{
[SerializeField] Transform target; // Get transform of Target
NavMeshAgent navMeshAgent; // NavMeshAgent Component
void Awake() // We can use awake to grab references before Start()
{
// Get reference to NavMeshAgent
navMeshAgent = GetComponent<NavMeshAgent>();
}
void Update()
{
// Every frame update the destination to current target position
navMeshAgent.destination = target.position;
}
}- Attach Target to Movement.cs on Ethan
- Press Play
- Turn off Apply Root Motion on Ethan's Animator
- Create a new Animator Controller named Movement and attach it to Ethan
- Add a new animator parameter called "Speed" of type
float - Create a new 1D Blend Tree called Move in the Animator. Make it dependent on the Speed parameter.
- Add three motions: HumanoidIdle, HumanoidWalk, HumanoidRun
- Uncheck Automate Thresholds in blend tree
- Use
Compute Thresholds > Velocity Zto calculate the thresholds based on animation forward velocity. - Set
Ethan > NavMeshAgent > Speedto the highest threshhold value. (5.66 w/ HumanoidRun) - In Movement.cs set the Speed parameter of the animator as the
zvelocity of the player.
using UnityEngine;
using UnityEngine.AI;
public class Movement : MonoBehaviour
{
[SerializeField] Transform target;
NavMeshAgent navMeshAgent;
Animator animator; // Declare animator as private property
void Awake()
{
navMeshAgent = GetComponent<NavMeshAgent>();
animator = GetComponent<Animator>(); // Get reference to animator
}
void Update()
{
navMeshAgent.destination = target.position;
UpdateAnimator();
}
void UpdateAnimator() {
// Get the velocity of the NavMeshAgent
Vector3 velocity = navMeshAgent.velocity;
// Convert the velocity from global to local.
Vector3 localVelocity = transform.InverseTransformDirection(velocity);
// Speed is a scalar. We can get it from the z axis of the local velocity.
float forwardSpeed = localVelocity.z;
// Now we can set the Speed parameter of the Animator.
animator.SetFloat("Speed", forwardSpeed);
}
}Additionally, Ethan will always follow the Target even if it moves.
- Create a new script called "Control.cs" and attach it to the main camera.
- Write code that raycasts from the mouse position ont he screen and sets the position of the target to the point where the ray hit.
using UnityEngine;
public class Control : MonoBehaviour
{
[SerializeField] Transform target;
Camera camera;
void Awake()
{
// Get a reference to the Camera component.
camera = GetComponent<Camera>();
}
void Update()
{
// Get the point on the screen where the mouse is.
Vector2 mousePosition = Input.mousePosition;
// Initialize a ray using the ScreenPointToRay method.
Ray cameraRay = camera.ScreenPointToRay(mousePosition);
// Declare a RaycastHit
RaycastHit hit;
// Start a raycast and pass in the RaycastHit to get data back.
bool hitSomething = Physics.Raycast(cameraRay, out hit);
// If the ray did not hit anything, end the function.
if (!hitSomething) return;
// Set the position of the target to the point where the ray hit.
target.position = hit.point;
}
}Now the target moves in response to the mouse position, but it moves to any point hit by the ray, including itself. This is problematic and not fun.
Trainer Note: Ask the students to explain why this is happening. It may uncover some gaps in knowledge that should be addressed.
- Add a new tag to the floor called "Ground".
- Edit the Control.cs script to check if the ray hit an object with the tag "Ground".
void Update()
{
Vector2 mousePosition = Input.mousePosition;
Ray cameraRay = camera.ScreenPointToRay(mousePosition);
RaycastHit hit;
bool hitSomething = Physics.Raycast(cameraRay, out hit);
if (!hitSomething) return;
// If we didn't hit the ground, end the function
if (!hit.collider.CompareTag("Ground")) return;
target.position = hit.point;
}- Edit the Control.cs script so that the Target only moves when the mouse is clicked.
void Update()
{
// If the left mouse button was NOT clicked on this frame, end the function.
if (!Input.GetMouseButton(0)) return;
Vector2 mousePosition = Input.mousePosition;
Ray cameraRay = camera.ScreenPointToRay(mousePosition);
RaycastHit hit;
bool hitSomething = Physics.Raycast(cameraRay, out hit);
if (!hitSomething) return;
if (!hit.collider.CompareTag("Ground")) return;
target.position = hit.point;
}- Change skybox to solid color
- Set camera projection to "orthographic"
- Adjust camera size and position
Grid Snapping Set Grid Size to 1 and Increment Settings > Move to 1








