Press "Enter" to skip to content

Lesson 05.1: Creating the Game Item Factory

Summary

In this lesson, we are going to create a class for the game items, a sub-class (for game weapons) and a factory class to create instances of those items.

Step 1: Create a new class: GameItem.cs, in Engine\Models.

This class will have three properties: ItemTypeID, Name, and Price.

The ItemTypeID will be a unique value for each type of item. For example, we will use 1001 for a “pointy stick” object. We will use 1002 for the “rusty sword” object.

In this class, we have a constructor. This is a function that is called when we instantiate a new object.

In this class, the constructor has three parameters: itemTypeID, name, and price. These parameters have the same name as the properties – except that the first characters are lower-case. This is not a requirement, but is a common way that programmers name parameters in a constructor.

Inside the constructor, we set the properties to the values that were passed in as the parameters.

GameItem.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Engine.Models
{
    public class GameItem
    {
        public int ItemTypeID { get; set; }
        public string Name { get; set; }
        public int Price { get; set; }
        public GameItem(int itemTypeID, string name, int price)
        {
            ItemTypeID = itemTypeID;
            Name = name;
            Price = price;
        }
    }
}

Step 2: Create a new class: Weapon.cs, in Engine\Models.

This is a sub-class of GameItem. It uses the properties from GameItem, and adds two new properties: MinimumDamage and MaximumDamage.

In the constructor for Weapon, we have the parameters we will pass in when we instantiate a new Weapon object. It has the three parameters we used in GameItem, plus the new damage parameters.

Because Weapon is a sub-class of GameItem, and GameItem has three parameters in its constructor, we need to pass values into the GameItem constructor.Part of instantiating a sub-class is to call the constructor of its “base class” – if its base class has a constructor.

We do that with the line “: base(itemTypeID, name, price)”. That means, “To instantiate a Weapon object, we pass in five parameters. We will take three of those parameters (itemTypeID, name, and price) and pass them into the constructor of the base class (GameItem)”.

Weapon.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Engine.Models
{
    public class Weapon : GameItem
    {
        public int MinimumDamage { get; set; }
        public int MaximumDamage { get; set; }
        public Weapon(int itemTypeID, string name, int price, int minDamage, int maxDamage) 
            : base(itemTypeID, name, price)
        {
            MinimumDamage = minDamage;
            MaximumDamage = maxDamage;
        }
    }
}

Step 3: Create a new class: ItemFactory.cs, in Engine\Factories.

When the game needs a new item, we will use this class to create the object.

Because this is a factory, this will be a static class – a class we do not need to instantiate.

We’re going to build this factory a little differently than how we built the WorldFactory class.

First, we are going to build a collection of all the possible items in the game. We’ll store these items in a List variable, which is how you often store a collection of several items that are the same datatype.

This is the _standardGameItems variable. It’s static, because we’re using it in a static class, with static methods. Its datatype is “List<GameItem>”, which means it can store several objects whose datatype is “GameItem”.

The first time we use the ItemFactory class, we are going to populate this list with all the game items. We’ll do this in code, for now. In the future, we will be able to populate this list from a database, or file.

Static classes do not have constructors, because they are never “constructed” (instantiated). Fortunately, we can create a function that is run the first time a static class is used. That is where we will populate the list of game items.

This is the “static ItemFactory()” function. The first time anything is used in ItemFactory, this function will run and populate our _standardGameItems variable with all the items in the game.

Notice that we are instantiating Weapon objects, and adding them to a variable whose datatype is “List<GameItem>”. Because Weapon is a sub-class of GameItem, it is both a Weapon object and a GameItem object.

We can add any object to _standardGameItems, as long as its datatype is GameItem, or it inherits from GameItem.

To instantiate a game item object, we will create the function “CreateGameItem”, which accepts an ID as its parameter. It will look through the _standardGameItems list, to find the item with the same ID. This uses LINQ, to query the list variable. If it finds a matching object, it will create a new instance of that object – using its name, price, etc. If there is not an item with a matching ID, the function will return “null” – nothing.

This will let us have unique items in the game – with the ability to modify each item individually. This will allow for enchanting an item, or having some items wear out with use, and require repair.

ItemFactory.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Engine.Models;
namespace Engine.Factories
{
    public static class ItemFactory
    {
        private static List<GameItem> _standardGameItems;
        static ItemFactory()
        {
            _standardGameItems = new List<GameItem>();
            _standardGameItems.Add(new Weapon(1001, "Pointy Stick", 1, 1, 2));
            _standardGameItems.Add(new Weapon(1002, "Rusty Sword", 5, 1, 3));
        }
        public static GameItem CreateGameItem(int itemTypeID)
        {
            GameItem standardItem = _standardGameItems.FirstOrDefault(item => item.ItemTypeID == itemTypeID);
            if(standardItem != null)
            {
            }
        }
    }
}

Step 4: Modify GameItem.cs and Weapon.cs.

In order to get a new instance of the game objects, we need to have a way for them to create copies of themselves. This is commonly called “cloning”. You create a new object, but it has the same property values as another object – in this case, the “standard” version of the object.

We will do the cloning by adding a new Clone function to the GameItem and Weapon classes.

