Chapter 10. Creating Characters and Monsters

This chapter covers character creation using a custom editor tool, and discusses the usual attributes associated with an RPG based on character race, class, and so on. You will learn how to take the designs of the character classes and make use of them in the game by applying the player character’s attributes to the combat system and other aspects of any traditional RPG, such as gaining experience and leveling up. Some of these issues will be dealt with in more detail in upcoming chapters, whereas the foundation is laid here for working with character data. The character editor uses the same XML format that was used by the level editor, so we will be able to use similar code in a new Character class to load character data files in C#.

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

  • Character classes and attributes

  • Gaining experience and leveling up

  • Base character classes

  • Enemy and monster classes

  • Loading character files in the game

  • Character artwork

Character Classes and Attributes

All of the previous chapters have focused on the difficult task of getting a fully animated player to walk around in a scrolling game world. Both the animation and the movement should be semi-realistic, and tile-collision detection should prevent the player from walking through solid and impassable tiles (which still requires some work but is coming together), and using portals. Now that these basic problems have been solved, we can get into the game’s design and the nuances of combat and NPC interaction.

Attributes

Attributes determine what a character is capable of doing in the game, whether it’s swinging a sword, firing arrows, or defending against attacks from others. The player attributes are the most important part of the character creation process that follows.

Strength (STR)

Strength represents the character’s ability to carry weight and deal damage with a melee weapon. It is generally good for the warrior and paladin classes, which use melee weapons. Strength is used to calculate the attack damage for the character if a hit is successful (see “Dexterity (DEX)” for details on the “to hit” factor). First, the player has to hit the target before damage is calculated. So, even an enormous amount of STR will rarely come into play if dexterity is too low. Therefore, both of these attributes are crucial for a melee fighter! STR is of little use to a priest (who favors intellect) and is of minor import to a hunter (who relies more on dexterity).

Dexterity (DEX)

Dexterity represents the agility of a character—the skillful use of one’s hands. This affects the ability to wield melee weapons and shields effectively to block and parry attacks and to hit accurately with a ranged weapon such as a bow. Low DEX leads to a somewhat clumsy character, while high DEX means the character can perform complex actions (perhaps wielding two weapons). Dexterity determines the defense and the chance to hit factors in combat. See Chapter 12, “Fighting Monsters, Gaining Experience, and Leveling Up,” for more details on combat calculations. The “chance to hit” value is rolled against the defender’s defense value to determine if an attack is successful. Thus, it is of little use for a level 1 character to attack someone who is level 20, because he will not be able to land hits, let alone do any damage.

Stamina (STA)

Stamina represents a character’s endurance, the ability to continue performing an activity for a long period of time, and it is used directly in the calculation of hit points (health). High STA provides a character with the ability to engage in lengthy battles without rest, while low STA causes a character to get tired quickly and fall in battle. Although every class benefits from stamina, it is more vital for the melee characters since they engage in “in your face” combat. Although a low STA will lead a hunter or priest to fall just as quickly, they aren’t likely to take as many hits since they attack at range.

Intellect (INT)

Intellect represents the character’s ability to learn, remember things, and solve problems. A very high INT is required by the priest class, while relatively low INT is common in fighters where brute force is more important than mental faculties. Also, INT affects the amount of experience gained for performing actions such as defeating enemies and completing quests, so a character with high INT will level up more quickly. This is important for the ranged classes since they usually have fewer battles.

Charisma (CHA)

Charisma represents the character’s attractiveness, which affects how others respond to the character. High CHA attracts people, while low CHA repels them—although in the violent world of the dungeon crawler, a “pretty boy” is of little use even to the ladies. In the converse, low CHA also represents the ugliness of a monster such as an undead zombie. CHA does not represent just a character or monster’s scariness, but is more related to personality and physical attractiveness. In other words, it is possible for a dangerous creature (such as a dragon) to be beautiful.

Hit Points

Hit points, or HP, represent the amount of health a character has. HP is calculated initially (at level 1) by adding a D8 roll to the character’s stamina.

Then, each time the character levels, additional HP is added with another die roll. Thus, it is entirely possible to create a weakling of a warrior (by consistently rolling badly) who has less HP than even a priest. It’s all left to chance, which is what makes RPGs so universally compelling. Purists will play with their rolled stats, while less serious players will re-roll their character until they get the points that they want. Generally, the class modifiers make up for bad initial rolls.

Gaining Experience and Leveling Up

