Chapter 12. Fighting Monsters, Gaining Experience, and Leveling Up

This chapter explores the all-important topic of combat and how gaining experience affects the player’s character. Chapter 11 gave us some tools that will be useful again now, such as code to figure out when the player is close to an enemy. This applies solely to melee fights, of course, and not to attacks at range, which will require a bit of extra effort. The simplest form of melee combat occurs when the player is close enough to an enemy and hits the attack button, similar to the way in which dialogue was engaged. One additional requirement for combat is that we must ensure the player is facing toward the enemy in order to deal any damage. In this chapter, we will explore these techniques and the random attack dice rolls needed to make realistic combat that even an RPG purist will appreciate.

Here’s what we’ll cover in this chapter:

  • Preparing for combat

  • Character animation templates

  • Creating the combat system

  • Attack and damage rolls

  • Facing your enemies

  • State-based combat

  • Dealing permanent damage

Preparing for Combat

In a normal game, hostile NPCs will be guarding locations or wandering around in the environment, usually following a series of waypoints or just walking back and forth between two points. For our first few examples of combat, the hostile NPCs will be positioned in the level randomly and will not move. We will give them movement behavior later, but for now I just want to show you how to engage an enemy in order to attack them. There are several ways we could go about handling combat in the game. In a real-time game, there is never a pause in the action: when the player engages one enemy, there may be other nearby enemies who charge into the battle as well. A variation is a partial real-time game where the player can move freely through the game but each round of combat causes the game to pause while it is resolved.

A second type of combat is turn-based. Although our game allows the player to roam the world freely in real time, I have decided to use a turn-based system with rounds to resolve combat. This choice comes after much play testing of a fully real-time combat system that just seemed to happen too quickly. With the player and hostile NPC swinging weapons at each other, basically as fast as the attacks are allowed to go, the HP for each slowly goes down until one falls or retreats. This form of combat works great for a game like Diablo and Baldur’s Gate, and our own pending Dungeon Crawler game. The combat system for our game can be either real time or turn-based with a few simple flags in the code.

Starting an Attack

The code to draw the monsters is the same code used in previous chapters to draw objects with a global position within the scrolling level. Before drawing objects, we must get its relative position with respect to the scroll position and then draw it at the relative position.

   //is monster in view?
   if (monsters[n].X > level.ScrollPos.X &&
       monsters[n].X < level.ScrollPos.X + 23 * 32 &&
       monsters[n].Y > level.ScrollPos.Y &&
       monsters[n].Y < level.ScrollPos.Y + 17 * 32)

If the object is within the viewport at the current scroll position, then we can figure out its relative position on the screen and draw it.

   relativePos = new PointF(
       Math.Abs(level.ScrollPos.X - monsters[n].X),
       Math.Abs(level.ScrollPos.Y - monsters[n].Y));
   monsterCenter = relativePos;
   monsterCenter.X += monsters[n].GetSprite.Width / 2;
   monsterCenter.Y += monsters[n].GetSprite.Height / 2;

Distance is calculated from the center of the NPC’s sprite to the center of the PC’s sprite.

   dist = hero.CenterDistance(monsterCenter);
   if (dist < ATTACK_RADIUS)

As you can see, most of this code was developed previously. We first learned how to calculate relative position back in Chapter 8, “Adding Objects to the Dungeon,” and then how to calculate distance to an NPC in Chapter 11. When the PC is within range of an NPC, its radius circle will change from red to blue, and then the Space key will trigger an attack. In this first demo, nothing happens beyond printing out the attack flag status. We’ll evolve this initial code as the chapter proceeds.

Combat Demo 1

The first Combat demo project shows how to calculate the attack radius around each NPC and the logic to determine when the player is in range to attack. At this point, the NPCs do not move or react to the player and combat isn’t working yet. We will build on this example while building a combat system. See Figure 12.1. In the truncated source code listing below, the sections related to the radius and combat are highlighted in bold. For the complete source code listing of each sample in this chapter, see the chapter resources or the final example at the end of the chapter.

A circle around each hostile NPC show its combat radius.

