Skip to main content Link Search Menu Expand Document (external link)

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:

Finished Die Roller

Table of contents
  1. 01. Creating a Die Button Prefab
  2. 02. Creating a DieButton Script
  3. 03. Requiring a DieController
  4. 04. Add Listeners to the Button and Die Controller
  5. 05. Adding Prefabs to the Scene
  6. 06. Adding a ButtonLabel property to a DieButton
  7. Good Time to Commit
  8. Challenge: Add an OnLabelChange Event to the Die Button Prefab
  9. Challenge: Add the Remaining DieButton Prefabs to the Scene
  10. Good Time to Merge
  11. 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 GameObjects 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.

  1. Navigate to the Assets folder
  2. Right click
  3. Select Create > Folder
  4. Rename the folder Prefabs

Create Prefab Folder

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.

  1. Open the Prefabs folder
  2. Drag the d4 Group from the Hierarchy into the Prefabs folder
  3. Rename it to Die Button

Create Prefab

If all went well, the d4 Group in the Hierarchy should be blue. This tells us that this GameObject is a Prefab.

  1. Double click on the Die Button prefab in the Project panel to open it

Open Prefab

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.

  1. Navigate to the Scripts folder
  2. Create a new folder UI
  3. Within the UI folder
  4. Create a new C# Script called DieButton
  5. Double click to open the DieButton script

Create Die Button

  • The DieButton acts as a UI element in our project so we will add it to the AdventureQuest.UI name space.
  1. Update your DieButton class to be in the AdventureQuest.UI name space.
  2. 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.

  1. Update the DieButton.cs file to use the AdventureQuest.Dice name space
    • This allows access to the DieController class
  2. Add the [RequireComponent(typeof(DieController))] attribute to the DieButton 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.

  1. Select Die Button in the Hierarchy
    • Be sure you are editing the Prefab
  2. In the Inspector select Add Component
  3. Search for DieButton
  4. Add the Component

Add Die Button Script

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.

  1. First, rename the d4 element to be Button

Rename Button

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.

  1. Select Button from the Hierarchy
  2. In the Inspector find the Button component
  3. Find the On Click listener
  4. Drag the Die Button into the None (Object) space

Add Listener

  1. Select the function drop down on the Die Button listener
  2. Select DieController > Roll()

Add Roll Function

Finally, we must register the Label on the DieController’s OnRollString event.

  1. Select Die Button in the Hierarchy
  2. In the Inspector find the DieController component
  3. Add a listener to the OnRollString by clicking the + button
  4. Drag Label from the Hierarchy onto the Object (None) field

Add OnRoll Listener

  1. Select the function drop down on the listener
  2. Select TextMeshPro > text

Function Update text

Test the Die Button in the Die Roller Scene

Before continuing, test that the DieButton Prefab is working as expected.

  1. Return to the Scene by clicking the < button near the top of the Hierarchy` panel.

Back to Die Scene

Notice: The d4 Group GameObject in the Hierarchy has “automagically” been updated to reflect the changes made in the Die Button Prefab

  1. Select d4 Group in the Hierarchy
  2. Find the Die Controller component in the Inspector
  3. Set Sides to 4

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.

Modify Sides

  1. 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.

  1. Delete each of the non-prefab button groups from the scene.

Delete Buttons

  1. Drag the Die Button Prefab from the Project panel into the Dice group in the Hierarchy
  2. Rename the object to d6 Button

Add Prefab

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 Prefabs root object.

06. Adding a ButtonLabel property to a DieButton

  1. Update the DieButton class to have a string property named ButtonLabel
    • The property should be public, provide a get, and have a private set.
    • Initialize the property to be string.Empty
  2. Add the [field: SerializeField] attribute to the property to expose it to the Inspector
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 DieControllers 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.

  1. Update the Start() method to check if ButtonLabel == string.Empty
  2. If it is string.Empty set ButtonLabel to be $”d{sides}”.
    • Use GetComponent<DieController>().Sides to get the number of sides.
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.

Validate Button Label

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)

  1. Ensure the files you would like to commit are checked in the Changes tab.

Check the Files to Commit

  1. 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
  2. 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.
  1. When you’re ready, click the Commit button

Add Description

  1. Lastly, push your commit to GitHub by clicking the Push origin button

Push to Origin

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:

  1. 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 the UnityEvent class.
  2. Update the Start() method to call OnLabelChange.Invoke(ButtonLabel)
    • This should happen after the label has been initialized
    • This should always happen exactly once (even if the label wasn’t set)
  3. Update the Button’s Text component to default to d??
  4. Register the Button’s Text component to listen to the OnLabelChange event
    • When it occurs, the TextMeshPro’s text property should update

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:

Finished Component

Additionally, when you run the Die Roller Scene, the button labels should “automagically” update.

Challenge Finished

Challenge: Add the Remaining DieButton Prefabs to the Scene

Now that you have a nice Die Button Prefab, you can delete all of the DieControllers from the Die Roller Scene and add Die Button Prefabs in their place.

  1. Delete the Dice group containing your previous DieControllers
  2. Add Die Button Prefabs for the remaining dice.

When you’re finished, your scene should look and act like this:

Final Challenge Complete

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)

  1. Ensure the files you would like to commit are checked in the Changes tab.

Check the Files to Commit

  1. 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
  2. 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.
  1. When you’re ready, click the Commit button

Add Description

  1. Lastly, push your commit to GitHub by clicking the Push origin button

Push to Origin

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.

  1. Ensure you are on the development branch.
  2. Click the Fetch button to ensure you’re synced with GitHub
  3. Ensure that you have No local changes (you may need to push first)

Ensure Development Branch

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.

  1. From the top menu select Branch > Merge into current branch...

Merge Into Current

  1. Select your feature branch
  2. Click Create a merge commit

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.

  1. Push your changes to GitHub

Push Changes

Finally, we can delete the feature branch.

  1. Switch to your feature branch.

Feature Branch

MAKE SURE YOU ARE ON YOUR FEATURE BRANCH. DELETING THE BRANCH IS PERMANENT! DO NOT DELETE THE development BRANCH

  1. From the top menu select Branch > Delete

Branch - Delete

  1. Ensure you are deleting the correct branch (you can’t undo this!)
  2. Select, Yes, delete this branch on the remote (this will delete it on GitHub too)
  3. Click Delete

Final Delete

  1. 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.