One of the most rewarding aspects of an RPG is gaining experience by performing actions in the game (usually combat) and leveling up your character. When you start the game, the character is also just starting out as a level 1 with no experience. This reflects the player’s own skill level with the game itself, and that is the appeal of an RPG: You, the player, gain experience with the game while your character gains experience at the same time in the virtual world. You grow together and become a seamless “person.”

Both you and your character improve as you play the game, so you transfer some of your own identity to the character, and in some cases, younger players even assume some of the identity of their inspiring hero. This fascinating give-and-take relationship can really draw someone into your game if you design it well! Like I have said, cut back on the magic and let players really get in the game world and experience some good, solid combat to make the whole experience feel more real, and less detached. You want to do everything possible to suspend the players’ disbelief that they are in a game—you want to bring them into your game world by doing things that cause them to invest emotionally in their characters.

The most common way to gain experience is through combat with monsters and enemy characters. Since there are a lot of calculations involved in the chance to hit, armor class, melee attack, ranged attack, spell attack, and other factors, I will reserve Chapter 12 for a discussion of the mechanics of combat.

The Base Character Classes

The standard, or base, classes can be used for the player as well as for the non-player characters (NPCs). You should feel free to create as many classes as you want to make your game world diversified and interesting. The classes I have described here are just the usual classes you find in an RPG, which you might consider the stock classes. Each class also has subclasses, or specialties within that class. For instance, Paladins are really just a subclass of the Knight, which may include Teutonic Knight, Crusader, and so on.

When you are designing a game, you can make it as historically accurate or as fictional as you want; don’t feel compelled to make every class realistic or historically based. You might make up a fictional type of Knight subclass, such as a Dark Knight or Gothic Knight, with some dark magic abilities. However, I want to encourage you to shy away from overdoing the magic system in a game. Many RPGs have character classes that might be thought of as wizards on steroids, because the whole game boils down to upgrading spells and magic, with little emphasis on “realistic” combat.

You would be surprised by how effective an RPG can be with just a few magic abilities. You can really go overboard with the hocus pocus, and that tends to trivialize a well-designed storyline and render interesting characters into fireball targets. No warrior should be able to do any magic whatsoever. Think about it: The warriors are basically barbarians—massive, hulking fighters who use brute force to bash skulls on the battlefield (think Arnold Schwarzenegger in the Conan movies). This type of character can become civilized and educated, but so many games blur the line here and allow any class to develop magical abilities. (I’m just pointing out some obvious design concerns with characters. If you really want a world of magic, then go ahead and create magical characters; that sounds like a really fun game, as a matter of fact!) If you are designing a traditional RPG, then be realistic with your classes and keep the magic reasonable. Think about The Lord of the Rings; these stories are a source of inspiration for every RPG ever made. Everything since J.R.R. Tolkien has been derivative!

The character editor tool has the ability to apply modifiers to the basic stats, but this is a manual process. If you add new classes to the cboClass list in the editor, then you’ll have to make changes to the modifiers manually in the code (hint: look for the code in cboClass_SelectedIndexChanged()).

One design consideration that we might use is the concept of class modifiers. Say you have a set of stock classes like those listed in the upcoming tables. Instead of re-creating a class from scratch using similar values, you can create a subclass based on the parent class—and modify the attributes by a small amount to produce the new class with custom attributes. Say, for instance, that you want to create a new type of Warrior called the Berserker, which is an extremely stupid and ugly character with immense strength and stamina. Sounds a little bit scary, doesn’t it? By setting the base class of the Berserker to Warrior, you can then modify the base class at any time and the Berserker automatically is changed along with the base class (Warrior). This works great for balancing the gameplay without requiring that you modify every single subclass that you have used in the game. Since our character class system in the dungeon crawler is based on classes, we can easily subclass the base character classes to create new types of characters in this manner.

Hint

It goes without saying that female characters are fun to play with in an RPG. Unfortunately, we have no female equivalents to the four player characters represented in the game artwork. Yes, there are some female NPCs available in the artwork from Reiner Prokein, but not for the primary character classes. If you have artwork available, I encourage you to add a gender property to the character editor. Gender is not extremely important to the gameplay, as it turns out.

Tables 10.1 to 10.4 present my idea of a character class structure that is programmed into the editor. Since this only applies to the editor, and not to any game source code, you may modify the editor to use your own preferred values and classes.

Table 10.1. Warrior Attributes

Attribute

Roll

Modifiers (+15)

Strength

2D6

+8

Dexterity

2D6

+3

Stamina

2D6

+4

Intellect

2D6

0

Charisma

2D6

0

Hit Points

1D8

+STA