Figure 12.1. A circle around each hostile NPC show its combat radius.

   private void doMonsters()
   {
       PointF relativePos;
       Pen color;
       PointF heroCenter;
       PointF monsterCenter;
       double dist=0;
       float spriteSize;
       Point center;
   RectangleF circleRect;
   
   //get center of hero sprite
   heroCenter = hero.CenterPos;
   game.Device.DrawRectangle(Pens.Red, heroCenter.X - 2,
       heroCenter.Y - 2, 4, 4);
   
   monstersInRange = 0;
   for (int n = 1; n < NUM_MONSTERS; n++)
   {
       //is monster in view?
       if (monsters[n].X > level.ScrollPos.X &&
           monsters[n].X < level.ScrollPos.X + 23 * 32 &&
           monsters[n].Y > level.ScrollPos.Y &&
           monsters[n].Y < level.ScrollPos.Y + 17 * 32)
       {
       monstersInRange += 1;
       relativePos = new PointF(
           Math.Abs(level.ScrollPos.X - monsters[n].X),
           Math.Abs(level.ScrollPos.Y - monsters[n].Y));
   
       //draw the monster sprite
       monsters[n].GetSprite.Draw((int)relativePos.X,
           (int)relativePos.Y);
   
       //get center of NPC
       monsterCenter = relativePos;
       monsterCenter.X += monsters[n].GetSprite.Width / 2;
       monsterCenter.Y += monsters[n].GetSprite.Height / 2;
       game.Device.DrawRectangle(Pens.Red, monsterCenter.X - 2,
           monsterCenter.Y - 2, 4, 4);
   
       //get distance to the NPC
       dist = hero.CenterDistance(monsterCenter);
   
       //draw line to NPCs in view
       if (dist < ATTACK_RADIUS)
           color = new Pen(Brushes.Blue, 2.0f);
       else
           color = new Pen(Brushes.Red, 2.0f);
       game.Device.DrawLine(color, heroCenter, monsterCenter);
   
       //print distance
       game.Print((int)relativePos.X, (int)relativePos.Y,
           "D = " + dist.ToString("NO"), Brushes.White);
   
       //draw circle around monster to show attack radius
       spriteSize = monsters[n].GetSprite.Width / 2;
       center = new Point((int)(relativePos.X + spriteSize),
           (int)(relativePos.Y + spriteSize));
       circleRect = new RectangleF(
           center.X - ATTACK_RADIUS, center.Y - ATTACK_RADIUS,
           ATTACK_RADIUS * 2, ATTACK_RADIUS * 2);
       game.Device.DrawEllipse(color, circleRect);
   }
       //is player trying to attack to this monster?
       if (dist < ATTACK_RADIUS)
       {
           if (attackFlag) attacking = true;
       }
       else attacking = false;
     }
   }

Character Templates

We have come to the point where additional artwork is needed to make any more progress with the Dungeon Crawler game, so in this section we will look at most of the character art planned for the game, including sprites for the player character and NPCs. Real characters will be created out of these templates in the final chapter when the game is fully realized. So, you’ll want to look at these character descriptions and animation artwork as mere templates of actual character classes, not characters themselves. Let’s take a short break from working on the combat system to look at the artwork. As you continue into the combat system in this chapter, begin thinking about which of these sprites you plan to use in the game.

Animations: Player Characters (PCs)

For the last two chapters, we have been using the paladin character file generated back in Chapter 10, “Creating Characters and Monsters.” That character has good animations and is representative of the PC for a dungeon crawler, but we have three additional sets of artwork available and need to bring them into the game world. Technically, we aren’t supposed to pre-roll a player character. Part of the fun factor of playing an RPG—one might even argue that it’s the most important part of the gameplay—is creating your own character. I don’t want to take away that joy from players by pre-rolling them with the character editor. This tool is meant to be used to create NPCs, but we use it to manage the artwork for the PC classes. The final game project includes a character generator! Let’s create random PC classes for use in this chapter. You have already seen the paladin, so we’ll roll the other three classes.

I will provide animation sets so that it is easy for you to add new characters to the game without requiring much extra work. The monsters need attack animations. I won’t focus on the player character classes here, but rather on what artwork is available for use in creating characters—because we have more artwork than we need for just the four classes (Warrior, Paladin, Hunter, and Priest). Feel free to use the animation sets specified in these template .char files for any unique classes of your own design. The purpose behind the .char file, at least for player characters, is to define the animation properties.

Hint

In the final game, these character templates will be used to create real game characters. The complete sets of animation and character editor data files are included for each one in this chapter’s resource files which can be downloaded from www.courseptr.com/downloads or from www.jharbour.com/forum.

Hero (Axe)

The hero sprite animations that show the character carrying an axe (Figure 12.2) are usually variations of the warrior, who may also carry a single sword, and is generally different from a paladin because he doesn’t have a shield.

Hero sprite wielding a single axe.

Figure 12.2. Hero sprite wielding a single axe.

Hero (Sword)

This hero sprite has a single sword and is often a variation of the warrior class (along with the axe sprite) (Figure 12.3).

Hero sprite wielding a single sword.

Figure 12.3. Hero sprite wielding a single sword.

Hero (Axe & Shield)

We have already seen the paladin character back in Chapter 11, but the actual character is not all that important prior to the player actually creating a player character at the start of a new game (Figure 12.4).

Hero sprite wielding an axe and shield.

Figure 12.4. Hero sprite wielding an axe and shield.

Hero (Sword & Shield)

The hero sprite animation featuring both a sword and shield (Figure 12.5) is also usually associated with the paladin class, but this is a loose interpretation that you are welcome to change if you wish!

Hero sprite wielding a sword and shield.

