Hello! Welcome back to our course on building a JRPG engine.
In our last two lessons, we assembled the core machinery for interactive storytelling. First, we built an EventInterpreter that can make decisions based on game flags (switches/variables), allowing events to have different "pages" of behavior. Then, we created the trigger system, which connects these events to the game world, allowing them to be started by player actions, touch, or automatically on map entry.
Today, we get to see the payoff. We will put all these pieces together to bring our first interactive object to life. This lesson fulfills the learning outcome: Script a treasure chest event that uses a game flag to prevent re-opening. We will combine an action_button trigger, multiple event pages, and a special type of game flag called a "self-switch" to create an object that remembers its state—the classic JRPG treasure chest.
This is a significant step, moving from building architectural components to creating actual game content.
1. The Anatomy of a Stateful Event
Before we define our event in JSON, let's break down the logic of a treasure chest. The experience we want to create is:
- The player sees a closed chest.
- The player interacts with it (action button).
- The chest opens, a sound plays, and a message appears: "You found a Potion!" The item is added to the player's inventory. The chest's appearance changes to open.
- If the player interacts with the open chest again, a different message appears: "The chest is empty."
This behavior implies the chest has two distinct states: unopened and opened. In our engine's architecture, these states map directly to the event pages we designed in a previous lesson.
- Page 1: The "unopened" state. This page runs by default.
- Page 2: The "opened" state. This page should only run after the chest has been opened.
The critical question is: how does the event know to switch from executing Page 1 to Page 2? This is where game flags come in.
2. Self-Switches: An Event's Private Memory
We could use one of our global switches (e.g., Switch #51: "Basement Chest Opened"). However, imagine a dungeon with 50 chests. You would quickly consume your global switches, which are better reserved for major plot points that many events might need to check (e.g., "Has the dragon been defeated?").
For events that only need to track their own internal state, there's a more elegant solution: Self-Switches.
A self-switch is a flag that belongs exclusively to a single event. Think of it in terms of your front-end development experience: a global switch is like a value in a global state store (like Redux or a Vuex store), accessible by any component. A self-switch, on the other hand, is like a private property within a single component's local state (e.g., this.isOpened = true). It's encapsulated and doesn't pollute the global scope.
To see a clear explanation of this distinction, watch the following video. It demonstrates the problem with using global switches for every small event and introduces self-switches as the solution.
RPG Maker MV Tutorial #17 - Self-Switches!
The video 'RPG Maker MV Tutorial #17 - Self-Switches!' by SomeRanDev provides an excellent conceptual breakdown of why self-switches are the correct tool for this job.
Watch the sections 'Solution 1: Using Global Switches (and its flaw)' (0:31 - 2:28) and 'Solution 2: Using Self-Switches' (2:28 - 3:18). This will solidify the difference between the two types of flags.
Each event in our engine will have its own set of self-switches (typically named A, B, C, and D, following the RPG Maker convention). When we turn on Self-Switch 'A' for Event_001, it has no effect on Self-Switch 'A' for Event_002.
3. Scripting the Treasure Chest Event
Now, let's write the JSON "script" for our treasure chest. This data structure brings together everything we've built in this module. We'll assume this event is on our map at coordinates (x: 7, y: 5).
{
"id": "chest_001",
"x": 7,
"y": 5,
"trigger": "action_button",
"pages": [
{
"id": 1,
"image": { "tileset": "dungeon", "frame": 0 },
"conditions": [],
"commands": [
{ "command": "play_sound", "file": "treasure.ogg" },
{ "command": "show_text", "text": "You found a Potion!" },
{ "command": "change_items", "itemId": 4, "quantity": 1 },
{ "command": "control_self_switch", "switch": "A", "value": "on" }
]
},
{
"id": 2,
"image": { "tileset": "dungeon", "frame": 1 },
"conditions": [
{ "type": "self_switch", "switch": "A", "value": "on" }
],
"commands": [
{ "command": "show_text", "text": "The chest is empty." }
]
}
]
}
Let's dissect this structure:
trigger: "action_button": This tells the engine to run this event only when the player is facing it and presses the action key. (From Lesson 3.6)- Page 1 (
"id": 1):image: Shows the closed chest graphic (frame 0).conditions: An empty array means this is the default page.commands: This is the sequence for the "unopened" state.- Play a success sound.
- Show the "found item" message.
- Add the item to the inventory (we assume
change_itemsis a command our interpreter now knows). control_self_switch: This is the key. It flips this event's internal Self-Switch 'A' to the ON state.
- Page 2 (
"id": 2):image: Shows the open chest graphic (frame 1).conditions: This page is only active if this event's Self-Switch 'A' is ON. (From Lesson 3.5)commands: A simple message indicating the chest is now empty.
4. How the Engine Executes the Event
Let's trace the logic flow within our engine.
First Interaction:
- The player presses the action button while facing the chest at (7, 5).
- Our
Gameloop callscheckActionTrigger(), which finds thechest_001event. - The
EventInterpreteris started with this event's data. - The interpreter evaluates the pages in reverse order. Page 2's condition (
self_switch A == on) isfalse. Page 1 has no conditions, so it is selected. - The commands from Page 1 are executed. The final command sets the internal
self_switches['A']property for this event instance totrue. - After the event concludes, the game continues. The
Gameobject, when rendering the event, will re-evaluate its pages to determine the correct graphic. Since Self-Switch 'A' is now ON, Page 2 has the highest priority, and the open chest graphic is displayed.
Second Interaction:
- The player interacts with the same event again.
checkActionTrigger()fires, and theEventInterpreterstarts.- The interpreter evaluates pages. This time, Page 2's condition (
self_switch A == on) istrue. Since it's the highest-numbered page with a met condition, it is selected. - The command from Page 2 is executed:
show_text("The chest is empty."). - The event concludes.
This demonstrates a complete, self-contained interactive loop. The same pattern can be used for levers, readable signs that change, one-time-use fountains, and countless other JRPG staples.
To see this exact logic demonstrated in RPG Maker, which uses the same event page and self-switch system, watch the following clip.
RPG Maker MV Tutorial: Basic Events
The video 'RPG Maker MV Tutorial: Basic Events' shows a practical application of this concept. You will see how setting 'Control Self Switch A: On' on the first page activates a second, empty page.
Watch from 'Implementing Self-Switches for Treasure Chest State' (12:05) to the end of 'Adding 'Chest is Empty' Message' (17:01). Notice how the second event page has the 'Self Switch A' condition and contains the 'Chest is empty' text.
Test your understanding!
Using the JSON format we've established, how would you script a one-time-use healing fountain?
- Interaction 1: The player interacts, a message says "The water is revitalizing! The party is fully healed!", the party's HP/MP are restored, and a self-switch is turned on.
- Interaction 2: The player interacts again, and a message says "The water is cool and refreshing."
Try to write out the pages array for this event. You can invent command names like {"command": "heal_party"}.
Show answer
Here is a possible implementation for the fountain event's pages array:
"pages": [
{
"id": 1,
"image": { "tileset": "world", "frame": 10 }, // Assuming a fountain graphic
"conditions": [],
"commands": [
{ "command": "show_text", "text": "The water is revitalizing! The party is fully healed!" },
{ "command": "heal_party", "amount": "full" },
{ "command": "control_self_switch", "switch": "A", "value": "on" }
]
},
{
"id": 2,
"image": { "tileset": "world", "frame": 10 }, // Image is the same
"conditions": [
{ "type": "self_switch", "switch": "A", "value": "on" }
],
"commands": [
{ "command": "show_text", "text": "The water is cool and refreshing." }
]
}
]
This follows the exact same pattern as the treasure chest, demonstrating the versatility of this design.
Conclusion
Congratulations! You have now fully scripted your first stateful, interactive JRPG event. By combining the event page system, triggers, and self-switches, we've created a reusable pattern for objects that need to remember past interactions.
Key Takeaways:
- Self-switches are flags used for an event's private, internal state, preventing pollution of the global switch list.
- The fundamental pattern for stateful events is:
- Page 1 (Initial State): Performs an action and sets a self-switch to 'ON'.
- Page 2 (Final State): Has a condition to check if that self-switch is 'ON' and performs a different action.
- Our declarative JSON structure is now powerful enough to "script" complex behaviors that our
EventInterpretercan execute without needing new engine code for every object.
Preview of the next lesson:
Now that we can create interactive objects within a map, what's next? Moving between maps! In the next lesson, "Script a map transition event (e.g., entering a building)," we will implement another classic JRPG feature. We'll use a player_touch trigger and a new transfer_player command to create seamless transitions between different areas, truly beginning to build a world for the player to explore.
Can't find a good explanation? Sign up and we'll make it for you
Sign up