Warrior Attributes

Table 10.2. Paladin Attributes

Attribute

Roll

Modifiers (+15)

Strength

2D6

+3

Dexterity

2D6

+3

Stamina

2D6

+8

Intellect

2D6

0

Charisma

2D6

+1

Hit Points

1D8

+STA

Paladin Attributes

Table 10.3. Hunter Attributes

Attribute

Roll

Modifiers (+15)

Strength

2D6

+2

Dexterity

2D6

+8

Stamina

2D6

+4

Intellect

2D6

0

Charisma

2D6

+1

Hit Points

1D8

+STA

Hunter Attributes

Table 10.4. Priest Attributes

Attribute

Roll

Modifiers (+15)

Strength

2D6

0

Dexterity

2D6

+6

Stamina

2D6

+1

Intellect

2D6

+8

Charisma

2D6

0

Hit Points

1D8

+STA

Priest Attributes

To keep your game character classes balanced, it’s important to use a standard total for all modifiers so that they all add up to the same amount. I have based the following class modifiers on a total of 15 points. In testing the game, it seemed that values much lower made the hero characters less impressive (compared to, say, a peasant), while values much higher resulted in unusually powerful characters. If you want a character to have one very high attribute, then that will have to be balanced with an equally low value for another. Note also that monsters need not follow this standard—go ahead and use whatever factors you want to create unique foes. The goal of this point modifier system is to create player characters that are based on the same fixed number of attribute points.

Hint

The character class modifiers should add up to a total of 15 points. The points can be applied to any of the attributes. These mod values are then added to 2D6 rolls to come up with the total attribute values.

Warrior Class

The warrior class represents the strongest melee fighter who deals enormous crushing blows against his opponents, but who is not especially accurate at lower levels. Warriors are like home run hitters, in that they tend to knock it out of the park or strike out. In gameplay, a low-level warrior will miss a lot but then do massive damage when he does land a blow, which usually takes out most lower-level creatures. At higher levels, warriors gain an appreciable amount of DEX that compensates for the initial low starting value. Since the warrior is a rage-filled barbarian, he has low INT and CHA because these attributes are not important. Warriors can wear chain or plate armor, and have abilities like rage and berserk that boost his attributes during combat. Drawing from inspiration such as Tolkien, one might look to Gimli.

Paladin Class

The paladin is a balanced melee fighter with massive survivability. Classically, a paladin was a melee fighter with some healing abilities, making him a cross between warrior and priest. If you want to follow this traditional view of the paladin class, you may do so. I have taken a simpler approach to the paladin, making him slightly less damaging than the warrior but able to take more damage. While the single point in CHA might seem like a waste, it reflects the nature of the paladin as an attractive, heroic knight. He has abilities that give a temporary boost to his weapons and armor points. Paladins can wear chain or plate armor, preferring the most brightly polished pieces of gear they can find. Drawing from popular inspiration, one might look to the Tolkien characters Baromir or Eomer.

Hunter Class

The hunter is a ranged class with no melee combat ability, but capable of dealing massive damage with a bow. Hunters are fast on their feet, wear light leather armor, and usually have many different types of arrows for their favorite bow. Hunters have a high DEX to improve ranged chance to hit, with less use for traits like STR and INT. Abilities revolve around ranged attack modifiers that improve accuracy (chance to hit). A good example from which to draw inspiration is the Tolkien character Legolas.

Priest Class

The priest class represents a holy man of the cloth who has been forced to fight the rampaging undead horde that has been destroying everything in its path. A priest is unlike a traditional magic user, both unwilling and unable to call on the evil powers required to conjure magic known to traditional wizards and mages (whom he would consider opponents). A priest uses holy power to fight evil, undead creatures, never to attack other human beings. His abilities include healing and exorcism. A loose example from Tolkien’s universe might be Arwen.

Peasants as NPCs

In addition to these player character classes, you might want to create base classes for some of the regular people in the world, like townsfolk, peasants, farmers, and so on. These non-combat NPCs might all just share the same character class (with weak combat skills, poor experience, and so on). We will need NPCs like this for the quest system coming up later. (See Table 10.5.) NPCs do not need to follow the same modifier rules reserved for player character classes (which, again, should be thought of as heroes). Note that NPCs and monsters generally have more HP than their levels imply to improve gameplay by increasing the difficulty early on—which makes the player eager to level up quickly.

Table 10.5. Peasant

Attribute

Roll

Modifiers

Strength

1D6

0

Dexterity

1D6

0

Stamina

1D6

0

Intellect

1D6

