


ROLE
Technical Designer
ROLE
Technical Designer
ROLE
Technical Designer
TOOL
C++, SDL, GitHub
TOOL
C++, SDL, GitHub
TOOL
C++, SDL, GitHub
TEAM
Team of 9
TEAM
Team of 9
TEAM
Team of 9
TIMELINE
4 Months
TIMELINE
4 Months
TIMELINE
4 Months
PLAY
PLAY
PLAY
OVERVIEW
OVERVIEW
Glitchblade is a fast-paced action combat game inspired by Sekiro and Hollow Knight, aiming to bring Sekiro's aggressive, parry-driven combat and Hollow Knight's arena-style encounter design into a 2D platformer, refitted for mobile. The core design tension was translating a genre built around precise timing and multi-input combat onto a touchscreen without cluttering it, while teaching those mechanics intuitively to players who likely have never played a Soulslike before, and pushing back against players' natural instinct to play defensively and wait rather than commit to aggressive combat.
Glitchblade is a fast-paced action combat game inspired by Sekiro and Hollow Knight, aiming to bring Sekiro's aggressive, parry-driven combat and Hollow Knight's arena-style encounter design into a 2D platformer, refitted for mobile. The core design tension was translating a genre built around precise timing and multi-input combat onto a touchscreen without cluttering it, while teaching those mechanics intuitively to players who likely have never played a Soulslike before, and pushing back against players' natural instinct to play defensively and wait rather than commit to aggressive combat.
Glitchblade is a fast-paced action combat game inspired by Sekiro and Hollow Knight, aiming to bring Sekiro's aggressive, parry-driven combat and Hollow Knight's arena-style encounter design into a 2D platformer, refitted for mobile. The core design tension was translating a genre built around precise timing and multi-input combat onto a touchscreen without cluttering it, while teaching those mechanics intuitively to players who likely have never played a Soulslike before, and pushing back against players' natural instinct to play defensively and wait rather than commit to aggressive combat.
AGGRESSION OVER ATTRITION
AGGRESSION OVER ATTRITION
For most Soulslike games, players are expected to wait for their turns and attack only when there is an opening. We want to break that cycle and encourage players to create their own opportunities through mastery of the mechanics and the enemies' movesets.
For most Soulslike games, players are expected to wait for their turns and attack only when there is an opening. We want to break that cycle and encourage players to create their own opportunities through mastery of the mechanics and the enemies' movesets.
LEARN FROM TRIAL-AND-ERROR
LEARN FROM TRIAL-AND-ERROR
Tutorials should only teach the players the utmost basic mechanics. Once they have the most fundamental skills, we shouldn't hold the players hand, but instead allow them to explore their abilities freely through a variety of challenges.
Tutorials should only teach the players the utmost basic mechanics. Once they have the most fundamental skills, we shouldn't hold the players hand, but instead allow them to explore their abilities freely through a variety of challenges.
ONE THUMB, FULL COMBAT
ONE THUMB, FULL COMBAT
All actions should be mapped to intuitive controls and kept minimal. It reduces cognitive load and allows players to focus on facing the actual challenges in the game instead of fighting with the controls and their fingers.
All actions should be mapped to intuitive controls and kept minimal. It reduces cognitive load and allows players to focus on facing the actual challenges in the game instead of fighting with the controls and their fingers.
DESIGN DEEP DIVE
DESIGN DEEP DIVE
#1: COMBAT SYSTEM
#1: COMBAT SYSTEM
#1: COMBAT SYSTEM
OVERVIEW
Design Intent
Design Intent
The combat system exists to make Aggression Over Attrition mechanically true, not just a stated philosophy: every core input (attack, guard, parry) is built to reward committing to offensive play. It's also the primary proving ground for One Thumb, Full Combat, since attacks, guarding, and parrying all have to coexist on a touchscreen without requiring more inputs than a player can realistically manage, and for Teach Without Telling, since none of this can rely on tutorial text to explain timing windows or risk/reward tradeoffs.
The combat system exists to make Aggression Over Attrition mechanically true, not just a stated philosophy: every core input (attack, guard, parry) is built to reward committing to offensive play. It's also the primary proving ground for One Thumb, Full Combat, since attacks, guarding, and parrying all have to coexist on a touchscreen without requiring more inputs than a player can realistically manage, and for Teach Without Telling, since none of this can rely on tutorial text to explain timing windows or risk/reward tradeoffs.
The combat system exists to make Aggression Over Attrition mechanically true, not just a stated philosophy: every core input (attack, guard, parry) is built to reward committing to offensive play. It's also the primary proving ground for One Thumb, Full Combat, since attacks, guarding, and parrying all have to coexist on a touchscreen without requiring more inputs than a player can realistically manage, and for Teach Without Telling, since none of this can rely on tutorial text to explain timing windows or risk/reward tradeoffs.
Rules
Rules
Left/right dash sets horizontal velocity and deals damage on contact; down-dash sets downward velocity and deals AOE damage, requiring the player to be elevated
Hitting an enemy applies knockback to both player and enemy, creating spacing
A brief IFrame follows any hit taken, guarded, parried, or unblocked
Guard input starts both a parry timer and a longer guard timer simultaneously; while the parry timer is active, a hit is treated as a parry
A successful parry stuns the enemy and deflects projectiles back at their sourceAttacks, parries, and guarded hits each contribute differently to the combo meter, with parry granting the largest increment
Left/right dash sets horizontal velocity and deals damage on contact; down-dash sets downward velocity and deals AOE damage, requiring the player to be elevated
Hitting an enemy applies knockback to both player and enemy, creating spacing
A brief IFrame follows any hit taken, guarded, parried, or unblocked
Guard input starts both a parry timer and a longer guard timer simultaneously; while the parry timer is active, a hit is treated as a parry
A successful parry stuns the enemy and deflects projectiles back at their sourceAttacks, parries, and guarded hits each contribute differently to the combo meter, with parry granting the largest increment
Left/right dash sets horizontal velocity and deals damage on contact; down-dash sets downward velocity and deals AOE damage, requiring the player to be elevated
Hitting an enemy applies knockback to both player and enemy, creating spacing
A brief IFrame follows any hit taken, guarded, parried, or unblocked
Guard input starts both a parry timer and a longer guard timer simultaneously; while the parry timer is active, a hit is treated as a parry
A successful parry stuns the enemy and deflects projectiles back at their sourceAttacks, parries, and guarded hits each contribute differently to the combo meter, with parry granting the largest increment
DESIGN DECISION 1: ATTACKS DOUBLE AS MOVEMENT, NOT JUST DAMAGE
DESIGN DECISION 1: ATTACKS DOUBLE AS MOVEMENT, NOT JUST DAMAGE
Problem
Problem
A fast-paced, aggressive combat game needs attacks that don't just deal damage, if attacking and moving are separate actions, players default to safe positioning and poking, exactly the passive turn-waiting cycle Aggression Over Attrition is meant to break.
A fast-paced, aggressive combat game needs attacks that don't just deal damage, if attacking and moving are separate actions, players default to safe positioning and poking, exactly the passive turn-waiting cycle Aggression Over Attrition is meant to break.
A fast-paced, aggressive combat game needs attacks that don't just deal damage, if attacking and moving are separate actions, players default to safe positioning and poking, exactly the passive turn-waiting cycle Aggression Over Attrition is meant to break.
Decision
Decision
Attacks are swipe-based dashes, left, right, or down, so every attack is simultaneously an offensive action, an evasive dodge, and a platforming tool. The down-dash specifically deals AOE damage but requires more precision, since it needs the player to be elevated to use it, trading accessibility for power. On hit, attacks apply knockback, creating space that gives the player a window to either react to an incoming enemy attack or follow up with another hit.
Attacks are swipe-based dashes, left, right, or down, so every attack is simultaneously an offensive action, an evasive dodge, and a platforming tool. The down-dash specifically deals AOE damage but requires more precision, since it needs the player to be elevated to use it, trading accessibility for power. On hit, attacks apply knockback, creating space that gives the player a window to either react to an incoming enemy attack or follow up with another hit.
Attacks are swipe-based dashes, left, right, or down, so every attack is simultaneously an offensive action, an evasive dodge, and a platforming tool. The down-dash specifically deals AOE damage but requires more precision, since it needs the player to be elevated to use it, trading accessibility for power. On hit, attacks apply knockback, creating space that gives the player a window to either react to an incoming enemy attack or follow up with another hit.
if (_player->isDashLeftBegin()) {
_player->faceLeft();
playerBody->SetLinearVelocity(b2Vec2(-_player->getDashF(), playerBody->GetLinearVelocity().y));
}
if (_player->isDashRightBegin()) {
_player->faceRight();
playerBody->SetLinearVelocity(b2Vec2(_player->getDashF(), playerBody->GetLinearVelocity().y));
}
if (_player->isDashDownBegin()) {
playerBody->SetLinearVelocity(b2Vec2(playerBody->GetLinearVelocity().x, -_player->getDashF()));
}if (_player->isDashLeftBegin()) {
_player->faceLeft();
playerBody->SetLinearVelocity(b2Vec2(-_player->getDashF(), playerBody->GetLinearVelocity().y));
}
if (_player->isDashRightBegin()) {
_player->faceRight();
playerBody->SetLinearVelocity(b2Vec2(_player->getDashF(), playerBody->GetLinearVelocity().y));
}
if (_player->isDashDownBegin()) {
playerBody->SetLinearVelocity(b2Vec2(playerBody->GetLinearVelocity().x, -_player->getDashF()));
}int dir = _player->getPosition().subtract(enemy->getPosition()).x > 0 ? 1 : -1;
Vec2 knockback = Vec2(dir * _defaultKnockback, _defaultVerticalKnockback);
_player->setKnocked(true, knockback);
enemy->setKnocked(true, enemy->getPosition().subtract(_player->getPosition()).normalize())
int dir = _player->getPosition().subtract(enemy->getPosition()).x > 0 ? 1 : -1;
Vec2 knockback = Vec2(dir * _defaultKnockback, _defaultVerticalKnockback);
_player->setKnocked(true, knockback);
enemy->setKnocked(true, enemy->getPosition().subtract(_player->getPosition()).normalize())
Iteration
Iteration
This went through two stages. The first version only had left and right attacks, which worked reasonably well on its own. Down-dash was added later for two reasons: it let the team design specific enemy types around a vertical attack option, adding real encounter variety, and it introduced a Hollow Knight pogo-style mechanic with a high skill ceiling for skilled players to express mastery, directly supporting Learn From Trial-and-Error. A second problem then surfaced once down-dash existed without AOE, it required enough precision that it became genuinely hard to land, especially in later levels where enemies moved more actively. Adding an AOE effect to the down-dash specifically addressed this.
This went through two stages. The first version only had left and right attacks, which worked reasonably well on its own. Down-dash was added later for two reasons: it let the team design specific enemy types around a vertical attack option, adding real encounter variety, and it introduced a Hollow Knight pogo-style mechanic with a high skill ceiling for skilled players to express mastery, directly supporting Learn From Trial-and-Error. A second problem then surfaced once down-dash existed without AOE, it required enough precision that it became genuinely hard to land, especially in later levels where enemies moved more actively. Adding an AOE effect to the down-dash specifically addressed this.
This went through two stages. The first version only had left and right attacks, which worked reasonably well on its own. Down-dash was added later for two reasons: it let the team design specific enemy types around a vertical attack option, adding real encounter variety, and it introduced a Hollow Knight pogo-style mechanic with a high skill ceiling for skilled players to express mastery, directly supporting Learn From Trial-and-Error. A second problem then surfaced once down-dash existed without AOE, it required enough precision that it became genuinely hard to land, especially in later levels where enemies moved more actively. Adding an AOE effect to the down-dash specifically addressed this.
Outcome
Outcome
The AOE addition gave players real margin for error on a mechanic that would otherwise demand pixel-precise timing against fast-moving enemies, while also turning down-dash into a crowd-control tool capable of handling multiple enemies at once, reinforcing Aggression Over Attrition by making an aggressive, high-skill option viable rather than frustrating to actually use.
The AOE addition gave players real margin for error on a mechanic that would otherwise demand pixel-precise timing against fast-moving enemies, while also turning down-dash into a crowd-control tool capable of handling multiple enemies at once, reinforcing Aggression Over Attrition by making an aggressive, high-skill option viable rather than frustrating to actually use.
The AOE addition gave players real margin for error on a mechanic that would otherwise demand pixel-precise timing against fast-moving enemies, while also turning down-dash into a crowd-control tool capable of handling multiple enemies at once, reinforcing Aggression Over Attrition by making an aggressive, high-skill option viable rather than frustrating to actually use.
DESIGN DECISION 2: ENCOURAGE PARRIES THROUGH REWARD, NOT OBLIGATION
DESIGN DECISION 2: ENCOURAGE PARRIES THROUGH REWARD, NOT OBLIGATION
Problem
Problem
Parry needed to stay genuinely difficult to execute, difficulty is what makes mastering it satisfying, in line with Learn From Trial-and-Error. But the team didn't want to punish players who never attempt it or fail at it; every enemy in the game is theoretically beatable without ever landing a parry. The real challenge was getting players to want to try a hard, risky mechanic anyway, without forcing it or making failure feel punishing.
Parry needed to stay genuinely difficult to execute, difficulty is what makes mastering it satisfying, in line with Learn From Trial-and-Error. But the team didn't want to punish players who never attempt it or fail at it; every enemy in the game is theoretically beatable without ever landing a parry. The real challenge was getting players to want to try a hard, risky mechanic anyway, without forcing it or making failure feel punishing.
Parry needed to stay genuinely difficult to execute, difficulty is what makes mastering it satisfying, in line with Learn From Trial-and-Error. But the team didn't want to punish players who never attempt it or fail at it; every enemy in the game is theoretically beatable without ever landing a parry. The real challenge was getting players to want to try a hard, risky mechanic anyway, without forcing it or making failure feel punishing.
Decision
Decision
The input itself was made as simple as possible, a single tap, so there's no mechanical barrier to attempting it, difficulty comes entirely from timing, not from complexity, keeping parry within One Thumb, Full Combat's constraint of minimal, intuitive controls. On top of that, rather than scrutinizing or penalizing players for skipping parry, the design leans entirely on the reward side, making success so valuable that players are pulled toward attempting it repeatedly, closer to gambling psychology than obligation. If the payoff is big enough, players keep trying even knowing they might fail, and it's through that repeated trying that they naturally learn the timing, directly serving Learn From Trial-and-Error through incentive rather than instruction or punishment.
The input itself was made as simple as possible, a single tap, so there's no mechanical barrier to attempting it, difficulty comes entirely from timing, not from complexity, keeping parry within One Thumb, Full Combat's constraint of minimal, intuitive controls. On top of that, rather than scrutinizing or penalizing players for skipping parry, the design leans entirely on the reward side, making success so valuable that players are pulled toward attempting it repeatedly, closer to gambling psychology than obligation. If the payoff is big enough, players keep trying even knowing they might fail, and it's through that repeated trying that they naturally learn the timing, directly serving Learn From Trial-and-Error through incentive rather than instruction or punishment.
The input itself was made as simple as possible, a single tap, so there's no mechanical barrier to attempting it, difficulty comes entirely from timing, not from complexity, keeping parry within One Thumb, Full Combat's constraint of minimal, intuitive controls. On top of that, rather than scrutinizing or penalizing players for skipping parry, the design leans entirely on the reward side, making success so valuable that players are pulled toward attempting it repeatedly, closer to gambling psychology than obligation. If the payoff is big enough, players keep trying even knowing they might fail, and it's through that repeated trying that they naturally learn the timing, directly serving Learn From Trial-and-Error through incentive rather than instruction or punishment.
else if (_player->isParryActive()) {
AudioHelper::playSfx("parry");
_player->incrementComboCounterByParry();
_player->_parryCounter++;
_player->damage(0);
if (hitbox->getIsParriable()) {
enemy->setStun(enemy->stunFrames);
}
}else if (_player->isParryActive()) {
AudioHelper::playSfx("parry");
_player->incrementComboCounterByParry();
_player->_parryCounter++;
_player->damage(0);
if (hitbox->getIsParriable()) {
enemy->setStun(enemy->stunFrames);
}
}Iteration
Iteration
The entire decision took a few iterations before we are satisfied by it. Initially, a non-perfect parry tier was added, blocking within guard but outside the precise parry window reduces damage by 50% rather than requiring a perfect read, giving players a safe fallback so imperfect attempts aren't punished as harshly as a full unblocked hit. However, as we see this wasn't enough, successful parries were given a stun on the enemy and a large combo meter fill, making the top-tier outcome dramatically better than the fallback, not just "no damage" but real tempo and reward. From there, we tuned the values until it felt right: making sure the parry felt genuinely worth chasing without becoming so strong it trivialized encounters, or so weak that the gamble stopped feeling worth taking.
The entire decision took a few iterations before we are satisfied by it. Initially, a non-perfect parry tier was added, blocking within guard but outside the precise parry window reduces damage by 50% rather than requiring a perfect read, giving players a safe fallback so imperfect attempts aren't punished as harshly as a full unblocked hit. However, as we see this wasn't enough, successful parries were given a stun on the enemy and a large combo meter fill, making the top-tier outcome dramatically better than the fallback, not just "no damage" but real tempo and reward. From there, we tuned the values until it felt right: making sure the parry felt genuinely worth chasing without becoming so strong it trivialized encounters, or so weak that the gamble stopped feeling worth taking.
The entire decision took a few iterations before we are satisfied by it. Initially, a non-perfect parry tier was added, blocking within guard but outside the precise parry window reduces damage by 50% rather than requiring a perfect read, giving players a safe fallback so imperfect attempts aren't punished as harshly as a full unblocked hit. However, as we see this wasn't enough, successful parries were given a stun on the enemy and a large combo meter fill, making the top-tier outcome dramatically better than the fallback, not just "no damage" but real tempo and reward. From there, we tuned the values until it felt right: making sure the parry felt genuinely worth chasing without becoming so strong it trivialized encounters, or so weak that the gamble stopped feeling worth taking.
Outcome
Outcome
A single, simple tap carries an outsized reward for precise timing, meaning the barrier to attempting parry is nonexistent, only the barrier to mastering it remains. Players are drawn to attempt it because the reward is too good to ignore, not because the game demands it or the input is hard to perform, learning the timing as a side effect of chasing that reward. This reinforces Aggression Over Attrition and One Thumb, Full Combat simultaneously, while Learn From Trial-and-Error is served by mastery emerging from repeated voluntary attempts rather than a required tutorial or punishing failure state.
A single, simple tap carries an outsized reward for precise timing, meaning the barrier to attempting parry is nonexistent, only the barrier to mastering it remains. Players are drawn to attempt it because the reward is too good to ignore, not because the game demands it or the input is hard to perform, learning the timing as a side effect of chasing that reward. This reinforces Aggression Over Attrition and One Thumb, Full Combat simultaneously, while Learn From Trial-and-Error is served by mastery emerging from repeated voluntary attempts rather than a required tutorial or punishing failure state.
A single, simple tap carries an outsized reward for precise timing, meaning the barrier to attempting parry is nonexistent, only the barrier to mastering it remains. Players are drawn to attempt it because the reward is too good to ignore, not because the game demands it or the input is hard to perform, learning the timing as a side effect of chasing that reward. This reinforces Aggression Over Attrition and One Thumb, Full Combat simultaneously, while Learn From Trial-and-Error is served by mastery emerging from repeated voluntary attempts rather than a required tutorial or punishing failure state.
DESIGN DECISION 3: COMBO METER TO FURTHER REINFORCE AGGRESSION
DESIGN DECISION 3: COMBO METER TO FURTHER REINFORCE AGGRESSION
Problem
Problem
Aggression Over Attrition as a stated pillar doesn't enforce itself, without some system tracking and rewarding sustained offense, nothing actually stops a player from playing cautiously and still succeeding. The game needed a mechanic whose entire purpose is to make consistent aggression measurably, tangibly worth more than cautious, sporadic engagement.
Aggression Over Attrition as a stated pillar doesn't enforce itself, without some system tracking and rewarding sustained offense, nothing actually stops a player from playing cautiously and still succeeding. The game needed a mechanic whose entire purpose is to make consistent aggression measurably, tangibly worth more than cautious, sporadic engagement.
Aggression Over Attrition as a stated pillar doesn't enforce itself, without some system tracking and rewarding sustained offense, nothing actually stops a player from playing cautiously and still succeeding. The game needed a mechanic whose entire purpose is to make consistent aggression measurably, tangibly worth more than cautious, sporadic engagement.
Decision
Decision
A combo meter that builds from attacking and parrying, and pays out a meaningful reward once filled, was added after the core combat system already existed, once the team recognized nothing was actually enforcing Aggression Over Attrition mechanically. The meter rewards consistency of aggressive play specifically, not just successful hits, and ties survivability directly to offense: healing comes from staying aggressive, not from playing safe, echoing Bloodborne's regain mechanic, where health lost is recoverable only by immediately attacking back rather than retreating. Any supporting rule underneath it, like paying out a heal versus an enhanced attack depending on current HP so the reward is never wasted, or decaying after 5 seconds of inactivity so players can't passively bank it, exists in service of that same core goal: keep playing aggressively, or lose the benefit.
A combo meter that builds from attacking and parrying, and pays out a meaningful reward once filled, was added after the core combat system already existed, once the team recognized nothing was actually enforcing Aggression Over Attrition mechanically. The meter rewards consistency of aggressive play specifically, not just successful hits, and ties survivability directly to offense: healing comes from staying aggressive, not from playing safe, echoing Bloodborne's regain mechanic, where health lost is recoverable only by immediately attacking back rather than retreating. Any supporting rule underneath it, like paying out a heal versus an enhanced attack depending on current HP so the reward is never wasted, or decaying after 5 seconds of inactivity so players can't passively bank it, exists in service of that same core goal: keep playing aggressively, or lose the benefit.
A combo meter that builds from attacking and parrying, and pays out a meaningful reward once filled, was added after the core combat system already existed, once the team recognized nothing was actually enforcing Aggression Over Attrition mechanically. The meter rewards consistency of aggressive play specifically, not just successful hits, and ties survivability directly to offense: healing comes from staying aggressive, not from playing safe, echoing Bloodborne's regain mechanic, where health lost is recoverable only by immediately attacking back rather than retreating. Any supporting rule underneath it, like paying out a heal versus an enhanced attack depending on current HP so the reward is never wasted, or decaying after 5 seconds of inactivity so players can't passively bank it, exists in service of that same core goal: keep playing aggressively, or lose the benefit.
if (_player->_lastComboElapsedTime >= 5 && _player->_comboMeter > 0) {
_player->_comboMeter = std::max(_player->_comboMeter - timestep * 10, 0.0f);
}
if (_player->_comboMeter >= 100) {
if (_player->getHP() < _player->getMaxHP()) {
_player->setHP(std::min(_player->getHP() + 20.0f, 100.0f));
} else {
_player->_isNextAttackEnhanced = true;
}
_player->_comboMeter = 0
if (_player->_lastComboElapsedTime >= 5 && _player->_comboMeter > 0) {
_player->_comboMeter = std::max(_player->_comboMeter - timestep * 10, 0.0f);
}
if (_player->_comboMeter >= 100) {
if (_player->getHP() < _player->getMaxHP()) {
_player->setHP(std::min(_player->getHP() + 20.0f, 100.0f));
} else {
_player->_isNextAttackEnhanced = true;
}
_player->_comboMeter = 0
Outcome
Outcome
The combo meter turns Aggression Over Attrition from a stated intention into an enforced system. Players who commit to sustained offense are directly and continuously rewarded with both damage potential and survivability, while passive or defensive play has a real, felt cost, since healing itself now requires staying aggressive rather than an alternative to it.
The combo meter turns Aggression Over Attrition from a stated intention into an enforced system. Players who commit to sustained offense are directly and continuously rewarded with both damage potential and survivability, while passive or defensive play has a real, felt cost, since healing itself now requires staying aggressive rather than an alternative to it.
The combo meter turns Aggression Over Attrition from a stated intention into an enforced system. Players who commit to sustained offense are directly and continuously rewarded with both damage potential and survivability, while passive or defensive play has a real, felt cost, since healing itself now requires staying aggressive rather than an alternative to it.
#2: ENEMIES
#2: ENEMIES
#2: ENEMIES
OVERVIEW
OVERVIEW
Design Intent
Design Intent
The original plan was a pure boss-rush structure, the player fights boss after boss with no lead-up. Playtesting showed this was too difficult, especially given Learn From Trial-and-Error's commitment to teaching through play rather than tutorials, players had no safe space to build the fundamental reads a boss fight demands before being thrown into one. Minions were introduced to solve this directly: give players a rhythm to learn against before the real test. Separately, boss behavior itself needed to avoid feeling flat or memorizable on a single pass, supporting Aggression Over Attrition by making a fight escalate in danger rather than staying static.
The original plan was a pure boss-rush structure, the player fights boss after boss with no lead-up. Playtesting showed this was too difficult, especially given Learn From Trial-and-Error's commitment to teaching through play rather than tutorials, players had no safe space to build the fundamental reads a boss fight demands before being thrown into one. Minions were introduced to solve this directly: give players a rhythm to learn against before the real test. Separately, boss behavior itself needed to avoid feeling flat or memorizable on a single pass, supporting Aggression Over Attrition by making a fight escalate in danger rather than staying static.
The original plan was a pure boss-rush structure, the player fights boss after boss with no lead-up. Playtesting showed this was too difficult, especially given Learn From Trial-and-Error's commitment to teaching through play rather than tutorials, players had no safe space to build the fundamental reads a boss fight demands before being thrown into one. Minions were introduced to solve this directly: give players a rhythm to learn against before the real test. Separately, boss behavior itself needed to avoid feeling flat or memorizable on a single pass, supporting Aggression Over Attrition by making a fight escalate in danger rather than staying static.
Rules
Rules
Enemy behavior is built from a shared, composable action system, MeleeActionModel (hitbox attacks with position, size, damage, knockback, and exact start/end frame timing), RangedActionModel (projectile attacks with spawn position and frame data), and MovementActionModel (directional movement, optionally toward the player)
Individual actions carry their own _isParriable flag, so specific moves can be flagged as unparriable, giving fine-grained control over which attacks reward a parry
Bosses track an _aggression value (0–100) that rises over time and increases further when damaged
Each boss move has its own aggression cost and trigger condition
Minion enemies are built from the same action architecture as bosses, reusing one or two of a boss's specific moves with adjusted parameters, rather than requiring separate logic
Enemy behavior is built from a shared, composable action system, MeleeActionModel (hitbox attacks with position, size, damage, knockback, and exact start/end frame timing), RangedActionModel (projectile attacks with spawn position and frame data), and MovementActionModel (directional movement, optionally toward the player)
Individual actions carry their own _isParriable flag, so specific moves can be flagged as unparriable, giving fine-grained control over which attacks reward a parry
Bosses track an _aggression value (0–100) that rises over time and increases further when damaged
Each boss move has its own aggression cost and trigger condition
Minion enemies are built from the same action architecture as bosses, reusing one or two of a boss's specific moves with adjusted parameters, rather than requiring separate logic
Enemy behavior is built from a shared, composable action system, MeleeActionModel (hitbox attacks with position, size, damage, knockback, and exact start/end frame timing), RangedActionModel (projectile attacks with spawn position and frame data), and MovementActionModel (directional movement, optionally toward the player)
Individual actions carry their own _isParriable flag, so specific moves can be flagged as unparriable, giving fine-grained control over which attacks reward a parry
Bosses track an _aggression value (0–100) that rises over time and increases further when damaged
Each boss move has its own aggression cost and trigger condition
Minion enemies are built from the same action architecture as bosses, reusing one or two of a boss's specific moves with adjusted parameters, rather than requiring separate logic
DESIGN DECISION 1: MINIONS AS EASIER, SIMPLER VERSIONS OF THE BOSS
DESIGN DECISION 1: MINIONS AS EASIER, SIMPLER VERSIONS OF THE BOSS
Problem
Problem
Simply scaling down a boss's full moveset into a weaker enemy risks two failure modes. Give minions too much of the boss's kit, and the real fight becomes trivial, the player has already seen and mastered everything. Give minions too little, and they don't actually prepare the player for what's coming, defeating the purpose of onboarding at all.
Simply scaling down a boss's full moveset into a weaker enemy risks two failure modes. Give minions too much of the boss's kit, and the real fight becomes trivial, the player has already seen and mastered everything. Give minions too little, and they don't actually prepare the player for what's coming, defeating the purpose of onboarding at all.
Simply scaling down a boss's full moveset into a weaker enemy risks two failure modes. Give minions too much of the boss's kit, and the real fight becomes trivial, the player has already seen and mastered everything. Give minions too little, and they don't actually prepare the player for what's coming, defeating the purpose of onboarding at all.
Decision
Decision
Each minion type is built around one or two moves taken directly from a specific boss, repurposed with a smaller range, lower damage, and longer anticipation/recovery windows, giving players a safer, more readable version of a move they'll later face at full difficulty. Critically, minions never show the boss's complete moveset, some attacks are held back entirely, so the boss fight retains genuine surprise and isn't fully trivialized by the time the player reaches it.
Each minion type is built around one or two moves taken directly from a specific boss, repurposed with a smaller range, lower damage, and longer anticipation/recovery windows, giving players a safer, more readable version of a move they'll later face at full difficulty. Critically, minions never show the boss's complete moveset, some attacks are held back entirely, so the boss fight retains genuine surprise and isn't fully trivialized by the time the player reaches it.
Each minion type is built around one or two moves taken directly from a specific boss, repurposed with a smaller range, lower damage, and longer anticipation/recovery windows, giving players a safer, more readable version of a move they'll later face at full difficulty. Critically, minions never show the boss's complete moveset, some attacks are held back entirely, so the boss fight retains genuine surprise and isn't fully trivialized by the time the player reaches it.
class MeleeActionModel : public ActionModel {
cugl::Vec2 _hitboxPos;
cugl::Vec2 _hitboxSize;
int _hitboxStartFrame;
int _hitboxEndFrame;
float _hitboxDamage;
float _hitboxKnockBack;
bool _isParriable;
}
class MeleeActionModel : public ActionModel {
cugl::Vec2 _hitboxPos;
cugl::Vec2 _hitboxSize;
int _hitboxStartFrame;
int _hitboxEndFrame;
float _hitboxDamage;
float _hitboxKnockBack;
bool _isParriable;
}
Iteration
Iteration
This decision itself emerged from a larger pivot, the team originally planned pure boss-rush and only added minions after finding that structure too difficult in practice. Once minions existed as a concept, the team specifically chose to hold back part of each boss's kit rather than exposing the full moveset through minions.
This decision itself emerged from a larger pivot, the team originally planned pure boss-rush and only added minions after finding that structure too difficult in practice. Once minions existed as a concept, the team specifically chose to hold back part of each boss's kit rather than exposing the full moveset through minions.
This decision itself emerged from a larger pivot, the team originally planned pure boss-rush and only added minions after finding that structure too difficult in practice. Once minions existed as a concept, the team specifically chose to hold back part of each boss's kit rather than exposing the full moveset through minions.
Outcome
Outcome
Minions function as a rehearsal space, players learn the timing, range, and punish windows for a subset of a boss's moves in a lower-stakes context, directly serving Learn From Trial-and-Error. Holding back part of the moveset keeps the boss fight from becoming a formality, preserving genuine challenge and surprise.
Minions function as a rehearsal space, players learn the timing, range, and punish windows for a subset of a boss's moves in a lower-stakes context, directly serving Learn From Trial-and-Error. Holding back part of the moveset keeps the boss fight from becoming a formality, preserving genuine challenge and surprise.
Minions function as a rehearsal space, players learn the timing, range, and punish windows for a subset of a boss's moves in a lower-stakes context, directly serving Learn From Trial-and-Error. Holding back part of the moveset keeps the boss fight from becoming a formality, preserving genuine challenge and surprise.
DESIGN DECISION 2: AGGRESSION AS AN ENEMY RESOURCE TO PACE THE FIGHT
DESIGN DECISION 2: AGGRESSION AS AN ENEMY RESOURCE TO PACE THE FIGHT
Problem
Problem
A flat, fixed probability for each boss move produces a fight with no real shape, the boss feels the same at full health as it does nearly dead, and nothing stops a powerful move like explode from appearing just as often as a cheap one like stab.
A flat, fixed probability for each boss move produces a fight with no real shape, the boss feels the same at full health as it does nearly dead, and nothing stops a powerful move like explode from appearing just as often as a cheap one like stab.
A flat, fixed probability for each boss move produces a fight with no real shape, the boss feels the same at full health as it does nearly dead, and nothing stops a powerful move like explode from appearing just as often as a cheap one like stab.
Decision
Decision
Boss move selection is gated by an aggression resource that rises over time and increases further when the boss takes damage, then gets spent at different costs per move. In the first boss encounter, for example, cheaper moves (slam, stab) cost 25 aggression on a straightforward roll; shoot needs a hard 75-aggression floor to unlock but costs only 10; explode costs 50 and requires clearing a much harder check, making it rare and tied to sustained pressure. A boss under pressure naturally becomes more dangerous and unpredictable, rather than following a fixed, learnable-once pattern.
Boss move selection is gated by an aggression resource that rises over time and increases further when the boss takes damage, then gets spent at different costs per move. In the first boss encounter, for example, cheaper moves (slam, stab) cost 25 aggression on a straightforward roll; shoot needs a hard 75-aggression floor to unlock but costs only 10; explode costs 50 and requires clearing a much harder check, making it rare and tied to sustained pressure. A boss under pressure naturally becomes more dangerous and unpredictable, rather than following a fixed, learnable-once pattern.
Boss move selection is gated by an aggression resource that rises over time and increases further when the boss takes damage, then gets spent at different costs per move. In the first boss encounter, for example, cheaper moves (slam, stab) cost 25 aggression on a straightforward roll; shoot needs a hard 75-aggression floor to unlock but costs only 10; explode costs 50 and requires clearing a much harder check, making it rare and tied to sustained pressure. A boss under pressure naturally becomes more dangerous and unpredictable, rather than following a fixed, learnable-once pattern.
void Boss1Model::slam() {
if (rand() % 100 <= _aggression) {
_aggression -= std::max(0.0f, _aggression - 25);
_isSlamming = true;
}
}
void Boss1Model::shoot() {
if (_aggression >= 75) {
_aggression -= std::max(0.0f, _aggression - 10);
_isShooting = true;
}
}
void Boss1Model::explode() {
if (rand() % 200 <= _aggression) { // effectively half the odds of slam/stab at the same aggression level
_aggression -= std::max(0.0f, _aggression - 50);
_isExploding = true;
}
}void Boss1Model::slam() {
if (rand() % 100 <= _aggression) {
_aggression -= std::max(0.0f, _aggression - 25);
_isSlamming = true;
}
}
void Boss1Model::shoot() {
if (_aggression >= 75) {
_aggression -= std::max(0.0f, _aggression - 10);
_isShooting = true;
}
}
void Boss1Model::explode() {
if (rand() % 200 <= _aggression) { // effectively half the odds of slam/stab at the same aggression level
_aggression -= std::max(0.0f, _aggression - 50);
_isExploding = true;
}
}Iteration
Iteration
The earliest version used fixed, flat probabilities per move with no underlying resource. The aggression-gated system and its per-move costs were added afterward to give the fight real pacing and make the boss's biggest attacks feel earned rather than randomly available.
The earliest version used fixed, flat probabilities per move with no underlying resource. The aggression-gated system and its per-move costs were added afterward to give the fight real pacing and make the boss's biggest attacks feel earned rather than randomly available.
The earliest version used fixed, flat probabilities per move with no underlying resource. The aggression-gated system and its per-move costs were added afterward to give the fight real pacing and make the boss's biggest attacks feel earned rather than randomly available.
Outcome
Outcome
The boss fight now has a genuine arc, players face a more measured, cheaper-move-heavy boss early on and a more volatile one as the fight escalates, reinforcing Learn From Trial-and-Error, since players have to adapt their read as the fight progresses rather than mastering one static pattern.
The boss fight now has a genuine arc, players face a more measured, cheaper-move-heavy boss early on and a more volatile one as the fight escalates, reinforcing Learn From Trial-and-Error, since players have to adapt their read as the fight progresses rather than mastering one static pattern.
The boss fight now has a genuine arc, players face a more measured, cheaper-move-heavy boss early on and a more volatile one as the fight escalates, reinforcing Learn From Trial-and-Error, since players have to adapt their read as the fight progresses rather than mastering one static pattern.
#3: DATA-DRIVEN SCRIPTING TOOL
#3: DATA-DRIVEN SCRIPTING TOOL
#3: DATA-DRIVEN SCRIPTING TOOL
OVERVIEW
OVERVIEW
Design Intent
Design Intent
With a team of 9 and only a semester, not everyone would touch the underlying C++ combat and enemy systems, but everyone still needed to shape enemy behavior, level pacing, and encounter design. The tool exists to let designers iterate on gameplay-facing values directly, without needing to understand the boilerplate physics, animation, and collision code underneath.
With a team of 9 and only a semester, not everyone would touch the underlying C++ combat and enemy systems, but everyone still needed to shape enemy behavior, level pacing, and encounter design. The tool exists to let designers iterate on gameplay-facing values directly, without needing to understand the boilerplate physics, animation, and collision code underneath.
With a team of 9 and only a semester, not everyone would touch the underlying C++ combat and enemy systems, but everyone still needed to shape enemy behavior, level pacing, and encounter design. The tool exists to let designers iterate on gameplay-facing values directly, without needing to understand the boilerplate physics, animation, and collision code underneath.
Rules
Rules
Every design-relevant parameter (hitbox position, enemy HP, spawn position, etc.) lives in JSON, while physics resolution, animation state machines, and collision routing stay in code
Enemies and their onboarding minions share the same action schema, differing only in the values assigned
Levels are broken into discrete waves, each with explicit spawn positions, spawn intervals, and enemy composition
Every design-relevant parameter (hitbox position, enemy HP, spawn position, etc.) lives in JSON, while physics resolution, animation state machines, and collision routing stay in code
Enemies and their onboarding minions share the same action schema, differing only in the values assigned
Levels are broken into discrete waves, each with explicit spawn positions, spawn intervals, and enemy composition
Every design-relevant parameter (hitbox position, enemy HP, spawn position, etc.) lives in JSON, while physics resolution, animation state machines, and collision routing stay in code
Enemies and their onboarding minions share the same action schema, differing only in the values assigned
Levels are broken into discrete waves, each with explicit spawn positions, spawn intervals, and enemy composition
DESIGN DECISION 1: EXTRACT DESIGN PARAMETERS INTO INDIVIDUAL DATA FILES
DESIGN DECISION 1: EXTRACT DESIGN PARAMETERS INTO INDIVIDUAL DATA FILES
Problem
Problem
If enemy behavior stayed hardcoded in C++, only programmers could adjust hitbox size, damage, timing, or encounter composition, creating a bottleneck where design iteration depended entirely on a small subset of the team. But exposing too much (raw engine internals, physics implementation details) would make the schema just as inaccessible as the code itself.
If enemy behavior stayed hardcoded in C++, only programmers could adjust hitbox size, damage, timing, or encounter composition, creating a bottleneck where design iteration depended entirely on a small subset of the team. But exposing too much (raw engine internals, physics implementation details) would make the schema just as inaccessible as the code itself.
If enemy behavior stayed hardcoded in C++, only programmers could adjust hitbox size, damage, timing, or encounter composition, creating a bottleneck where design iteration depended entirely on a small subset of the team. But exposing too much (raw engine internals, physics implementation details) would make the schema just as inaccessible as the code itself.
Decision
Decision
The team deliberately identified every parameter that actually mattered for design and extracted it into JSON, while everything else (physics resolution, animation state machines, collision routing) stayed in code. Anyone on the team could open a level or enemy file and change what a boss's move does, or which minions appear before which boss, without touching C++ at all.
The team deliberately identified every parameter that actually mattered for design and extracted it into JSON, while everything else (physics resolution, animation state machines, collision routing) stayed in code. Anyone on the team could open a level or enemy file and change what a boss's move does, or which minions appear before which boss, without touching C++ at all.
The team deliberately identified every parameter that actually mattered for design and extracted it into JSON, while everything else (physics resolution, animation state machines, collision routing) stayed in code. Anyone on the team could open a level or enemy file and change what a boss's move does, or which minions appear before which boss, without touching C++ at all.
// boss_1 - slam
{
"type": "melee",
"name": "slam",
"hitboxPos": [4, 0],
"hitboxSize": [4, 6],
"hitboxStartFrame": 25,
"hitboxEndFrame": 31,
"hitboxDamage": 20,
"hitboxKnockBack": 75,
"parriable": true
}// boss_1 - slam
{
"type": "melee",
"name": "slam",
"hitboxPos": [4, 0],
"hitboxSize": [4, 6],
"hitboxStartFrame": 25,
"hitboxEndFrame": 31,
"hitboxDamage": 20,
"hitboxKnockBack": 75,
"parriable": true
}Outcome
Outcome
Whole encounter and boss-progression decisions became directly editable in data. Level 3's wave sequence shows this concretely, spawning minion_1B and minion_1A (each carrying a reduced subset of boss_1's actual moves) across multiple waves before boss_1 itself appears as the final wave, the entire onboarding structure from the Enemy AI deep dive, expressed as data anyone could arrange and rebalance without engine knowledge.
Whole encounter and boss-progression decisions became directly editable in data. Level 3's wave sequence shows this concretely, spawning minion_1B and minion_1A (each carrying a reduced subset of boss_1's actual moves) across multiple waves before boss_1 itself appears as the final wave, the entire onboarding structure from the Enemy AI deep dive, expressed as data anyone could arrange and rebalance without engine knowledge.
Whole encounter and boss-progression decisions became directly editable in data. Level 3's wave sequence shows this concretely, spawning minion_1B and minion_1A (each carrying a reduced subset of boss_1's actual moves) across multiple waves before boss_1 itself appears as the final wave, the entire onboarding structure from the Enemy AI deep dive, expressed as data anyone could arrange and rebalance without engine knowledge.
DESIGN DECISION 2: WAVE-BASED LEVEL STRUCTURE CONTROLS PACING AND COORDINATES MULTI-ENEMY ENCOUNTERS
DESIGN DECISION 2: WAVE-BASED LEVEL STRUCTURE CONTROLS PACING AND COORDINATES MULTI-ENEMY ENCOUNTERS
Problem
Problem
Simply listing which enemies exist in a level isn't enough to control how a level actually plays. Spawning too many enemies at once, or spacing them out with arbitrary timing, risks overwhelming the player unpredictably or making encounters feel randomly placed rather than composed with intent.
Simply listing which enemies exist in a level isn't enough to control how a level actually plays. Spawning too many enemies at once, or spacing them out with arbitrary timing, risks overwhelming the player unpredictably or making encounters feel randomly placed rather than composed with intent.
Simply listing which enemies exist in a level isn't enough to control how a level actually plays. Spawning too many enemies at once, or spacing them out with arbitrary timing, risks overwhelming the player unpredictably or making encounters feel randomly placed rather than composed with intent.
Decision
Decision
Levels are broken into discrete waves, each with its own explicit spawn positions, spawn intervals (staggering multiple enemies within the same wave rather than dropping them all at once), and specific enemy composition, giving designers deliberate control over pacing and coordination.
Levels are broken into discrete waves, each with its own explicit spawn positions, spawn intervals (staggering multiple enemies within the same wave rather than dropping them all at once), and specific enemy composition, giving designers deliberate control over pacing and coordination.
Levels are broken into discrete waves, each with its own explicit spawn positions, spawn intervals (staggering multiple enemies within the same wave rather than dropping them all at once), and specific enemy composition, giving designers deliberate control over pacing and coordination.
{
"spawn_intervals": [0, 0],
"spawn_positions": [
{ "x": 4200, "y": 100 },
{ "x": 4250, "y": 100 }
],
"enemies": ["minion_2B", "minion_1A"]
}{
"spawn_intervals": [0, 0],
"spawn_positions": [
{ "x": 4200, "y": 100 },
{ "x": 4250, "y": 100 }
],
"enemies": ["minion_2B", "minion_1A"]
}





