This repository was archived by the owner on Feb 9, 2026. It is now read-only.

Latest commit

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Click Navigation Project

Unity Version: 2019.3

Build

Setup

Import Ethan

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

Step 1: Initialize Environment

  • Floor and Obstacles
  • Lighting
  1. Add a floor plane
  2. Add an obstacle cube
  3. Prefab and name both
  4. Give obstacle a color
  5. Create an Empty called Environment in Hierarchy
  6. Parent both to environment

Step 2: Set Grid Snapping Settings

At Edit > Grid and Snap

  1. Set Grid Size to 1
  2. Set Move to 1
  3. Set Scale to 1
  4. Set Rotate to 90

GridAndSnap.png

Step 3: Create a Maze

Use the move, scale, and rotate tools to create a maze with the obstacle instances.

Maze.png

Step 4: Add Ethan

  1. Add the Ethan model to the maze.
  2. Change the material on EthanBody to EthanGrey
  3. Scale the environment up to (2,2,2)

EthanGrey

Step 5: Add NavMeshAgent

  1. Add NavMeshAgent to Ethan
  2. Select all environment objects and set Navigation > Object > Navigation Static to true.
  3. Select all obstacles and set Navigation Area to "Not Walkable"
  4. Open Navigation window and Bake
  5. Tweak Agent Radius to define NavMesh area

NavMesh

Step 6: Follow Target

  1. Add a Sphere called "Target"
  2. Give the Target a material that contrasts Obstacle
  3. 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;}}
  1. Attach Target to Movement.cs on Ethan
  2. Press Play

Target Move

Step 7: Animation

  1. Turn off Apply Root Motion on Ethan's Animator
  2. Create a new Animator Controller named Movement and attach it to Ethan
  3. Add a new animator parameter called "Speed" of type float
  4. Create a new 1D Blend Tree called Move in the Animator. Make it dependent on the Speed parameter.
  5. Add three motions: HumanoidIdle, HumanoidWalk, HumanoidRun
  6. Uncheck Automate Thresholds in blend tree
  7. Use Compute Thresholds > Velocity Z to calculate the thresholds based on animation forward velocity.
  8. Set Ethan > NavMeshAgent > Speed to the highest threshhold value. (5.66 w/ HumanoidRun)
  9. In Movement.cs set the Speed parameter of the animator as the z velocity 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);}}

Animated

Additionally, Ethan will always follow the Target even if it moves.

Here

Step 8: Click To Move

  1. Create a new script called "Control.cs" and attach it to the main camera.
  2. 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.

Raycast Bad

  1. Add a new tag to the floor called "Ground".
  2. 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;}
  1. 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;}

Step 9: Polish

  1. Change skybox to solid color
  2. Set camera projection to "orthographic"
  3. Adjust camera size and position

Finished

Resources

Grid Snapping Set Grid Size to 1 and Increment Settings > Move to 1

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
This repository was archived by the owner on Feb 9, 2026. It is now read-only.

Latest commit

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Click Navigation Project

Unity Version: 2019.3

Build

Setup

Import Ethan

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

Step 1: Initialize Environment

  • Floor and Obstacles
  • Lighting
  1. Add a floor plane
  2. Add an obstacle cube
  3. Prefab and name both
  4. Give obstacle a color
  5. Create an Empty called Environment in Hierarchy
  6. Parent both to environment

Step 2: Set Grid Snapping Settings

At Edit > Grid and Snap

  1. Set Grid Size to 1
  2. Set Move to 1
  3. Set Scale to 1
  4. Set Rotate to 90

GridAndSnap.png

Step 3: Create a Maze

Use the move, scale, and rotate tools to create a maze with the obstacle instances.

Maze.png

Step 4: Add Ethan

  1. Add the Ethan model to the maze.
  2. Change the material on EthanBody to EthanGrey
  3. Scale the environment up to (2,2,2)

EthanGrey

Step 5: Add NavMeshAgent

  1. Add NavMeshAgent to Ethan
  2. Select all environment objects and set Navigation > Object > Navigation Static to true.
  3. Select all obstacles and set Navigation Area to "Not Walkable"
  4. Open Navigation window and Bake
  5. Tweak Agent Radius to define NavMesh area

NavMesh

Step 6: Follow Target

  1. Add a Sphere called "Target"
  2. Give the Target a material that contrasts Obstacle
  3. 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;}}
  1. Attach Target to Movement.cs on Ethan
  2. Press Play

Target Move

