Supporting files for VG1 quests are part of a single archive that you can download here.
This assignment will continue with the Adventure scene.
Import zelda_world.png from the course files.
Use the following import settings:
Apply whenever prompted.
Open Sprite Editor and use the following Slice settings:
Slice and Apply whenever prompted.
Create a new game object called Breakable and attach a Sprite Renderer and a BoxCollider2D. The Sprite component's Order in Layer should be -1 and should use zelda world sprite #31. Create a Breakable.cs script and attach it to the object.
Prefab this game object and duplicate at least 4 of them to place around the environment. Use a Z-position of 0 to ensure these objects are in front of the camera and visible in the Game view.
Open Breakable.cs and implement a public Break function that destroys the obstacle.
Breakable.cs
using UnityEngine;
namespace Adventure {
public class Breakable : MonoBehaviour
{
public void Break() {
Destroy(gameObject);
}
}
}
In order for our player to attack, we need to keep track of what direction they are facing. An enumeration is a way of defining your own data type to keep track of such information. In PlayerController.cs, create an enumeration for Direction.
PlayerController.cs
namespace Adventure {
public enum Direction {
Up = 0,
Down = 1,
Left = 2,
Right = 3
}
public class PlayerController : MonoBehaviour
{
In the same file, define a variable for keeping track of the player’s facing direction using the new enum as the variable type. We will also need to access the SpriteRenderer soon and will configure a list of sprites that represent each facing direction.
PlayerController.cs
public class PlayerController : MonoBehaviour
{
// Outlets
Rigidbody2D _rigidbody;
Animator _animator;
SpriteRenderer _spriteRenderer;
// Configuration
public Key keyUp;
public Key keyDown;
public Key keyLeft;
public Key keyRight;
public float moveSpeed;
public Sprite[] sprites;
// State Tracking
public Direction facingDirection;
// Methods
void Start() {
_rigidbody = GetComponent<Rigidbody2D>();
_animator = GetComponent<Animator>();
_spriteRenderer = GetComponent<SpriteRenderer>();
}
The animation state will drive the logic for what direction we are facing. By checking the currently rendering player sprite, we can determine facing direction. The logic follows this flow:
A) Controller Input → B) Physics Forces → C) Animation Blending → D) Visible Sprite → E) Facing Direction
To match the Visible Sprite (D) with a Facing Direction (E), the sprites variable on PlayerController should list the sprites that represent the various directions. The array indexes must match the enumerations. For example, Element 0 must be the sprite for Up because the Up Direction is also identified by 0.
Choose zelda sprites 3, 0, 9, and 2 to represent up, down, left, and right, respectively.
Because animations can happen after the Update loop, we will put our direction logic in LateUpdate which happens after all animations have resolved. (Review Unity's event function execution order, if you are trying to visualize this sequence.) In this code, we loop through each of the configured sprites to see which one is being rendered. When a match is found, its integer index is casted to the matching Direction enumeration. This is why it was important for us to match all of those indexes.
PlayerController.cs
void LateUpdate() {
for(int i = 0; i < sprites.Length; i++) {
if(_spriteRenderer.sprite == sprites[i]) {
facingDirection = (Direction)i;
break;
}
}
}
Playtest your game and inspect the PlayerController to check if Facing Direction updates appropriately for all 8 idle/walk scenarios. For example, when you walk Down, the Facing Direction variable should also report Down.
Now that we have a variable measuring what direction the player is facing, we can define the 4 attack zones where the player will deal damage. Create an empty child object within Player and name it AttackZones
Next create four empty children game objects within AttackZones and name them Up, Down, Left, and Right. To help with positioning, change the game object icon on these four children to the red dot. The red dots will help you visualize their positions in the next step.
These positions will determine where the player can do damage. Arrange them around the player matching the following diagram (up is above, down is below, etc).
The PlayerController needs to keep track of these four attack points. Because we only need their position, the variable type is Transform.
PlayerController.cs
public class PlayerController : MonoBehaviour
{
// Outlets
Rigidbody2D _rigidbody;
Animator _animator;
SpriteRenderer _spriteRenderer;
public Transform[] attackZones;
Fill in the blanks for the attack zones in the inspector. Just like the sprites variable, the element order in this array must match the sequence defined by the enumeration numbers.
Whenever the player attacks, we will use the Physics OverlapCircle function to check for targets under our various attack zones. For example, if the player attacks and we are facing up, we will check within a circle at attack zone 0 which represents up.
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");
// Convert the enumeration to an index
int facingDirectionIndex = (int)facingDirection;
// Get an attack zone from index
Transform attackZone = attackZones[facingDirectionIndex];
// What objects are within a circle at that attack zone?
Collider2D[] hits = Physics2D.OverlapCircleAll(attackZone.position, 0.1f);
// Handle each hit target
foreach(Collider2D hit in hits) {
Breakable breakableObject = hit.GetComponent<Breakable>();
if(breakableObject) {
breakableObject.Break();
}
}
}
}
When OverlapCircleAll returns results, we loop through each object and react based on the attached components. If an object has the Breakable component, we will break it.
Playtest your game and ensure you can break targets from all 4 directions. The sword should appear and targets should disappear simultaneously on pushing spacebar. (There should not be a delay in which the sword awkwardly appears after the target has already been destroyed.) Also check for false positives. If you are facing left, while the target is above you, it should not break.
Playtest to ensure all interactions work as expected and that the addition of any new features hasn’t broken any earlier interactions.
SAVE any open files or scenes.
Submit your assignment for grading following the instructions supplied for your particular classroom.