Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 48

Designing a Conversational UI Flow

Recommended resources

Hello! Welcome to the next lesson in our journey to build a life simulation game.

In our last session, we created the data foundation for our conversation system. We built two JSON files, friends.json and strangers.json, which define a clear Pack -> Topic -> Interaction hierarchy. This data-driven approach is powerful, but data is only useful when the player can interact with it.

Today, we'll bridge the gap between our backend data and the player's experience. Our goal is to build a conversation UI in Unity that allows the player to navigate from pack to topic to interaction. We'll focus on creating a flexible and reusable UI component that can display these hierarchical choices in an intuitive way.

For a game like ours, a radial menu is an excellent choice for presenting interaction options. It's clean, efficient, and a staple in the genre.

Unity Tutorial Radial Menu Thumbnail
A radial menu, like the one shown here, is a common and effective UI pattern for presenting a selection of actions to the player in a game context.

1. The Core UI Components

Before we construct the complex radial menu, let's start with a simpler, yet essential, piece of the puzzle: the dialogue box itself. Eventually, when an interaction is chosen, we'll need a place to display the resulting dialogue text. It's good practice to build this simple component first.

We will create a basic UI panel that contains text fields for the character's name and their dialogue.

Dialogue Systems in Unity - Game Dev Beginner

Let's review a standard approach for creating a dialogue display in Unity. This article provides a concise overview and a simple script for a dialogue box controller.

Read the section 'How to display dialogue in your game'. Pay attention to the UI setup (a Panel with Text Mesh Pro objects) and the simple C# script that provides ShowDialogue and EndDialogue functions. You can create this simple prefab in your Unity project now; we'll use it in later lessons.

With that simple display element conceptualized, we can now move on to the main event: the hierarchical selection menu.



2. Building a Reusable Radial Menu

We are going to build a dynamic, reusable radial menu from scratch. Our approach will be to create a RadialMenu controller that can be populated with any set of options. This makes it perfect for our Pack -> Topic -> Interaction structure, as we can simply reuse the same UI component for each level of the hierarchy.

We will follow a clear, step-by-step tutorial to ensure our component is robust and well-structured.

Radial Menu Tutorial for Unity

This video by PabloMakes provides an excellent, hands-on guide to creating a modular radial menu in Unity. We will use it as our primary guide for this section. It covers everything from creating the individual button prefabs to handling clicks with delegates.

You will be working through several sections of this video. I'll break it down step-by-step. For now, just have the video ready.

Let's begin.

Step 2.1: The Menu Entry Prefab

First, we need to create a template for a single button in our menu. This button will represent a conversation option (a pack, a topic, or an interaction) and will consist of an icon and a text label.

  • Action: Follow the instructions in the video from 00:00:36 to 00:02:10.
  • Goal: Create a RadialMenuEntry prefab that includes a background image, an icon, and a TextMeshPro label. Create the basic RadialMenuEntry.cs script and attach it.

Step 2.2: The Radial Menu Controller

Next, we'll create the main controller script. This script will be responsible for spawning our entry prefabs and managing them as a group.

  • Action: Follow the video from 00:02:10 to 00:04:24.
  • Goal: Create an empty RadialMenu GameObject and a RadialMenu.cs script. In the script, set up the public fields for the entry prefab and a list to hold the instantiated entries. Hook up the prefab and the Open function to a test button in the Unity Editor.

Step 2.3: The Circular Layout

Currently, the entries spawn on top of each other. Now, we'll add the logic to arrange them in a circle. This involves a bit of trigonometry, which the video explains clearly.

  • Action: Follow the video from 00:04:35 to 00:06:30.
  • Goal: Implement the Rearrange function in RadialMenu.cs. This function will use Mathf.Sin and Mathf.Cos to calculate the position of each entry around a central point. You will also add a public radius field to control the size of the menu.

At this point, you should have a button that, when clicked, spawns a set of entries in a perfect circle.

Step 2.4: Handling Clicks with Delegates

This is the most critical step for making our menu functional. We need a way for each button to perform a specific action when clicked. A simple OnClick() function won't work, because the "Ask About Day" button needs to do something different from the "Share Secret" button.

