1.4: Die Button Prefab
If you completed the challenge at the end of [Chapter 3: Adding an OnRoll Event], you may have felt that “wiring” up all the labels, controllers, and buttons was incredibly tedious. If you decide to make changes to the buttons in the future, it will be painful to update all of them again. In this section, we will learn how to create a Prefab
for a Die Button
to manage the complexity of adding new buttons and updating existing buttons.
Before starting this section, you should complete [Chapter 3: Adding an OnRoll Event] as well as the challenge at the end.
When you’re ready, your scene should look and act similar to this:
Table of contents
- 01. Creating a Die Button Prefab
- 02. Creating a DieButton Script
- 03. Requiring a DieController
- 04. Add Listeners to the Button and Die Controller
- 05. Adding Prefabs to the Scene
- 06. Adding a ButtonLabel property to a DieButton
- Good Time to Commit
- Challenge: Add an OnLabelChange Event to the Die Button Prefab
- Challenge: Add the Remaining DieButton Prefabs to the Scene
- Good Time to Merge
- What’s Next
01. Creating a Die Button Prefab
In programming, we consider copy/paste coding a “code-smell”. It is indicative that we should refactor our code to use a method, class, or loop to reuse existing code and manage the complexity.
The same thing can be said about performing the same work over and over in the Unity editor / inspector. When this happens, it is usually a sign that we should find a way to manage the complexity. In this case, we will use a Prefab
to manage the complexity of a Die Button
.
The word Prefab
is short for Prefabrication
. Essentially, it is a “blueprint” for a GameObject
we would like to add to a Scene
. If we update the Prefab
it will update all of the GameObject
s throughout the project that use it.
Create a Folder for Prefabs
As always, we want to manage the complexity of our project. Let’s start by creating a place to store our prefabs.
- Navigate to the
Assets
folder - Right click
- Select
Create
>Folder
- Rename the folder
Prefabs
Create a Prefab from an Existing GameObject
Our d4 Group
will serve as the initial template for our Prefab
. To turn an existing object into a Prefab
you can drag it into a folder within the Project
panel.
- Open the
Prefabs
folder - Drag the
d4 Group
from theHierarchy
into thePrefabs
folder - Rename it to
Die Button
If all went well, the d4 Group
in the Hierarchy
should be blue. This tells us that this GameObject
is a Prefab
.
- Double click on the
Die Button
prefab in theProject
panel to open it
02. Creating a DieButton Script
Our goal is to make the Die Button
completely self contained. That is, all of the functionality should be encapsulated with a few control options in the Inspector
that will allow it to be customized when added to a Scene
. Currently, it is dependent on the d4 Controller
within the Scene
.
To do this, we will create a new C# Script
called DieButton
which will have allow us to expose values to the Inspector
.
Because the DieButton
will be part of the User Interface, we will create a new folder in our scripts directory for it.
- Navigate to the
Scripts
folder - Create a new folder
UI
- Within the
UI
folder - Create a new
C# Script
calledDieButton
- Double click to open the
DieButton
script
- The
DieButton
acts as aUI
element in our project so we will add it to theAdventureQuest.UI
name space.
- Update your
DieButton
class to be in theAdventureQuest.UI
name space. - We won’t need the
Update
method so remove it from the generated template
Note: We will use the Start
method to initialize the Prefab
so you can leave it
using UnityEngine;
namespace AdventureQuest.UI
{
public class DieButton : MonoBehaviour
{
void Start()
{
}
}
}
03. Requiring a DieController
A DieButton
must be connected to a DieController
. To enforce this, we can use the RequireComponent
attribute.
- Update the
DieButton.cs
file to use theAdventureQuest.Dice
name space- This allows access to the
DieController
class
- This allows access to the
- Add the
[RequireComponent(typeof(DieController))]
attribute to theDieButton
class
using UnityEngine;
using AdventureQuest.Dice; // <-- Required to use DieController
namespace AdventureQuest.UI
{
[RequireComponent(typeof(DieController))] // <-- Enforces that a DieController is available
public class DieButton : MonoBehaviour { // omitted for brevity }
}
Next, add the DieButton
script to the DieButton
Prefab
.
- Select
Die Button
in theHierarchy
- Be sure you are editing the
Prefab
- Be sure you are editing the
- In the
Inspector
selectAdd Component
- Search for
DieButton
- Add the Component
Notice: By adding the DieButton
script, it automagically added the DieController
script as well! This is because the DieButton
script enforces that the GameObject
it is attached to must have a DieController
.
04. Add Listeners to the Button and Die Controller
Now that we have a Die Controller
, we can register it on the Die Button
’s Button
listener.
- First, rename the
d4
element to beButton
Our Die Button
Prefab
won’t always have a d4
so it doesn’t make sense to have a child component called d4
.
Next, register the DieController
’s Roll()
method on the Button
’s OnClick
event.
- Select
Button
from theHierarchy
- In the
Inspector
find theButton
component - Find the
On Click
listener - Drag the
Die Button
into theNone (Object)
space
- Select the function drop down on the
Die Button
listener - Select
DieController
>Roll()
Finally, we must register the Label
on the DieController
’s OnRollString
event.
- Select
Die Button
in theHierarchy
- In the
Inspector
find theDieController
component - Add a listener to the
OnRollString
by clicking the+
button - Drag
Label
from theHierarchy
onto theObject (None)
field
- Select the function drop down on the listener
- Select
TextMeshPro
>text
Test the Die Button in the Die Roller Scene
Before continuing, test that the DieButton
Prefab
is working as expected.
- Return to the
Scene
by clicking the<
button near the top of the Hierarchy` panel.
Notice: The d4 Group
GameObject
in the Hierarchy
has “automagically” been updated to reflect the changes made in the Die Button
Prefab
- Select
d4 Group
in theHierarchy
- Find the
Die Controller
component in theInspector
- Set
Sides
to4
Notice: After modifying a Prefab
a small blue line appears on the left of the Inspector
next to the field. This indicates that this value has been modified on this Prefab
and won’t be changed if the Prefab
is modified.
- Run the scene and verify that the
d4
button updates properly.
05. Adding Prefabs to the Scene
Next, let’s replace each of the button groups with an instance of our Die Button
prefab.
- Delete each of the non-prefab button groups from the scene.
- Drag the
Die Button
Prefab
from theProject
panel into theDice
group in theHierarchy
- Rename the object to
d6 Button
Notice, the label of the button still says d4
. We could edit the label in the Scene
for each of our buttons. This would require us to be change a child component of the Prefab
. Typically, we want all of the options of a prefab to be accessible from the Prefab
s root object.
06. Adding a ButtonLabel property to a DieButton
- Update the
DieButton
class to have astring
property namedButtonLabel
- The property should be
public
, provide aget
, and have aprivate set
. - Initialize the property to be
string.Empty
- The property should be
- Add the
[field: SerializeField]
attribute to the property to expose it to theInspector
public class DieButton : MonoBehaviour
{
[field: SerializeField]
public string ButtonLabel { get; private set; } = string.Empty;
void Start()
{
}
}
When the DieButton
starts, we will check to see if the ButtonLabel
has been set in the Inspector
. If it has not been set. We will initialize it to be d{Sides}
using the DieController
s value. This gives the user flexibility to set the ButtonLabel
OR use a default value.
Because we have enforced that a DieButton
requires a DieController
using the RequireComponent
attribute. We can use the GetComponent<DieController>()
method to access the DieController
.
- Update the
Start()
method to check ifButtonLabel == string.Empty
- If it is
string.Empty
setButtonLabel
to be $”d{sides}”.- Use
GetComponent<DieController>().Sides
to get the number of sides.
- Use
void Start()
{
if (ButtonLabel == string.Empty)
{
DieController dieController = GetComponent<DieController>();
int sides = dieController.Sides;
ButtonLabel = $"d{sides}";
}
}
Test it in the Scene
Before continuing, verify that the ButtonLabel
field is visible in the Inspector
and that when you run the Scene
the field is updated to match the expected value.
Note: The label on the button will not update just yet.
Good Time to Commit
Now would be a good time to make a git
commit. Since you have not yet finished a feature, this would be a work-in-progress commit . More specifically, you just added a Die Button Prefab
. Before committing, be sure you save your Die Roller
scene (Unity doesn’t auto save for you).
Committing with GitHub Desktop (Click to Expand)
- Ensure the files you would like to commit are checked in the
Changes
tab.
-
Enter a summary for your commit. Think of this as the subject line of an email. It should be SHORT and to the point. Aim to be less than 50 characters. It is good practice to prefix the commit with the type of work that was done. For example:
- A feature:
feat: Implemented Die class
- A chore:
chore: Added image assets to project
- A bug fix:
fix: Removed off by 1 error
- A work in progress:
wip: Partial implementation of DieGroup class
- A feature:
-
Add a description to your commit. This should provide additional details about what is included in the commit. For example:
This commit adds a Die class which models a multi-sided die providing an
interface with 2 properties: `Sides` and `LastRolled`. Additionally, it provides
a single method: `Roll()` which "rolls" the die and randomly selecting one of
the sides.
Additionally, added unit tests to test the Die class specification.
- When you’re ready, click the
Commit
button
- Lastly, push your commit to GitHub by clicking the
Push origin
button
Challenge: Add an OnLabelChange Event to the Die Button Prefab
Alright! You’re almost there. All that is left is to update the Button
’s text when the DieButton
starts: To do this, you will:
- Add a
UnityEvent<string> OnLabelChange
property- Follow good practices with a
private set
- Expose it to the
Inspector
using the[field: SerializeField]
attribute - You will need to use the
UnityEngine.Events
name space to access theUnityEvent
class.
- Follow good practices with a
- Update the
Start()
method to callOnLabelChange.Invoke(ButtonLabel)
- This should happen after the label has been initialized
- This should always happen exactly once (even if the label wasn’t set)
- Update the
Button
’sText
component to default tod??
- Register the
Button
’sText
component to listen to theOnLabelChange
event- When it occurs, the
TextMeshPro
’stext
property should update
- When it occurs, the
Hint: In 1.3: Adding an OnRoll Event, we added an OnRollString
event to the DieController
. You should be able to apply the same steps here.
When you’ve finished the DieButton
component should look like this:
Additionally, when you run the Die Roller Scene
, the button labels should “automagically” update.
Challenge: Add the Remaining DieButton Prefabs to the Scene
Now that you have a nice Die Button
Prefab
, you can delete all of the DieController
s from the Die Roller
Scene
and add Die Button
Prefab
s in their place.
- Delete the
Dice
group containing your previousDieController
s - Add
Die Button
Prefab
s for the remaining dice.
When you’re finished, your scene should look and act like this:
Good Time to Merge
Now would be a good time to make a git
commit and merge into development
. You have just finished a feature. More specifically, you have implemented a Die Roller Scene.
Committing with GitHub Desktop (Click to Expand)
- Ensure the files you would like to commit are checked in the
Changes
tab.
-
Enter a summary for your commit. Think of this as the subject line of an email. It should be SHORT and to the point. Aim to be less than 50 characters. It is good practice to prefix the commit with the type of work that was done. For example:
- A feature:
feat: Implemented Die class
- A chore:
chore: Added image assets to project
- A bug fix:
fix: Removed off by 1 error
- A work in progress:
wip: Partial implementation of DieGroup class
- A feature:
-
Add a description to your commit. This should provide additional details about what is included in the commit. For example:
This commit adds a Die class which models a multi-sided die providing an
interface with 2 properties: `Sides` and `LastRolled`. Additionally, it provides
a single method: `Roll()` which "rolls" the die and randomly selecting one of
the sides.
Additionally, added unit tests to test the Die class specification.
- When you’re ready, click the
Commit
button
- Lastly, push your commit to GitHub by clicking the
Push origin
button
Merging with GitHub Desktop (Click to Expand)
On a team, you would typically create a Pull Request into your development
branch and request a code review. After the review is complete, you would merge your changes into the development
branch and delete your feature
branch.
If you are working on a project alone, there is no reason to create a Pull Request. Instead, you can merge directly into your development
branch.
- Ensure you are on the
development
branch. - Click the
Fetch
button to ensure you’re synced with GitHub - Ensure that you have
No local changes
(you may need to push first)
Although it isn’t absolutely necessary for there to be No local changes
, you will save yourself a lot of potential pain by ensuring you’re not merging into a “dirty” state.
- From the top menu select
Branch
>Merge into current branch...
- Select your feature branch
- Click
Create a merge commit
In a team, you will often make a Squash and merge commit
which takes all of the commits from your feature branch and reduces it to a single commit. When you have multiple devs, this can help reduce clutter in the commit history.
- Push your changes to GitHub
Finally, we can delete the feature branch.
- Switch to your feature branch.
MAKE SURE YOU ARE ON YOUR FEATURE BRANCH. DELETING THE BRANCH IS PERMANENT! DO NOT DELETE THE development
BRANCH
- From the top menu select
Branch
>Delete
- Ensure you are deleting the correct branch (you can’t undo this!)
- Select,
Yes, delete this branch on the remote
(this will delete it on GitHub too) - Click
Delete
- Lastly, if necessary, switch back to your
development
branch to continue working.
What’s Next
Congratulations! You’ve completed Part 1: Creating a Die Roller!
In Part 2 you will create a DicePool
class that allows for complex combinations of dice to be rolled together. For example, a weapon might deal damage equal to the result of rolling 2 six-sided dice and 3 four-sided dice. In table top RPG lingo this would be 2d6 + 3d4
.
Additionally, in Part 2 you will create a Roll Analysis
tool to explore the probability of the outcomes that rolling a DicePool
could produce. For example with 2d6 + 3d4
we know the minimum is 5 (5 dice total) and the maximum is 24 (26 + 34) but what are the odds of rolling a 12
?
When you’re ready, continue to Part 5: Modeling a Dice Group
Join the Discussion
If you're stuck, have questions, or want to provide feedback, you can do so below. However, I ask that you please refrain from posting complete solutions to any of the challenges.
Before commenting, you will need to authorize giscus. Alternatively, you can add a comment directly on the GitHub Discussion Board.