Penny Pixel Platformer – Part 3 – Improving The Game


Note: This tutorial is a work in progress. Please let us know if you have any comments. You might also want to see the Getting Started tutorial series to get a jump start on some of the features whilst this tutorial is completed.

This tutorial will build upon the Unity “Live Session: 2D World building w/ Tilemap & Cinemachine for 2D” platformer tutorial and show how you can use Game Framework to turn the tutorial project into a real multi level game ready for release.

Our game is taking shape, however there is still a lot that can be done. In this third part we will look at cleaning up a few loose ends and also adding some new functionality.

Cleaning Up Some Loose Ends

Our game is still a bit rough around the edges, and you might have noticed certain things happening from which we can’t recover such as falling off the screen. We will handle those first before adding new features.

Handling out of bounds

Run the game and try jumping off to the left of the screen. Our character penny just falls into infinity so we need to add functionality to handle that. In our case we will cause this to trigger a game over, although if you want to do something more advanced you could record the last standing position and return penny to that (possibly losing a life).

Add a new game object to your scene root and call it OutOfBounds set the transfor position to 0, -6.5, -5. To this gameobject add a Physics2D | Box Collider 2D component. Set Is Trigger to selected and Size to 100,1 to create a big collision area to catch anything that falls off the botom of the screen.

Finally add a Collision Handler component (Game Framework | Game Structure) and in the When Entering A Collision section add a Level | Lose Level action.

Run the game and we will now lose by falling off the screen.

Move only when the Level is running

After winning the level you will see that you can still move Penny, even though the game over dialog is shown. Level Manager holds the state of the level such as whether it is started, paused etc. We will modify the PlayerPlatformController script to take this into consideration so we can only give input when the level is running.

Open the PlayerPlatformController.cs script from PennyPixel/Scripts and at line 22 just inside the ComputeVelocity() method add the following to stop checking for movement if the level is not running:

Similarly open the PhysicsObject.cs script from PennyPixel/Scripts and at line 48 add the following just inside the FixedUpdate() method to stop updating movement calculations:

GameOver Gems Not Showing

You may have noticed that the GameOver window always shows 0 for the gems collected. This is because it is showing the gems collected for the level rather than the total player gems. To change this we will also increase the level counter.

On the CollectableGem\CollectableGem prefab within the Collision Handler, add a new Level | Change Coins action with the default properties.

If you run the game now you will see the collected stars shown in the GameOver window. We could easily display the level gems, but it isn’t really needed as we always add to the player. If for debugging you want to see how many are collected just look at the Cheat Function window.

GameOver Text

<TODO: GameOver window shows Coins rather than Gems>

In Game Menu

In the default level design there are a few scenarios where the player can end up trapped not able to do anything. Ideally this would be avoided due to good level design. It would be good for the user to have a way, in game, of quitting or restarting the level. For this we can use the pause menu which gives several different options (including restarting the level).

To the Canvas gameobject add a new UI | Button gameobject. Rename this to PauseButton, change the Text contents to Pause and anchor the button to the top left by modifying the button’s Rect Transform as shown below.

pausebutton

You can customise the button display as you see fit with your own sprites or ones from one of the themes in the Extras Bundle. In the below we have used the Cartoon theme RoundButtonMediumBlue sprite with a nested UI image containing the ButtonImagesMediumPause sprite.

You will also need to adjust the Pos X of the Lives gameobject to accommodate your new button.

Onto the PauseButton add the OnButtonClickPauseLevel component (Game Framework | UI | Dialogs).

Next drag a PauseWindow prefab (\FlipwebApps\GameFramework\Themes\Minimalist\Prefabs\UI) into the root of your hierarchy.You can either take the standard one from (\FlipwebApps\GameFramework\Prefabs\UI\ or \FlipwebApps\GameFramework\Themes\Minimalist\Prefabs\UI) or if you have the Extras Bundle installed then use one of the themed GameOver dialogs from (\FlipwebApps\GameFrameworkSamples\Themes\[Theme Name]\Prefabs\UI\).

Enable the Show Restart and Show Exit Levels option and set Exit Level Scene to Menu for a future menu scene that we will add.

<add image>

Run your game and you should now be able to use the pause window to restart (and in the future exit) the level.

Improvements

Health

Health follows a similar concept to lives and in Game Framework the two can be either linked together, or work independently.

As we did for lives, on _SceneScope | Level Manager enable Game Over When Health Is Zero. So that the health is reset, also add the Set Health component (Game Framework | Game Structure | Players) onto _SceneScope and ensure Health is set to 1 (health should be in the range 0 to 1).