Figure 12.5. Hero sprite wielding a sword and shield.

Hero (Bow)

The hero sprite with bow animations (Figure 12.6) is obviously associated with a hunter, archer, or scout class, but you may invent any name for the class using these animation sets.

Hero sprite wielding a bow.

Figure 12.6. Hero sprite wielding a bow.

Hero (Staff)

The staff-wielding hero character (Figure 12.7) usually represents a cloth wearing mage, wizard, or priest. To show how these animations are created, I’ve included a screenshot of the priest sprite as it appeared in Pro Motion (the sprite animation software—see www.cosmigo.com for a trial copy). The sprite sheet produced by Pro Motion is shown next to it. Pro Motion saves the file as a .bmp file, so it must be converted to a .png with an alpha channel, and I prefer to use GIMP for that step. It’s easy to use the Color Select tool to highlight the background color and convert it to a Layer Mask.

Hero sprite wielding a staff, shown here with animation assembled with Pro Motion.

Figure 12.7. Hero sprite wielding a staff, shown here with animation assembled with Pro Motion.

Hero (Unarmed)

The hero sprite animation with no weapon or shield (Figure 12.8) still has unarmed animations, with a kick attack, which might be fun to explore in a game. How about a Kung Fu character?

Hero sprite wielding only fists of fury!

Figure 12.8. Hero sprite wielding only fists of fury!

Animations: Hostile NPCs

The hostile NPC character sheets show just what is possible with these animation sets, but you are encouraged to create characters of your own design with the character editor using these template files as a starting point (with the animations already in place).

Skeleton (Bow)

The skeleton animation set with a bow (Figure 12.9) is an undead archer or hunter.

Undead skeleton sprite with bow.

Figure 12.9. Undead skeleton sprite with bow.

Skeleton (Sword & Shield)

The sword & shield animation set for the undead skeleton (Figure 12.10) can be used for a typical former warrior or paladin turned undead, with any number of actual characters derived from it. Since there is no other variation of an undead melee character, this one will be reused. The image here shows the sprite animation as it appears in Pro Motion, along with the saved sprite sheet.

Undead skeleton sprite with sword & shield.

Figure 12.10. Undead skeleton sprite with sword & shield.

Skeleton (Unarmed)

The unarmed undead skeleton sprite (Figure 12.11) is a good general-purpose template for lower-level enemy characters, which may be good for early leveling by player characters.

This undead skeleton sprite has no weapon.

Figure 12.11. This undead skeleton sprite has no weapon.

Zombie (Unarmed)

This undead zombie sprite (Figure 12.12) has no weapon, but that is traditionally accurate of zombies in popular culture (namely, zombie films). With an appetite for gray matter, the player would do well to take them out at range!

The zombie is undead—but what else could it be?

Figure 12.12. The zombie is undead—but what else could it be?

Creating the Combat System

Melee combat occurs when opponents fight each other with hand-held weapons (while unarmed combat occurs when no weapons are used, as in a martial art). The game engine already has support for melee combat, but it’s just not put into place yet. First, we must determine when the player is near an enemy, then cause the enemy to turn toward the player, and then allow combat to ensue. Fortunately, the sprites available from Reiner’s Tilesets (www.reinerstilesets.de) also include the attack and falling animations. We already have the first step working. Next, we’ll work on causing sprites to face each other and attack.

Combat requires another layer of logic added to the state engine that controls the characters. Although higher-level interaction with characters would make the game more realistic, we’ll just be treating this like an old-style hack-and-slash game where the goal is not to follow some sort of complex storyline, but rather to gain experience and explore the world. A simple state-based system will be used to cause enemies to attack the player when in close range.

Making Up Our Own Rules

There are many role-playing game systems that we can emulate for our own games, or we can just borrow ideas from many different games and come up with totally unique gameplay and rules. I am not proposing following any particular franchise for the rules in Dungeon Crawler, but some common rules will be familiar to an experienced RPG player.

The current project at this point is a template game that has most of the functionality you need to create an RPG, but is lacking most of the finer details.

There is just an enormous amount of detail that must be put into even the simplest of RPGs. We will create a fun hack-and-slash game where the goal is to gain experience and go up levels, with the corresponding new abilities and skills, but there won’t be an overriding plot or story—that’s your job!

Chapter 11 developed the ability for the player to have encounters with NPCs, which is an important first step in the game’s NPC interaction. From this point, you can engage the NPCs in dialogue or combat, and the game responds appropriately. A higher level of behavior over the NPCs is also needed to turn this skeleton game into a polished game, a system of behavior that causes NPCs to seek out and engage the player, rather than always responding to the player. At the very least, you can add the ability for NPCs to fight back.

Spawning Enemy NPCs

A non-player character (NPC) can be friendly or hostile—we might refer to them as “friendly NPCs” or “hostile NPCs”—but they are still grouped together as “not the player,” for the sake of discussion. We’re going to combine the work done already to create random monsters that will react to the player with, well, hostility.