Step 7: Animation

  1. Turn off Apply Root Motion on Ethan's Animator
  2. Create a new Animator Controller named Movement and attach it to Ethan
  3. Add a new animator parameter called "Speed" of type float
  4. Create a new 1D Blend Tree called Move in the Animator. Make it dependent on the Speed parameter.
  5. Add three motions: HumanoidIdle, HumanoidWalk, HumanoidRun
  6. Uncheck Automate Thresholds in blend tree
  7. Use Compute Thresholds > Velocity Z to calculate the thresholds based on animation forward velocity.
  8. Set Ethan > NavMeshAgent > Speed to the highest threshhold value. (5.66 w/ HumanoidRun)
  9. In Movement.cs set the Speed parameter of the animator as the z velocity 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);}}

Animated

Additionally, Ethan will always follow the Target even if it moves.

Here

Step 8: Click To Move

  1. Create a new script called "Control.cs" and attach it to the main camera.
  2. 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.

Raycast Bad

  1. Add a new tag to the floor called "Ground".
  2. 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;}
  1. 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;}

Step 9: Polish

  1. Change skybox to solid color
  2. Set camera projection to "orthographic"
  3. Adjust camera size and position

Finished

Resources

Grid Snapping Set Grid Size to 1 and Increment Settings > Move to 1

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Feb 9, 2026. It is now read-only.

Latest commit

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Click Navigation Project

Unity Version: 2019.3

Build

Setup

Import Ethan

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

Step 1: Initialize Environment

  • Floor and Obstacles
  • Lighting
  1. Add a floor plane
  2. Add an obstacle cube
  3. Prefab and name both
  4. Give obstacle a color
  5. Create an Empty called Environment in Hierarchy
  6. Parent both to environment

Step 2: Set Grid Snapping Settings

At Edit > Grid and Snap

  1. Set Grid Size to 1
  2. Set Move to 1
  3. Set Scale to 1
  4. Set Rotate to 90

GridAndSnap.png

Step 3: Create a Maze

Use the move, scale, and rotate tools to create a maze with the obstacle instances.

Maze.png

Step 4: Add Ethan

  1. Add the Ethan model to the maze.
  2. Change the material on EthanBody to EthanGrey
  3. Scale the environment up to (2,2,2)

EthanGrey

Step 5: Add NavMeshAgent

  1. Add NavMeshAgent to Ethan
  2. Select all environment objects and set Navigation > Object > Navigation Static to true.
  3. Select all obstacles and set Navigation Area to "Not Walkable"
  4. Open Navigation window and Bake
  5. Tweak Agent Radius to define NavMesh area

NavMesh

Step 6: Follow Target

  1. Add a Sphere called "Target"
  2. Give the Target a material that contrasts Obstacle
  3. 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;}}
  1. Attach Target to Movement.cs on Ethan
  2. Press Play

Target Move

Step 7: Animation

  1. Turn off Apply Root Motion on Ethan's Animator
  2. Create a new Animator Controller named Movement and attach it to Ethan
  3. Add a new animator parameter called "Speed" of type float
  4. Create a new 1D Blend Tree called Move in the Animator. Make it dependent on the Speed parameter.
  5. Add three motions: HumanoidIdle, HumanoidWalk, HumanoidRun
  6. Uncheck Automate Thresholds in blend tree
  7. Use Compute Thresholds > Velocity Z to calculate the thresholds based on animation forward velocity.
  8. Set Ethan > NavMeshAgent > Speed to the highest threshhold value. (5.66 w/ HumanoidRun)
  9. In Movement.cs set the Speed parameter of the animator as the z velocity 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);}}

Animated

Additionally, Ethan will always follow the Target even if it moves.

Here

Step 8: Click To Move

  1. Create a new script called "Control.cs" and attach it to the main camera.
  2. 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.

Raycast Bad

  1. Add a new tag to the floor called "Ground".
  2. 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;}
  1. 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;}

Step 9: Polish

  1. Change skybox to solid color
  2. Set camera projection to "orthographic"
  3. Adjust camera size and position

Finished

Resources

Grid Snapping Set Grid Size to 1 and Increment Settings > Move to 1

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Feb 9, 2026. It is now read-only.

Latest commit

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Click Navigation Project

Unity Version: 2019.3

Build

Setup

Import Ethan

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

Step 1: Initialize Environment

  • Floor and Obstacles
  • Lighting
  1. Add a floor plane
  2. Add an obstacle cube
  3. Prefab and name both
  4. Give obstacle a color
  5. Create an Empty called Environment in Hierarchy
  6. Parent both to environment

Step 2: Set Grid Snapping Settings

At Edit > Grid and Snap

  1. Set Grid Size to 1
  2. Set Move to 1
  3. Set Scale to 1
  4. Set Rotate to 90

GridAndSnap.png

Step 3: Create a Maze

Use the move, scale, and rotate tools to create a maze with the obstacle instances.

Maze.png

Step 4: Add Ethan

  1. Add the Ethan model to the maze.
  2. Change the material on EthanBody to EthanGrey
  3. Scale the environment up to (2,2,2)

EthanGrey

Step 5: Add NavMeshAgent

  1. Add NavMeshAgent to Ethan
  2. Select all environment objects and set Navigation > Object > Navigation Static to true.
  3. Select all obstacles and set Navigation Area to "Not Walkable"
  4. Open Navigation window and Bake
  5. Tweak Agent Radius to define NavMesh area

NavMesh

Step 6: Follow Target

  1. Add a Sphere called "Target"
  2. Give the Target a material that contrasts Obstacle
  3. 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;}}
  1. Attach Target to Movement.cs on Ethan
  2. Press Play

Target Move

Step 7: Animation

  1. Turn off Apply Root Motion on Ethan's Animator
  2. Create a new Animator Controller named Movement and attach it to Ethan
  3. Add a new animator parameter called "Speed" of type float
  4. Create a new 1D Blend Tree called Move in the Animator. Make it dependent on the Speed parameter.
  5. Add three motions: HumanoidIdle, HumanoidWalk, HumanoidRun
  6. Uncheck Automate Thresholds in blend tree
  7. Use Compute Thresholds > Velocity Z to calculate the thresholds based on animation forward velocity.
  8. Set Ethan > NavMeshAgent > Speed to the highest threshhold value. (5.66 w/ HumanoidRun)
  9. In Movement.cs set the Speed parameter of the animator as the z velocity 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);}}

Animated

Additionally, Ethan will always follow the Target even if it moves.

Here

Step 8: Click To Move

  1. Create a new script called "Control.cs" and attach it to the main camera.
  2. 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.

Raycast Bad

  1. Add a new tag to the floor called "Ground".
  2. 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;}
  1. 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;}

Step 9: Polish

  1. Change skybox to solid color
  2. Set camera projection to "orthographic"
  3. Adjust camera size and position

Finished

Resources

Grid Snapping Set Grid Size to 1 and Increment Settings > Move to 1

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
This repository was archived by the owner on Feb 9, 2026. It is now read-only.

Latest commit

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Click Navigation Project

Unity Version: 2019.3

Build

Setup

Import Ethan

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

Step 1: Initialize Environment

  • Floor and Obstacles
  • Lighting
  1. Add a floor plane
  2. Add an obstacle cube
  3. Prefab and name both
  4. Give obstacle a color
  5. Create an Empty called Environment in Hierarchy
  6. Parent both to environment

Step 2: Set Grid Snapping Settings

At Edit > Grid and Snap

  1. Set Grid Size to 1
  2. Set Move to 1
  3. Set Scale to 1
  4. Set Rotate to 90

GridAndSnap.png

Step 3: Create a Maze

Use the move, scale, and rotate tools to create a maze with the obstacle instances.

Maze.png

Step 4: Add Ethan

  1. Add the Ethan model to the maze.
  2. Change the material on EthanBody to EthanGrey
  3. Scale the environment up to (2,2,2)

EthanGrey

Step 5: Add NavMeshAgent

  1. Add NavMeshAgent to Ethan
  2. Select all environment objects and set Navigation > Object > Navigation Static to true.
  3. Select all obstacles and set Navigation Area to "Not Walkable"
  4. Open Navigation window and Bake
  5. Tweak Agent Radius to define NavMesh area

NavMesh

Step 6: Follow Target

  1. Add a Sphere called "Target"
  2. Give the Target a material that contrasts Obstacle
  3. 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;}}
  1. Attach Target to Movement.cs on Ethan
  2. Press Play

Target Move

Step 7: Animation

  1. Turn off Apply Root Motion on Ethan's Animator
  2. Create a new Animator Controller named Movement and attach it to Ethan
  3. Add a new animator parameter called "Speed" of type float
  4. Create a new 1D Blend Tree called Move in the Animator. Make it dependent on the Speed parameter.
  5. Add three motions: HumanoidIdle, HumanoidWalk, HumanoidRun
  6. Uncheck Automate Thresholds in blend tree
  7. Use Compute Thresholds > Velocity Z to calculate the thresholds based on animation forward velocity.
  8. Set Ethan > NavMeshAgent > Speed to the highest threshhold value. (5.66 w/ HumanoidRun)
  9. In Movement.cs set the Speed parameter of the animator as the z velocity 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);}}