This is a perfect use case for delegates. A delegate is essentially a variable that holds a reference to a function. We can give each menu entry its own unique delegate, allowing us to assign custom behavior at runtime.

  • Action: Follow the video from 00:14:56 to 00:17:10.
  • Goal:
    1. In RadialMenuEntry.cs, define a delegate type RadialMenuEntryDelegate.
    2. Add a callback variable of this delegate type.
    3. In the OnPointerClick function, invoke the callback if it has been assigned.
    4. In RadialMenu.cs, create a function (like SetTargetIcon in the video) that will serve as the callback.
    5. When adding entries, use the SetCallback function to assign this behavior to each button.

While the video uses the callback to change a central icon, in our system, this callback will be the key to navigating deeper into the conversation tree.



3. Designing the Hierarchical Navigation Logic

You have now built a powerful, reusable RadialMenu component. How do we use it to navigate the Pack -> Topic -> Interaction hierarchy we designed?

We don't need to implement the full Rust-to-Unity data pipeline today. Instead, let's design the C# logic in Unity that will drive the navigation. We can create a manager script, let's call it ConversationUIManager, to orchestrate the process.

The flow would look like this:

  1. Player clicks a character. The ConversationUIManager is triggered. It asks the Rust backend for a list of available conversation packs for the two characters involved.
  2. Display Packs. The manager receives the list of packs. It then calls a function on your RadialMenu component, let's say Show(packList). For each pack, it sets the button's text to the pack's name and assigns a callback function: OnClickPack(pack).
  3. Player selects a pack. The player clicks the "Friendly" pack button. The OnClickPack delegate is invoked.
  4. Display Topics. Inside OnClickPack, the manager clears the current menu and asks for the list of topics within the "Friendly" pack. It then calls radialMenu.Show(topicList). For each topic, it assigns a new callback: OnClickTopic(topic).
  5. Player selects a topic. The player clicks "Catch Up". The OnClickTopic delegate is invoked.
  6. Display Interactions. The manager again clears the menu and shows the final list of interactions within the "Catch Up" topic, assigning the final callback: OnSelectInteraction(interaction).
  7. Player selects an interaction. The player clicks "Ask About Day." The OnSelectInteraction delegate is invoked. This function closes the radial menu, sends the chosen interaction id to the Rust backend to be processed, and waits for the outcome (e.g., dialogue text to display in the simple dialogue box we discussed earlier).

This hierarchical flow is very similar to the branching dialogue tree described in the Game Dev Beginner article.

Dialogue Systems in Unity - Game Dev Beginner

This section of the article describes how to build a dialogue tree using Scriptable Objects and structs. While our data is in JSON, the navigation logic is conceptually the same: one choice leads to a new set of options.

Read the section 'How to create a dialogue system with multiple options (using a dialogue tree)'. Focus on the RunDialogue coroutine. Notice how after displaying some lines, it presents a BranchPoint with answers. When an answer is chosen, it recursively calls RunDialogue with the new section index. This is the same logic we will use: a selection on our radial menu will trigger a function that repopulates the same menu with new data.

By using the same radial menu component and simply feeding it different data and callbacks at each step, we can create a seamless and intuitive navigation system for the player.



Conclusion

Fantastic work today! You've tackled a significant piece of the frontend puzzle. You now have a functional, data-driven UI component that is ready to be integrated with our game's logic.

Key Takeaways:

  • Component-Based UI: We built a reusable RadialMenu component in Unity, capable of displaying a dynamic set of options in a circle.
  • Interactive Callbacks: You learned how to use C# delegates to assign unique behaviors to each menu button at runtime, a crucial pattern for dynamic UIs.
  • Hierarchical Navigation Design: We designed the logical flow for using this single component to navigate our three-level Pack -> Topic -> Interaction conversation structure.

In the next lesson, we will move back to the Rust backend. Now that we have a UI ready to display options, we need to write the logic that decides which options to show. We will filter available conversation topics based on the relationship type between two characters, bringing our conditions from the JSON files to life.