Hello and welcome to the next module in our course!
In the last lesson, you did an excellent job building the relationship UI panel. We now have a way to visualize the friendship and romance scores between characters, making the social simulation tangible for the player. That panel sets the stage perfectly for what's next: giving characters the ability to interact and influence those very scores through conversation.
This lesson marks the beginning of Module 8, where we'll build a Hierarchical Conversation System. Our goal today is to tackle the first, most crucial step: designing a JSON schema for conversation packs with a hierarchy of topics and specific interactions. We will define the data structure that will power every conversation in our game, making it flexible, data-driven, and ready for modding.
1. The Building Blocks: JSON and JSON Schema
Before we design our specific structure, let's ensure we have a solid grasp of the tools we'll be using. Our entire conversation system will be defined in JSON files, making it easy for us (and modders) to read, write, and extend.
As a CS student, you are almost certainly familiar with JSON. However, a quick review of how to represent hierarchical data is always useful.
This video from Web Dev Simplified offers a concise 10-minute refresher on JSON, focusing on its syntax and how to structure nested data.
Please watch the segments on JSON types (01:54 - 02:56) and structuring a JSON file (02:56 - 06:35). Pay close attention to how objects and arrays are nested to create a data hierarchy, as this is the fundamental pattern we will be using.
While JSON lets us structure our data, JSON Schema lets us define the rules for that structure. It acts as a contract or a blueprint, ensuring that any conversation pack we or a modder creates is valid. This is critical for preventing crashes and making the system robust.
Let's explore what JSON Schema is and how it works.
An Introduction to JSON Schema: A Practical Look
This video from Erik Wilde provides a practical look at JSON Schema. It explains its purpose and walks through creating a schema to validate data, which is exactly what we need to do.
Please watch the following parts: What is JSON Schema for? (01:02 - 03:38): Understand its role in data validation. Schema Fundamentals (10:05 - 14:08): Focus on the core keywords like type, properties, and required, and how they define the shape of a JSON object.
2. Designing a Hierarchical Conversation Structure
Our goal is a system that is both organized and context-aware. A flat list of every possible dialogue line would be unmanageable. Instead, we'll use a three-level hierarchy inspired by established dialogue systems, similar to the concepts discussed in the "Modular Design for Delivering Character Focused Dialogue in Games" paper.
Our hierarchy will be:
- Conversation Pack: The top-level container, grouping conversations by broad context (e.g., "Friends," "Strangers," "Romance"). This is analogous to a "module" in the paper.
- Topic: A specific subject of conversation within a pack (e.g., "Getting to Know You," "Gossip," "Deep Talk").
- Interaction: A single, actionable line of dialogue the player can choose (e.g., "Ask About Day," "Share a Secret," "Tell a Joke").
This structure allows us to filter options efficiently. First, the game checks which packs are available based on the relationship type. Then, it shows the relevant topics from that pack. Finally, it lists the specific interactions for the chosen topic.
Let's look at how this can be modeled in JSON, drawing inspiration from the practical example in the AFCode tutorial.
Creating Interactive Dialogue in Javascript with JSON: Part 1
This article provides a great, simple JSON structure for an interactive story. We'll use its core ideas—nodes, choices, and conditions—to build our more complex hierarchy.
Read through the article, paying close attention to the conceptual diagram and the final JSON example. Note how nodes are linked together and how choices can set variables. We will adapt this pattern for our system.
Our JSON Structure
Here is a concrete example of what a simple "Strangers" pack might look like in JSON, following our three-level hierarchy.
{
"id": "pack_strangers",
"name": "Strangers",
"conditions": {
"relationship_type": "Acquaintance"
},
"topics": [
{
"id": "topic_introduction",
"name": "Introductions",
"interactions": [
{
"id": "interaction_ask_name",
"prompt": "Ask name",
"outcomes": [
{ "type": "relationship_change", "target": "friendship", "value": 2 },
{ "type": "mood_change", "value": 1 }
]
},
{
"id": "interaction_wave",
"prompt": "Wave",
"outcomes": [
{ "type": "relationship_change", "target": "friendship", "value": 1 }
]
}
]
}
]
}
Let's break this down:
pack_strangers: The top-level object is the pack. It has a uniqueidand a displayname.conditions: This object dictates when this pack is available. Here, it's only for characters who are "Acquaintances".topics: An array of topic objects. This pack has one topic: "Introductions".interactions: Inside the topic, we have an array of interaction objects. Each has anid, aprompt(what the player sees in the UI), and an array ofoutcomes.outcomes: This is the "effect" of the interaction. It's an array of objects describing what happens, such as changing a relationship score or mood. This makes the system very data-driven.
3. Formalizing with JSON Schema
Now that we have a target JSON structure, let's create the JSON Schema to enforce it. This schema will be a file in our project that our Rust code can use to validate any conversation pack files it loads.
We'll define the structure for each part of our hierarchy: Interaction, Topic, and ConversationPack.
To handle the arrays of objects (like the interactions within a topic), we'll use array-specific keywords. The video on JSON Schema demonstrated this well.
An Introduction to JSON Schema: A Practical Look
Let's revisit the JSON Schema video to see how to define schemas for arrays and how to reuse definitions, which will be key for our hierarchical structure.
Rewatch the sections on handling arrays (20:03 - 22:12) and using references ($ref) for reusability (23:52 - 27:40). These concepts are directly applicable to defining a schema for our list of topics and interactions.
Here is the complete JSON Schema for our conversation packs. We use $defs (definitions) to create reusable schemas for Interaction, Topic, and Outcome, which keeps the main schema clean and readable.
conversation_pack_schema.json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Conversation Pack",
"description": "A collection of topics and interactions for a specific context.",
"type": "object",
"properties": {
"id": { "type": "string", "description": "Unique identifier for the pack." },
"name": { "type": "string", "description": "Display name for the pack." },
"conditions": { "type": "object" },
"topics": {
"type": "array",
"items": { "$ref": "#/$defs/Topic" }
}
},
"required": ["id", "name", "topics"],
"$defs": {
"Outcome": {
"type": "object",
"properties": {
"type": { "type": "string", "enum": ["relationship_change", "mood_change", "need_change"] },
"target": { "type": "string" },
"value": { "type": "number" }
},
"required": ["type", "value"]
},
"Interaction": {
"type": "object",
"properties": {
"id": { "type": "string" },
"prompt": { "type": "string" },
"conditions": { "type": "object" },
"outcomes": {
"type": "array",
"items": { "$ref": "#/$defs/Outcome" }
}
},
"required": ["id", "prompt", "outcomes"]
},
"Topic": {
"type": "object",
"properties": {
"id": { "type": "string" },
"name": { "type": "string" },
"conditions": { "type": "object" },
"interactions": {
"type": "array",
"items": { "$ref": "#/$defs/Interaction" }
}
},
"required": ["id", "name", "interactions"]
}
}
}
This schema precisely defines our data contract:
- A conversation pack must be an object with an
id,name, and an array oftopics. - Each item in the
topicsarray must conform to theTopicdefinition. - A
Topicmust have anid,name, and an array ofinteractions. - An
Interactionmust have anid,prompt, and an array ofoutcomes. - An
Outcomehas atype, avalue, and an optionaltarget(e.g., for specifying which relationship or need to change).
This schema is the blueprint we'll use to build our entire social interaction system. It's structured enough to be reliable but flexible enough to allow for rich, moddable content.
Conclusion
Fantastic work today. We've moved from a conceptual idea of a conversation system to a formal, well-defined data structure. This design is the cornerstone of all social interactions in our game.
Key Takeaways:
- Hierarchical Design: We've structured conversations into a logical Pack -> Topic -> Interaction hierarchy, making them manageable and context-aware.
- Data-Driven Approach: By defining interactions with
conditionsandoutcomesdirectly in the JSON, we empower ourselves and modders to create new content without writing new code. - JSON Schema as a Contract: We created a formal JSON Schema to validate our conversation packs, ensuring data integrity and preventing errors. This is a professional practice that makes our system far more robust.
In our next lesson, we will put this schema to use. We'll create our first two conversation pack JSON files—friends.json and strangers.json—and write the Rust code to load and parse them, bringing our design to life in the game engine.