Hint

In the final chapter, you will see how to position NPCs and objects in the game world using script code. For the sake of clarity, we will not be getting into the editor data in this chapter.

When you are fighting with an NPC and kill that character, there should be a death animation. These are not always possible in every case, due to a limited number of sprites. You are limited overall by the availability of artwork, without which you have to get creative with your sprites. Rather than dealing with a whole slew of death animations for each NPC, I have seen some games use the fade effect, where a character blinks out of existence or fades away. You might use the alpha color parameter in the sprite class to cause a character to fade out of existence after dying rather than using a death animation. The important thing is that you recycle your sprites in the game, which means recycling the NPCs. You don’t want the NPCs to just respawn at the same place every time, because then the player can see the spawning taking place (which seriously ruins the realism of the game). In addition, if a player learns where some of the NPCs are respawning on the map, he or she will be able to spawn camp (which refers to hiding out near a spawn point and killing new players that appear) and rack up a ridiculous amount of experience, which also ruins the game.

Tip

The fully prepared sprite sheets (with all three animations) and the .char file for several hostile and friendly NPC characters are included in this chapter’s resource files in folders called PCs and NPCs.

Attack Rolls

What really happens when you attack another character in the game? That is the basis of the game’s combat system, and it has to do with each player’s attributes, including weapon and armor class. Usually, the defender’s defensive value is compared to the attacker’s attack value, and a simulated “roll” of dice is made to determine if the attack succeeded (before calculating damage). All of the attributes are available already from the character editor files.

If the attack value is less than the defense value, then basically you can do no damage to your opponent! So, say you are a new warrior with an axe that does +10 damage, and you attack a level 10 zombie with 93 defense points. What happens in this situation? You can stand there and bang against this monster all day long with your pathetic little axe and do no damage to him! In a situation like this, you are helplessly outclassed by the monster, which swiftly and easily kills you with a single blow.

This is called the “to-hit roll” and it adds a nice layer of realism to the game (as opposed to some games where just swinging your sword kills enemies nearby). Knowing that not every swing does damage requires you to use some tactics in your fighting method, and this gives players the ability to be somewhat creative in how they fight enemies. You can swing and run or swing several times in a row, hoping to get a hit. It’s a hit-or-miss situation. Figure 12.13 shows an example of several types of dice.

Six different dice with 4, 6, 8, 10, 12, and 20 sides. Image courtesy of Wikipedia.

Figure 12.13. Six different dice with 4, 6, 8, 10, 12, and 20 sides. Image courtesy of Wikipedia.

Many RPGs allow the player to equip modifiers such as rings and special weapons with bonuses for the to-hit value. These modifiers increase your chances of scoring a hit when you attack. Not only is it essential for a good RPG, but working with miscellaneous items as well as different types of swords, shields, armor, helmets, and so on, is an extremely fun part of the game! Our Character class will be modified over the next two chapters to add support for gear such as the weapon, armor, and other items that the player can equip, and you have an opportunity to use these special items to customize characters. You may even allow the player to pick up items found in the world and equip them.

Armor Class (AC)

A character’s armor class determines whether an attack made against him will succeed or not. If the attack fails, then no damage is applied at all. Usually, regardless of the AC, an attack to-hit roll of 1 is an epic fail and does not hit. Here’s one possible way to calculate AC:

AC = DEX + Armor Points + Shield Points

where Armor Points represent the sum total of all armor items and Shield 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, only that combat is balanced within your own game system. In other words, as long 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 too quickly. It’s generally necessary to artificially raise the hit points (HP) of monsters 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 around taking out enemies with a single blow as if they’re using a lightsaber. We do want the player’s attributes to play an important role in the to-hit roll as well as the damage done in an attack.

For Dungeon Crawler, I’m going to use a D20 (a 20-sided die) as the basis for the to-hit roll. In RPG lingo, a D20 roll of 1 is an epic fail while a roll of 20 is a critical 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 with DEX instead of STR as a modifier. The character’s agility contributes to his ability 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 how much damage was done to the target. This is where the weapon attributes come into 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 can scatter 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 the Reiner’s Tileset collection.)

Melee “Damage” Rolls

The melee damage value is calculated primarily from STR and weapon damage with a 1D8 roll added to the mix. This damage factor is then reduced by the defender’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!

Attack Roll Example

Let’s simulate one half of an attack round where just one player attacks and the other defends, to see how the calculations are done and what results we get. First of all, we’ll give the player these attributes:

  • STR: 18

  • DEX: 12

  • STA: 9

  • Weapon: 2–8 dmg

  • Armor: 10

  • HP: 14

The monster will have these attributes:

  • STR: 15

  • DEX: 14

  • STA: 16

  • Weapon: 1–6 dmg

  • Armor: 12

  • HP: 16

Armor Class

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?