0

Charisma

1D6

0

Hit Points

1D8

+STA

The Monster Classes

We can also create fantasy creatures and monsters described in Tables 10.6 to 10.9. These creatures are unprecedented because they have no equal on the “good side.” But you will not want to make all of the bad guys too powerful—save that for the unusual monsters that are rarely encountered or the game will be way too hard to play. You will generally want to have at least one type of bad guy for each type of character class available to the player, and duplicate that character all over the game world. In addition, you must add weaker enemy characters that make good fodder for the player to help with leveling up. Another thing to remember is that monsters do not gain experience and level up, so they should start out with higher level stats than a typical level 1 player character. Since the human but otherwise bad characters share the same stats as the human good guys, we don’t need to define them separately.

Table 10.6. Level 4 Skeleton Warrior

Attribute

Roll

Modifiers

Strength

4D6

+10

Dexterity

4D6

+6

Stamina

4D6

+8

Intellect

4D6

0

Charisma

0

0

Hit Points

4D8

+STA

Level 4 Skeleton Warrior

Table 10.7. Level 8 Skeleton Archer

Attribute

Roll

Modifiers

Strength

8D6

+14

Dexterity

8D6

+20

Stamina

8D6

+16

Intellect

8D6

0

Charisma

0

0

Hit Points

8D8

+STA

Level 8 Skeleton Archer

Table 10.8. Level 12 Berserker

Attribute

Roll

Modifiers

Strength

12D6

+20

Dexterity

12D6

+18

Stamina

12D6

+16

Intellect

12D6

0

Charisma

0

0

Hit Points

12D8

+STA

Level 12 Berserker

Table 10.9. Level 16 Zombie

Attribute

Roll

Modifiers

Strength

16D6

+22

Dexterity

16D6

+12

Stamina

16D6

+28

Intellect

16D6

0

Charisma

0

0

Hit Points

16D8

+STA

Level 16 Zombie

Remember, these are generic class types, or races, not individuals. Since we don’t need to follow a modifier standard, you may get as creative as you want. Since these classes represent various level ranges, the attributes are calculated with die roll specifications and modifiers (both of which are supported in the character editor).

If you want to create a level 10 monster, then I recommend rolling 10D6 for its attributes. If desired level is L, then each attribute roll is LD6. The modifiers may then be used to adjust the dice rolls to ensure minimum or maximum values are reached for the monster’s intended abilities. For instance, if you want to create a zombie with a minimum of 20 STR while still using the attribute roll, then add 20 to the STR roll and the result will be 20 + STR. As long as the minimums are capped to zero, it’s okay to add negative modifiers. If you want to specify a monster’s level specifically in the editor data, go ahead and add it as a new property—I just prefer to get away from the numbers game and let the player learn each monster’s abilities by fighting them (in the interest of improving the suspense of disbelief!).

As a final note, there is no reason to roll the charisma attribute for monsters so I have set CHA to 0 in these tables. If you have some purpose for this attribute in your own game, by all means go ahead and use it!

Skeleton Warrior

Skeleton warriors are the mainstay of the undead horde army, and as such, they can be found almost everywhere in the game world. At level 4, these guys are pretty tough for a new player but are soon dealt with handily once the player goes up a few levels. The skeleton warrior has high strength and stamina, and a lot of hit points!

Skeleton Archer

Skeleton archers were once support units for some unknown army before they became undead, so there aren’t as many archers as there are warriors and berserkers but they tend to be better trained and hit much harder—at range. Despite their undead condition, they retain their original skills in battle—they are crack shots with their arrows so be sure to close in fast and take them out before they get too many shots off.

Berserker

Berserkers are lost explorers who have gone insane while trying to find their way out of the dungeon, and pose a threat to the player and other explorers. Bereft of most of their original equipment (as well as their sanity), Berserkers roam the passageways of the dungeon’s many levels, no longer remembering what their purpose was, but still feeling a longing to go somewhere. That somewhere is back to the top level and out of the dungeon, but having lost their minds, they do not remember that goal any longer and simply fight everything that comes near them.

Zombie

Zombies are the mainstay of the undead horde you will find in the depths of the dungeon. A combination of lost adventurers and foolhardy peasants who were unlucky enough to fall into a pit above the dungeon or (worse) were grabbed by other zombies and dragged down into the depths, a zombie is a mindless killer with a hunger for human brains. As a result, zombies are confused about their existence and believe they still need to feed. They carry no weapons or armor. Despite having no weapons, zombies are extremely dangerous because they can take an extraordinary amount of damage before they fall.

