Creating spawnable health packs in Unreal Engine
Health packs are often a basic component of video games. While many games have a dedicated class of the type healer, whose main purpose is to heal other players, health packs are often found lying around regardless. The mechanic works in one of the following two ways - you walk over the health pack, it heals you up to a maximum value, and then it takes a little while to respawn again; or, you "pick up" the health pack by interacting with it, and you carry it in your inventory, to be used later at a convenient time. The latter approach requires us to build a functional inventory system, and as such, will involve more work. For now, I will explain how to create health packs of the former variety, and maybe take up the other type in another article.
Let's set up the scene before we dig into programming. Start a new project with a third person blueprint template. The next step is to add a health pack actor to our scene, for which we need a kick-ass 3D asset. Thankfully, an awesome artist on SketchFab called saruedaacevedo has made a beautiful model for us that is completely free, which you can download here -
Once you download and unzip everything, you should find the 3D model (the
.obj file) in "source/Archive" and the textures in the "textures" folder. Import everything to your Unreal project (I recommend making at least one new folder for this to keep things organized), and then set up the material "HealthPackMat" as shown in the figure below. You should have yourself a game-ready 3D health pack in no time.
(Here's how the textures should be mapped to their corresponding material nodes:
- HealthPack_Albedo: Base Color
- HealthPack_Metallic: Metallic
- HealthPack_roughness: Roughness
- HealthPack_Emission: Emissive Color
- HealthPack_Normal: Normal
- HealthPack_Occlusion: Ambient Occlusion
The last step in making this health pack actor is to create a new blueprint (of the type "Actor"). Once you've done that, click on "Add Component", select "Static Mesh" from the list, and name it "HealthPackMesh". Then, go to the Static Mesh property in the Details pane, and assign our new "HealthPack" mesh to it.
To make it look a little more attractive and noticeable, let's add a Rotating Movement component to the blueprint. Also, select the static mesh and make the X component of its rotation -45°. After completing these two steps, test it out by dragging the blueprint onto the scene. You should see something similar to this -
Making a consumable health pack
The next step is adding the basic interactivity. Our task is basically twofold -
- Make the health pack disappear when the player walks into it
- Respawn the health pack after a fixed amount of time
For step 1, we'll make use of the "BeginOverlap" event of the blueprint. The idea is, when a playable character "runs over" an instance of this blueprint in the scene, it will be hidden for a while and as such can no longer be interacted with. After the specified cooldown time has passed, we'll make it reappear. This is what this section of the event graph will look like.
First, we're checking if the class of the actor that hit our health pack is "Third Person Character" (In a production-ready game, this would most likely be a custom base class, but since we don't have that we'll just use Third Person Character). If the actor that touched the health pack is indeed of that class, we will
- set the health pack blueprint invisible,
- wait for 3 seconds (by using the "Delay" node),
- then make it visible once again.
This looks good to go, but isn't. The problem is, with default collision settings, the player would "bump into" the health pack rather than running over it. From the perspective of the game engine, this means that a Hit event will be fired, not an Overlap one. To change that, select the HealthPackMesh in the blueprint editor, and then in the details pane, look under the Collision section. You'll find a property called "Collision Presets", which is most likely set as "Default". Change that to "OverlapAll".
A better visual rendition would be to render the health pack translucent (semi-transparent) while it's on cool down, like in Overwatch. That process involves dynamic materials and material instances. While not complicated, it is unfortunately outside the scope of this discussion. The official Unreal YouTube channel is a great place to learn about dynamic material instances.
We've completed the first part of this exercise - creating an interactive object that respawns at regular intervals. However, it doesn't quite behave like a health pack yet, because we don't even have a damage/healing system in place for it to be able to actually heal. Let's fix that now.
The first thing we will do is have some kind of mechanism to cause damage to the player. We don't have enemies that hurt the player, so we're going to devise a simple environmental damage system instead - we'll create a "damage zone" inside the map, such that the player takes damage when they walk into it. As a visual indicator, I'll use a cube (that I have styled with a custom material). The most important part of this arrangement will be something called a "Pain Causing Volume" (which we'll refer to as PCV, for short, here onwards). A PCV is an actor that has built-in damage-causing capabilities, while allowing us to customize the damage parameters from the details panel.
Once you have added the cube, place the PCV so that it more or less covers the same surface area. Once you're done, you should get something like this -
Now let's modify the damage parameters. Select the PCV and look into the details pane. Under the section "Pain Causing Volume", you should be able to find the keys Damage Per Sec and Pain Interval. Change their values to 0.15 and 0.01, respectively. This means that the player will take 0.15 damage over a period of 1 second, but the damage is going to be applied in incremental bits, every 0.01s. Doing this will ensure the damage over time occurs in a smooth manner (we'll see this in action when we create the health bar, don't worry). Leave the other parameters unchanged for now.
Damage Type is a very useful parameter if your game has multiple kinds of damage (fire damage, freeze damage, fall damage, for example). If you want the pain causing volume to be inflicting just one particular type of damage, you can create your own DamageType class and pick that in the details pane.
Now we need... something to keep track of the player's health. To achieve this, we'll add a new Float variable called
Health inside the player character blueprint (the Third Person Character blueprint, in our case). Once we compile the blueprint, we'll be able to assign a default value to it. Because health is typically displayed as a percentage of the character's maximum HP, we'll keep its default value as 1.0. This way, any fractional value between 0 and 1 will represent how much health the player has.
Next, we'll make use of the event "Any Damage". The idea is, whenever this event is registered, it means that the player is taking some damage, so we'll reduce the value of current health by the amount of damage they are taking. This is also why we set the value of Damage Per Sec to 0.15 for the PCV, because we want the damage applied to be a fraction between 0 and 1.
To see this in action, let's create a HUD. We'll create a Widget blueprint in out Content Browser, name it "HUD", and add it to the player viewport. The last part can be done via any blueprint as long as the HUD gets attached at the start of the game. To keep things simple, I'm doing it in the Character blueprint itself (in a bigger game, using GameMode might be a better idea).
Now we'll launch the HUD widget blueprint. The big rectangular canvas element you see here represents the player screen, and anything you add on it will be displayed in the player UI. Because health is usually displayed as a bar and not a number, we will add a progress bar widget to this screen, which will change according to the health information. Once you've done that, look in the details pane for the progress bar. You'll find a key called Percent, a value next to it, and a button which reads "Bind" after that. If you click on the button, you will see an option called "Create Binding".
When you click on that option, Unreal will automatically create a function called
GetPercent_0 and take you to that function. What we need to do now is have this function return the current health, which will be bound dynamically to the value of the progress bar. Progress bar by default takes a float value between 0 and 1, as does health the way we designed it. So all we need to do is to retrieve the value of the player health, and return that value from this function. To do that, right-click anywhere in the empty space, and find and select "Get Player Character". Because we know that our player character is of the type third person, we can now cast it to a Third Person Character, and retrieve the value of health from that. Once we have got the health information, we can plug it in directly to the return node.
In computer programming, "casting" is the process of converting an object of a class into an object of a derived class, or to that of a parent class. Here, the class Character (which "Get Player Character" returns) doesn't have the health information associated with it, so we're casting it to Third Person Character in order to access that.
Now, we only have one last part left - to make the health pack actually "heal" the player. The procedure for that will be the exact opposite of implementing damage - instead of subtracting from the current health, we'll add a definite amount to it every time the player walks over a health pack. We also need to make sure the health pack never "overheals" the player, i.e., never tries to push the player's health beyond 100%. For that, we'll use a "Clamp" node, which will force the final value of health to be between 0% and 100% (in the range 0.0-1.0, in our case).
And once you've completed the steps above, you should be able to see it working perfectly in action.
And that should be all! Let me know if you have questions in the comments below. And as always, don't forget to subscribe!
(Cover image from Unreal Marketplace)