Animated

Additionally, Ethan will always follow the Target even if it moves.

Here

Step 8: Click To Move

  1. Create a new script called "Control.cs" and attach it to the main camera.
  2. 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.

Raycast Bad

  1. Add a new tag to the floor called "Ground".
  2. 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;}
  1. 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;}

Step 9: Polish

  1. Change skybox to solid color
  2. Set camera projection to "orthographic"
  3. Adjust camera size and position

Finished

Resources

Grid Snapping Set Grid Size to 1 and Increment Settings > Move to 1

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Feb 9, 2026. It is now read-only.

Latest commit

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Click Navigation Project

Unity Version: 2019.3

Build

Setup

Import Ethan

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

Step 1: Initialize Environment

  • Floor and Obstacles
  • Lighting
  1. Add a floor plane
  2. Add an obstacle cube
  3. Prefab and name both
  4. Give obstacle a color
  5. Create an Empty called Environment in Hierarchy
  6. Parent both to environment

Step 2: Set Grid Snapping Settings

At Edit > Grid and Snap

  1. Set Grid Size to 1
  2. Set Move to 1
  3. Set Scale to 1
  4. Set Rotate to 90

GridAndSnap.png

Step 3: Create a Maze

Use the move, scale, and rotate tools to create a maze with the obstacle instances.

Maze.png

Step 4: Add Ethan

  1. Add the Ethan model to the maze.
  2. Change the material on EthanBody to EthanGrey
  3. Scale the environment up to (2,2,2)

EthanGrey

Step 5: Add NavMeshAgent

  1. Add NavMeshAgent to Ethan
  2. Select all environment objects and set Navigation > Object > Navigation Static to true.
  3. Select all obstacles and set Navigation Area to "Not Walkable"
  4. Open Navigation window and Bake
  5. Tweak Agent Radius to define NavMesh area

NavMesh

Step 6: Follow Target

  1. Add a Sphere called "Target"
  2. Give the Target a material that contrasts Obstacle
  3. 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;}}
  1. Attach Target to Movement.cs on Ethan
  2. Press Play

Target Move

Step 7: Animation

  1. Turn off Apply Root Motion on Ethan's Animator
  2. Create a new Animator Controller named Movement and attach it to Ethan
  3. Add a new animator parameter called "Speed" of type float
  4. Create a new 1D Blend Tree called Move in the Animator. Make it dependent on the Speed parameter.
  5. Add three motions: HumanoidIdle, HumanoidWalk, HumanoidRun
  6. Uncheck Automate Thresholds in blend tree
  7. Use Compute Thresholds > Velocity Z to calculate the thresholds based on animation forward velocity.
  8. Set Ethan > NavMeshAgent > Speed to the highest threshhold value. (5.66 w/ HumanoidRun)
  9. In Movement.cs set the Speed parameter of the animator as the z velocity 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);}}

Animated

Additionally, Ethan will always follow the Target even if it moves.

Here

Step 8: Click To Move

  1. Create a new script called "Control.cs" and attach it to the main camera.
  2. 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.

Raycast Bad

  1. Add a new tag to the floor called "Ground".
  2. 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;}
  1. 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;}

Step 9: Polish

  1. Change skybox to solid color
  2. Set camera projection to "orthographic"
  3. Adjust camera size and position

Finished

Resources

Grid Snapping Set Grid Size to 1 and Increment Settings > Move to 1

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Feb 9, 2026. It is now read-only.

Latest commit

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Click Navigation Project

Unity Version: 2019.3

Build

Setup

Import Ethan

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

Step 1: Initialize Environment

  • Floor and Obstacles
  • Lighting
  1. Add a floor plane
  2. Add an obstacle cube
  3. Prefab and name both
  4. Give obstacle a color
  5. Create an Empty called Environment in Hierarchy
  6. Parent both to environment

Step 2: Set Grid Snapping Settings

At Edit > Grid and Snap

  1. Set Grid Size to 1
  2. Set Move to 1
  3. Set Scale to 1
  4. Set Rotate to 90

GridAndSnap.png

Step 3: Create a Maze

Use the move, scale, and rotate tools to create a maze with the obstacle instances.

Maze.png

Step 4: Add Ethan

  1. Add the Ethan model to the maze.
  2. Change the material on EthanBody to EthanGrey
  3. Scale the environment up to (2,2,2)

EthanGrey

Step 5: Add NavMeshAgent

  1. Add NavMeshAgent to Ethan
  2. Select all environment objects and set Navigation > Object > Navigation Static to true.
  3. Select all obstacles and set Navigation Area to "Not Walkable"
  4. Open Navigation window and Bake
  5. Tweak Agent Radius to define NavMesh area