The Character Editor

The character editor is a Visual Basic program designed to create and edit game characters. I know what you’re thinking—Basic? We do not need to reinvent the wheel when it comes to tools programming just because of the language! The character editor was developed over a period of several weeks and has reached a level of refinement that works exceptionally well with the codebase for our dungeon crawler engine. I considered porting it to C# just for this chapter but I would rather spend that time working on the game instead of re-inventing the editor just for the sake of the programming language. C# and Basic are partners and almost interchangeable anyway due to the .NET Framework library. Each character will be stored in its own file with an extension of .char, although this data is also just XML like the level editor data. Figure 10.1 shows the Character Editor program running.

The character editor tool.

Figure 10.1. The character editor tool.

I generally do not see the point of sharing source for a complex form-based application like this editor, because you can’t create the editor from just this source code and it’s too complex to list the properties for every control in an attempt to build it, tutorial style. The full source code for the character editor tool is included in the chapter resource files if you’re interested in learning how the tool works.

Hint

The "DROP GOLD" and "DROP ITEMS" fields are not used yet, but reserved for Chapter 13, “Equipping Gear and Looting Treasure.” When we have the code to work with these data fields, then we can edit monster character files and specify what we want them to drop, but first we need an item editor.

Table 10.10 provides a list of fields stored in a character data file.

Table 10.10. Character Data Fields

Field Name

Description

name

Character’s full name

class

Character’s class (warrior, etc.)

race

Character’s race (human, etc.)

desc

Short description of this character

str

Strength attribute

dex

Dexterity attribute

sta

Stamina attribute

int

Intellect attribute

cha

Charisma attribute

hitpoints

Hit points (health)

anim_walk_filename

Walk animation filename

anim_walk_width

Walk animation frame width

anim_walk_height

Walk animation frame height

anim_walk_columns

Walk animation sheet columns

anim_attack_filename

Attack animation filename

anim_attack_width

Attack animation frame width

anim_attack_height

Attack animation frame height

anim_attack_columns

Attack animation sheet columns

anim_die_filename

Die animation filename

anim_die_width

Die animation frame width

anim_die_height

Die animation frame height

anim_die_columns

Die animation sheet columns

dropgold1

Minimum drop gold

dropgold2

Maximum drop gold

Loading Character Files

You know what type of data you want to use in the game based on the descriptions of the various classes discussed so far, and that data is now editable with the new character editor tool. How, then, do you make use of these character files in the game? We already have a very convenient Level class that makes the game world scroll very easily with code like this:

Level level = new Level(game, 25, 19, 32);
level.loadTilemap("sample.level");
level.loadPalette("palette.png", 5);

After loading the level, we can scroll and draw the level with simple properties based entirely on the data inside the .level file! I want the same kind of functionality for game characters as well! We have a great character editor available, but it uses a lot of data to define a character with unique properties, so we need a class to handle characters as well. I want to be able to load a .char file and have the class automatically load up the three sprite sheets (for walking, attacking, and dying). The class should also keep track of which “state” it’sin, and draw the appropriate sprite animation automatically based on the animation state and all of the animation properties, completely wrapped up in a single Draw() routine. Here’s an example:

Character hero = new Character(ref game);
hero.Load("paladin.char");
hero.Position = new Point(400, 300);
...
hero.Draw();

The Character Class

The Character class is the biggest class of the entire book so far, but that doesn’t mean it’s overly complex, it just has a lot of data to keep track of and makes use of a lot of convenient properties. This is a very user-friendly class, but that means there’s a lot of code up front in the class. The end result is a lot of code now in the class definition, but much less code in our game required to work with characters. This class will necessarily require changes in the upcoming chapters to accommodate features that we haven’t covered yet, like gaining experience and leveling (which are not found in the class yet!). Not to worry, our characters will gain experience and level up—and loot treasure and go on quests too!

using System;
using System.Collections.Generic;
using System.Xml;
using System.Drawing;
using System.Windows;
using System.Windows.Forms;

namespace RPG
{
    class Character
    {

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

        private Game p_game;
        private PointF p_position;
        private int p_direction;
        private AnimationStates p_state;
        //character file properties;
        private string p_name;
        private string p_class;
        private string p_race;
        private string p_desc;
        private int p_str;
        private int p_dex;
        private int p_sta;
        private int p_int;
        private int p_cha;
        private int p_hitpoints;
        private int p_dropGold1;
        private int p_dropGold2;
        private string p_walkFilename;
        private Sprite p_walkSprite;
        private Size p_walkSize;
        private int p_walkColumns;
        private string p_attackFilename;
        private Sprite p_attackSprite;
        private Size p_attackSize;
        private int p_attackColumns;
        private string p_dieFilename;
        private Sprite p_dieSprite;
        private Size p_dieSize;
        private int p_dieColumns;