To-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
Damage = 6 + 18 + 7 - 26 = 5

Hit Points

The monster’s HP is reduced by the total damage until it reaches zero (which is death):

HP = 16 - 2 = 14 (normal damage)
HP = 16 - 5 = 11 (critical damage)

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 screen, and something as cheesy as a load/save feature takes away from that. I’ll admit, though, most players abuse the save/load game feature and complain if you don’t have one. After all, you want the player to be able to quit at a moment’s notice without going through any hassle. Let’s face it: Sometimes the real world asserts itself into the reverie you are experiencing in the game, and you have to quit playing.

But just for the sake of gameplay, what is the best way to deal with the player character’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 bit before resuming the fight they were in previously? Re-spawning too close to the last 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 to get 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 attack against an NPC (Figure 12.14). This demo uses the Dialogue class to show the results 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 a target NPC. Most RPG purists will enjoy seeing this information, whereas casual RPG fans will prefer to just hurry up and kill the monster so they can loot its corpse for items and gold. It’s up to you to decide how much information you want to share with the player.

Demonstration of an attack roll against a hostile NPC.

Figure 12.14. Demonstration of an attack roll against a hostile NPC.

On the one hand, it might be impressive to see what all is involved in an attack with the various rolls and calculations, since the casual player might just assume your combat system uses a simple attack roll versus defense roll system. If you don’t show any information, and just show damage dealt (as in games like Baldur’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 every player knows this intuitively, but it’s easy to forget if combat tends to happen very 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 hand, 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-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 how attackFlag is now handled.

  private void Form1_KeyDown(object sender, KeyEventArgs e)
  {
      switch (e.KeyCode)
      {
          case Keys.Escape: gameover = true; break;
          case Keys.Up:
          case Keys.W: keyState.up = true; break;
          case Keys.Down:
          case Keys.S: keyState.down = true; break;
          case Keys.Left:
          case Keys.A: keyState.left = true; break;
          case Keys.Right:
          case Keys.D: keyState.right = true; break;
      }
  }
  private void Form1_KeyUp(object sender, KeyEventArgs e)
  {
      switch (e.KeyCode)
      {
          case Keys.Up:
          case Keys.W: keyState.up = false; break;
          case Keys.Down:
          case Keys.S: keyState.down = false; break;
          case Keys.Left:
          case Keys.A: keyState.left = false; break;
          case Keys.Right:
          case Keys.D: keyState.right = false; break;
          case Keys.Space: attackFlag = true; break;
      }
  }

More Dialogue

We need the Dialogue class again to show the results of an attack. You can now see how useful Dialogue is beyond its original intended use as a way to talk with NPCs! Granted, the window is not very attractive yet. We will need to add some more configuration options to it so the buttons look better and the height is adjusted 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 add some new features to the Dialogue class. See, I warned you that this was likely to happen! 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 a Visible property.

  private bool p_visible;
  public bool Visible
  {
      get { return p_visible; }
      set { p_visible = value; }
  }

The Draw() function will check p_visible before 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 new doUpdate() function, which now handles scrolling, hero, monsters, attacking, and dialogue.

  private void doUpdate()
  {
      int frameRate = game.FrameRate();
      int ticks = Environment.TickCount;
      if (ticks > drawLast + 16)
      {
          drawLast = ticks;
          doScrolling();
          doHero();
          doMonsters();
          doAttack();
          doDialogue();
          game.Print(0, 0, "Monsters in range: " +
                  monstersInRange.ToString());
          game.Print(320, 570, "Press SPACE to Attack");
          game.Update();
          Application.DoEvents();
      }
      else Thread.Sleep(1);
  }

The doDialogue() function does not automatically move, but you may use that feature if you want (see Chapter 15 for details). I want the combat dialogue to stay in the same place.

  private void doDialogue()
  {
      dialogue.updateMouse(game.MousePos, game.MouseButton);
      dialogue.setCorner(Dialogue.Positions.UpperRight);
      dialogue.Draw();
      if (dialogue.Selection > 0)
      {
          dialogue.Visible = false;
          dialogue.Selection = 0;
      }
  }

The doDialogue() function is called continuously from the main loop, and properties determine what it should do. To trigger a dialogue to “pop up,” we can call on this new showDialogue() function, which automatically formats the dialogue with two buttons:

  private void showDialogue(string title, string message,
      string button1, string button2)
  {
      dialogue.Title = title;
      dialogue.Message = message;
      dialogue.NumButtons = 2;
      dialogue.setButtonText(1, button1);
      dialogue.setButtonText(2, button2);
      dialogue.Visible = true;
  }

Attack!

The doAttack() 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;
      int roll = 0;
      int AC = 0;
      int damage = 0;
      string text="";
  
      if (!attacking) return;
  
      //calculate target's AC
      AC = monsters[target].DEX + DEF_ARMOR + DEF_SHIELD;
  
      //calculate chance to-hit for PC
      roll = game.Random(1, 20);
      text += "To-Hit Roll: " + roll.ToString();
      if (roll == 20)
      {
          //critical hit!
          hit = true;
          critical = true;
          text += " (CRITICAL!)
";
      }
      else if (roll == 1)
      {
          fail = true;
          text += " (EPIC FAIL!)
";
      }
      else
  {
      //normal hit
      roll += hero.STR;
      if (roll > AC) hit = true;
      text += " + STR(" + hero.STR.ToString() + ") = " +
          roll.ToString() + "
";
  }
  
  //did attack succeed?
  if (hit)
  {
      //calculate base damage
      damage = game.Random(1, 8);
  
      //add critical
      if (critical) damage *= 2;
  
      text += "Damage roll: " + damage.ToString() + "
";
  
      //add STR
      damage += hero.STR;
      text += " + STR(" + hero.STR.ToString() + ") = " +
          damage.ToString() + "
";
  
      //add weapon damage (usually a die roll)
      damage += WEAPON_DMG;
      text += " + weapon(" + WEAPON_DMG.ToString() + ") = " +
          damage.ToString() + "
";
  
      //subtract AC
      damage -= AC;
      text += " - monster AC(" + AC.ToString() + ") = " +
          damage.ToString() + "
";
  
      //minimal hit
      if (damage < 1) damage = 1;
  
      //show result
      text += "Attack succeeds for " + damage.ToString() +
          " damage.";
      }
      else
          text += "Attack failed.
";
  
      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.

This demo shows how to cause sprites to face toward each other in order to fight.

Figure 12.15. This demo shows how to cause sprites to face toward each other in order to fight.

Hint

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)
  {
  
       int 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
           else if (source.Y > target.Y + 8)
               direction = 7; //north west
           else
               direction = 6; //west
       }
       else
       {
           //facing north or south
           if (source.Y < target.Y - 8)
               direction = 4; //south
           else if (source.Y > target.Y + 8)
               direction = 0; //north
       }
       return direction;
}

