Supporting files for VG1 quests are part of a single archive that you can download here.
Create and open a new scene called Platformer. (We will use this scene for multiple assignments in the platformer genre throughout the semester.)
Import this quest’s asset files into /Textures/Platformer/
Set the items_spritesheet, tiles_spritesheet, and p1_walk Sprite Mode to Multiple, Pixels Per Unit to 69, and click Sprite Editor. Click Apply if prompted.
In Sprite Editor, click the Slice dropdown, set Type to Automatic, click the Slice button, then click Apply.
Make sure that these steps are duplicated on both items_spritesheet and tiles_spritesheet because they are both multi-sprite sheets instead of single sprite files.
The target-icon will use 980 Pixels Per Unit because it is such a large image.
We need to prepare Object Layers, so we can control what kinds of objects can collide with each other.
From Edit > Project Settings > Tags and Layers, add Layers for "Ground", "PlayerProjectile", and "Player".
From Edit > Project Settings > Physics2D, alter the collision matrix, so that Players cannot collider with their own PlayerProjectiles. We also do not want PlayerProjectiles to hit themselves.
In the Scene hierarchy, create an Empty Object and add a Sprite Renderer component. Assign a desired Ground Sprite. Add a BoxCollider2D component.
Assign a Layer of "Ground".
Making Prefabs is a way to prepare a template copy of an object that will be re-used multiple times throughout a project. You only need to change the template and all of the copies will update with those changes. Because we have a lot of ground blocks, it is a good candidate for a prefab.
Create a /Prefabs/Platformer/ folder to hold prefabs for this exercise. Drag the Ground game object from the Hierarchy list to the Project library to create a reusable ground Prefab.
Notice how prefabs have a different icon in the Hierarchy and a new row of Prefab options in the Inspector.
Add a Sprite Renderer component to an Empty Object. Assign a desired Target Block Sprite. Add a BoxCollider2D component.
Assign a Layer of "Ground".
Drag the Target game object from the Hierarchy list to the Project library to create a reusable target Prefab.
Add a Sprite Renderer component to an Empty Object. Assign a desired Character Sprite.
Assign a Layer of "Player".
Add a CapsuleCollider2D component. We use a Collider2D that has a rounded bottom to prevent the character from getting snagged on terrain.
Add a Rigidbody2D component. To prevent the character from rolling, check the Constraints > Freeze Rotation Z checkbox.
Add a Sprite Renderer component to an Empty Object. Assign a desired Projectile Sprite.
Assign a Layer of "PlayerProjectile".
Add a CapsuleCollider2D component with a Direction setting of Horizontal.
Add a Rigidbody2D component and set Gravity to 0.
Drag the Projectile gameObject from the Hierarchy list to the Project library to create a reusable projectile Prefab.
With the Projectile prefab in the Library, DELETE the Projectile from the Scene Hierarchy. Do NOT leave an unfired Projectile in the scene.
Create a Level Layout using your Character, Ground prefabs, and Target prefabs. You need ground for the character to stand on, a hole in the ground the player might fall through, and targets that pose some kind of challenge to hit.
To use a prefab, you just drag it from the Project library to the Scene. If you edit one Prefab, you can update all of the other Copies of that prefab to match by clicking the Apply button in its inspector.
Similarly, updating the Prefab in the Project library will update all copies of it in the Scene.
Create a new C# Script called "PlayerController" in /Code/Platformer/ and attach it to the Character game object.
We will be using namespaces to help avoid contradicting code from different exercises. Enclose ALL classes created as part of this exercise in the Platformer namespace.
Create a Reference Outlet to the Character gameObject’s Rigidbody2D component, so that we can reference it in code.
Every Frame Update, we will check if the A or D keys are held down to move the character Left or Right using physics forces. The appropriate amount of force depends on Game Feel and might be a different number. Using a Force Mode of Impulse means we want the force to be applied instantly for that frame rather than computed as force per second.
On the Character gameObject, adjust the Linear Drag of the Rigidbody2D component for a proper Game Feel. This controls how the character slows down while moving.
Add a BoxCollider2D to an empty object. Position the object below the Level Layout and use Edit Collider to ensure the green collider zone encompasses any area where the player might fall.
Do NOT use Scale to size the collider. Do NOT make boundaries for the sides or top of the level.
Check the Is Trigger option. This means this collision zone is not solid.
Create a new C# Script called "LevelBounds" (no spaces) and attach it to the game object.
In your script, add the SceneManagement namespace to give us easier access to the SceneManager functions.
We will use a Trigger2D event instead of a Collision2D because our green zone is a non-solid Trigger.
You must also make sure your Scene is added to Unity’s File > Build Settings, otherwise, SceneManager may throw an error when trying to switch scenes.
Create an Empty Child Game Object within the Character and name it AimPivot. Not additional Components are necessary. Ensure that your Move Tool is set to Pivot mode. Notice how the Pivot Point of AimPivot is centered within Character.
AimPivot is the gameObject we will rotate to aim with the mouse.
Create an Empty Child Game Object within AimPivot and name it Reticle. Add a SpriteRenderer component and assign an appropriate graphic. You may need to adjust the Pixels Per Unit to get the graphic the correct size.
Notice how the Pivot Point of Reticle is positioned outside of the character.
In your PlayerController script, add a new Public Outlet for aimPivot, so that we can reference in code and rotate it towards the mouse.
Because this is a Public Outlet, we will get a Fill-in-the-Blank field in the Inspector for our Character gameObject where we can tell Unity what Transform component we want to use as aimPivot.
Assign AimPivot to this blank by dragging or through the Selection Window circle.
We will add Mouse Aiming code to the Update event of our PlayerController script on our Character gameObject.
We need to add a Public Outlet for the Prefab gameObject our character will shoot as a Projectile.
This creates a Fill-in-the-Blank in the character gameObject’s Inspector. Assign our Projectile Prefab from the Project library to this blank.
Beneath the code for our Mouse Aiming within the Update Event of our PlayerController, we will add code for shooting projectiles.
When the MouseButtonDown (not MouseButton “held”) event of the left-click occurs, we create a new instance of our projectile prefab, position it where our character is, and rotate it to match our aimPivot. Right now the projectile just floats in mid-air.
Create a new C# script called Projectile and add it to your Projectile Prefab.
We will control our Projectile movement with physics, so we must create an Outlet for Rigidbody2D to reference it in code.
At the Start event of our component, we will set the relative velocity of our projectile toward the Right. Because we are rotating the projectile through angles relative to the positive x-axis, our projectile will appear to travel in whatever direction we aim. You may want a different speed than 10.
We also want our projectile to disappear any time it his another object. We do not have to worry about the projectile hitting the player when it’s fired because we setup a collision matrix in the Physics2D settings.
We setup an OnCollisionEnter2D event where we tell our projectile to remove itself from the game.
Create a new C# script named Target and assign it to the Target Prefab in your Project library to ensure that all Target instances obtain the new script.
We will use another OnCollisionEnter2D event, but we will specifically check if the colliding object has a Projectile component before destroying our Target block. We do this to make sure touching the Player does not mis-trigger this game mechanic.
In our PlayerController, we can make jump count configurable by creating a Public Property to keep track of how many jumps we have left.
This Public Property creates a Fill-in-the-Blank in the Character’s Inspector. For Double Jump, set the Jumps Left to 2.
Below our shooting code in PlayerController, we are going to add code for Jumping.
We check to see if SpaceBar has just been pressed (not held) and if we have any jumps left in the property we were using as a counter.
When our character jumps, we reduce the number of jumps left and add an upward physics force. You may want a different force strength depending on your Game Feel.
We use a ForceMode2D of Impulse so that the full jump force is applied instantaneously.
Right now, our player will jump twice and then become stuck to the ground.
For a proper Double Jump, we must reset the jump counter when the player solidly lands on ground.
INCORRECT implementations involve resetting the jump counter if the player touches the side of the ground (Wall Jumping) or if the player touches their head to the ground (Ceiling Climbing).
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.