You can now run the game and using the Cheat Functions Window, use the health buttons to decrease the health and see that if the health reaches 0 the game ends.

CheatFunctions

Advanced tip: From Code call GameManager.Instance.Player.Health -= 0.1f ; or similar to decrease the health. You can access the same variable to get the current health.

As with lives there are some components and prefabs that will help us out. They are also under Game Framework | Game Structure | Player. The ShowHealthImage component can be added to a gameobject that contains a UI Image component, whose Image Type is set to Filled. This component also allows for changing the color of this image as the health changes (leave to the default white for no color change).

<image>

The HealthBar prefab (under GameFramework\Prefabs\GameStructure\Player) shows how you can use the ShowHealthImage component and again comes in both standard and themed versions.

On our Game scene, drag the HealthBar prefab under the Canvas gamebject, set Pos X  to 185 and Pos Y  to -120 to position the prefab below the life icons. Run the game and you should now have a health bar that changes as the player health changes.

Health

At this stage, we could chose to remove the lives functionality if we just wanted a health based system, however we can easily link health and lives together so loss of health involves the loss of a life.

On _SceneScope | Level Manager unselect Game Over When Health Is Zero as we no longer want the game to exit on this condition. Next add the component DecreaseLivesWhenHealthIsZero (Game Framework 1 Game Structure | Players) onto _SceneScope to have lives automatically decreased when health is zeroIf you run the game now and decrease health, you will see that when the health reaches zero, a life is lost.

Advanced tip: If you look at the source code of this component you will see that it is pretty simple and draws upon the power of the Game Framework messaging system to easily connect together different actions and increase performance. This is discussed further in the getting started tutorial.

Obstacles

So far the only way we can lose lives or health is through the cheat functions window. Let’s rectify that by adding a new spike tile to our environment. As they will have common behaviour and we expect to have many obstacles, we will add them to the Tile Palette as sprites so you can paint them into the scene and have them automatically aligned with the grid rather than positioning these manually. This will also let us create a Composite Collider if we want to simplify handling multiple collisions from adjacent obstacles.

Create a new Tilemap gameobject by right clicking the Grid root gameobject in the Hierarchy, and selecting 2D | Tilemap. Name this new gameobject Obstacles. On the Obstacles Timemap Renderer set Sorting Layer to Background.

Save the below image to your Sprites/ folder:

In Unity check the sprite has a Texture Type of Sprite (2D and UI) and set Pixels Per Unity to 64 to match the sprite pixel size. Open the Tile Palette window, select Obstacles as the Active Tilemap and the Grass Platform Palette  as the active palette from the dropdown. Drag the Spikes sprite onto an empty location in the Tile Palette window and when prompted save the new tile to Tiles\Spikes.asset. You should now have the spikes tile available for painting into the UI

Select the spikes tile in the Tile Palette and using the paint tool add the tile into the hole just before the goal as shown below.

Click to enlarge

To react to the obstacle and lose lives all you need to do now is add a Tilemap | Tilemap Collider 2D component to the Obstacles gameobject selecting Is Trigger. Next add a Collision Handler (Game Framework | Game Structure), enable When Within a Collision add an action Player | Change Health and set Amount to -0.01 as this is the amount of health we will lose every frame and we don’t want this to deplete too quickly.

Lives with the default settings so they lose a life every 2 seconds when they enter the spikes.

For extra polish we will add a particle effect. Right click the root of your Hierarchy and select Effects | Particle System. Rename the new game object ObstacleHitParticles. Modify the following settings:

  • Transform | Position: 0,0,0
  • Looping: Off
  • Start Lifetime: 1
  • Start Speed: 3
  • Start Size: 0.1
  • Start Color: Red for blood, or something more child friendly
  • Gravity Modifier: 0.5
  • Emission | Rate over Time: 0
  • Emission | Bursts: Add 1 row with could set to 30
  • Shape | Radius: 0.2

So we aren’t left with an empty gameobject when the particle system has finished you should also add the AutoDestructWhenParticleSystemFinished component (Game Framework | Display | Particles) to the gameobject with the default settings.

Now in the project pane create a folder named Prefabs in the project root. Drag ObstacleHitParticles into this folder to create a prefab (reusable copy of our effect) and delete the original from the scene. To the Collision Handler on the Obstacles Tilemap gameobject add a new action Hierarchy | Instantiate Prefab to the When Entering a Collision section (not when within as we will only show our particles once when they collide with the obstacle). Select our new ObstacleHitParticles prefab in the Prefab field and set Location to Colliding GameObject to instantiate the prefab particles where the collision occurs.