NavMesh

Step 6: Follow Target

  1. Add a Sphere called "Target"
  2. Give the Target a material that contrasts Obstacle
  3. 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;}}
  1. Attach Target to Movement.cs on Ethan
  2. Press Play

Target Move

Step 7: Animation

  1. Turn off Apply Root Motion on Ethan's Animator
  2. Create a new Animator Controller named Movement and attach it to Ethan
  3. Add a new animator parameter called "Speed" of type float
  4. Create a new 1D Blend Tree called Move in the Animator. Make it dependent on the Speed parameter.
  5. Add three motions: HumanoidIdle, HumanoidWalk, HumanoidRun
  6. Uncheck Automate Thresholds in blend tree
  7. Use Compute Thresholds > Velocity Z to calculate the thresholds based on animation forward velocity.
  8. Set Ethan > NavMeshAgent > Speed to the highest threshhold value. (5.66 w/ HumanoidRun)
  9. In Movement.cs set the Speed parameter of the animator as the z velocity 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);}}

Animated

Additionally, Ethan will always follow the Target even if it moves.

Here

Step 8: Click To Move

  1. Create a new script called "Control.cs" and attach it to the main camera.
  2. 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.

Raycast Bad

  1. Add a new tag to the floor called "Ground".
  2. 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;}
  1. 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;}

Step 9: Polish

  1. Change skybox to solid color
  2. Set camera projection to "orthographic"
  3. Adjust camera size and position

Finished

Resources

Grid Snapping Set Grid Size to 1 and Increment Settings > Move to 1

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
This repository was archived by the owner on Feb 9, 2026. It is now read-only.

Latest commit

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Click Navigation Project

Unity Version: 2019.3

Build

Setup

Import Ethan

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

Step 1: Initialize Environment

  • Floor and Obstacles
  • Lighting
  1. Add a floor plane
  2. Add an obstacle cube
  3. Prefab and name both
  4. Give obstacle a color
  5. Create an Empty called Environment in Hierarchy
  6. Parent both to environment

Step 2: Set Grid Snapping Settings

At Edit > Grid and Snap

  1. Set Grid Size to 1
  2. Set Move to 1
  3. Set Scale to 1
  4. Set Rotate to 90

GridAndSnap.png

Step 3: Create a Maze

Use the move, scale, and rotate tools to create a maze with the obstacle instances.

Maze.png

Step 4: Add Ethan

  1. Add the Ethan model to the maze.
  2. Change the material on EthanBody to EthanGrey
  3. Scale the environment up to (2,2,2)

EthanGrey

Step 5: Add NavMeshAgent

  1. Add NavMeshAgent to Ethan
  2. Select all environment objects and set Navigation > Object > Navigation Static to true.
  3. Select all obstacles and set Navigation Area to "Not Walkable"
  4. Open Navigation window and Bake
  5. Tweak Agent Radius to define NavMesh area

NavMesh

Step 6: Follow Target

  1. Add a Sphere called "Target"
  2. Give the Target a material that contrasts Obstacle
  3. 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;}}
  1. Attach Target to Movement.cs on Ethan
  2. Press Play

Target Move

Step 7: Animation

  1. Turn off Apply Root Motion on Ethan's Animator
  2. Create a new Animator Controller named Movement and attach it to Ethan
  3. Add a new animator parameter called "Speed" of type float
  4. Create a new 1D Blend Tree called Move in the Animator. Make it dependent on the Speed parameter.
  5. Add three motions: HumanoidIdle, HumanoidWalk, HumanoidRun
  6. Uncheck Automate Thresholds in blend tree
  7. Use Compute Thresholds > Velocity Z to calculate the thresholds based on animation forward velocity.
  8. Set Ethan > NavMeshAgent > Speed to the highest threshhold value. (5.66 w/ HumanoidRun)
  9. In Movement.cs set the Speed parameter of the animator as the z velocity 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);}}

Animated

Additionally, Ethan will always follow the Target even if it moves.

Here

Step 8: Click To Move

  1. Create a new script called "Control.cs" and attach it to the main camera.
  2. 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.

Raycast Bad

  1. Add a new tag to the floor called "Ground".
  2. 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;}
  1. 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;}

Step 9: Polish

  1. Change skybox to solid color
  2. Set camera projection to "orthographic"
  3. Adjust camera size and position

Finished

Resources

Grid Snapping Set Grid Size to 1 and Increment Settings > Move to 1

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages