In the previous chapter, we started work on a 2D adventure game. On reaching this point, we've now created a controllable character that can navigate a level using 2D physics.
Sprite shaping allows you to create freeform 2D environments in the Editor. Dynamic and exciting environments that would otherwise require custom sprite work can instead be generated from within the Editor by tiling sprites along spline paths. We'll go step by step through how to install the Sprite Shape Unity package, create custom shape profiles, and create several platforms to form a level for our 2D adventure game.
Up to this point, our levels have been relatively static. The only moving element (apart from particles) is that of the player. In this chapter, we'll fix that by adding moving platforms that the player can jump on. By implementing platforms in this game, you'll be able to do the same in any future game you develop, whether 2D or 3D.
By implementing a health system, including the UI, you can then create a similar system for any game, no matter the genre. Need a health system for your units in a real-time strategy (RTS) game or for a base on an alien planet in your next space game? After completing this chapter, you'll gain the necessary skills to implement this functionality.
This chapter will provide a crash course into these topics and act as a springboard for future projects.
This chapter continues the game by adding the following features:
This chapter assumes that you have not only completed the projects from the previous chapters but also have a good, basic knowledge of C# scripting generally, though not necessarily in Unity. In this chapter, you will continue the project started in Chapter 5, Creating a 2D Adventure Game, so you should read that chapter first if you haven't already.
The starting project and assets can be found in the book's companion files in the Chapter06/Start folder. You can start there and follow along with this chapter if you don't have your own project already. The end project can be found in the Chapter06/End folder.
Unlike the other games created in the book so far, our adventure game will span multiple scenes, which the player can move between freely. Supporting this functionality introduces us to some new and exciting problems in Unity that are well worth exploring, as we'll see later. In this section, we'll briefly introduce two levels that have been created using premade assets, and then detail how you can create a custom level using Unity's new sprite shaping tool. We'll start with the first two premade levels.
For now, let's make a second and third scene for the game, using the remaining background and foreground objects. The details to create a level are covered in depth in Chapter 5, Creating a 2D Adventure Game, and the completed levels can be found in Chapter06/End, so we won't go into detail here but rather briefly introduce them before moving on to exciting new topics.
Important Note
When testing the project from the GitHub repo, start the game at the Scenes/scene_start scene. This scene sets the correct player position and includes the QuestManager (more on quests later), which is required by the other scenes.
Level two is divided across two vertically arranged ledges, which will shortly be gapped by a set of moving platforms that we will create in the next section. The upper ledge is, for now, non-hazardous, but this will also be changed later as we add gun turrets that can shoot at the player. This level can be reached from the first level by walking off the left edge of the screen:
Level three can be reached from the first level by walking off the right edge of the screen. It consists of one ground-level plane featuring a house. It will be home to an NPC character that the player can meet and receive a quest to collect an item. This character will be created in the next chapter:
Both of these levels were created entirely with the techniques covered in the previous chapter using premade textures. However, Unity also provides tools to sculpt levels directly in the Editor. We'll look at one of these tools next as we create the last level of the game using Unity's sprite shaper.
We've just created two levels using existing assets. But what if you want to sculpt your own levels? There are several options available to you; for example, you could do the following:
Of course, there are many more options available, but these are some of the most common ones.
Tip
For more information on procedural generation in Unity, refer to https://www.packtpub.com/game-development/procedural-content-generation-unity-game-development.
In this section, we'll go with the third option and use Unity's new Sprite Shape tool to create a custom level. The Sprite Shape package allows us to shape platforms by dragging points in the Scene view. Unity will automatically generate a tiled platform along a spline path between the positions we specify.
Important Note
If you're not familiar with splines, that's fine. A quick internet search will bring up numerous, generally complicated definitions. For our specific purpose (that is, creating platforms using the sprite shaper), it is enough to know that a spline is a curve that connects two points. The actual implementation is handled internally by Unity, as you will see shortly.
Constructing platforms using the Sprite Shape package involves these steps:
We'll work through these in order, beginning with importing the package.
Importing the Sprite Shape package works in the same way as the packages we imported earlier:
Unity will then import the package to your project. We're now ready to create the Shape Profile, which will define how our platforms look.
As previously mentioned, the Sprite Shape tool works by tiling a texture along a path created by us and relies on a Shape Profile. The Shape Profile stores information on which tile to display and whether to fill the space in between the platform with a texture. You can also set different tiles to be drawn based on the angle of the tile, among other things.
Creating a new profile is simple:
Important Note
You may have noticed that you have two options when creating a profile; you could have created a Closed Shape or Open Shape profile. We selected the Open Shape Profile as this is the recommended choice for smaller platforms. It enables us to create single-edged platforms if we desire and initially has only one angle range to implement (more on this shortly), meaning that we can start creating platforms quicker. The Closed Shape Profile will enable the creation of shapes that enclose an area. Unity recommends this profile for larger platforms. For more information on the differences between the two, see the online documentation at https://docs.unity3d.com/Packages/[email protected]/manual/SSProfile.html.
An open platform profile defaults to only one angle range – 180 to -180 – which covers the full 360 degrees. You can rotate the image in the circle for a preview of what the tile will look like at various angles. For more detailed platforms, we could create several angle ranges and add unique textures for those angles. For example, we could add a specific texture that would only be shown if the tile was facing down. You can also add multiple textures for one angle range, and a random texture will then be selected.
Important Note
Creating new angle ranges and using multiple textures is not shown here as we only need the one texture regardless of the tiles' facing direction. For more information on creating new ranges, refer to the resources listed in the Further reading section of this chapter.
You may have noticed that the preview doesn't look quite right as there are gaps between each tile. To remove the gaps and enable the correct tiling of the sprite, we'll need to edit the border of the sprite:
You can play around with the location of this border to create drastically different-looking platforms.
If you now go back to the Platform sprite shaping profile, you'll notice that the preview has been updated and looks much better:
This is all the setup we need to do for our level. We're now ready to create the platforms using our newly created profile.
For our level, we'll want several different platforms, each with a unique look. We can accomplish this easily using the profile created in the last section:
With that done, you'll notice that the white square transforms. While it is still a square, it will now be using the textures we previously assigned to the profile. With this groundwork complete, now comes the fun bit – shaping the platforms.
You can change the Tangent Mode setting to create uniquely shaped platforms by selecting a new mode in the Inspector, just below the Edit Spline button. Linear (the default option) does not create curves between the selected points. This is the option used to create the platform in Figure 6.13. Continuous mode creates a curve between the control point and its neighbors. In this mode, the angle between the points is always at 180 degrees. The last option is Broken Mirrored; this mode also creates curves between the selected point and its neighbors. However, the angle of the curve can be adjusted and is not necessarily always 180 degrees.
Tip
You can delete a point by selecting it and pressing the Delete button.
And that's it, we've added three new levels to the game. The levels as they stand are relatively static, there are particle effects that add movement, but there are no moving interactive elements. We'll fix that now by adding moving platforms.
While the level structure is complete, they are relatively static environments. The only moving element is that of the player. We'll rectify this situation by creating a moving platform. The platform should move up and down continuously, and the player should be able to jump on it anytime to hitch a ride. We'll construct the object as a prefab so that it can be reused across multiple scenes. See Figure 6.19 for the result:
Now that we have an idea of the end goal, let's start by creating the platform GameObject.
To create the platform object, take the following steps:
Great, we have a platform that behaves mostly as expected. I say mostly because, for a moving platform, it's not very mobile. We'll fix that next by writing the scripts that will animate the platform.
The platform so far is static and motionless. To fix this, we could create a predefined animation sequence using the Animation Editor. However, instead, we'll write a script. Frequently, when making animations, you'll often need to make decisions about which option is best: C# animations or baked animations. Typically, you'll tend to use a script when an animation is simple and must apply to many objects with slight variations. Create and add the following PingPongMotion.cs script to the platform object:
public class PingPongMotion : MonoBehaviour
{
public Vector3 MoveAxes = Vector2.zero;
public float Distance = 3f;
private Vector3 OrigPos = Vector3.zero;
void Start ()
{
OrigPos = transform.position;
}
void Update ()
{
transform.position = OrigPos + MoveAxes * Mathf.PingPong(Time. time, Distance);
}
}
The following points summarize the preceding code block:
Attach the completed script to the platform object in the scene. It can also be reused easily for any other object that should move up and down regularly (or left and right).
As you test the new platforms, you may notice that if the player falls off the platform and down one of the holes at the bottom of the level, nothing happens. We would expect the player to lose a life when they fall down these pits, and that's what we'll implement next.
A common scripted feature required by all scenes, but not yet implemented, is the kill zone. This is the functionality to mark out a region of 2D space in the level that, when entered by the player, will kill or damage them. This is an especially useful tool to kill the player whenever they fall down a hole in the ground. The kill zone will be required in most levels because nearly every level created so far contains pits and holes in the ground. To implement this functionality, take the following steps:
A trigger differs from a collider; colliders prevent objects from passing through, and triggers detect when objects pass through, allowing you to perform custom behaviors.
Next, create a new script called KillZone.cs, which should be attached to the kill zone object in the scene. This script will be responsible for damaging the player's health for as long as they are in the kill zone. At this stage, there are several ways to approach implementation. One way is to destroy the player as soon as they enter the zone. The other is to damage the player for as long as they are in the zone. The second method is preferred here because of its versatility and contribution toward code reuse. Specifically, we get the option to damage the player by reducing their health at a particular rate (if we need to), as well as killing the player instantly by increasing the rate at which it is reduced. Let's see this at work in the following code block:0
public class KillZone : MonoBehaviour
{
public float Damage = 100f;
void OnTriggerStay2D(Collider2D other)
{
if(!other.CompareTag("Player"))return;
if(PlayerControl.PlayerInstance!=null)
{
PlayerControl.Health -= Damage * Time.deltaTime;
}
}
}
The following points summarize the preceding code block:
To use the new script, do the following:
We now have a method of reducing the player's health, yet the eventual player of our game has no way of knowing the current health status of the character. To fix this, we'll implement a UI health bar.
In the previous section, we introduced the first danger and hazard to the game: a zone that can damage and potentially kill the player. As a result, their health has the potential to reduce from its starting state. It's therefore useful both to us as developers and to gamers to visualize the health status. For this reason, let's focus on rendering the player's health to the screen as a UI health bar. Figure 6.25 offers a glimpse into the future, displaying the result of our work to come:
We'll start by configuring a canvas object that will contain our new UI.
To get started, we'll create and configure a canvas object and associated UI camera:
Important Note
Adding a canvas object to a scene will automatically create an EventSystem object if one does not exist already. This object is essential for the proper use of the UI system; without it, interaction with the UI will not work. If you accidentally delete it, the EventSystem can be recreated by choosing GameObject | UI | Event System from the application menu.
The created camera is almost ready to go! However, right now, it's configured to render everything in the scene, just like any other camera. This means that the scene is effectively being rendered twice by two cameras. This is not only wasteful and poor for performance, but it also makes the second camera redundant. Instead, we want the first original camera to show everything in the scene, in terms of characters and environments, but to ignore UI objects, and the newly created UI camera should only show UI objects.
To fix this, do the following:
By default, any newly created canvas is configured to work in the Screen Space Overlay mode, which means it renders on top of everything else in the scene that is not associated with any specific camera. In addition, all the UI elements will be sized and scaled based on this. To make our work simpler, let's start creating the UI by first configuring the Canvas object to work with the newly created UI camera:
Next, let's configure the Canvas Scaler component, which is attached to the Canvas object. This component is responsible for how the UI appears when the screen size is changed. For our game, the UI should scale relative to the screen size:
With the scene prepared for our game's UI, we can start creating and adding the elements required for our health interface.
Adding UI elements
Now we know that the UI elements will display correctly we can start creating them. We'll first add an image of the player:
If you cannot see the added head image, remember to assign the UI layer to render by the UI camera. In addition, you may need to offset the UI camera back along the z axis to include the head sprite within the camera frustum (viewing area).
To create the health bar, take the following steps:
The UI as it stands is static and doesn't update as the player takes damage. To fix this, we'll need to write a new script.
To implement the health bar functionality, we're going to overlap two identical health bars on top of each other, one red and one green. We'll then scale the green bar as the health reduces so that it reveals the red bar underneath. Before scripting this behavior, further configuration is necessary. Let's change the pivot of the health bar away from the center and to the middle-left point—the point from which the health bar should scale as it reduces and increases. To do this, take the following steps:
Before we jump into writing code, there's one more thing we need to do: create the green overlay for the health bar. This can be accomplished by duplicating the current health bar:
Now, we need to make a new script file linking the width of the green bar to the health of the player. This means that reductions in health will reduce the width of the green bar, revealing the red bar beneath. Create a new script file named HealthBar.cs and attach it to the green bar:
public class HealthBar : MonoBehaviour
{
public float MaxSpeed = 10f;
private RectTransform ThisTransform = null;
…
void Update ()
{
float HealthUpdate = 0f;
if(PlayerControl.PlayerInstance!=null)
{
HealthUpdate = Mathf.MoveTowards(ThisTransform. rect.width, PlayerControl.Health, MaxSpeed);
}
ThisTransform.sizeDelta = new Vector2(Mathf. Clamp(HealthUpdate,0,100),ThisTransform.sizeDelta.y);
}
}
The following points summarize the preceding code block:
To use the script, do the following:
Now whenever the player takes damage, the UI will update accordingly! With every point of damage the player takes, the green bar will shrink, and more of the red bar will be shown. To properly test the UI, it would be useful to have something else in the game that can damage the player (I know we currently have kill zones, but they kill the player too quickly). Luckily for us, in the next chapter, we'll create gun turrets that will do precisely that.
By learning how to take advantage of sprite shaping, you have added a new tool to your game development belt. If you're not so artistically minded (I'm certainly not), the ability to use a small set of tiles to create a diverse range of environments is hugely useful. Even if you are terrific at creating game art (lucky you!), sprite shaping can help bring your art to life in ways that weren't possible before.
Many games rely on a health system of some variety, and the knowledge gained by implementing the system in this chapter will be useful for a long time to come, no matter what genre of game you're working on (although there are some exceptions; a health system in most sports games wouldn't make much sense, for example). By building on this system by creating a custom health UI, you've laid the foundation for creating a dynamic UI for games in the future. While we focused on creating a health bar in this chapter, using the same principles, you could just as easily create a UI for your score, or the number of collectibles obtained.
By adding platforms to the game, you've introduced the first dynamic element. This dynamism will be extended in the next chapter as we complete the game by adding gun turrets, an NPC, and a quest system.
Q1. OnTriggerExit2D is a function that runs...
A. Every frame
B. When an object leaves a trigger
C. When the player completes the level
D. When the level exits
Q2. To interact with the UI, you need a(n)…
A. EventSystem
B. Animation System
C. Collider
D. Component
Q3. Enumerations are good for...
A. Storing lists of values
B. Counting objects
C. Searching for objects
D. Sorting objects by name
Q4. You can pick a random number using…
A. Random.Range
B. SelectRandomNumber
C. ChooseRandom
D. GetRandomNumber
For more information on Unity serialization, sprite shaping, and more, take a look at the following links: