Chapter 14. Creating the Character Editor

This chapter covers character creation using a custom new 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.

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

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” 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 16, “Fighting, Getting XP, 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 Celtic Crusader, 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. We will study combat in detail in Chapter 16, “Fighting, Getting XP, and Leveling Up,” and quests in Chapter 19, “Creating the Quest Editor.” These are the only two ways to get experience and level up. 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 16 for a discussion of the details.

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 Celtic Crusader will be 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 Celtic Crusader. 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 14.1 to 14.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 14.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 14.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 14.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 14.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 Quest-Giving 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 14.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 14.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 Enemy/Monster Classes

We can also create fantasy creatures and monsters described in Tables 14.6 to 14.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 14.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 14.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 14.8. Level 12 Viking Guard

Attribute

Roll

Modifiers

Strength

12D6

+20

Dexterity

12D6

+18

Stamina

12D6

+16

Intellect

12D6

0

Charisma

0

0

Hit Points

12D8

+STA

Level 12 Viking Guard

Table 14.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 the 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 the Viking army that became undead, so there aren’t as many as the warrior class but they tend to be better trained and hit much harder—at range. 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.

Viking Guard

Viking guards are remnants of the Viking army who were not involved in the debacle that gave rise to the undead army, and they’re lucky to have been spared! Normal Viking troops are somewhat like a traditional warrior but with training they have achieved a high hit rating as well as high strength, so not only can they hit, they hit hard!

Zombie

Zombies are also part of the undead horde of former Vikings, but due to some mishap they retained most of their original appearance and clothing, and did not get fully transformed into undead skeletons. As a result, zombies are confused about their existence and believe they still need to feed. They carry no weapons or armor because they were not in the army, but rather Viking citizens brought to colonize the land and further push out the native inhabitants. 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 VB program designed to create and edit game characters. 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 14.1 shows the Character Editor program running.

The Character Editor tool.

Figure 14.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. As a compromise, I will go over the source code and explain each function clearly if it is not already self-explanatory.

Hint

The “DROP GOLD” and “DROP ITEMS” fields are not used yet, but reserved for Chapter 17, “Creating the Item Editor” and Chapter 18, “Looting Treasure and Drop Items.” 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. Furthermore, when quests are working, we’ll want certain characters to drop certain items to satisfy quests!

This is not the complete source code for the character editor, just an explanation of the major functions in the code, in the interest of saving space. All of the user interface events (like button clicks) have been left out since they are just logistical and not vital to an understanding of the editor.

Public Class Form1
    Dim device As Graphics
    Dim surface As Bitmap

    Dim animationImage As Bitmap
    Dim sprite As Sprite
    Dim rand As Random
    Dim g_filename As String
    Dim currentAnim As String

    Private Sub Form1_Load(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) Handles MyBase.Load
       surface = New Bitmap(Size.Width, Size.Height)
       PictureBox1.Image = surface
       device = Graphics.FromImage(surface)
       sprite = New Sprite(device)
       animationImage = Nothing
       rand = New Random()
   End Sub

The btnWalkFile_Click() subroutine is called by the first “File. . .” button, next to the Walk animation filename field. This button is used to bring up the Open File dialog in order to choose a bitmap file to load for the walk animation.

Private Sub btnWalkFile_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles btnWalkFile.Click
   Open1.DefaultExt = ".png"
   Open1.Filter = "Bitmap Files|*.png;*.png;*.jpg"
   Open1.Multiselect = False
   Open1.Title = "Load Bitmap File"
   Open1.InitialDirectory = Environment.CurrentDirectory
   Dim result As DialogResult
   result = Open1.ShowDialog(Me)
   If result <> DialogResult.OK Then Return
   txtWalkFile.Text = IO.Path.GetFileName(Open1.FileName)
   animationImage = LoadBitmap(txtWalkFile.Text)
End Sub

The btnAttackFile_Click() subroutine is called by the second “File. . .” button, next to the Attack animation filename field. This button is used to bring up the Open File dialog in order to choose a bitmap file to load for the attack animation.

