VG1, Quest 9 - Adventure, PT1

Download Files

Supporting files for VG1 quests are part of a single archive that you can download here.

Scene Clean-up

Our adventure genre prototype has a very similar foundation as the SplitScreen assignment. We will repeat a few steps from the prior assignment in new files, so that the SplitScreen scene will still work even after you finish this assignment. Begin by DUPLICATING the SplitScreen scene and renaming the new copy Adventure.

Be sure that all new edits take place in the Adventure scene.

This assignment will be singleplayer. Remove the Smoothdamp camera and its target player by deleting MainCamera and Player.

Revise Camera2 to take up the whole screen.

Fix the zoom level by changing the Lens Orthographic Size on the Cinemachine Camera.

Because our camera boundaries are now too big for our level design, remove the Cinemachine Camera Confiner.

Reset the tint color of the player to the default white.

To avoid breaking the SplitScreen assignment, remove the SplitScreen PlayerController component. We will replace it with an Adventure PlayerController file.

Right-click Player2 in the hierarchy and unpack the prefab so we are no longer hooked up to the SplitScreen prefab.

Create a new PlayerController in the /Assets/Scripts/Adventure/ folder. Attach this to the Player object in the Adventure scene.

We will use the Adventure namespace. As a start, the code is identical to what we wrote in the prior assignment, but now we can add more to it without breaking the SplitScreen scene.

PlayerController.cs

using UnityEngine;
using UnityEngine.InputSystem;

namespace Adventure {
	public class PlayerController : MonoBehaviour
	{
		// Outlets
		Rigidbody2D _rigidbody;

		// Configuration
		public Key keyUp;
		public Key keyDown;
		public Key keyLeft;
		public Key keyRight;
		public float moveSpeed;
		
		// Methods
		void Start() {
			_rigidbody = GetComponent<Rigidbody2D>();
		}

		void FixedUpdate() {
			if(Keyboard.current[keyUp].isPressed) {
				_rigidbody.AddForce(moveSpeed * Time.fixedDeltaTime * Vector2.up, ForceMode2D.Impulse);
			}
			if(Keyboard.current[keyDown].isPressed) {
				_rigidbody.AddForce(moveSpeed * Time.fixedDeltaTime * Vector2.down, ForceMode2D.Impulse);
			}
			if(Keyboard.current[keyLeft].isPressed) {
				_rigidbody.AddForce(moveSpeed * Time.fixedDeltaTime * Vector2.left, ForceMode2D.Impulse);
			}
			if(Keyboard.current[keyRight].isPressed) {
				_rigidbody.AddForce(moveSpeed * Time.fixedDeltaTime * Vector2.right, ForceMode2D.Impulse);
			}
		}
	}
}

Configure the player's controls and movement speed.

Playtest with this configuration ensure you have a single player that is able to move using WASD keys with the camera still smoothly following.

Animator and Animations

Store all of your Adventure Player animations in /Assets/Animations/Link/

Attach an Animator Component to the Player and open the Animation Window.

Create an IdleUp animation using graphic #3 at a Sample rate of 1 frames per second.

Create an IdleDown animation using graphic #0 at a Sample rate of 1 frames per second.

Create an IdleLeft animation using graphic #9 at a Sample rate of 1 frames per second.

Create an IdleRight animation using graphic #2 at a Sample rate of 1 frames per second.

Create a WalkUp animation using graphic #3 and #8 at a Sample rate of 5 frames per second.

Create a WalkDown animation using graphic #0 and #1 at a Sample rate of 5 frames per second.

Create a WalkLeft animation using graphic #9 and #13 at a Sample rate of 5 frames per second.

Create a WalkRight animation using graphic #2 and #12 at a Sample rate of 5 frames per second.

Create an AttackUp animation using graphic #30 at a Sample rate of 1 frames per second.

Create an AttackDown animation using graphic #26 at a Sample rate of 1 frames per second.

Create an AttackLeft animation using graphic #35 at a Sample rate of 1 frames per second.

Create an AttackRight animation using graphic #28 at a Sample rate of 1 frames per second.

Animator Parameters

Set up the following four Animator Parameters:

To reference the Animator Component from the PlayerController, add an outlet and fill the reference during the Start event.

PlayerController.cs

namespace Adventure {
	public class PlayerController : MonoBehaviour
	{
		// Outlets
		Rigidbody2D _rigidbody;
		Animator _animator;

		// Configuration
		public Key keyUp;
		public Key keyDown;
		public Key keyLeft;
		public Key keyRight;
		public float moveSpeed;
		
