Supporting files for VG1 quests are part of a single archive that you can download here.
Creating TileMap graphics in Unity is built on a hierarchy of several asset types:
Grids and TileMaps live in the Scene Hierarchy. TilePalettes, Tiles, and Sprites are kept in the Project library.
To keep our project organized, we’ll prepare a TilePalettes and Tiles folder. We already have a Textures folder for our Sprites.
Before we can paint on a TileMap, we have to create a TilePalette. As a convenience, the Tile Palette tool can create Tiles from Sprites. You might have different Palettes for different collections of graphics. For example, some games might have a Water, Fire, and Castle palette for different game zones.
Open the Tile Palette tool from Window > 2D > Tile Palette. Create a new Tile Palette with Manual settings. Save your new Palette in the TilePalette folder.
Select the spritesheet_ground from Textures and drag it into TilePalette drop zone to create Tiles from those sprites. Create an Assets/Tiles/World folder to save these Tiles. Drag spritesheet_tiles into the TilePalette.
If your TilePalette graphics don't line up nicely on a grid, you might have forgotten to set your Palette sizing to Manual. You can delete the Palette from the TilePalette folder AND the Tiles from the Tiles/World folder to try again.
The tiles in your palette should conform to the grid, as shown in this screenshot:
Because we are going to replace our old Platformer landscape with a TileMap, you should delete all of the old Ground scene objects. Leave the Target blocks in the scene. Even though we could also create the Target blocks on the tilemap, keeping them as separate objects makes it easier for the Targets to have separate functionality compared to the rest of the ground.
Create a TileMap from the Hierarchy by clicking Create > 2D Object > Tilemap > Rectangular.
Notice how it creates two nested objects: a parent Grid and a child Tilemap. You can create additional TileMap child objects inside the same Grid parent for additional graphical layers. Also notice how much more emphasized the grid is in the Scene when a Tilemap is highlighted.
With the desired Tilemap active, use the Tile Palette window in tandem with the Scene window. Select the tile you want from the Tile Palette and use the brush tool to paint different tiles directly onto the Scene view. Recreate the same level design required from the prior assignment.
It is helpful to close the Tile Palette tab when you are done using it to avoid any mistakes or errors from the tool still running unintended operations as you continue to click around your project.
If you play your game right now, you’ll notice that your player just falls through the landscape. Similar to Sprite Renderer objects, you also need to add a Collider to make Tilemaps solid.
The correct collider for a Tilemap is a Tilemap Collider 2D.
Also notice we change the Layer to Ground because our Jump mechanics check for Ground collisions.
Download mario.gif which is the Mario character sprite sheet from the course files. Set your Import Settings to Sprite Mode = Multiple, Pixels Per Unit = 16, and Filter Mode = Point. Even though Unity may appear to slice the spritesheet automatically, perform an automatic slice yourself in the Sprite Editor tool to get rid of any excess spacing around the characters.
On the Character's Sprite Renderer, change the assigned Sprite to Mario #13 graphic shown below. Reset your Capsule Collider to match the new graphics. The raycast code in this assignment is sized for the best fit shape that comes from resetting the collider. If you resize it by hand instead, your collider might not work with the raycast length in the upcoming code.
Because our Character is now taller, we have to increase how far below the character we Raycast when looking for Ground to reset the Double Jump mechanic. The rest of the jump logic can remain the same, since all we did was choose new character graphics.
PlayerController.cs
void OnCollisionStay2D(Collision2D collision) {
// Check that we collided with Ground
if(collision.gameObject.layer == LayerMask.NameToLayer("Ground")) {
// Check what is directly below our character's feet
RaycastHit2D[] hits = Physics2D.RaycastAll(transform.position, Vector2.down, 0.9f);
// Debug.DrawRay(transform.position, Vector2.down * 0.9f); // Visualize Raycast
// We might have multiple things below our character's feet
for(int i = 0; i < hits.Length; i++) {
RaycastHit2D hit = hits[i];
// Check that we collided with ground below our feet
if(hit.collider.gameObject.layer == LayerMask.NameToLayer("Ground")) {
// Reset jump count
jumpsLeft = 2;
}
}
}
}
Having a player face left or right can be handled a few different ways. As an introductory approach, we are going to use the Flip X setting to mirror our Character Sprite when the player pushes A or D.
PlayerController.cs
namespace Platformer {
public class PlayerController : MonoBehaviour
{
// Outlets
Rigidbody2D _rigidbody2D;
public Transform aimPivot;
public GameObject projectilePrefab;
SpriteRenderer spriteRenderer;
// State Tracking
public int jumpsLeft;
// Methods
void Start() {
_rigidbody2D = GetComponent<Rigidbody2D>();
spriteRenderer = GetComponent<SpriteRenderer>();
}
void Update() {
// Move Player Left
if(Keyboard.current.aKey.isPressed) {
_rigidbody2D.AddForce(18f * Time.deltaTime * Vector2.left, ForceMode2D.Impulse);
spriteRenderer.flipX = true;
}
// Move Player Right
if(Keyboard.current.dKey.isPressed) {
_rigidbody2D.AddForce(18f * Time.deltaTime * Vector2.right, ForceMode2D.Impulse);
spriteRenderer.flipX = false;
}
Add an Animator Component to your character. Be careful not to get this mixed up with Animation.
Open the Animation tool from Window > Animation > Animation. The Animation tool is contextual, so make sure you have your Character selected. From the Animation tool, click the Create button to make a new Animation called Idle and save it in an /Assets/Animations/Mario/ folder.
Some versions of Unity hide the Samples setting which allows you to adjust the frame rate of the animation. You can reveal this option by clicking the Settings gear (or Settings triple dots in older versions of Unity) in the upper-right of the Animation tool. Enable the Show Sample Rate option.
For the Idle animation, use 2 for the number of Samples per second. Be sure to configure the intended Samples setting FIRST in any of these exercises.
Click the Add Property button and click the + icon next to Sprite Renderer > Sprite.
Our character’s Idle animation will consists of Mario graphics #3 and #4 on Frames 0 and 1 as diagrammed below. Animated togther, these will show Mario waving at a steady pace.
Always DELETE any excess frames that may have been added automatically if they aren’t shown in these screenshots.
Create a New Animation called Walk and save it in the Animations/Mario/ folder.
Our Walk animation will run at 15 samples per second and uses frames #13, #14, #12, and #14.
Create a New Animation called Jump and save it in the Animations/Mario folder.
Our Jump animation will run at 1 frame per second and uses frame #26.
It is helpful to close the Animation tab when you are done using it to avoid any mistakes or errors from the tool still running unintended operations as you continue to click around your project.
Objects are capable of showing a series of animations. An Animator controls which animation is active. You are already familiar with the Animation window. You will use the Animator window to coordinate transitions between animations.
Open the Animator screen from Window > Animation > Animator. Like the Inspector, the Animator window is contextual. Select your Character to see its Animator details.
Our Idle animation is highlighted in orange to represent that it is our default animation. The Animator window shows all Animations created for the selected object and allows us to create contextual transitions between our animations.
Animations can transition between each other. "Any State" is a shortcut for saying that all animations can be a valid source for a transition.
To create a transition, right-click the Source animation, click Make Transition, then click the Destination animation. Create a Transition from Idle to Walk.
Click the Transition Arrow to view its configuration in the Inspector. These animation tools are also used for 3D animations, so there are a lot of extra options for blending that we don't need for our 2D game. We will uncheck "Has Exit Time," expand Settings and set Transition Duration to 0.
You’ll see the transition preview (visualized as a bar diagram) show a solid cut-off transition rather than the prior blended transition that wouldn't work for 2D sprite art.
Notice the empty Conditions box at the bottom. Conditions define the scenarios when a Transition will occur. Filling out the conditions is like programming an if-statement. Two requirements must overlap: 1) The currently active animation must be the source animation configured at the base of the transition arrow, and 2) All Conditions must be met for the transition arrow to execute.
When writing an if-statement in code, we often compare against variables. To use the Conditions in an Animation Transition, we need to configure Parameters, which behave like variables.
Conditions are configured using Parameters. Parameters can be variety of variable types. For example, we will use a Float parameter to keep track of our character's Speed.
An Animator is sometimes called an Animation Controller because it is like a control center for managing animations. The logic flow works like this:
Make sure your Idle-to-Walk Transition Arrow is selected and click the + button in the Conditions box. We will Transition from Idle to Walk if our Speed parameter is Greater Than 0.1. (We don't use exactly 0 because this would cause the Character to animate walking even at the slightest nudge.)
Create a Transition going the other direction with the same settings. We will Transition from Walk to Idle if our Speed parameter is Less Than 0.1.
If you tested gameplay right now, you would see that the Speed Parameter never changes. We must modify our PlayerController script to update our Animator parameters as physics values change. This is also an opportunity to experiment with comparing FixedUpdate and Update events.
PlayerController.cs
namespace Platformer {
public class PlayerController : MonoBehaviour
{
// Outlets
Rigidbody2D _rigidbody2D;
public Transform aimPivot;
public GameObject projectilePrefab;
SpriteRenderer spriteRenderer;
Animator animator;
// State Tracking
public int jumpsLeft;
// Methods
void Start() {
_rigidbody2D = GetComponent<Rigidbody2D>();
spriteRenderer = GetComponent<SpriteRenderer>();
animator = GetComponent<Animator>();
}
void FixedUpdate() {
// This Update Event is sync'd with the Physics Engine
float speed = _rigidbody2D.linearVelocity.magnitude;
animator.SetFloat("Speed", speed);
}
Playing the game at this step shows the Charater transitioning between Idle and Walk animation states, though jumping can make things look awkward.
To configure the conditions for Jump animation transitions, we need a new Integer parameter called JumpsLeft.
We update the number of JumpsLeft every Update event. Do NOT put this statement inside the if-conditional code, otherwise it won't execute often enough.
PlayerController.cs
// Jump
if(Keyboard.current.spaceKey.wasPressedThisFrame) {
if(jumpsLeft > 0) {
jumpsLeft--;
_rigidbody2D.AddForce(Vector2.up * 15f, ForceMode2D.Impulse);
}
}
animator.SetInteger("JumpsLeft", jumpsLeft);
Because we can enter a Jump state from either Idle or Walk, we will Transition from "Any State" to Jump. For simplicity, our Jump will always return to Idle on landing. Create these two Transitions as diagrammed.
Implement the AnyState-to-Jump transition diagrammed below. Unchecking "Can Transition To Self" helps avoid visual glitches such as a jump animation endlessly transitioning into the same jump animation.
Implement the Jump-to-Idle transition diagrammed below.
One last detail is the speed of our Walk animation. If you play the game now, you’ll notice the Walk animation plays at the same speed no matter how fast the character is actually moving. This means that at some movement speeds, the motion of the feet does not match the movement of the character.
We can solve this by dynamically adjusting the speed of the Walk animation relative to the actual movement speed of the character.
PlayerController.cs
void FixedUpdate() {
// This Update Event is sync'd with the Physics Engine
float speed = _rigidbody2D.linearVelocity.magnitude;
animator.SetFloat("Speed", speed);
if(speed > 0.1f) {
animator.speed = speed / 3f;
} else {
animator.speed = 1f;
}
}
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.