Ranged Chance To-Hit = DEX + D20 Rolling for Damage If the to-hit roll results in a hit, the next step is to roll again to determine howmuch damage was done to the target.. Ranged “Damag
Trang 1whereArmor Pointsrepresent the sum total of all armor items andShield Points
represent the defense value of an equipped shield I say possible way because this
is not the only way to perform the calculation Some game systems do not allow
a DEX bonus for plate armor wearers because that represents a slow-moving
character, whereas high DEX represents high agility To keep the rules simple in
Dungeon Crawler, I just apply the full DEX and full AP to the calculation
Based on the type of gear available in your game, you may want to add a
modifier to the AC calculation to help balance the gameplay a bit if it seems that
too many attack rolls are an instant hit I would expect about half of all attacks to
fail when rolled against a foe at the same level If you find that significantly more
than half of all attacks are succeeding, then that’s a sign you need to add a
modifier to the AC (such as +5)
Melee “Chance To-Hit” Rolls
The mechanics of combat for any game is entirely up to the designer The
important thing is not that your game works like many other RPGs out there,
Figure 12.13
Six different dice with 4, 6, 8, 10, 12, and 20 sides Image courtesy of Wikipedia.
Creating the Combat System 311
Trang 2only that combat is balanced within your own game system In other words, aslong as the PC and hostile NPCs attack with the same set of rules, then the game
is playable One thing you really don’t want to happen is for combat to end tooquickly It’s generally necessary to artificially raise the hit points (HP) ofmonsters at the lower levels so they don’t fall with one hit You want the player
to feel as if real combat is taking place, not that they’re just walking aroundtaking out enemies with a single blow as if they’re using a lightsaber We dowant the player’s attributes to play an important role in the to-hit roll as well asthe damage done in an attack
For Dungeon Crawler, I’m going to use a D20 (a 20-sided die) as the basis forthe to-hit roll In RPG lingo, a D20 roll of 1 is an epic fail while a roll of 20 is acritical hit, which usually means a definite hit (ignoring the defender’s AC)
Melee Chance To-Hit = STR + D20
Ranged “Chance To-Hit” Rolls
Ranged attacks with a bow or spell are similar to melee with a D20 roll, but withDEX instead of STR as a modifier The character’s agility contributes to hisability to hit accurately at a distance, where his strength has little or no effect
Ranged Chance To-Hit = DEX + D20
Rolling for Damage
If the to-hit roll results in a hit, the next step is to roll again to determine howmuch damage was done to the target This is where the weapon attributes comeinto play If the game features real items that you can give your character to use
in combat, then it makes a big difference in the gameplay For one thing, you canscatter treasure chests around the game world that contain unique quest items(like magical swords, shields, and armor), as well as valuable jewels and gold.(These types of items are all modeled and available in the sprites provided in theReiner’s Tileset collection.)
Melee “Damage” Rolls
The melee damage value is calculated primarily from STR and weapon damagewith a 1D8 roll added to the mix This damage factor is then reduced by the
Trang 3defender’s AC to come up with a total damage, which goes against the defender’s
HP
Melee Damage = D8 + STR + Weapon Damage - Defender’s AC
Some games apply a different die roll based on the type of weapon, such as a
2D6 for a two-handed sword, 2D8 for a two-handed mace, and 1D10 for a bow
You may use modifiers such as this if you want, but it adds an additional bit of
information to the item database I found it easier to use a base random die roll
(D8) and the weapon damage as an additional die roll The result is very nearly
the same, but it results in more reasonable weapon damage factors For instance,
we wouldn’t expect a rusty short sword to deal 12–16 damage where normally it
should be 1–4 By using the D8 roll in addition to the weapon damage range, the
damage factors will be more reasonable
Ranged “Damage” Rolls
The ranged damage value is calculated primarily from DEX and weapon damage
with a 1D8 roll added for some randomness A range penalty is then subtracted
from the total to arrive at a new attack value, which is further reduced by the
defender’s AC The final value is the total damage dealt against the defender’s
HP
Ranged Damage = D8 + DEX + weapon damage - range penalty - Defender’s AC
Ranged damage differs slightly from melee due to the range penalty, but it’s a
reasonable subtraction, because without it the player would be nearly invincible,
able to deal out full damage at long range where no monster would ever be able
to catch him before being cut down
Critical Hits ( “Crit”)
If the chance to-hit roll of the D20 results in a 20, then the attack is a critical hit
and incurs additional damage! You may add whatever modifier you want to the
attack damage factor, such as a 2x roll factor So, if the damage was calculated
with 1D8, then the critical damage will be 2D8 Optionally, you may just double
the 1D8 damage roll Remember, your system doesn’t have to mimic the combat
mechanic of any other system—be creative and unique!
Creating the Combat System 313
Trang 4Attack Roll Example
Let’s simulate one half of an attack round where just one player attacks and theother defends, to see how the calculations are done and what results we get First
of all, we’ll give the player these attributes:
First, we’ll calculate the AC for the monster:
AC = DEX + Armor Points + Shield Points
AC = 14 + 12 + 0
AC = 26
Attack Roll
Now, we’ll calculate the attacker’s attack chance to-hit:
To-Hit = Attack Roll (STR + D20) - Defender’s AC
Attack roll = STR + D20
Attack roll = 18 + 9 (roll) = 27
Did the attack succeed?
Trang 5To-Hit = Attack Roll (27) - AC (26) = 1 (Hit!)
Damage Roll
Since our attack succeeded, but was not a critical hit, we calculate normal
damage
Damage = D8 + STR + Weapon Damage - Defender’s AC
Damage = roll (1-8) + 18 + roll (2-8) - 26
Damage = roll (3) + 18 + roll (7) - 26
Damage = 3 + 18 + 7 - 26 = 2
Had the attack been a critical hit with an attack roll of 20, then critical damage
would be calculated with a factor of 2 as follows:
Damage = D8 * 2 + STR + Weapon Damage - Defender’s AC
Damage = roll (1-8) * 2 + 18 + roll (2-8) - 26
Damage = roll (3) * 2 + 18 + roll (7) - 26
As you can see from these results, the die rolls are crucial! After all those many
calculations, our hero only dealt 2 points of damage to the monster, and the
monster then gets to strike back at the player This continues round after round
until one or the other loses all their HP or flees
Dealing with the Player ’s Death
One drawback to combat is that you can die It’s a cold, hard, truth, I realize, but
it can happen What should you do, as the game’s designer and programmer,
when the player’s character (PC) dies? That is a tough decision that requires
some thought and should be based on the overall design of your game You
might let the player save and load the game, but that takes away from the
suspension of disbelief You want the player to be completely immersed in the
game and unaware of a file system, an operating system, or even of the
computer You want your players to be mesmerized by the content on the
Creating the Combat System 315
Trang 6screen, and something as cheesy as a load/save feature takes away from that I’lladmit, though, most players abuse the save/load game feature and complain ifyou don’t have one After all, you want the player to be able to quit at amoment’s notice without going through any hassle Let’s face it: Sometimes thereal world asserts itself into the reverie you are experiencing in the game, andyou have to quit playing.
But just for the sake of gameplay, what is the best way to deal with the playercharacter’s death, aside from having a save/load feature? I recommend just re-spawning the PC at the starting point of a level file The location of a re-spawn is
up to you as the game’s designer Do you want to make it too easy for the player
to die and come back too quickly, or do you want to make them work a little bitbefore resuming the fight they were in previously? Re-spawning too close to thelast fight might make the game too easy, so a spawn point at a central hub town
or other location might be better, and then the player must walk and portal toget back to the location where they were at prior to dying
Combat Demo 2
The second Combat demo shows how to make these calculations for an attackagainst an NPC (Figure 12.14) This demo uses the Dialogue class to show theresults of attack rolls with each part of the calculation shown for you to study.This scene, for instance, shows a critical attack roll that dealt 14 damage to atarget NPC Most RPG purists will enjoy seeing this information, whereas casualRPG fans will prefer to just hurry up and kill the monster so they can loot itscorpse for items and gold It’s up to you to decide how much information youwant to share with the player
On the one hand, it might be impressive to see what all is involved in an attackwith the various rolls and calculations, since the casual player might just assumeyour combat system uses a simple attack roll versus defense roll system If youdon’t show any information, and just show damage dealt (as in games likeBaldur’s Gate), the player might assume just a random attack roll is all there is to
it Every attribute is important and affects the outcome of combat, and everyplayer knows this intuitively, but it’s easy to forget if combat tends to happenvery quickly One advantage to turn-based combat is that it will reflect a pencil-and-paper game, which is at the root of every computer RPG On the other
Trang 7hand, some players might get annoyed with the slow pace of combat and give up
on your game You have to decide on the best balance between information
overload (TMI) and dumbed-down gameplay
Turn-based Combat
When a turn-based combat system is the way to go, we need to make a few
minor changes to the input system In the previous example, we used the Space
key to trigger a flag called attackFlag, which was set to false when the Space
key was released That works for a real-time combat system, but not for a
turn-based one For turn-turn-based combat, we need to wait until the user releases the
attack key Otherwise, some sort of timing mechanism must be used and that
can get messy So, here is the new keyboard code—note howattackFlag is now
handled
Figure 12.14
Demonstration of an attack roll against a hostile NPC.
Creating the Combat System 317
Trang 8private void Form1_KeyDown(object sender, KeyEventArgs e)
{
switch (e.KeyCode) {
case Keys.Escape: gameover = true; break;
private void Form1_KeyUp(object sender, KeyEventArgs e)
{
switch (e.KeyCode) {
case Keys.D: keyState.right = false; break;
case Keys.Space: attackFlag = true; break;
} }
More Dialogue
We need theDialogueclass again to show the results of an attack You can nowsee how usefulDialogueis beyond its original intended use as a way to talk withNPCs! Granted, the window is not very attractive yet We will need to add somemore configuration options to it so the buttons look better and the height isadjusted automatically to the number of buttons in use But, the important thing
is, we have a way to interact with the player Before using it, we need to addsome new features to theDialogueclass See, I warned you that this was likely to
Trang 9happen! But, we can’t possibly foresee in the future what new things we’ll need
to do with our code, so this is to be expected
As you’ll recall, the Dialogue class will display the dialogue window until a
button is clicked, and then set the Selection property equal to the button
number Previously, the Dialogue class did not hide itself after a selection was
made or reset any of its properties The new feature we need to add is aVisible
property
private bool p_visible;
public bool Visible
{
get { return p_visible; }
set { p_visible = value; }
}
TheDraw()function will checkp_visiblebefore drawing anything Now we will
have the ability to continually update the Dialogue object and have it display
whatever we want to the player, and selectively show it as needed
public void Draw()
{
if (!p_visible) return;
.
}
Back to our main source code for Combat demo 2 Here is the newdoUpdate()
function, which now handles scrolling, hero, monsters, attacking, and dialogue
private void doUpdate()
{
int frameRate = game.FrameRate();
int ticks = Environment.TickCount;
Trang 10game.Print(0, 0, "Monsters in range: " + monstersInRange.ToString());
game.Print(320, 570, "Press SPACE to Attack");
game.Update();
Application.DoEvents();
} else Thread.Sleep(1);
}
ThedoDialogue() function does not automatically move, but you may use thatfeature if you want (see Chapter 15 for details) I want the combat dialogue tostay in the same place
private void doDialogue()
dialogue.Visible = false;
dialogue.Selection = 0;
} }
The doDialogue() function is called continuously from the main loop, andproperties determine what it should do To trigger a dialogue to “pop up,” wecan call on this new showDialogue() function, which automatically formats thedialogue with two buttons:
private void showDialogue(string title, string message,
string button1, string button2) {
Trang 11ThedoAttack()function handles a single round of combat Well, technically, it’s
just one-half of a round since the NPC doesn’t fight back yet Study the
calculations in this function to learn more about how the armor class, attack
roll, and damage roll are related
private void doAttack()
{
const int DEF_ARMOR = 10;
const int DEF_SHIELD = 0;
const int WEAPON_DMG = 5;
bool hit = false;
bool critical = false;
bool fail = false;
AC = monsters[target].DEX + DEF_ARMOR + DEF_SHIELD;
//calculate chance to-hit for PC
Trang 12{ //normal hit roll += hero.STR;
if (roll > AC) hit = true;
text += " + STR(" + hero.STR.ToString() + ") = " + roll.ToString() + "\n";
}
//did attack succeed?
if (hit) {
//calculate base damage damage = game.Random(1, 8);
//add critical
if (critical) damage *= 2;
text += "Damage roll: " + damage.ToString() + "\n";
//add STR damage += hero.STR;
text += " + STR(" + hero.STR.ToString() + ") = " + damage.ToString() + "\n";
//add weapon damage (usually a die roll) damage += WEAPON_DMG;
text += " + weapon(" + WEAPON_DMG.ToString() + ") = " + damage.ToString() + "\n";
//subtract AC damage -= AC;
text += " - monster AC(" + AC.ToString() + ") = " + damage.ToString() + "\n";
//minimal hit
if (damage < 1) damage = 1;
//show result text += "Attack succeeds for " + damage.ToString() +
" damage.";
Trang 13else
text += "Attack failed.\n";
showDialogue("Attack", text, "Attack", "Cancel");
}
Facing Your Enemies
It goes without saying that attacking an enemy who is behind you is kind of silly
No, it’s ridiculous No one can swing a sword accurately behind them, let alone
shoot an arrow backward So, the game shouldn’t allow it either! What’s worse,
we can deal damage to a monster without even swinging at it The code that
figures out the direction to a target is like the code that sets the player’s
animation based on its direction The getTargetDirection() function will
“point” a character from its current angle toward a target This is also useful
for pitting NPCs against each other, or for having NPCs face the player when
you talk to them Figure 12.15 shows the Combat demo 3 running with new
code to cause sprites to face toward each other
H i n t
Note: the code in these examples is not meant to be based on step-by-step modifications to the
first example, but only to show the most relevant code as the chapter example is developed You
will want to open the complete project for each example and observe it running as you study the
text These projects do evolve toward a final, working combat system, but the complete code at
each step is not listed.
Which Way Did He Go?
The logic behind figuring out the direction from one point to another is really
just about brute-force If statements First, we look at the X position of both
points to find out whether the target is left, right, or directly in line with the
source Then, it checks the Y position to figure out whether the target is above,
below, or right in line with the source Based on these conditions, we set the
source in a direction that will most closely match the target’s location (within
the limits of the 8-way directions for our animations)
private int getTargetDirection(PointF source, PointF target)
{
Facing Your Enemies 323
Trang 14int direction = 0;
if (source.X < target.X - 16) {
//facing eastward
if (source.Y < target.Y - 8) direction = 3; //south east else if (source.Y > target.Y + 8) direction = 1; //north east else
direction = 2; //east }
else if (source.X > target.X + 16) {
//facing westward
if (source.Y < target.Y - 8) direction = 5; //south west Figure 12.15
This demo shows how to cause sprites to face toward each other in order to fight.
Trang 15else if (source.Y > target.Y + 8)
direction = 7; //north west else
direction = 6; //west }
direction = 0; //north }
return direction;
}
Using this function, we can modifydoMonsters() and force the PC and NPC to
face each other when the player triggers an attack! The result is much improved
over the previous example
//is player trying to attack this monster?
//make PC and NPC face each other
int dir = getTargetDirection(monsterCenter, hero.CenterPos);
Trang 16} }
A Change of Character
A minor change is required in the Character class to support the feature offorcing sprites to face toward each other The original single Character.Draw()
function is replaced with these three versions:
public void Draw()
case AnimationStates.Walking:
p_walkSprite.Position = p_position;
if (p_direction > -1) {
startFrame = p_direction * p_walkColumns;
endFrame = startFrame + p_walkColumns - 1;
p_walkSprite.AnimationRate = 30;
p_walkSprite.Animate(startFrame, endFrame);
} p_walkSprite.Draw(x,y);
break;
case AnimationStates.Attacking:
p_attackSprite.Position = p_position;
if (p_direction > -1) {
startFrame = p_direction * p_attackColumns;
endFrame = startFrame + p_attackColumns - 1;
p_attackSprite.AnimationRate = 30;
p_attackSprite.Animate(startFrame, endFrame);
Trang 17} p_attackSprite.Draw(x,y);
break;
case AnimationStates.Dying:
p_dieSprite.Position = p_position;
if (p_direction > -1) {
startFrame = p_direction * p_dieColumns;
endFrame = startFrame + p_dieColumns - 1;
p_dieSprite.AnimationRate = 30;
p_dieSprite.Animate(startFrame, endFrame);
} p_dieSprite.Draw(x,y);
break;
}
}
State-Based Combat
The combat system is now complex enough to require a state variable
Previously, a bool variable, attacking, kept track of just whether combat was
supposed to happen Now, we need to involve several steps for combat:
1 Player triggers an attack
2 Attack introduction
3 Attack commences
4 Report the attack results
This enumeration will handle the four states in the combat system:
public enum AttackStates
Trang 18AttackStates attackState = AttackStates.ATTACK_NONE;
By the time we’re done adding state to the combat engine for this Combat demo
4 project, shown in Figure 12.16, the game will allow you to make distinct,individual attacks against an enemy with a click of the Attack button
Dialogue Improvements
Now that we’re using a state-based system for combat, we need to modify otherparts of the game code to also work correctly: namely, the Dialogue class.Previously, we just looked for a mouse click to trigger a button selection event.Now, that will not work because the dialogue will be repeatedly updated so amouse click will be seen as many clicks while the button is being held No matterhow fast you press and release the mouse button, it will pick up several eventsbecause the loop is running at 60 fps What we need to do is look for a buttonrelease event instead A new variable is needed:
Figure 12.16
The new state-based combat system slows down and improves the gameplay.
Trang 19private MouseButtons p_oldMouseBtn;
When updating the mouse, we also need to keep track of the previous click state:
public void updateMouse(Point mousePos, MouseButtons mouseBtn)
And this change has been made to the Dialogue.Draw() function:
//clicked on this button?
if (p_mouseBtn == MouseButtons.None && p_oldMouseBtn == MouseButtons.Left)
p_selection = n;
else
p_selection = 0;
Plugging in Attack State
I will not go over every line of the next example, but suffice it to say there were a
lot of changes made to move the combat system over to a state-based system
The most important changes were made todoMonsters()anddoCombat(), which
mainly involved just checking the current state and acting appropriately For
instance, in doMonsters(), rather than simply setting the target to whatever
monster the player is close to without regard for any previously targeted
monster, the code now checks to see if the player isn’t already in a fight
//make PC and NPC face each other
int dir = getTargetDirection(monsterCenter, hero.CenterPos);
monsters[target].Direction = dir;
monsters[target].Draw();
State-Based Combat 329
Trang 20dir = getTargetDirection(hero.CenterPos, monsterCenter);
hero.Direction = dir;
hero.Draw();
break;
} }
Also, after all of the monsters have been processed in the loop, then we need toreset combat if the player has walked away or cancelled combat The new
attackText variable is defined in Form1as a string
state condition
private void doAttack()
{
const int DEF_ARMOR = 10;
const int DEF_SHIELD = 0;
const int WEAPON_DMG = 5;
bool hit = false;
bool critical = false;
bool fail = false;
attackState = AttackStates.ATTACK_NONE;
return;
Trang 21AC = monsters[target].DEX + DEF_ARMOR + DEF_SHIELD;
//calculate chance to-hit for PC
Trang 22//normal hit roll += hero.STR;
if (roll > AC) hit = true;
text += " + STR(" + hero.STR.ToString() + ") = " + roll.ToString() + "\n";
}
//did attack succeed?
if (hit) {
//calculate base damage damage = game.Random(1, 8);
//add critical
if (critical) damage *= 2;
text += "Damage roll: " + damage.ToString() + "\n";
//add STR damage += hero.STR;
text += " + STR(" + hero.STR.ToString() + ") = " + damage.ToString() + "\n";
//add weapon damage (usually a die roll) damage += WEAPON_DMG;
text += " + weapon(" + WEAPON_DMG.ToString() +
") = " + damage.ToString() + "\n";
//subtract AC damage -= AC;
text += " - monster AC(" + AC.ToString() + ") = " + damage.ToString() + "\n";
//minimal hit
if (damage < 1) damage = 1;
//show result text += "Attack succeeds for " + damage.ToString() +
" damage.";
}
Trang 23else text += "Attack failed.\n";
break;
}
}
Dealing Permanent Damage
The final step to complete the combat system is to give the player experience
after defeating a monster This will require some new fields in the Character
class since we did not account for experience or leveling when originally
designing the class Some additional code will be required in this final example,
Combat demo 5, to allow the animation for the killed monsters to stay on the
screen after they fall Figure 12.17 shows the result The amount of experience
awarded is a random value from 50 to 100, which is just an arbitrary range that I
made up as filler How should we award experience in the real game? It should
be a factor that involves the monster’s level, which is not something we’re
currently using in the character editor files What would you do with this design
decision: add a level field, or a pair of fields that define how much experience the
player receives? Something to ponder between now and the final chapter
T i p
Even this last Combat demo 5 project is not quite polished, but it is meant to serve as a working
example of combat For a production game, you would want the player to walk over the corpses
and stop them from “bobbing” as the scroller moves These issues are easily fixed as the final
version of the game demonstrates.
Dealing Permanent Damage 333