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
usingUnityEngine;usingUnityEngine.AI;// Need this for NavMeshAgentpublicclassMovement:MonoBehaviour{[SerializeField]Transformtarget;// Get transform of TargetNavMeshAgentnavMeshAgent;// NavMeshAgent ComponentvoidAwake()// We can use awake to grab references before Start(){// Get reference to NavMeshAgentnavMeshAgent=GetComponent<NavMeshAgent>();}voidUpdate(){// Every frame update the destination to current target positionnavMeshAgent.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.
usingUnityEngine;usingUnityEngine.AI;publicclassMovement:MonoBehaviour{[SerializeField]Transformtarget;NavMeshAgentnavMeshAgent;Animatoranimator;// Declare animator as private propertyvoidAwake(){navMeshAgent=GetComponent<NavMeshAgent>();animator=GetComponent<Animator>();// Get reference to animator}voidUpdate(){navMeshAgent.destination=target.position;UpdateAnimator();}voidUpdateAnimator(){// Get the velocity of the NavMeshAgentVector3velocity=navMeshAgent.velocity;// Convert the velocity from global to local.Vector3localVelocity=transform.InverseTransformDirection(velocity);// Speed is a scalar. We can get it from the z axis of the local velocity. floatforwardSpeed=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.
usingUnityEngine;publicclassControl:MonoBehaviour{[SerializeField]Transformtarget;Cameracamera;voidAwake(){// Get a reference to the Camera component.camera=GetComponent<Camera>();}voidUpdate(){// Get the point on the screen where the mouse is.Vector2mousePosition=Input.mousePosition;// Initialize a ray using the ScreenPointToRay method.RaycameraRay=camera.ScreenPointToRay(mousePosition);// Declare a RaycastHitRaycastHithit;// Start a raycast and pass in the RaycastHit to get data back.boolhitSomething=Physics.Raycast(cameraRay,outhit);// 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".
voidUpdate(){Vector2mousePosition=Input.mousePosition;RaycameraRay=camera.ScreenPointToRay(mousePosition);RaycastHithit;boolhitSomething=Physics.Raycast(cameraRay,outhit);if(!hitSomething)return;// If we didn't hit the ground, end the functionif(!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.
voidUpdate(){// If the left mouse button was NOT clicked on this frame, end the function.if(!Input.GetMouseButton(0))return;Vector2mousePosition=Input.mousePosition;RaycameraRay=camera.ScreenPointToRay(mousePosition);RaycastHithit;boolhitSomething=Physics.Raycast(cameraRay,outhit);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