Private Sub btnAttackFile_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles btnAttackFile.Click
   Open1.DefaultExt = ".png"
   Open1.Filter = "Bitmap Files|*.png;*.png;*.jpg"
   Open1.Multiselect = False
   Open1.Title = "Load Bitmap File"
   Open1.InitialDirectory = Environment.CurrentDirectory
   Dim result As DialogResult
   result = Open1.ShowDialog(Me)
   If result <> DialogResult.OK Then Return
   txtAttackFile.Text = IO.Path.GetFileName(Open1.FileName)
   animationImage = LoadBitmap(txtAttackFile.Text)
End Sub

The btnDieFile_Click() subroutine is called by the third “File. . .” button, next to the Die animation filename field. This button is used to bring up the Open File dialog in order to choose a bitmap file to load for the die animation.

Private Sub btnDieFile_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles btnDieFile.Click
   Open1.DefaultExt = ".png"
   Open1.Filter = "Bitmap Files|*.png;*.png;*.jpg"
   Open1.Multiselect = False
   Open1.Title = "Load Bitmap File"
   Open1.InitialDirectory = Environment.CurrentDirectory
   Dim result As DialogResult
   result = Open1.ShowDialog(Me)
   If result <> DialogResult.OK Then Return
   txtDieFile.Text = IO.Path.GetFileName(Open1.FileName)
   animationImage = LoadBitmap(txtDieFile.Text)
End Sub

The Timer1_Tick() subroutine performs animation using the specified sprite sheet for either the walk, attack, or die animation files. A scratch sprite is used to animate all three of the sprite sheets, depending on which “Animate” button was pressed.