Run the game and see the lives decrease and our particle effect shown.

TODO: Add Audio, import file, Game Manager audio sources.

Replenish Health

We will give the player the option to replenish their lives. We will add this as a Tile Brush so you can easily paint them into the scene rather than positioning these manually. In this case we will add them as prefabs (similar to the gems) so that each one can have it’s own collider and action script.

Rename the Gems tilemap gameobject (under Grid) to Items.

Save the below image to your Sprites/ folder:

In Unity check the sprite has a Texture Type of Sprite (2D and UI) and set Pixels Per Unity to 128 to match the sprite pixel size. If using the prefabs from one of the extras themes then feel free to use the IconHeartBig sprite under FlipWebApps/GameFrameworkExtras/Themes/<Theme Name>/Sprites/UI to match the graphical style.

Next drag the sprite into the root of your scene to create a new GameObject and then drag that GameObject into your Prefabs folder to create a prefab of it. Delete the Game Object from the Hierarchy.

We now need to create a Tile Brush that uses our prefab so right click the Brushes folder in the Project tab and select Create | Prefab Brush. Rename the newly created Tile Brush to LifeBrush. Set Prefabs | Size to 1 and select the prefab that you created above.

Open the Tile Palette window, select Items as the Active Tilemap and LifeBrush as the active brush from the bottom left dropdown. Using the paint tool you should be able to add our life prefab to the scene. Add one as shown below.

Click to enlarge

If you run the game now you will see the sprite shows up, but nothing happens as we haven’t added any collider. Select the created prefab and add a Physics 2D | Circle Collider 2D component to the Obstacles gameobject selecting Is Trigger. Next add a Collision Handler (Game Framework | Game Structure). Change Disable After Use to GameObject and to When Entering a Collision add an action Player | Change Health with the Amount set to 1.

Again we will add a particle effect. We will use an image for the particle system so we need a custom material that defines how to render the particles. Add a new folder to the root of your Project called Materials. Right click the folder and select Create | Material. Rename the new material HealthGotParticles, select Mobile/Particles/Alpha Blended as the shader and select our Heart sprite as the image. Right click the root of your Hierarchy and select Effects | Particle System. Rename the new game object HealthGotParticles. Modify the following settings:

  • Transform | Position: 0,0,0
  • Looping: Off
  • Start Lifetime: 1
  • Start Speed: 3
  • Start Size: 0.5
  • Emission | Rate over Time: 0
  • Emission | Bursts: Add 1 row with could set to 30
  • Shape | Radius: 0.2
  • Size Over Lifetime: Enable, set Size to Curve from dropdown and change the curve at the bottom to fall from 0.5 to 0
  • Renderer | Material: Select our HealthGotParticles material

<image of settings>

Add also a AutoDestructWhenParticleSystemFinished component as before.

Drag the HealthGotParticles game object into the Prefabs folder and delete the original from the scene. Go back to the Heart prefab and to the Collision Handler add a new action Hierarchy | Instantiate Prefab. Select our new HealthGotParticles prefab in the Prefab field.

Run the game and see the health refill and our particle effect shown.

<TODO: Add audio>

CC-BY https://opengameart.org/content/3-heal-spells

Stars

Stars are a great way of getting people coming back to your game and can be used to give players an extra challenge requiring them to explore the levels more. Stars differ slightly from what we have seen so far in that they are not a counter making them useful for slightly different scenarios.

The number of stars a player has collected can be shown automatically shown on level select buttons, the game over dialog and anywhere else we want through the components that we will look at below or the API.

Advanced Tip: The number of stars defaults to 3, but can be set either through the level configuration files or code.

As our game stands currently, we can use the Stars buttons under the Level tab in the Cheat Functions Window to set what stars are won for the selected level. Note: Cheat Functions seems to have some issues with setting start due to caching (works from in game)!

StarsCheat

Similar to lives there are 2 components that will let us show start in our game. They are under Game Framework | Game Structure | Level:

  • Enable Based Upon Number Of Stars Won – allows us to show one of 2 different gameobjects based upon whether the specified star is won.
  • Create Star Icons – creates a given number of instances of a prefab (representing a star) based upon the global or level specific number of stars.

There are already created 2 prefabs (StarIcon and Stars ) that make use the above components. These lie under GameFramework\Prefabs\GameStructure\Level and there are both standard and themed versions available. As mentioned above we can create n StarIcon instances manually (1 for each star the user can have), or use Stars to automatically set them up.