GameItem.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Engine.Models
{
    public class GameItem
    {
        public int ItemTypeID { get; set; }
        public string Name { get; set; }
        public int Price { get; set; }
        public GameItem(int itemTypeID, string name, int price)
        {
            ItemTypeID = itemTypeID;
            Name = name;
            Price = price;
        }
        public GameItem Clone()
        {
            return new GameItem(ItemTypeID, Name, Price);
        }
    }
}
Weapon.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Engine.Models
{
    public class Weapon : GameItem
    {
        public int MinimumDamage { get; set; }
        public int MaximumDamage { get; set; }
        public Weapon(int itemTypeID, string name, int price, int minDamage, int maxDamage) 
            : base(itemTypeID, name, price)
        {
            MinimumDamage = minDamage;
            MaximumDamage = maxDamage;
        }
        public new Weapon Clone()
        {
            return new Weapon(ItemTypeID, Name, Price, MinimumDamage, MaximumDamage);
        }
    }
}

Step 5: Finish editing ItemFactory.cs

Now, we can go back to itemFactory, and finish the CreateGameItem function.

Inside the “if” statement that checks if an item was found in _standardGameItems, add a line to return a clone of the found game item object.

After the “if” statement, add the line to return null, for when no object was found.

This lets the CreateGameItem function create new instances of game items.

ItemFactory.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Engine.Models;
namespace Engine.Factories
{
    public static class ItemFactory
    {
        private static List<GameItem> _standardGameItems;
        static ItemFactory()
        {
            _standardGameItems = new List<GameItem>();
            _standardGameItems.Add(new Weapon(1001, "Pointy Stick", 1, 1, 2));
            _standardGameItems.Add(new Weapon(1002, "Rusty Sword", 5, 1, 3));
        }
        public static GameItem CreateGameItem(int itemTypeID)
        {
            GameItem standardItem = _standardGameItems.FirstOrDefault(item => item.ItemTypeID == itemTypeID);
            if(standardItem != null)
            {
                return standardItem.Clone();
            }
            return null;
        }
    }
}

NEXT LESSON: Lesson 05.2: Creating the Player Inventory

PREVIOUS LESSON: Lesson 04.5: Improving the World – Factory and Guard Clauses

7 Comments

  1. Jay Bakata
    Jay Bakata 2021-10-03

    Scott, I noticed my movement arrows are not working correctly. By that I mean they do not hide if there is no place to go. from start (0,0 town square) all movement is available. I move East to town gate and only North is hidden. If I click south nothing happens.

    From start (0,0 town square) I go North to herbalist hut and only the North button is showing. If I then travel North to herbalist garden no direction arrow shows. I am not sure what I did but I have yet to find the problem. There are other problems like when at the farmer’s house it shows I can go South. I know it must be something simple, any chance you can point me in the right direction (pun not intended) to fix this problem? Thanks for your time and your help.

    • SOSCSRPG
      SOSCSRPG 2021-10-03

      Hi Jay,

      Can you upload your solution (including the directories under it, and all the files in those directories) to GitHub, Dropbox, or some other file-sharing location so I can look at it?

      • Joe Bakata
        Joe Bakata 2021-10-03

        LINK REMOVED FOR PRIVACY

        Honestly I am just learning and have no idea how to use github. I hope this works.

        • SOSCSRPG
          SOSCSRPG 2021-10-03

          Hi Joe,

          I didn’t see the WPFUI.csproj file in the GitHub project. If you can’t upload it there, can you send it to me through Discord (https://discord.gg/qTma89NFFS), or upload it to some other file-sharing location?

          Thanks

  2. Alex Utkin
    Alex Utkin 2022-07-11

    Hello! I have a question about static constructor. I have Sonar extension istalled in VS and using static constructor generates Sonar warning. The warning says that if the static constructor is only used for initialization it has high performance cost because the compiler generates a check before each static method or instance constructor invocation. And highly recommends to initialize all members inline. It is possible to init the list like this

    “public List items = new List() {new Weapon(), new Weapon, …}

    Although here we have only one member List. And in Sonar example there were multiple members.

    Could you please explain what is the reasoning behind using the constructor?

    • SOSCSRPG
      SOSCSRPG 2022-07-11

      Hi Alex,

      In older versions of .NET, setting static class-level variables to instantiated objects was not ensured to be thread-safe. If the class was being called from multiple threads, at the same time, there might be a problem. So, I got in the habit of populating static class-level variables in the static “constructor” function. For a real program, I would also add a sync lock in there, to ensure thread-safety.

      But, Microsoft stated that recent versions of .NET are thread-safe when instantiating values for static class-level variables. So, the way you described would work, and be safe. I just did it the old way out of habit. For a small program like this, and an early lesson, I didn’t want to get into some more-complex things, like threads. I’m also not too concerned about any performance difference for this program. For a real app that dealt with a larger amount of data, I’d be more concerned about performance.

      But, you can definitely change your version of the program to populate the static class-level variables inline.

      • Alex Utkin
        Alex Utkin 2022-07-11

        Thank you! I havent not studied Threads yet. These things are important for me to keep in mind when i start to learn new material on C#.

Leave a Reply

Your email address will not be published. Required fields are marked *