Private Sub Timer1_Tick(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles Timer1.Tick
    If currentAnim = "walk" Then
        sprite.Width = numWidth.Value
        sprite.Height = numHeight.Value
        sprite.Columns = numColumns.Value

    ElseIf currentAnim = "attack" Then
        sprite.Width = numWidth2.Value
        sprite.Height = numHeight2.Value
        sprite.Columns = numColumns2.Value
    ElseIf currentAnim = "die" Then
        sprite.Width = numWidth3.Value
        sprite.Height = numHeight3.Value
        sprite.Columns = numColumns3.Value
    End If
    sprite.TotalFrames = sprite.Columns * 8
    sprite.Image = animationImage
    sprite.Animate(0, sprite.TotalFrames - 1)
    device.Clear(Color.DarkGray)
    sprite.Draw()
    PictureBox1.Image = surface
End Sub

The btnRoll_Click() subroutine rolls the random “dice” calculations for the five main character attributes: STR, DEX, STA, INT, and CHA.

Private Sub btnRoll_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles btnRoll.Click
    txtRollStr.Text = rand.Next(1, 6 * numDCount.Value).ToString()
    txtRollDex.Text = rand.Next(1, 6 * numDCount.Value).ToString()
    txtRollSta.Text = rand.Next(1, 6 * numDCount.Value).ToString()
    txtRollInt.Text = rand.Next(1, 6 * numDCount.Value).ToString()
    txtRollCha.Text = rand.Next(1, 6 * numDCount.Value).ToString()
End Sub

The loadFile() subroutine loads a .char file containing xml data for a character. The getElement() function assists with the loading of each field in the xml data with error handling in case of a load error. As each field is copied into the user interface TextBox and NumericUpDown controls, data type conversions are performed as needed.

Private Function getElement(ByVal field As String, _
        ByRef element As XmlElement) As String
   Dim value As String = ""
   Try
       value = element.GetElementsByTagName(field)(0).InnerText

     Catch ex As Exception
         REM ignore error, just return empty
         Console.WriteLine(ex.Message)
     End Try
     Return value
End Function
Private Sub loadFile(ByVal filename As String)
    Try
        REM open the xml file
        Dim doc As New XmlDocument()
        doc.Load(filename)
        Dim list As XmlNodeList = doc.GetElementsByTagName("character")
        Dim element As XmlElement = list(0)
        REM read data fields
        txtName.Text = getElement("name", element)
        cboClass.Text = getElement("class", element)
        cboRace.Text = getElement("race", element)
        txtDesc.Text = getElement("desc", element)
        txtStr.Text = getElement("str", element)
        txtDex.Text = getElement("dex", element)
        txtSta.Text = getElement("sta", element)
        txtInt.Text = getElement("int", element)
        txtCha.Text = getElement("cha", element)
        txtHP.Text = getElement("hitpoints", element)
        txtWalkFile.Text = getElement("anim_walk_filename", element)
        numWidth.Value = Convert.ToInt32( _
            getElement("anim_walk_width", element))
        numHeight.Value = Convert.ToInt32( _
            getElement("anim_walk_height", element))
        numColumns.Value = Convert.ToInt32( _
            getElement("anim_walk_columns", element))
        txtAttackFile.Text = getElement( _
            "anim_attack_filename", element)
        numWidth2.Value = Convert.ToInt32( _
            getElement("anim_attack_width", element))
        numHeight2.Value = Convert.ToInt32( _
            getElement("anim_attack_height", element))
        numColumns2.Value = Convert.ToInt32( _
            getElement("anim_attack_columns", element))

        txtDieFile.Text = getElement( _
            "anim_die_filename", element)
        numWidth3.Value = Convert.ToInt32( _
            getElement("anim_die_width", element))
        numHeight3.Value = Convert.ToInt32( _
            getElement("anim_die_height", element))
        numColumns3.Value = Convert.ToInt32( _
            getElement("anim_die_columns", element))
        numGold1.Value = Convert.ToInt32( _
            getElement("dropgold1", element))
        numGold2.Value = Convert.ToInt32( _
            getElement("dropgold2", element))
    Catch ex As Exception
        MessageBox.Show(ex.Message)
    End Try
End Sub

The saveFile() subroutine has the job of building the xml schema as well as saving the character data to an xml file based on that schema.

Private Sub saveFile(ByVal filename As String)
    Try
        REM create xml schema
        Dim table As New DataTable("character")
        table.Columns.Add(New DataColumn("name", _
            System.Type.GetType("System.String")))
        table.Columns.Add(New DataColumn("class", _
            System.Type.GetType("System.String")))
        table.Columns.Add(New DataColumn("race", _
            System.Type.GetType("System.String")))
        table.Columns.Add(New DataColumn("desc", _
            System.Type.GetType("System.String")))
        table.Columns.Add(New DataColumn("str", _
            System.Type.GetType("System.Int32")))
        table.Columns.Add(New DataColumn("dex", _
            System.Type.GetType("System.Int32")))
        table.Columns.Add(New DataColumn("sta", _
            System.Type.GetType("System.Int32")))
        table.Columns.Add(New DataColumn("int", _
            System.Type.GetType("System.Int32")))

        table.Columns.Add(New DataColumn("cha", _
            System.Type.GetType("System.Int32")))
        table.Columns.Add(New DataColumn("hitpoints", _
            System.Type.GetType("System.Int32")))
        table.Columns.Add(New DataColumn("anim_walk_filename", _
            System.Type.GetType("System.String")))
        table.Columns.Add(New DataColumn("anim_walk_width", _
            System.Type.GetType("System.Int32")))
        table.Columns.Add(New DataColumn("anim_walk_height", _
            System.Type.GetType("System.Int32")))
        table.Columns.Add(New DataColumn("anim_walk_columns", _
            System.Type.GetType("System.Int32")))
        table.Columns.Add(New DataColumn("anim_attack_filename", _
            System.Type.GetType("System.String")))
        table.Columns.Add(New DataColumn("anim_attack_width", _
            System.Type.GetType("System.Int32")))
        table.Columns.Add(New DataColumn("anim_attack_height", _
            System.Type.GetType("System.Int32")))
        table.Columns.Add(New DataColumn("anim_attack_columns", _
            System.Type.GetType("System.Int32")))
        table.Columns.Add(New DataColumn("anim_die_filename", _
            System.Type.GetTypeC"System.String")))
        table.Columns.Add(New DataColumn("anim_die_width", _
            System.Type.GetType("System.Int32")))
        table.Columns.Add(New DataColumn("anim_die_height", _
            System.Type.GetType("System.Int32")))
        table.Columns.Add(New DataColumn("anim_die_columns", _
            System.Type.GetType("System.Int32")))
        table.Columns.Add(New DataColumn("dropgold1", _
            System.Type.GetType("System.Int32")))
        table.Columns.Add(New DataColumn("dropgold2", _
            System.Type.GetType("System.Int32")))

        REM copy character data into datatable
        Dim row As DataRow = table.NewRow()
        row("name") = txtName.Text
        row("class") = cboClass.Text
        row("race") = cboRace.Text
        row("desc") = txtDesc.Text
        row("str") = txtStr.Text
        row("dex") = txtDex.Text

        row("sta") = txtSta.Text
        row("int") = txtInt.Text
        row("cha") = txtCha.Text
        row("hitpoints") = txtHP.Text
        row("anim_walk_filename") = txtWalkFile.Text
        row("anim_walk_width") = numWidth.Value
        row("anim_walk_height") = numHeight.Value
        row("anim_walk_columns") = numColumns.Value
        row("anim_attack_filename") = txtAttackFile.Text
        row("anim_attack_width") = numWidth2.Value
        row("anim_attack_height") = numHeight2.Value
        row("anim_attack_columns") = numColumns2.Value
        row("anim_die_filename") = txtDieFile.Text
        row("anim_die_width") = numWidth3.Value
        row("anim_die_height") = numHeight3.Value
        row("anim_die_columns") = numColumns3.Value
        row("dropgold1") = numGold1.Value
        row("dropgold2") = numGold2.Value
        table.Rows.Add(row)

        REM save xml file
        table.WriteXml(filename)
        table.Dispose()
    Catch es As Exception
        MessageBox.Show(es.Message)
    End Try