        public Character(ref Game game)
        {
            p_game = game;
            p_position = new PointF(0, 0);
            p_direction = 1;
            p_state = AnimationStates.Walking;

            //initialize loadable properties
            p_name = "";
            p_class = "";
            p_race = "";
            p_desc = "";
            p_str = 0;
            p_dex = 0;
            p_sta = 0;
            p_int = 0;
            p_cha = 0;
            p_hitpoints = 0;
            p_dropGold1 = 0;
            p_dropGold2 = 0;
            p_walkSprite = null;
            p_walkFilename = "";
            p_walkSize = new Size(0, 0);
            p_walkColumns = 0;
            p_attackSprite = null;
            p_attackFilename = "";
            p_attackSize = new Size(0, 0);
            p_attackColumns = 0;
            p_dieSprite = null;
            p_dieFilename = "";
            p_dieSize = new Size(0, 0);
            p_dieColumns = 0;

        }

        public string Name
        {
            get { return p_name; }
            set { p_name = value; }
        }

        public string PlayerClass
        {
            get { return p_class; }
            set { p_class = value; }
        }

        public string Race
        {
            get { return p_race; }
            set { p_race = value; }
        }

        public string Description
        {
            get { return p_desc; }
            set { p_desc = value; }
        }

        public int STR
        {
            get { return p_str; }
            set { p_str = value; }
        }

        public int DEX
        {
            get { return p_dex; }
            set { p_dex = value; }
        }

        public int STA
        {
            get { return p_sta; }
            set { p_sta = value; }
        }

        public int INT
        {
            get { return p_int; }
            set { p_int = value; }
        }

        public int CHA
        {
            get { return p_cha; }
            set { p_cha = value; }
        }

        public int HitPoints
        {
            get { return p_hitpoints; }
            set { p_hitpoints = value; }
        }
        public int DropGoldMin
        {
            get { return p_dropGold1; }
            set { p_dropGold1 = value; }
        }

        public int DropGoldMax
        {
            get { return p_dropGold2; }
            set { p_dropGold2 = value; }
        }

        public Sprite GetSprite
        {
            get {

                switch (p_state)
                {
                    case AnimationStates.Walking:
                        return p_walkSprite;
                    case AnimationStates.Attacking:
                        return p_attackSprite;
                    case AnimationStates.Dying:
                        return p_dieSprite;
                    default:
                        return p_walkSprite;
                }
            }
        }

        public PointF Position
        {
            get { return p_position; }
            set { p_position = value; }
        }

        public float X
        {
            get { return p_position.X; }
            set { p_position.X = value; }
        }
        public float Y
        {
            get { return p_position.Y; }
            set { p_position.Y = value; }
        }

        public int Direction
        {
            get { return p_direction; }
            set { p_direction = value; }
        }

        public AnimationStates AnimationState
        {
            get { return p_state; }
            set { p_state = value; }
        }

        public void Draw()
        {
            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();
                    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();
                    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();
                    break;
            }
        }

