Recommended resources
The freely available resources this lesson will direct you to.
Hello! Welcome back to our journey into building a classic JRPG.
In the last lesson, we laid the architectural groundwork for all combat strategy by designing a comprehensive data structure for skills. We defined how a skill's cost, target type (scope), power, and effects can be stored as clean, manageable JSON data.
Today, we'll bring that design to life. This lesson focuses on the learning outcome: Implement logic for MP consumption and target selection for skills. We'll write the code that reads the cost and scope properties from our skill data and makes them functional within the battle system. You'll see how the engine checks if a character can afford a skill, deducts the cost, and presents the correct targeting options to the player.
1. The Targeting Pipeline: From Selection to Execution
Before we write code, let's establish the architectural flow. When a player selects a skill, the game needs to determine who or what can be targeted. This is dictated by the skill's scope. Your front-end development experience with state machines and user interaction flows will be very relevant here.
A robust way to think about this is to understand how professional game engines handle it.
A Critique on the Default Target System
The article 'A Critique on the Default Target System' by HimeWorks provides a superb architectural overview of how targeting works in RPG Maker. It's a bit older, but the core concepts are timeless and will give us a strong foundation for our own implementation.
Please read from the beginning down to the 'Target Index' section. Focus on these key ideas: Target Scope: How the scope property determines the potential targets. Ally and the Enemy: The fundamental distinction between friends_unit and opponents_unit. Targeting Workflow: The step-by-step process from selecting a skill to creating a Game_Action object. This is the exact process we will be building.
As the article outlines, the general workflow is:
- The player selects a skill from the command menu.
- The engine reads the skill's
scope(e.g.,one_enemy,all_allies,self). - The battle UI enters a "targeting mode".
- The engine determines the list of valid targets based on the scope.
- The player selects a target from this list (or the system auto-selects if the scope is 'all' or 'self').
- The chosen action (user, skill, and target) is stored, ready for execution.
2. Implementing Target Selection
Let's translate this into code. We need a function that takes the skill's scope and the current battle state to return an array of valid targets.
Assuming you have a battleState object that contains partyMembers and enemies arrays, the logic could look like this:
function getValidTargets(scope, user, battleState) {
const { partyMembers, enemies } = battleState;
switch (scope) {
case 'one_enemy':
case 'all_enemies':
// Return all living enemies
return enemies.filter(e => e.stats.hp > 0);
case 'one_ally':
case 'all_allies':
// Return all living party members
return partyMembers.filter(p => p.stats.hp > 0);
case 'one_dead_ally':
// For revival skills
return partyMembers.filter(p => p.stats.hp <= 0);
case 'self':
// The target is the user themself
return [user];
default:
return [];
}
}
This function is the core of our targeting logic. In your UI code, you would:
- Call
getValidTargetswhen the player chooses a skill. - If the scope is for a single target (e.g.,
one_enemy), you would activate a targeting cursor and allow the player to cycle through the returned list of valid targets. - If the scope is for multiple targets (
all_enemies) or self, you can often skip the selection step and confirm the action immediately.
To see what this looks like in practice, the following video segment shows how the RPG Maker UI handles different scopes.
RPG Maker MZ The Basics How to make Custom Skills
In the video 'RPG Maker MZ The Basics How to make Custom Skills', observe how the 'Scope' setting affects the targeting UI in a battle test.
Watch the section on 'Skill Scope and Target Selection' (04:11 - 04:38). Notice the different options like 'One Enemy', 'All Enemies', '1 Ally', etc. This dropdown menu directly corresponds to the scope property in our data structure and dictates how the targeting cursor behaves in-game.
3. Implementing Resource Consumption
A character can't use a skill if they can't pay the cost. This involves two steps: checking if the skill is usable and then paying the cost upon execution.
Step 1: The canUse Check
A clean architectural pattern is to create a "gatekeeper" function that checks all requirements for using a skill. For now, this will just be MP, but it can be expanded easily.
/**
* Checks if a character can use a given skill.
* @param {object} user The character attempting to use the skill.
* @param {object} skill The skill data object.
* @returns {boolean} True if the skill can be used, false otherwise.
*/
function canUse(user, skill) {
// If there's no cost, it's always usable.
if (!skill.cost || skill.cost.value === 0) {
return true;
}
// Check for MP cost
if (skill.cost.type === 'mp') {
return user.stats.mp >= skill.cost.value;
}
// Future-proofing: we can add checks for HP, TP, items, etc. here.
// if (skill.cost.type === 'hp') { ... }
return true; // Default to true if cost type is unknown for now
}
You would call this function when drawing the skill list in the battle menu. If canUse(character, skill) returns false, you would render that skill's name in a disabled state (e.g., greyed out) and prevent the player from selecting it. This is analogous to disabling a button in a web UI when a form is invalid.
Step 2: Paying the Cost
Once the player has selected a valid skill and target, and the action is about to be executed, you need to deduct the resource. This is a straightforward state update.
/**
* Deducts the resource cost for using a skill from the user.
* @param {object} user The character using the skill.
* @param {object} skill The skill data object.
*/
function payCost(user, skill) {
if (!skill.cost || skill.cost.value === 0) {
return; // No cost to pay
}
if (skill.cost.type === 'mp') {
user.stats.mp -= skill.cost.value;
// Ensure MP doesn't drop below 0
if (user.stats.mp < 0) user.stats.mp = 0;
}
// ... logic for other cost types
}
This function would be called as part of the action execution sequence, right before the skill's damage or effects are calculated and applied.
Test your understanding!
Imagine you have a battleMenu component with a list of skills. In pseudocode, how would you use the canUse function to render the list, and what happens when a player clicks a skill?
Show answer
function renderSkillMenu(user, skills) {
clearMenu();
skills.forEach(skill => {
const isEnabled = canUse(user, skill);
const skillButton = createButton(skill.name);
skillButton.disabled = !isEnabled;
if (isEnabled) {
skillButton.onClick(() => {
// Transition to targeting mode
startTargeting(user, skill);
});
}
add(skillButton);
});
}
This demonstrates the separation of concerns: canUse determines the UI state, and the UI triggers the next step in the game logic (startTargeting).
4. Expanding the System: Advanced Costs
Classic JRPGs feature a variety of skill costs beyond simple MP deduction—HP for "dark knight" abilities, consuming items for archers, or even Gold for special moves. Our system should be flexible enough to accommodate this.
RPG Maker handles this through a powerful plugin system that extends the base engine. We can learn from this to evolve our own data structure.
Skill Cost Manager | Yanfly Channel - WordPress.com
The 'Skill Cost Manager' documentation for a popular RPG Maker plugin by Yanfly shows exactly how a data-driven system can be extended to handle complex costs. The concept of 'notetags' is very similar to us adding custom properties to our JSON objects.
This is a rich resource. Focus on understanding the concepts presented: MP/HP/Gold Costs (Sections near the top): Skim the notetags like <mp cost: x>, <hp cost: x>, and <mp cost: y%>. This shows how to define flat, percentage, and alternative resource costs. Lunatic Mode: Custom Costs (Section 'Lunatic Mode'): Read about <custom cost requirement> and <custom cost perform>. This is the most important part. It allows designers to write small snippets of code (eval) to define any cost imaginable. This is directly analogous to us expanding our canUse and payCost functions. Practical Examples (Comments Section): Skim the comments section (section index 5 in the resource data). You don't need to read it all, but look for user discussions about implementing costs for items (like arrows). For example, search for comments by users like 'Jordan', 'anxte2009', 'MindCreep', and 'Fonstw'. Their code snippets show the practical application of custom costs: one line checks if the item exists, the other removes it.
Inspired by this, we can make our cost property more robust. Instead of a single object, it could be an array of costs.
New skill data structure:
{
"id": "power_shot_1",
"name": "Power Shot",
// ...
"cost": [
{ "type": "tp", "value": 15 },
{ "type": "item", "itemId": "arrow", "value": 1 }
],
"scope": "one_enemy"
}
With this structure, we can refactor our canUse and payCost functions to iterate through the array, checking and paying each cost in sequence.
Refactored canUse function:
function canUse(user, skill) {
if (!skill.cost) return true;
// Check every cost in the array. If any check fails, return false.
return skill.cost.every(c => {
switch (c.type) {
case 'mp':
return user.stats.mp >= c.value;
case 'hp':
// Note: a skill shouldn't kill you just by its cost.
return user.stats.hp > c.value;
case 'item':
return user.inventory.hasItem(c.itemId, c.value);
// ... more cost types
default:
return true;
}
});
}
This data-driven and extensible approach is a hallmark of good game engine design. It empowers you to create rich mechanics without having to rewrite core engine code for every new idea.
Conclusion
In this lesson, you've bridged the gap between data design and implementation for two critical combat systems. You now have the logic to handle both resource consumption and target selection in a way that is clean, scalable, and directly driven by the skill data we designed previously.
Key Takeaways:
- Targeting is a State Flow: Selecting a skill triggers a process that reads its
scopeto determine valid targets and guide the player's choice. - Gatekeeper Functions are Key: A function like
canUsecleanly separates the rules for skill eligibility (like affording the cost) from the UI that displays it. - Decouple Checking from Paying: The check (
canUse) happens during the command selection phase, while the payment (payCost) happens during the action execution phase. - Data Structures Evolve: By observing professional tools, we saw how to evolve our
costdata from a single object to an array, allowing for complex, multi-resource costs without complicating the core logic.
Preview of the next lesson:
Now that we can successfully target a combatant and pay the price for a skill, we need to broaden the range of things that can happen to the target. In the next lesson, we will implement the logic that was defined in our skill data's effects array, covering: Implement status effects (e.g., Poison, Sleep) and elemental properties.