Recommended resources
The freely available resources this lesson will direct you to.
Hello! Welcome back.
In our last lesson, we built the core of our EventInterpreter, the engine that will run all our scripted sequences. We used your front-end expertise with async/await to create a clean system for executing a list of commands like show_text and wait. However, our interpreter had a key limitation: it couldn't make decisions. It would always execute the same list of commands for an event.
Today, we address that by tackling the learning outcome: Implement a system for game flags (switches/variables) to track story progression. We are going to build the "memory" of our game world. This system will allow our EventInterpreter to check the state of the game—"Has the player defeated the dragon?", "How many potions has the player delivered?"—and run different commands accordingly. This is the mechanism that transforms a static world into a dynamic one that reacts to the player's actions.
1. The Concept of Game Flags: Switches and Variables
In game development, especially in JRPGs, "flags" are simple pieces of data used to track the state of the game. RPG Maker, and the classic games that inspire us, divide these into two primary types: Switches and Variables.
- Switches are simple boolean (ON/OFF) flags. They are perfect for binary states: a door is locked/unlocked, a quest is started/not started, an NPC has been spoken to/not spoken to.
- Variables are integer values. They are used for tracking numbers: the amount of gold you have, a character's reputation score, or the current step in a complex, multi-part quest.
From your perspective as a developer, this is a global state management system. Think of it as a simplified, game-focused version of a state store like Redux or Vuex, where the entire application can read from and write to a centralized source of truth.
To see how these concepts are used in practice within RPG Maker's UI, please watch the following short tutorial. It provides an excellent conceptual foundation for what we're about to build in code.
RPG Maker MV Tutorial #15 - Switches!
This video from SomeRanDev demonstrates the fundamental use of Switches to create a dynamic NPC interaction. It clearly shows the two main ways flags are used: to select different event pages or to control logic within a single page using conditional branches.
Please watch the entire video. Pay close attention to how a Switch being 'ON' or 'OFF' determines which event page is active (1:12) and how a Conditional Branch (if/else) can achieve a similar result within a single page (3:51).
While Switches handle on/off states, Variables allow for more nuanced tracking. The next video shows a creative use of variables to manage a multi-stage item.
RPG Maker MV Tutorial: Magical Book of Plot Convenience! (Eventing Request)
This tutorial by Echo607 uses Variables to create a 'magical book'. Each page of the book has a state tracked by a variable: 0 for 'not found', 1 for 'found', and 2 for 'viewed'. This is a perfect example of why we need variables in addition to simple switches.
Watch from 01:20 to 04:11. Focus on how a variable is used to store the player's input (1:20), and then how conditional branches check that variable's value to determine what happens next (2:07). Notice how the variable's state is changed after the event to prevent re-access (3:00).
2. Architecting the Flag Management System
Inspired by these examples, let's design the code. We'll create simple classes to manage our game's state. These will be instantiated once and attached to our main Game object, making them easily accessible to any system that needs them, especially our EventInterpreter.
Game_Switches and Game_Variables
These will be straightforward containers. In a production game, their data would be part of the save file.
// Game_Switches.js
class Game_Switches {
constructor() {
this._data = {}; // A simple object to store switch states
}
/**
* Gets the value of a switch. Defaults to false if not set.
* @param {number} switchId The ID of the switch.
* @returns {boolean}
*/
getValue(switchId) {
return !!this._data[switchId];
}
/**
* Sets the value of a switch.
* @param {number} switchId The ID of the switch.
* @param {boolean} value The new value (true for ON, false for OFF).
*/
setValue(switchId, value) {
this._data[switchId] = value;
}
}
// Game_Variables.js
class Game_Variables {
constructor() {
this._data = {}; // A simple object to store variable values
}
/**
* Gets the value of a variable. Defaults to 0 if not set.
* @param {number} variableId The ID of the variable.
* @returns {number}
*/
getValue(variableId) {
return this._data[variableId] || 0;
}
/**
* Sets the value of a variable.
* @param {number} variableId The ID of the variable.
* @param {number} value The new integer value.
*/
setValue(variableId, value) {
this._data[variableId] = Math.floor(value); // Ensure it's an integer
}
}
We can now integrate these into our main Game class:
// In your main Game class
class Game {
constructor() {
// ...
this.switches = new Game_Switches();
this.variables = new Game_Variables();
this.interpreter = new EventInterpreter(this);
// ...
}
}
Now, any part of our engine with access to the game object can check state, like game.switches.getValue(5).
3. Upgrading the Event Interpreter
With our state management system in place, we can finally complete the findValidPage method from the last lesson. As the RPG Maker tutorials showed, event pages are checked in reverse order, and the first one whose conditions are met is the one that runs.
Let's update our EventInterpreter:
// Inside the EventInterpreter class from the previous lesson
class EventInterpreter {
// ... constructor ...
// The 'start' method now takes the event definition from our data file
async start(eventDefinition) {
if (this._isRunning) return;
this._isRunning = true;
// Find the correct page to run based on current game flags
const page = this.findValidPage(eventDefinition);
if (page) {
for (const command of page.commands) {
await this.executeCommand(command);
}
}
this.finish();
}
findValidPage(eventDefinition) {
// Loop through pages in reverse order (highest index first)
for (let i = eventDefinition.pages.length - 1; i >= 0; i--) {
const page = eventDefinition.pages[i];
if (this.checkConditions(page.conditions)) {
return page; // Return the first valid page found
}
}
return null; // No valid page found
}
checkConditions(conditions) {
if (!conditions) {
return true; // A page with no conditions is always valid
}
// Check for a switch condition
if (conditions.switchId !== undefined) {
const switchValue = this.game.switches.getValue(conditions.switchId);
if (switchValue !== conditions.value) {
return false; // Condition not met
}
}
// Check for a variable condition
if (conditions.variableId !== undefined) {
const variableValue = this.game.variables.getValue(conditions.variableId);
// Example condition: { variableId: 1, value: 5, operator: 'greater_or_equal' }
switch (conditions.operator) {
case 'equal':
if (variableValue !== conditions.value) return false;
break;
case 'greater_or_equal':
if (variableValue < conditions.value) return false;
break;
// ... you can add 'less_than', 'not_equal', etc.
default: // Default to 'greater_or_equal' like RPG Maker
if (variableValue < conditions.value) return false;
}
}
return true; // All conditions were met
}
// ... executeCommand, finish, etc. ...
}
Our interpreter is now "intelligent." Before running an event, it consults the game's state to select the appropriate behavior.
4. Implementing Commands to Change State
Checking state is only half the battle. Events must also be able to change the state. Let's implement the commands to control our new Game_Switches and Game_Variables objects.
For a clearer understanding of the commands we are about to implement, you can refer to the official RPG Maker documentation. It describes the UI and function of these commands, which we are replicating in code.
RPG Maker MZ Help Documentation
This section of the official RPG Maker MZ documentation explains the Control Switches, Control Variables, and Conditional Branch event commands. This is the 'user-facing' view of the logic we are currently building.
Read the subsections on 'Evaluating Play Conditions Using Switches', 'Variables That Store Values', 'Control Switches', 'Control Variables', and 'Conditional Branch'. This will give you a solid understanding of the design patterns we are following.
Now, let's add these to our executeCommand dispatcher:
// Inside the EventInterpreter's executeCommand method
async executeCommand(command) {
switch (command.type) {
// ... previous commands like 'show_text', 'wait' ...
case 'set_switch':
await this.command_set_switch(command);
break;
case 'control_variable':
await this.command_control_variable(command);
break;
}
}
// Command Format: { type: 'set_switch', switchId: 1, value: true }
async command_set_switch(command) {
this.game.switches.setValue(command.switchId, command.value);
return Promise.resolve(); // This is an instantaneous action
}
// Command Format: { type: 'control_variable', variableId: 1, operation: 'add', operand: 1 }
async command_control_variable(command) {
const currentValue = this.game.variables.getValue(command.variableId);
let newValue = currentValue;
// The 'operand' can be a constant value or the value of another variable
const operandValue = command.operand.variableId
? this.game.variables.getValue(command.operand.variableId)
: command.operand.constant;
switch (command.operation) {
case 'set':
newValue = operandValue;
break;
case 'add':
newValue = currentValue + operandValue;
break;
case 'sub':
newValue = currentValue - operandValue;
break;
// ... add 'mul', 'div', 'mod' as needed
}
this.game.variables.setValue(command.variableId, newValue);
return Promise.resolve();
}
Notice that these command handlers are async for consistency but resolve immediately because they don't need to wait for time or user input.
5. A Practical Example: The Treasure Chest
A treasure chest is the classic example of event-local state. A chest can be opened once. We don't want to use a global switch like "TreasureChest1_Opened" because that would be unmanageable for hundreds of chests. This is where Self Switches come in.
A Self Switch is a flag that belongs only to a single event. RPG Maker provides four per event: A, B, C, and D.
To implement this, we need to make a small but important architectural update. Our interpreter needs to know which specific event instance on the map is running, so it can modify its local self-switches.
-
Management Class: We can create a
Game_SelfSwitchesclass to handle this globally. It will store the state keyed by a combination of the map ID and the event's ID.class Game_SelfSwitches { constructor() { this._data = {}; } _key(mapId, eventId, switchLetter) { return `${mapId},${eventId},${switchLetter}`; } getValue(mapId, eventId, switchLetter) { return !!this._data[this._key(mapId, eventId, switchLetter)]; } setValue(mapId, eventId, switchLetter, value) { this._data[this._key(mapId, eventId, switchLetter)] = value; } } -
Update Interpreter: Modify the interpreter to be aware of the context.
// Inside EventInterpreter // The 'start' method now also receives the event instance that was triggered async start(eventDefinition, eventInstance) { // ... const page = this.findValidPage(eventDefinition, eventInstance); if (page) { for (const command of page.commands) { await this.executeCommand(command, eventInstance); } } // ... } // Update findValidPage and checkConditions to accept eventInstance findValidPage(eventDefinition, eventInstance) { ... } checkConditions(conditions, eventInstance) { // ... other checks // Check for a self switch condition if (conditions.selfSwitchLetter) { const value = this.game.selfSwitches.getValue( eventInstance.mapId, eventInstance.id, conditions.selfSwitchLetter ); if (value !== conditions.value) return false; } return true; } // Update executeCommand to pass the context down async executeCommand(command, eventInstance) { ... } -
Implement the Treasure Chest Event:
Now we can define the data for a treasure chest event. Let's say this is event #5 on map #2.
// Event Definition for a treasure chest
{
"id": 5,
"mapId": 2,
"pages": [
{
"conditions": { "selfSwitchLetter": "A", "value": true },
"commands": [
{ "type": "show_text", "text": "The chest is empty." }
]
},
{
"conditions": null, // Default page
"commands": [
{ "type": "show_text", "text": "You found a Potion!" },
// { "type": "change_item", "itemId": 4, "quantity": 1 },
{ "type": "set_self_switch", "letter": "A", "value": true }
]
}
]
}
When the player interacts with this event:
- The interpreter starts. It checks the pages in reverse.
- The first time, Self Switch 'A' for this event is OFF. The condition for the top page
{ "selfSwitchLetter": "A", "value": true }isfalse. - The interpreter moves to the next page. It has no conditions, so it's valid.
- It runs the commands: shows "You found a Potion!" and then executes
set_self_switch, turning Self Switch 'A' for this specific event ON. - The next time the player interacts, the interpreter again checks the top page first. This time, Self Switch 'A' is ON, so the condition is
true. The interpreter runs this page's command: "The chest is empty."
Test your understanding!
You want to create an old man NPC who gives the player a key, but only if the player has spoken to the town's mayor first. After giving the key, his dialogue should change permanently.
How would you use a global Switch to accomplish this? What would the event data for the old man look like (in pseudocode or JSON)?
Show answer
You would use two global switches:
- Switch #10: "Spoke to Mayor": This is turned ON by the mayor's event.
- Switch #11: "Received Key from Old Man": This is turned ON by the old man's event.
The old man's event data would have three pages, checked in this order:
Page 3:
- Conditions:
Switch #11isON. - Commands:
Show Text: "Be careful with that key, young one."
Page 2:
- Conditions:
Switch #10isON. - Commands:
Show Text: "Ah, the mayor sent you? Here is the key to the old ruins."Change Item: Add 1 Key.Control Switch: Turn #11 ON.
Page 1:
- Conditions: (None - this is the default)
- Commands:
Show Text: "I can't help you unless you've spoken with the mayor."
This structure ensures the correct dialogue and action occurs based on the player's progression.
Conclusion
You have now built a robust state management system, a crucial pillar of any JRPG. You have not only implemented the data structures for switches and variables but also integrated them deeply into the event system we built previously.
Key Takeaways:
- Switches and Variables are the game's memory, tracking binary states and numerical progress, respectively.
- We created simple, centralized classes (
Game_Switches,Game_Variables,Game_SelfSwitches) to manage this state. - The
EventInterpreterwas upgraded to be context-aware, checkingconditionson event pages to decide which block of commands to execute. - We implemented new event commands (
set_switch,control_variable,set_self_switch) that allow our scripted events to modify the game state dynamically. - The concept of Self Switches provides an elegant solution for managing event-local state, preventing the pollution of the global state space.
Preview of the next lesson:
Our event system is now powerful and reactive. It can be triggered, and it can respond intelligently based on game state. But how are events triggered? Right now, we're imagining a manual call. In the next lesson, "Implement event triggers for player interaction, automatic execution, and map entry," we will build the system that automatically starts events when the player steps on a certain tile, presses a button in front of an NPC, or enters a map for the first time.