End Sub

In addition to rolling for the character’s five primary stats, we can also roll the hit points for a character using this button event. It’s very simple: a random value from 1 to 8 (1D8) is added to the existing stamina value to come up with the character’s hit points. Obviously you will want to roll the stats first before calling this.

Private Sub btnRollHP_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles btnRollHP.Click
   Dim stamina As Integer = Convert.ToInt32(txtSta.Text)
   Dim hp As Integer = stamina + rand.Next(1, 8)
    txtHP.Text = hp.ToString()
End Sub

The three Animate buttons cause the specified animation to be rendered.

Private Sub btnAnimate3_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles btnAnimate3.Click
    currentAnim = "die"
    animationImage = LoadBitmap(txtDieFile.Text)
    Timer1.Interval = numRate.Value
    Timer1.Enabled = Not Timer1.Enabled
End Sub
Private Sub btnAnimate2_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles btnAnimate2.Click
    currentAnim = "attack"
    animationImage = LoadBitmap(txtAttackFile.Text)
    Timer1.Interval = numRate.Value
    Timer1.Enabled = Not Timer1.Enabled
End Sub
Private Sub btnAnimate1_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles btnAnimate.Click
    currentAnim = "walk"
    animationImage = LoadBitmap(txtWalkFile.Text)
    Timer1.Interval = numRate.Value
    Timer1.Enabled = Not Timer1.Enabled