Using this function, we can modify doMonsters() 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?
  if (dist < ATTACK_RADIUS)
  {
      game.Device.DrawEllipse(new Pen(Brushes.Blue, 2.0f),
          monsterCenter.X - 24, monsterCenter.Y, 48, 48);
      if (attackFlag)
      {
          attacking = true;
          target = n;
          attackFlag = false;
  
          //make PC and NPC face each other
          int dir = getTargetDirection(monsterCenter, hero.CenterPos);
          monsters[target].Direction = dir;
          monsters[target].Draw();
  
          dir = getTargetDirection(hero.CenterPos, monsterCenter);
          hero.Direction = dir;
          hero.Draw();
  
          break;
      }
  }

A Change of Character

A minor change is required in the Character class to support the feature of forcing sprites to face toward each other. The original single Character.Draw() function is replaced with these three versions:

    public void Draw()
    {
        Draw((int)p_position.X, (int)p_position.Y);
    }
    public void Draw(PointF pos)
    {
        Draw((int)pos.X, (int)pos.Y);
    }
    public void Draw(int x, int y)
    {
        int startFrame, endFrame;
        switch (p_state)
        {
            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);
                }
                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
  {
      ATTACK_NONE,
      ATTACK_TRIGGER,
      ATTACK_ATTACK,
      ATTACK_RESULT,
  }

This is the variable we will be using to keep track of the current state of the combat system:

AttackStates 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.

The new state-based combat system slows down and improves the gameplay.

Figure 12.16. The new state-based combat system slows down and improves the gameplay.

Dialogue Improvements