		// Methods
		void Start() {
			_rigidbody = GetComponent<Rigidbody2D>();
			_animator = GetComponent<Animator>();
		}

We previously wrote our physics interactions in the FixedUpdate loop to synchronize with physics. Momentary input such as the spacebar press for our sword slash should be handled in the Update event because there isn't always a FixedUpdate event executing at the same time as the "wasPressedThisFrame" input event.

PlayerController.cs

		void Update() {
			float movementSpeed = _rigidbody.linearVelocity.sqrMagnitude;
			_animator.SetFloat("speed", movementSpeed);
			if(movementSpeed > 0.1f) {
				_animator.SetFloat("movementX", _rigidbody.linearVelocity.x);
				_animator.SetFloat("movementY", _rigidbody.linearVelocity.y);
			}

			if(Keyboard.current.spaceKey.wasPressedThisFrame) {
				_animator.SetTrigger("attack");
			}
		}

Blend Trees

Transitioning arbitrarily between 12 animation states would be unwieldy.

Working with this many animations would benefit from a new technique known as Blend Trees because the animations fit into distinct categories (idle, walk, and attack) and directions (up, down, left, and right).

In chart form, our many animations are just the intersection of these categories and directions. Recognizing this pattern allows us to drastically simplify our animation logic rather than creating a tangled web of transitions.

Direction/Category Idle Walk Attack
Up IdleUp WalkUp AttackUp
Down IdleDown WalkDown AttackDown
Left IdleLeft WalkLeft AttackLeft
Right IdleRight WalkRight AttackRight

The categories will become 3 "Blend Trees" and the directions will become 4 "motions" inside each tree.

First, we will actually delete all 12 animations from the Animator window. (This simply removes then from the flow chart. The animations we created in prior steps still exist as assets in the project files.)

Right-click in the empty Animator screen to add a New Blend Tree.

Click the new Blend Tree and in the Inspector, rename it Idle.

Double-click the Blend Tree to see its details. Notice how the Animator portrays navigation and hierarchy.

Click the Blend Tree and view the details in the Inspector.

First, set Blend Type to 2D Simple Directional. You can use parameters to specify the amount that motions (animations) blend together. Using two parameters creates a two-dimensional blend. We will use the two parameters of movementX and movementY to blend a direction similar to a Vector2. Make sure the Inspector window is wide enough that you can fully read which parameters are blending.

Within the Inspector, click the plus (+) to add four Motion Fields. Motions are just another word for Animations. Use the settings in the diagram so that any combination movementX and movementY measurements will activate the appropriate Up, Down, Left, or Right idle animation.

Return to the Base Layer and copy and paste the Idle state twice. Rename one to Walk and rename the other to Attack. All 3 categories will use similar directional blending.

Update the Walk blend tree to use walk animations.

Update the Attack blend tree to use attack animations.

Blend Tree Transitions

With all the direction animations categorized into different Blend Trees, transitioning between them works the same as prior animator techniques. Set "Idle" as the Default Layer State and create transitions in both directions between the three states. There should be six transitions in total.

Implement the following settings for each Animator Transition. Be precise and make sure every field matches. Not all transitions are the same. Notably, any transitions leaving Attack utilize an exit time so that the sword is shown long enough to provide visual feedback that the player has attacked.

Idle → Walk

Walk → Idle

Idle → Attack

Attack → Idle

Walk → Attack

Attack → Walk

If you playtest the game now:

There is one major bug at the moment: When you attack, the player sprite moves out of alignment because all the sprites are set to use their center as the pivot.

Notice how our character is pushed outside of his circle collider because of the mis-aligned sprite.

Fix Pivot Points

Spritesheet pivot points are changed using the Sprite Editor. Select the zelda image asset in /Assets/Textures/Adventure/ and click the Sprite Editor button from the Inspector.

For each sprite that you used for AttackUp, AttackDown, AttackLeft, and AttackRight, set the Pivot to Custom and use the value shown in the matching image. These values realign the attack sprite to keep the pivot point in the same place as the corresponding idle sprite.

Be sure to apply any changes when asked.

Check that when Link attacks, the sword is shown stabbing outward rather than Link's character being shoved back.

Save and Test

Playtest to ensure all interactions work as expected and that the addition of any new features hasn’t broken any earlier interactions.

Submit Assignment

SAVE any open files or scenes.

Submit your assignment for grading following the instructions supplied for your particular classroom.