End Sub
Private Sub numRate_ValueChanged(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles numRate.ValueChanged
    Timer1.Interval = numRate.Value
End Sub

Now we come to the code responsible for applying the class-specific attribute modifiers discussed earlier. This is all manual code, which is not the best way to do it, but this approach keeps the code and user interface simpler. Otherwise, we’re looking at a secondary form and additional data files to keep track of the stats. Since there are only a few actual classes shared by most characters and monsters in the game, this is the approach I have decided to take for now. Remember, you can always change the modifiers directly in the editor’s fields as well as change the final value for each attribute manually.

Private Sub cboClass_SelectedIndexChanged( _
        ByVal sender As System.Object, ByVal e As System.EventArgs) _
        Handles cboClass.SelectedIndexChanged

    Dim cls As String = cboClass.Text.ToLower()
    If cls = "warrior" Then
        txtModStr.Text = "8"
        txtModDex.Text = "3"
        txtModSta.Text = "4"
        txtModInt.Text = "0"
        txtModCha.Text = "0"
    ElseIf cls = "paladin" Then
        txtModStr.Text = "3"
        txtModDex.Text = "3"
        txtModSta.Text = "8"
        txtModInt.Text = "0"
        txtModCha.Text = "1"
    ElseIf cls = "hunter" Then
        txtModStr.Text = "2"
        txtModDex.Text = "8"
        txtModSta.Text = "4"
        txtModInt.Text = "0"
        txtModCha.Text = "1"
    ElseIf cls = "priest" Then
        txtModStr.Text = "0"
        txtModDex.Text = "6"
        txtModSta.Text = "1"
        txtModInt.Text = "8"
        txtModCha.Text = "0"
    End If
End Sub

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:

Private level As 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’s in, 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:

hero = New Character(game)
hero.Load("paladin.char")
hero.Position = New Point(400 - 48, 300 - 48)
...
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 much less code in our game for dealing 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! The properties in the class have been omitted to conserve space—so this is not a complete source code listing, just a reference.

Public Class Character
    Public Enum AnimationStates
        Walking
        Attacking
        Dying
    End Enum
    Private p_game As Game
    Private p_position As PointF
    Private p_direction As Integer
    Private p_state As AnimationStates

    REM character file properties
    Private p_name As String
    Private p_class As String
    Private p_race As String
    Private p_desc As String
    Private p_str As Integer
    Private p_dex As Integer
    Private p_sta As Integer
    Private p_int As Integer
    Private p_cha As Integer
    Private p_hitpoints As Integer
    Private p_dropGold1 As Integer
    Private p_dropGold2 As Integer
    Private p_walkFilename As String
    Private p_walkSprite As Sprite
    Private p_walkSize As Point
    Private p_walkColumns As Integer
    Private p_attackFilename As String
    Private p_attackSprite As Sprite
    Private p_attackSize As Point
    Private p_attackColumns As Integer
    Private p_dieFilename As String
    Private p_dieSprite As Sprite
    Private p_dieSize As Point
    Private p_dieColumns As Integer

    Public Sub New(ByRef game As Game)
        p_game = game
        p_position = New PointF(0, 0)
        p_direction = 1
        p_state = AnimationStates.Walking

        REM initialize loadable properties
        REM some code omitted due to space
    End Sub

    REM class properties omitted

    Public ReadOnly Property GetSprite() As Sprite
        Get

              Select Case p_state
                  Case AnimationStates.Walking
                      Return p_walkSprite
                  Case AnimationStates.Attacking
                      Return p_attackSprite
                  Case AnimationStates.Dying
                      Return p_dieSprite
                  Case Else
                      Return p_walkSprite
              End Select
         End Get
    End Property

    REM This function animates and draws the character sprite
    REM based on the current state (walking, attacking, or dying)
    Public Sub Draw()
        Dim startFrame As Integer
        Dim endFrame As Integer
        Select Case p_state
            Case AnimationStates.Walking
                p_walkSprite.Position = p_position
                If p_direction > -1 Then
                     startFrame = p_direction * p_walkColumns
                     endFrame = startFrame + p_walkColumns - 1
                     p_walkSprite.AnimationRate = 30
                     p_walkSprite.Animate(startFrame, endFrame)
                 End If
                 p_walkSprite.Draw()
            Case AnimationStates.Attacking
                 p_attackSprite.Position = p_position
                 If p_direction > -1 Then
                     startFrame = p_direction * p_attackColumns
                     endFrame = startFrame + p_attackColumns - 1
                     p_attackSprite.AnimationRate = 30
                     p_attackSprite.Animate(startFrame, endFrame)
                 End If
                 p_attackSprite.Draw()
             Case AnimationStates.Dying
                 p_dieSprite.Position = p_position
                 If p_direction > -1 Then

                 startFrame = p_direction * p_dieColumns
                 endFrame = startFrame + p_dieColumns - 1
                 p_dieSprite.AnimationRate = 30
                 p_dieSprite.Animate(startFrame, endFrame)
             End If
             p_dieSprite.Draw()
     End Select
End Sub

REM Load a character .char file
Public Function Load(ByVal filename As String)
    Try

        REM open the xml file
        Dim doc As New XmlDocument()
        doc.Load(filename)
        Dim list As XmlNodeList = doc.GetElementsByTagName("character")
        Dim element As XmlElement = list(0)

        REM read data fields
        p_name = getElement("name", element)
        p_class = getElement("class", element)
        p_race = getElement("race", element)
        p_desc = getElement("desc", element)
        p_str = getElement("str", element)
        p_dex = getElement("dex", element)
        p_sta = getElement("sta", element)
        p_int = getElement("int", element)
        p_cha = getElement("cha", element)
        p_hitpoints = getElement("hitpoints", element)
        p_walkFilename = getElement("anim_walk_filename", element)
        p_walkSize.X = Convert.ToInt32( _
            getElement("anim_walk_width", element))
        p_walkSize.Y = Convert.ToInt32( _
            getElement("anim_walk_height", element))
        p_walkColumns = Convert.ToInt32( _
            getElement("anim_walk_columns", element))
        p_attackFilename = getElement( _
            "anim_attack_filename", element)
        p_attackSize.X = Convert.ToInt32( _
            getElement("anim_attack_width", element))

        p_attackSize.Y = Convert.ToInt32( _
            getElement("anim_attack_height", element))
        p_attackColumns = Convert.ToInt32( _
            getElement("anim_attack_columns", element))
        p_dieFilename = getElement( _
            "anim_die_filename", element)
        p_dieSize.X = Convert.ToInt32( _
            getElement("anim_die_width", element))
        p_dieSize.Y = Convert.ToInt32( _
            getElement("anim_die_height", element))
        p_dieColumns = Convert.ToInt32( _
            getElement("anim_die_columns", element))
        p_dropGold1 = Convert.ToInt32( _
            getElement("dropgold1", element))
        p_dropGold2 = Convert.ToInt32( _
            getElement("dropgold2", element))
    Catch ex As Exception
        MessageBox.Show(ex.Message)
        Return False
    End Try

    REM create character sprites (with error handling)
    Try
        If p_walkFilename <> "" Then
            p_walkSprite = New Sprite(p_game)
            p_walkSprite.Image = LoadBitmap(p_walkFilename)
            p_walkSprite.Size = p_walkSize
            p_walkSprite.Columns = p_walkColumns
            p_walkSprite.TotalFrames = p_walkColumns * 8
        End If
        If p_attackFilename <> "" Then
            p_attackSprite = New Sprite(p_game)
            p_attackSprite.Image = LoadBitmap(p_attackFilename)
            p_attackSprite.Size = p_attackSize
            p_attackSprite.Columns = p_attackColumns
            p_attackSprite.TotalFrames = p_attackColumns * 8
        End If
        If p_dieFilename <> "" Then
            p_dieSprite = New Sprite(p_game)
            p_dieSprite.Image = LoadBitmap(p_dieFilename)

                 p_dieSprite.Size = p_dieSize
                 p_dieSprite.Columns = p_dieColumns
                 p_dieSprite.TotalFrames = p_dieColumns * 8
             End If
         Catch ex As Exception
              MessageBox.Show(ex.Message)
              Return False
         End Try
         Return True
    End Function

    Private Function getElement(ByVal field As String, _
             ByRef element As XmlElement) As String
         Dim value As String = ""
         Try
             value = element.GetElementsByTagName(field)(0).InnerText
         Catch ex As Exception
              REM ignore error, just return empty
              Console.WriteLine(ex.Message)
         End Try
         Return value
    End Function

    REM note: portions of this source code have been omitted
    REM refer to the complete project in the chapter's resources

End Class

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 Celtic Crusader 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 14.2 shows the walking animation sprite sheet for the paladin class.

Walking animation for the paladin sprite.

Figure 14.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 Celtic Crusader 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 Celtic Crusader 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 14.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 14.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 the previous chapter. 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 14.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 14.4. We can now kill the player character—wait, is that a good thing?

Since the code for the Character Demo is derived from the previous chapter’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:

Private hero As Character

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

hero = New Character(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 Sub Form1_KeyUp(ByVal sender As System.Object, _
        ByVal e As System.Windows.Forms.KeyEventArgs) _
        Handles MyBase.KeyUp
   Select Case (e.KeyCode)
        Case Keys.Escape : End
        Case Keys.Up, Keys.W : keyState.up = False
        Case Keys.Down, Keys.S : keyState.down = False
        Case Keys.Left, Keys.A : keyState.left = False
        Case Keys.Right, Keys.D : keyState.right = False
        Case Keys.Space
            If portalFlag Then
               level.GridPos = portalTarget
            End If
        Case Keys.D1
            hero.AnimationState = Character.AnimationStates.Walking
        Case Keys.D2
            hero.AnimationState = Character.AnimationStates.Attacking
        Case Keys.D3
            hero.AnimationState = Character.AnimationStates.Dying
    End Select
End Sub

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

If ticks > drawLast + 16 Then
    drawLast = ticks

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

    REM draw the hero
    hero.Draw()
  ...
End If

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 Celtic Crusader game and have the new character moving around in a matter of minutes, with only a few lines of code! 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.147.49.252