        private string getElement(string field, ref XmlElement element)
        {
            string value = "";
            try
            {
                value = element.GetElementsByTagName(field)[0].InnerText;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            return value;
        }


        public bool Load(string filename)
        {
            try
            {
                //open the xml file
                XmlDocument doc = new XmlDocument();
                doc.Load(filename);
                XmlNodeList list = doc.GetElementsByTagName("character");
                XmlElement element = (XmlElement)list[0];

                //read data fields
                string data;
                p_name = getElement("name", ref element);
                p_class = getElement("class", ref element);
                p_race = getElement("race", ref element);
                p_desc = getElement("desc", ref element);

                data = getElement("str", ref element);
                p_str = Convert.ToInt32(data);

                data = getElement("dex", ref element);
                p_dex = Convert.ToInt32(data);

                data = getElement("sta", ref element);
                p_sta = Convert.ToInt32(data);

                data = getElement("int", ref element);
                p_int = Convert.ToInt32(data);

                data = getElement("cha", ref element);
                p_cha = Convert.ToInt32(data);

                data = getElement("hitpoints", ref element);
                p_hitpoints = Convert.ToInt32(data);

                data = getElement("anim_walk_filename", ref element);
                p_walkFilename = data;

                data = getElement("anim_walk_width", ref element);
                p_walkSize.Width = Convert.ToInt32(data);

                data = getElement("anim_walk_height", ref element);
                p_walkSize.Height = Convert.ToInt32(data);
                data = getElement("anim_walk_columns", ref element);
                p_walkColumns = Convert.ToInt32(data);

                data = getElement("anim_attack_filename", ref element);
                p_attackFilename = data;

                data = getElement("anim_attack_width", ref element);
                p_attackSize.Width = Convert.ToInt32(data);

                data = getElement("anim_attack_height", ref element);
                p_attackSize.Height = Convert.ToInt32(data);

                data = getElement("anim_attack_columns", ref element);
                p_attackColumns = Convert.ToInt32(data);

                data = getElement("anim_die_filename", ref element);
                p_dieFilename = data;

                data = getElement("anim_die_width", ref element);
                p_dieSize.Width = Convert.ToInt32(data);

                data = getElement("anim_die_height", ref element);
                p_dieSize.Height = Convert.ToInt32(data);

                data = getElement("anim_die_columns", ref element);
                p_dieColumns = Convert.ToInt32(data);

                data = getElement("dropgold1", ref element);
                p_dropGold1 = Convert.ToInt32(data);

                data = getElement("dropgold2", ref element);
                p_dropGold2 = Convert.ToInt32(data);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                return false;
            }

            //create character sprites
            try
            {
                if (p_walkFilename != "")
                {
                    p_walkSprite = new Sprite(ref p_game);
                    p_walkSprite.Image = LoadBitmap(p_walkFilename);
                    p_walkSprite.Size = p_walkSize;
                    p_walkSprite.Columns = p_walkColumns;
                    p_walkSprite.TotalFrames = p_walkColumns * 8;
                }

                if (p_attackFilename != "")
                {
                    p_attackSprite = new Sprite(ref p_game);
                    p_attackSprite.Image = LoadBitmap(p_attackFilename);
                    p_attackSprite.Size = p_attackSize;
                    p_attackSprite.Columns = p_attackColumns;
                    p_attackSprite.TotalFrames = p_attackColumns * 8;
                }

                if (p_dieFilename != "")
                {
                    p_dieSprite = new Sprite(ref p_game);
                    p_dieSprite.Image = LoadBitmap(p_dieFilename);
                    p_dieSprite.Size = p_dieSize;
                    p_dieSprite.Columns = p_dieColumns;
                    p_dieSprite.TotalFrames = p_dieColumns * 8;
                }
            }

            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                return false;
            }
            return true;
        }

        private Bitmap LoadBitmap(string filename)
        {
            Bitmap bmp=null;
            try
            {
                bmp = new Bitmap(filename);
            }
            catch (Exception ex){}
            return bmp;
        }

     }
}

The Animated Character Artwork

Now I’d like to discuss how you can prepare a sprite for use in this game. Each sprite is somewhat different in the number of frames it uses for each type of animation, as well as the types of animation available. All of the character sprites that I’m using in the game have the full eight-direction walking animation sequences, as well as frames for attacking with a weapon. Some sprites have a death animation, and some have running and falling. Normally, to keep the game as uniform as possible, you would use character sprites that have the exact same number of animation frames for the key animation that takes place in the game so that it’s easy to switch character classes without changing any source code. But since our editor stores the sprite data in the character data files, we don’t need to worry about keeping the animations all uniform. Figure 10.2 shows the walking animation sprite sheet for the paladin character.

Walking animation for the paladin sprite.

Figure 10.2. Walking animation for the paladin sprite.

The source artwork from Reiner’s Tilesets does not come in this format, but it comes with each frame of animation stored in a separate bitmap file. The easiest way to combine these frames into a sprite animation sheet is with Cosmigo’s Pro Motion sprite animation program. Because Pro Motion works best with single animation strips, I decided to import each group of bitmaps for the character’s walking animation in all eight directions. Using Pro Motion, I converted all 64 frames of animation into a single sprite sheet.

Nothing beats experimentation, so it is up to you to use the freely available sprites provided by Reiner’s Tilesets (and other sources) to enhance the Dungeon Crawler game to suit your own needs. We can only accomplish so much in this book, so I want to give you as many tools, tips, and tricks as I can possibly squeeze in at this time. There are thousands of sprites and tiles available at www.reinerstilesets.de that you can use for your own games! There is a sprite for everything you can possibly imagine adding to an RPG!

