Lesson 3.1: Game Items and Factory

We are going to build the start of an inventory system. To encapsulate the concept of items in our game, we are going to define a GameItem model class. This class will have the base properties of all GameItems. Then, we will define a derived class for Weapon. Weapon is a specialized item that also has the ability to deal damage.

Defining Game Item

Let’s create a new GameItem.cs file in SimpleRPG.Game.Engine and Models folder.

namespace SimpleRPG.Game.Engine.Models
{
    public class GameItem
    {
        public GameItem(int itemTypeID, string name, int price)
        {
            ItemTypeID = itemTypeID;
            Name = name;
            Price = price;
        }

        public int ItemTypeID { get; set; }

        public string Name { get; set; } = string.Empty;

        public int Price { get; set; }

        public virtual GameItem Clone() =>
            new GameItem(ItemTypeID, Name, Price);
    }
}

It’s a simple model class with 3 properties and a constructor to set the properties. However, we also create a Clone method that creates a deep copy of this type. We’re going to use this in the GameItem factory because players will get multiple instances of items throughout the game.

Next we create the Weapon class, which inherits from GameItem (learn more about C# inheritance here). This means that Weapon has the same properties and methods as GameItem, but also adds its own constructor and properties.

Let’s add another file to the SimpleRPG.Game.Engine project in the Models folder:

namespace SimpleRPG.Game.Engine.Models
{
    public class Weapon : GameItem
    {
        public Weapon(int itemTypeID, string name, int price, int minDamage, int maxDamage)
            : base(itemTypeID, name, price)
        {
            MinimumDamage = minDamage;
            MaximumDamage = maxDamage;
        }

        public int MinimumDamage { get; set; }

        public int MaximumDamage { get; set; }

        public override GameItem Clone() =>
            new Weapon(ItemTypeID, Name, Price, MinimumDamage, MaximumDamage);
    }
}

We add two properties that manage how much damage the weapon can do. And notice the constructor now takes 5 parameters. The first three (shared with the GameItem class) are used in calling the base constructor. The last two are set to weapon’s properties. This let us extend the GameItem with more properties which is useful for sharing behavior in base classes.

Also, the Clone method now clones a Weapon, rather than a GameItem because we need to account for the additional properties. Notice the override attribute on the method, that means this Clone method is called rather than the GameItem.Clone base method for items of the Weapon type.

Inheritance is a power and sometimes dangerous concept in object-orient programming languages, like C#. Learn about it and it’s uses, so that you are sure when to apply it, and when it’s being misused.

The GameItem Factory

As we did earlier with the World object (Lesson 2.9), we are going to create use the Factory Method design pattern to provide a way for create items when they are needed.

In the SimpleRPG.Game.Engine project and Factories folder, create a ItemFactory class.

using SimpleRPG.Game.Engine.Models;
using System.Collections.Generic;
using System.Linq;

namespace SimpleRPG.Game.Engine.Factories
{
    internal static class ItemFactory
    {
        private static List<GameItem> _standardGameItems = new List<GameItem>
        {
            new Weapon(1001, "Pointy Stick", 1, 1, 2),
            new Weapon(1002, "Rusty Sword", 5, 1, 3)
        };

        public static GameItem? CreateGameItem(int itemTypeID)
        {
            var standardItem = _standardGameItems.FirstOrDefault(i => i.ItemTypeID == itemTypeID);
            return standardItem?.Clone();
        }
    }
}

The CreateGameItem method finds the specified game item by Id and then returns a new instance of that item. If the GameItem isn’t found in the list, then this method will return a null item. The standardItem?.Clone() notation means call the Clone method only if the standardItem is not null. If it is null, then the method just returns null.

ItemFactory Testability

We made the ItemFactory internal (as we did with WorldFactory) because we don’t want to expose the internals of how we create objects to external code. However, we also want to test ItemFactory to ensure that it works as we expect, but we don’t want to make that class public just to be tested. So to enable our unit tests, we are going to make the internal class visible to our test project using the InternalsVisibleTo attribute.

In the SimpleRPG.Game.Engine project, we’re going to create the AssemblyInfo.cs file. That file will just have our assembly attributes.

using System.Runtime.CompilerServices;

[assembly:InternalsVisibleTo("SimpleRPG.Game.Engine.Tests")]

The assembly: part means the attribute applies to the assembly/library, not to a type in the library. InternalsVisibleTo is an attribute that gives other assemblies access to internal types in this library. And the name we pass into the attribute is the unit test full project name. After building, our test project can now reference the ItemFactory class.

InternalsVisibleTo is a dangerous attribute. It allows us to open up our library to the use of internal types and opens up having breaking changes of internal implementation code when we think it is safe to change. But this attribute is very helpful in testing some components. But it should never be used to give non-test libraries access to internal code. If you need to reference an internal type in another library, then that’s usually a sign that the type should be public. So use it to enable testing, and question if you see it used for anything else.

Now we can write ItemFactory tests, like these:

using SimpleRPG.Game.Engine.Factories;
using System;
using System.Collections.Generic;
using System.Text;
using Xunit;

namespace SimpleRPG.Game.Engine.Tests.Factories
{
    public class ItemFactoryTests
    {
        [Fact]
        public void CreateGameItem_WithValidItemTypeId()
        {
            // arrange

            // act
            var item = ItemFactory.CreateGameItem(1001);

            // assert
            Assert.NotNull(item);
            Assert.Equal(1001, item.ItemTypeID);
            Assert.Equal("Pointy Stick", item.Name);
            Assert.Equal(1, item.Price);
        }

        [Fact]
        public void CreateGameItem_WithInvalidItemTypeId()
        {
            // arrange

            // act
            var item = ItemFactory.CreateGameItem(1);

            // assert
            Assert.Null(item);
        }
    }
}

As we wrap up this lesson, we now have a couple of different game item types defined, and an item factory to create instances of items whenever they are needed in the game. Next we will look at the initial player inventory.

One thought on “Lesson 3.1: Game Items and Factory

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s