Advanced Tip: The Level object contains a number of methods for manipulating and handling stars including the method StarWon(starNumber, isWon) for setting whether a star is won, and IsStarWon(starNumber) for checking if a star is won.

Drag the Stars prefab under the Canvas gamebject, set Pos X  to -10 and Pos Y  to –125 to position the prefab under the score in the top right of the display. Run the game use the cheat functions to update the stars won and verify the display updates.

Stars from targets

Stars can be used as a type of counter,  rewarding the player when certain targets are met. Add the StarsWonHandlerCoins component (Game Framework | Game Structure | Level) onto _SceneScope and set Target 1 Star to 2, Target 2 Star to 4 and Target 3 Star to 6. Run the game and collect gems to win stars.

Advanced Tip: This component uses the messaging system for increased speed and listens for the LevelCoinsChangedMessage.

Advanced Tip: The Targets From Level Config option would allow us to get the threshold from level configuration files. We will look at these later.

Stars by number

We don’t need to collect stars in order so we can have collected the third star, but not the second or first. We will use this approach in this game with the star number representing its position within the level where star 1 will be near the start, star 2 the middle, and star 3 near the end.

Remove the StarsWonHandlerCoins component from _SceneScope, and in the Cheat Functions window press the Preferences Reset button (general tab) to clear all settings including number of stars won.

Like with health we use a couple of extra steps to add these as a Tile Brush for convenience. Drag the FlipWebApps/GameFramework/Themes/Minimalist/Sprites/UIStarActiveBig sprite (or if using a theme from under FlipWebApps/GameFrameworkExtras/Themes/<Theme Name>/Sprites/UI) into the root of your scene to create a new GameObject. Set it’s Scale to 0.5, 0.5, 0.5 and then drag the GameObject into your Prefabs folder to create a prefab of it. Delete the GameObject from the Hierarchy.

We now need to create a Tile Brush that uses our prefab so right click the Brushes folder in the Project tab and select Create | Prefab Brush. Rename the newly created Tile Brush to StarBrush. Set Prefabs | Size to 1 and select the prefab that you created above.

Open the Tile Palette window, select Items as the Active Tilemap and LifeBrush as the active brush from the bottom left dropdown. Using the paint tool to add 3 stars to the scene with 1 near the start, 1 near the middle and 1 near the end.

Before running the game we still need to add a collider and handler. Select the created prefab and add a Physics 2D | Circle Collider 2D component to the Obstacles gameobject selecting Is Trigger. Next add a Collision Handler (Game Framework | Game Structure). Change Disable After Use to GameObject and to When Entering a Collision add an action Level | Win Star.

We will need to set the Win Star | Star Number individually for each star instance as this needs to be different, so find the 3 stars that were added under Grid/Items in the Hierarchy. Click each star in the Hierarchy to see which one in the scene it represents. You might want to drag these to reorder so they reflect the order in the level. Set the collision handler Star Number for each GameObject; 1 for the first star, 2 the second and 3 the third.

Run the level and you should see we can now e.g. collect the third star but not the first.

Finally to polish things off we add a particle effect and sound.

From our material we can’t reference the star sprite directly as it is part of a sprite sheet so save the below image to your Sprites/ folder:

Next right click the Materials folder and select Create | Material. Rename the new material StarGotParticles, select Mobile/Particles/Alpha Blended as the shader and select our Star sprite as the image. Right click the root of your Hierarchy and select Effects | Particle System. Rename the new game object HealthGotParticles. Modify the following settings:

  • Transform | Position: 0,0,0
  • Looping: Off
  • Start Lifetime: 1
  • Start Speed: 3
  • Start Size: 0.5
  • Gravity Modifier: 0.1
  • Emission | Rate over Time: 0
  • Emission | Bursts: Add 1 row with could set to 30
  • Shape | Radius: 0.2
  • Size Over Lifetime: Enable, set Size to Curve from dropdown and change the curve at the bottom to fall from 0.5 to 0
  • Renderer | Material: Select our HealthGotParticles material

<image of settings>

Add also a AutoDestructWhenParticleSystemFinished component as before.

Drag the StarGotParticles game object into the Prefabs folder and delete the original from the scene. Go back to the Star prefab and to the Collision Handler add a new action Hierarchy | Instantiate Prefab and select our new StarGotParticles prefab in the Prefab field.

Run the game and see the stars working in all their glory.

Advanced Tip: There is also support for animations in the game over dialog when new stars are won to give a nice effect – we will come back to this in a later tutorial.

Advanced Tip: If you want to reward with stars only at the end of a level based upon some other condition you can extend GameOver and override the GetNewStarsWon method.

<TODO: Add audio>