Now that we’re using a state-based system for combat, we need to modify other parts 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 a mouse click will be seen as many clicks while the button is being held. No matter how fast you press and release the mouse button, it will pick up several events because the loop is running at 60 fps. What we need to do is look for a button release event instead. A new variable is needed:

  private 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)
  {
      p_mousePos = mousePos;
      p_oldMouseBtn = p_mouseBtn;
      p_mouseBtn = 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 to doMonsters() and doCombat(), 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.

  if (attackState == AttackStates.ATTACK_NONE)
  {
      if (attackFlag)
      {
          attackState = AttackStates.ATTACK_TRIGGER;
          target = n;
          attackFlag = false;
          dialogue.Visible = true;
  
          //make PC and NPC face each other
          int dir = getTargetDirection(monsterCenter, hero.CenterPos);
          monsters[target].Direction = dir;
          monsters[target].Draw();
          dir = 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 to reset combat if the player has walked away or cancelled combat. The new attackText variable is defined in Form1 as a string.

  if (monstersInRange == 0)
  {
      target = 0;
      attackText = "";
      dialogue.Visible = false;
      attackState = AttackStates.ATTACK_NONE;
  }

Likewise, some serious changes have been made to doAttack() to support the new state-based combat system. Here is the new code for the function. All of the previous code in doAttack() is now contained solely inside the ATTACK_ATTACK 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;
        int roll = 0;
        int AC = 0;
        int damage = 0;
        string text="";

        if (dialogue.Selection == 2)
        {
            attackState = AttackStates.ATTACK_NONE;
            return;
        }

        switch (attackState)
        {
            case AttackStates.ATTACK_NONE:
                break;

            case AttackStates.ATTACK_TRIGGER:
                if (target > 0)
                {
                    text = "You are facing a " + monsters[target].Name +
                        ". " + monsters[target].Description;
                    showDialogue("Prepare to Attack", text, "ATTACK",
                        "CANCEL");
                }
                if (dialogue.Selection == 1)
                    attackState = AttackStates.ATTACK_ATTACK;
                break;

            case AttackStates.ATTACK_ATTACK:
                //calculate target's AC
                AC = monsters[target].DEX + DEF_ARMOR + DEF_SHIELD;

               //calculate chance to-hit for PC
               roll = game.Random(1, 20);
               text += "To-Hit Roll: " + roll.ToString();
               if (roll == 20)
               {
                   //critical hit!
                   hit = true;
                   critical = true;
                   text += " (CRITICAL!)
";
               }
               else if (roll == 1)
               {
                    fail = true;
                    text += " (EPIC FAIL!)
";
               }
               else
               {

                   //normal hit
                   roll += hero.STR;
                   if (roll > AC) hit = true;
                   text += " + STR(" + hero.STR.ToString() + ") = " +
                        roll.ToString() + "
";
                }

                //did attack succeed?
                if (hit)
                {
                   //calculate base damage
                   damage = game.Random(1, 8);

                   //add critical
                   if (critical) damage *= 2;

                   text += "Damage roll: " + damage.ToString() + "
";

                   //add STR
                   damage += hero.STR;
                   text += " + STR(" + hero.STR.ToString() + ") = " +
                       damage.ToString() + "
";

                   //add weapon damage (usually a die roll)
                   damage += WEAPON_DMG;
                   text += " + weapon(" + WEAPON_DMG.ToString() +
                       ") = " + damage.ToString() + "
";

                   //subtract AC
                   damage -= AC;
                   text += " - monster AC(" + AC.ToString() + ") = " +
                       damage.ToString() + "
";

                   //minimal hit
                   if (damage < 1) damage = 1;

                   //show result
                   text += "Attack succeeds for " + damage.ToString() +
                       " damage.";
               }

               else
                   text += "Attack failed.
";

               attackText = text;
               attackState = AttackStates.ATTACK_RESULT;
               break;

           case AttackStates.ATTACK_RESULT:
               showDialogue("Attack Roll", attackText, "ATTACK AGAIN",
                   "CANCEL");
               if (dialogue.Selection == 1)
                   attackState = AttackStates.ATTACK_ATTACK;
               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.

The skeletons don’t get a free ride anymore!

Figure 12.17. The skeletons don’t get a free ride anymore!

Tip

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.

These few changes are needed in the doAttack() function to add the ability to kill monsters, gain experience, and level up. Portions of the code that have not changed that we have already recently covered are omitted. There are two states involved when a battle is concluded: ATTACK_RESULT and ATTACK_LOOT. The former tallies the experience, kills the monster, and displays the dialogue message. The latter is simply used to hold the dialogue window up until the player has acknowledged the information by clicking the “CLOSE” button.

Note: some variables in this code will be new; refer to the source code in the finished project to see the variable declarations.

  switch (attackState)
  {
      case AttackStates.ATTACK_NONE:
          hero.AnimationState = Character.AnimationStates.Walking;
          dialogue.Visible = false;

          break;
  
      case AttackStates.ATTACK_TRIGGER:
          if (target > 0)
          {
              text = "You are facing a " + monsters[target].Name +
                  ". " + monsters[target].Description;
              showDialogue("Prepare to Attack", text, "ATTACK",
                  "CANCEL");
          }
          if (dialogue.Selection == 1)
              attackState = AttackStates.ATTACK_ATTACK;
  
          break;
  
      case AttackStates.ATTACK_ATTACK:
          //calculate target's AC
          AC = monsters[target].DEX + DEF_ARMOR + DEF_SHIELD;
  
          //calculate chance to-hit for PC
          roll = game.Random(1, 20);
          text += "To-Hit Roll: " + roll.ToString();
          if (roll == 20)
          {
             //critical hit!
             hit = true;
             critical = true;
             text += " (CRITICAL!)
";
          }
          else if (roll == 1)
          {
               fail = true;
               text += " (EPIC FAIL!)
";
          }
          else
          {
               //normal hit
               roll += hero.STR;
               if (roll > AC) hit = true;
               text += " + STR(" + hero.STR.ToString() + ") = " +

                  roll.ToString() + "
";
          }
  
          //did attack succeed?
          if (hit)
          {
               //calculate base damage
               damage = game.Random(1, 8);
  
               //add critical
               if (critical) damage *= 2;
  
               text += "Damage roll: " + damage.ToString() + "
";
  
               //add STR
               damage += hero.STR;
               text += " + STR(" + hero.STR.ToString() + ") = " +
                   damage.ToString() + "
";
  
               //add weapon damage (usually a die roll)
               damage += WEAPON_DMG;
               text += " + weapon(" + WEAPON_DMG.ToString() +
                   ") = " + damage.ToString() + "
";
  
               //subtract AC
               damage -= AC;
               text += " - monster AC(" + AC.ToString() + ") = " +
                   damage.ToString() + "
";
  
               //minimal hit
               if (damage < 1) damage = 1;
  
               //show result
               text += "Attack succeeds for " + damage.ToString() +
                            " damage.";
          }
          else
               text += "Attack failed.
";
  
          attackText = text;
          attackState = AttackStates.ATTACK_RESULT;
          break;
  
      case AttackStates.ATTACK_RESULT:
          hero.AnimationState = Character.AnimationStates.Walking;
  
          //is monster dead?
          if (monsters[target].HitPoints <= 0)
          {
              monsters[target].Alive = false;
              int xp = game.Random(50, 100);
              addExperience(xp);
              text = monsters[target].Name + " Defeated!";
              attackText = "You have slain the " + monsters[target].Name +
                  "! You gained " + xp.ToString() + " experience.";
              showDialogue(text, attackText, "CLOSE");
              attackState = AttackStates.ATTACK_LOOT;
          }
          else
          {
              showDialogue("Attack Roll", attackText, "ATTACK AGAIN",
                  "CANCEL");
              if (dialogue.Selection == 1)
                  attackState = AttackStates.ATTACK_ATTACK;
          }
          break;
  
      case AttackStates.ATTACK_LOOT:
          hero.AnimationState = Character.AnimationStates.Walking;
          if (dialogue.Selection == 1)
          {
              attackState = AttackStates.ATTACK_NONE;
              target = 0;
          }
          break;
  }

Gaining Experience

A helper function called addExperience() will take care of granting the player experience and leveling up. At this early stage of development, leveling up is a trivial affair meant only to demonstrate that it can be done. In the finished game, we will want to grant the player new attribute points (to STR, DEX, etc.) to reflect the fact that their new level makes them stronger. This function is just a stopgap for the time being to demonstrate experience and leveling, while a more permanent solution will be offered in the final chapter.

  private void addExperience(int xp)
  {
      hero.Experience += xp;
      if (hero.Experience > 200)
      {
          hero.Level += 1;
          hero.Experience -= 200;
      }
  }

The Character class needs some new features to accommodate the requirements for gaining experience and leveling up the character.

  private int p_experience;
  public int Experience
  {
      get { return p_experience; }
      set { p_experience = value; }
  }
  
  private int p_level;
  public int Level
  {
      get { return p_level; }
      set { p_level = value; }
  }

We will also add a new Character.Alive property to make it easier to flag monsters as they are killed and prevent the code from updating those characters.

  private bool p_alive;
  public bool Alive
  {
      get { return p_alive; }
      set { p_alive = value; }
  }

The death animation will require a new item in the Character.AnimationStates enumeration so that when the animation finishes one cycle, the sprite will remain on the ground, showing the final frame of the death animation for good.

  public enum AnimationStates
  {
      Walking = 0,
      Attacking = 1,
      Dying = 2,
      Dead = 3
  }

The final change required to get this code to work is the addition of another showDialogue() method overload with just one button instead of two.

  private void showDialogue(string title, string message, string button1)
  {
      dialogue.Title = title;
      dialogue.Message = message;
      dialogue.NumButtons = 1;
      dialogue.setButtonText(1, button1);
      dialogue.Visible = true;
  }

Level Up!

After play testing the Combat demo 5 project a few times, I’m extremely pleased with the decision to use a turn-based combat system instead of real time. This slows down the game, gives it more character, and allows the player to become more involved in the gameplay. Combat in a traditional pen-and-paper RPG is not a quick “hack-n-slash” affair that ends in seconds—the players must calculate the results of each attack, roll the dice for their attack roll and damage roll, while the defender often has “succeed or fail” rolls to block the attack, based on their abilities. It is this interaction that makes the game fun. Simply swinging the weapon as fast as the game will let you and mopping up the loot afterward takes away a huge amount of the fun factor. However, there most certainly is a balance to be found: you don’t want to bore your player with micro managing his character. One type of combat we did not address in this already lengthy chapter is ranged attacks (of the bow and spell variety). As it turns out, these types of attacks tend to get handled very much like melee attacks but with a little bit more range, so all we need to do is increase the attack radius around each monster to allow for ranged weapons. Perhaps that is an attribute best handled by the weapon’s stats? Ironically, that is the very subject we’re covering in the next chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.134.103.74