All of the characters and monsters discussed in this chapter have been chosen very carefully because we have artwork available for them. Generally, when a game is being designed from the ground up, the game designer will not limit himself to what artwork is available, because none exists before the game goes into development. But in our case, we have all of this artwork provided by Reiner Prokein (www.reinerstileset.de). I strongly recommend that you start with artwork and design your game characters around that instead of designing first and looking for artwork later (unless you know a talented artist who can do the work!).

All of the sprite sheets used in the game were significantly manipulated from their original sources provided by Reiner Prokein. All of the sprites arranged in columns and rows in a sprite sheet and transparent regions have been converted to an alpha channel in each file, which is saved in the Portable Network Graphics (PNG) file format. When you visit Reiner’s website, you will not find sprite sheets like these, as they are provided in individual bitmaps. Just be aware that additional work will be required on your part to add new characters or animations to your game. Figure 10.3 shows the three sheets used for the warrior character—note the different number of columns for each sheet, which is handled by the character editor and the Character class!

The warrior sprite sheets for walking, attacking, and dying.

Figure 10.3. The warrior sprite sheets for walking, attacking, and dying.

Character Demo

Let’s take the new Character class and artwork for a spin. The Character demo program is not much different, functionally, from the Portal demo in Chapter 9. However, all of the character code is now transferred over to the Character class, which knows how to load a .char file (created by the character editor tool), parse the XML fields, and create the three sprites needed for each character. In addition, the three animation states can be changed using the standard numeric keys 1, 2, and 3. The result is shown in Figure 10.4. The demo looks a little bit goofy since you have to move in order to show the attack and die animations, but that’s okay, as those animations will not be used during normal walking, only when another action is triggered. The point is, the Character class works!

We can now kill the player character—wait, is that a good thing?

Figure 10.4. We can now kill the player character—wait, is that a good thing?

Since the code for the Character demo is derived from Chapter 9’s example, I will instead just show you the relevant sections of code related to the new Character class, and let you open the project to see the complete sources. Since the Character class mimics some of the Sprite class’ properties and also makes available the current sprite object via the GetSprite() function, we can replace most of the Sprite-specific code in this demo with Character-based code without making significant changes.

First, we declare a new Character variable:

Character hero;

Next, we create the hero object and set its initial position.

hero = new Character(ref game);
hero.Load("paladin.char");
hero.Position = new Point(400 - 48, 300 - 48);

In the Form1_KeyUp() event, the AnimationState property is changed with the 1, 2, and 3 keys, to test the three different character states (which are Walking, Attacking, and Dying).

private void Form1_KeyUp(object sender, KeyEventArgs e)
{
    switch (e.KeyCode)
    {
        case Keys.Escape: gameover = true; break;
        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:
            if (portalFlag) level.GridPos = portalTarget;
            break;
        case Keys.D1:
            hero.AnimationState = Character.AnimationStates.Walking;
            break;
        case Keys.D2:
            hero.AnimationState = Character.AnimationStates.Attacking;
            break;
        case Keys.D3:
            hero.AnimationState = Character.AnimationStates.Dying;
            break;
    }
}

Tip

Press the keys 1, 2, 3 to change the player sprite’s animation.

In the main loop function, doUpdate(), we simply call hero.Draw(), which both animates and draws the character sprite. This Draw() function is so smart that it even figures out automatically which sprite to draw based on the Animation State. Notice how very little the code has changed! This is due to the similarities between the Sprite and Character classes (which were intentional!).

int ticks = Environment.TickCount;
if (ticks > drawLast + 16)
{
    drawLast = ticks;

    //draw the tilemap
    level.Draw(0, 0, 800, 600);

    //draw the hero
    hero.Draw();
    ...
}

This is not the entire code listing to the Character demo project, which must be omitted due to length. Please open the complete project (www.courseptr.com/downloads) and peruse the source code for this program.

Level Up!

The new character editor tool and the Character class that knows how to work with the new .char files have together dramatically improved our game’s potential gameplay with even more data-driven features! It is now possible to design a totally new character or monster, edit the sprite sheet images, and save the new character to a data file, then load it up inside the game and have the new character moving around in a matter of minutes, with only a few lines of code! Not solely to be used for player characters, we will also use the Character class for monsters and NPCs as well! This is really getting exciting, because it means you aren’t stuck with just what the designer has put into a game (at least, a game based on these tools). If you want to tweak a character, you won’t have to edit any source code, you’ll just open the file in the character editor, make the changes, save it, then try it out in the game again. That’s the beautiful thing about game editor tools, and why this is such a hot topic in the game industry, with skilled tool programmers in high demand.

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

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