Press "Enter" to skip to content

Lesson 11.1 – Using a static class

Lesson Objectives

At the end of this lesson, you will know…

  • What a static class is, and how to create one.
  • When you want, and don’t want, to use static classes

 

What is a static class?

All the classes we’ve created so far have been public classes. That means they are visible in all of your program. However, in order to use one of them, you need to create an object of the class. Then you work with that object.

A static class is one that is always available to the rest of your program – you don’t need to create an object from it. In fact, you can’t create an object from a static class. It’s just there.

A static class is a place to hold shared resources (variables and methods), since there will only be the one “instance” of it, and everything else in your program will use that one, shared set of methods and variables.

 

Why would you need a static class?

For our game, we need to store some things that will be used in several places in the program.

Things like the list of locations, the types of items, the types of monsters, etc. This information is never going to change. We’re going to populate these lists at the start of the game, and never change it. We’re also going to use those lists in several places in the game.

Using a static class to hold all this information is one way to make all this information available everywhere.

 

When else would you use a static class?

Another thing you can do with a static class (and a static variable) is to hold values such as a system-wide counter.

Let’s say you want a program to hand out unique, sequential numbers to the people who use it.

You could create a static NumberAssigner class, with a static GetNextNumber method, that keeps track of a static _nextNumber variable.

namespace Engine
{
    public static class NumberAssigner
    {
        static int _nextNumber = 0;
        public static int GetNextNumber()
        {
            _nextNumber = (_nextNumber + 1);
            return _nextNumber;
        }
    }
}

When you start the program, _nextNumber has a value of 0.

When a user calls the GetNextNumber method, the code adds 1 to _nextNumber and returns the value (in this case, 1) to the program. The next time the GetNextNumber method is called, it adds 1 to _nextNumber (resulting in 2 this time) and returns 2 to the program.

 

What problems can happen with static classes?

The problem with static methods and variables, is that sometimes you don’t want a shared resource, you want each user to have their own copy of the object or variable.

The game we’re creating is a single-player one. So, we don’t really have a problem using static variables.

However, if we were to make a UI for this game a website on the Internet, we might have several people playing it at the same time.

So, let’s say we stored the player’s current hit points somewhere as a static variable – CurrentHitPoints.

When player A is attacked, the program would subtract their damage and change the value of CurrentHitPoints. But if a different player did something in the game (attacked a monster or healed themselves with a potion), since we only have a static, single, shared CurrentHitPoints variable, they’d be using the value from player A, and not their real current hit points value.

That’s how static classes and variables can be dangerous. When you use a static variable to hold a value, make sure it’s one that you really want to be shared for every user.

 

Populating our game world in a static class

Now that you have an understanding of static classes and variables, we’re going to create a “World” class to hold lists of all the things in our game – locations, items, monsters, and quests.

Since we’re only going to read from it, once we do the initial population of the values, it’s OK to use a static class.

Step 1: Start Visual Studio and open the solution.

Step 2: Create a new class by right-clicking on the Engine project and selecting “Add”, then “Class…”. Name the class “World.cs”

Step 3: Copy the code for the class from here: https://gist.github.com/ScottLilly/803df1021fbc404b38f5

 

What does the code do?

The purpose of the World class is to have one place to hold everything that exists in the game world. In it, we’ll have things such as the monster that exist at a location, the loot items you can collect after defeating a monster, etc. It will also show how the locations connect with each other, building our game map.

GameMap

Here is what is happening in the different parts of the World class.

Lines 11 – 14: Static list variables. These work similar to the properties in a class. We’ll populate them with all the things in our game world, then read from them in the rest of the program.

Line 16 – 42: Constants. Constants look, and work, like variables, except for one big difference – they can never have their values changed.

We’re going to use these constants, which have a sort-of English name, so we don’t have to remember the numeric ID for each of the different games objects. Without them, if we wanted to create a giant spider for the player to fight, we’d need to remember that the giant spider monster has an ID value of 2. With the constants, we can say, get me a monster where the ID equals MONSTER_ID_GIANT_SPIDER.

If you don’t fully understand how we’ll do that, it should become clearer when we get to the lesson where we start having the player move around and we need to lookup locations, quests, monsters, and items.

Lines 44 – 50: This is the static constructor. You might be thinking, “Wait! We can’t instantiate a static class, so why does it have a constructor? After all, that’s what a constructor is used for – instantiating an object!”

With a static class, the constructor code is run the first time someone uses anything in the class. So, when we start the game and want to display information about the player’s current location, and we try to get that data from the World class, the constructor method will be run, and the lists will get populated.

Inside the constructor, we call the four methods to populate the different lists. We don’t need to have separate methods, and we could have put all the code from lines 48 through 169 into the constructor. But breaking them up makes them easier to read and work with.

Lines 52 – 173: These are the methods we use to create the game objects and add them to the static lists.

By calling the Add() method on a list variable or property, we add an object to that list.

Look at line 54. Here we are adding a new Weapon object to the Items list. When we call “new Weapon()”, the constructor of the Weapon class returns a Weapon object with the parameters passed in. Since that’s all inside “Items.Add()”, that object gets added to the Items list.

You might hear that called “inlining”, since we did multiple things (created the value and added it to the list), all in one line.

On line 68, we create a new Monster object and save it to the variable “rat”. On lines 69 and 70, we add items to the (list) property of PotentialLootItems that you might find on the rat. Then, on line 80, we add the rat variable to the static Monsters list.

Lines 175 – 225: These methods are ones we can call to get values from the static lists. We could access the lists from lines 7 through 10 directly, since they are public. But these “wrapper” methods make it a little clearer exactly what we want to do.

We pass in the ID of the object we want to retrieve from its list (by using the constants from lines 16 through 42). The method will look at each item in the list (using the “foreach”) and see if the ID we passed in matches the ID of the object. If so, it returns that object to us. If we get to the end of the list, and nothing matches (which should really never happen), the method returns “null” – nothing.

 

Summary

Now we have a populated “world” for the game. We can use the static methods from this static class at any place in the rest of our program, and get the information we need about the objects in our game world.

 

World.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Engine
{
    public static class World
    {
        public static readonly List<Item> Items = new List<Item>();
        public static readonly List<Monster> Monsters = new List<Monster>();
        public static readonly List<Quest> Quests = new List<Quest>();
        public static readonly List<Location> Locations = new List<Location>();
        public const int ITEM_ID_RUSTY_SWORD = 1;
        public const int ITEM_ID_RAT_TAIL = 2;
        public const int ITEM_ID_PIECE_OF_FUR = 3;
        public const int ITEM_ID_SNAKE_FANG = 4;
        public const int ITEM_ID_SNAKESKIN = 5;
        public const int ITEM_ID_CLUB = 6;
        public const int ITEM_ID_HEALING_POTION = 7;
        public const int ITEM_ID_SPIDER_FANG = 8;
        public const int ITEM_ID_SPIDER_SILK = 9;
        public const int ITEM_ID_ADVENTURER_PASS = 10;
        public const int MONSTER_ID_RAT = 1;
        public const int MONSTER_ID_SNAKE = 2;
        public const int MONSTER_ID_GIANT_SPIDER = 3;
        public const int QUEST_ID_CLEAR_ALCHEMIST_GARDEN = 1;
        public const int QUEST_ID_CLEAR_FARMERS_FIELD = 2;
        public const int LOCATION_ID_HOME = 1;
        public const int LOCATION_ID_TOWN_SQUARE = 2;
        public const int LOCATION_ID_GUARD_POST = 3;
        public const int LOCATION_ID_ALCHEMIST_HUT = 4;
        public const int LOCATION_ID_ALCHEMISTS_GARDEN = 5;
        public const int LOCATION_ID_FARMHOUSE = 6;
        public const int LOCATION_ID_FARM_FIELD = 7;
        public const int LOCATION_ID_BRIDGE = 8;
        public const int LOCATION_ID_SPIDER_FIELD = 9;
        static World()
        {
            PopulateItems();
            PopulateMonsters();
            PopulateQuests();
            PopulateLocations();
        }
        private static void PopulateItems()
        {
            Items.Add(new Weapon(ITEM_ID_RUSTY_SWORD, "Rusty sword", "Rusty swords", 0, 5));
            Items.Add(new Item(ITEM_ID_RAT_TAIL, "Rat tail", "Rat tails"));
            Items.Add(new Item(ITEM_ID_PIECE_OF_FUR, "Piece of fur", "Pieces of fur"));
            Items.Add(new Item(ITEM_ID_SNAKE_FANG, "Snake fang", "Snake fangs"));
            Items.Add(new Item(ITEM_ID_SNAKESKIN, "Snakeskin", "Snakeskins"));
            Items.Add(new Weapon(ITEM_ID_CLUB, "Club", "Clubs", 3, 10));
            Items.Add(new HealingPotion(ITEM_ID_HEALING_POTION, "Healing potion", "Healing potions", 5));
            Items.Add(new Item(ITEM_ID_SPIDER_FANG, "Spider fang", "Spider fangs"));
            Items.Add(new Item(ITEM_ID_SPIDER_SILK, "Spider silk", "Spider silks"));
            Items.Add(new Item(ITEM_ID_ADVENTURER_PASS, "Adventurer pass", "Adventurer passes"));
        }
        private static void PopulateMonsters()
        {
            Monster rat = new Monster(MONSTER_ID_RAT, "Rat", 5, 3, 10, 3, 3);
            rat.LootTable.Add(new LootItem(ItemByID(ITEM_ID_RAT_TAIL), 75, false));
            rat.LootTable.Add(new LootItem(ItemByID(ITEM_ID_PIECE_OF_FUR), 75, true));
            Monster snake = new Monster(MONSTER_ID_SNAKE, "Snake", 5, 3, 10, 3, 3);
            snake.LootTable.Add(new LootItem(ItemByID(ITEM_ID_SNAKE_FANG), 75, false));
            snake.LootTable.Add(new LootItem(ItemByID(ITEM_ID_SNAKESKIN), 75, true));
            Monster giantSpider = new Monster(MONSTER_ID_GIANT_SPIDER, "Giant spider", 20, 5, 40, 10, 10);
            giantSpider.LootTable.Add(new LootItem(ItemByID(ITEM_ID_SPIDER_FANG), 75, true));
            giantSpider.LootTable.Add(new LootItem(ItemByID(ITEM_ID_SPIDER_SILK), 25, false));
            Monsters.Add(rat);
            Monsters.Add(snake);
            Monsters.Add(giantSpider);
        }
        private static void PopulateQuests()
        {
            Quest clearAlchemistGarden =
                new Quest(
                    QUEST_ID_CLEAR_ALCHEMIST_GARDEN,
                    "Clear the alchemist's garden",
                    "Kill rats in the alchemist's garden and bring back 3 rat tails. You will receive a healing potion and 10 gold pieces.", 20, 10);
            clearAlchemistGarden.QuestCompletionItems.Add(new QuestCompletionItem(ItemByID(ITEM_ID_RAT_TAIL), 3));
            clearAlchemistGarden.RewardItem = ItemByID(ITEM_ID_HEALING_POTION);
            Quest clearFarmersField =
                new Quest(
                    QUEST_ID_CLEAR_FARMERS_FIELD,
                    "Clear the farmer's field",
                    "Kill snakes in the farmer's field and bring back 3 snake fangs. You will receive an adventurer's pass and 20 gold pieces.", 20, 20);
            clearFarmersField.QuestCompletionItems.Add(new QuestCompletionItem(ItemByID(ITEM_ID_SNAKE_FANG), 3));
            clearFarmersField.RewardItem = ItemByID(ITEM_ID_ADVENTURER_PASS);
            Quests.Add(clearAlchemistGarden);
            Quests.Add(clearFarmersField);
        }
        private static void PopulateLocations()
        {
            // Create each location
            Location home = new Location(LOCATION_ID_HOME, "Home", "Your house. You really need to clean up the place.");
            Location townSquare = new Location(LOCATION_ID_TOWN_SQUARE, "Town square", "You see a fountain.");
            Location alchemistHut = new Location(LOCATION_ID_ALCHEMIST_HUT, "Alchemist's hut", "There are many strange plants on the shelves.");
            alchemistHut.QuestAvailableHere = QuestByID(QUEST_ID_CLEAR_ALCHEMIST_GARDEN);
            Location alchemistsGarden = new Location(LOCATION_ID_ALCHEMISTS_GARDEN, "Alchemist's garden", "Many plants are growing here.");
            alchemistsGarden.MonsterLivingHere = MonsterByID(MONSTER_ID_RAT);
            Location farmhouse = new Location(LOCATION_ID_FARMHOUSE, "Farmhouse", "There is a small farmhouse, with a farmer in front.");
            farmhouse.QuestAvailableHere = QuestByID(QUEST_ID_CLEAR_FARMERS_FIELD);
            Location farmersField = new Location(LOCATION_ID_FARM_FIELD, "Farmer's field", "You see rows of vegetables growing here.");
            farmersField.MonsterLivingHere = MonsterByID(MONSTER_ID_SNAKE);
            Location guardPost = new Location(LOCATION_ID_GUARD_POST, "Guard post", "There is a large, tough-looking guard here.", ItemByID(ITEM_ID_ADVENTURER_PASS));
            Location bridge = new Location(LOCATION_ID_BRIDGE, "Bridge", "A stone bridge crosses a wide river.");
            Location spiderField = new Location(LOCATION_ID_SPIDER_FIELD, "Forest", "You see spider webs covering covering the trees in this forest.");
            spiderField.MonsterLivingHere = MonsterByID(MONSTER_ID_GIANT_SPIDER);
            // Link the locations together
            home.LocationToNorth = townSquare;
            townSquare.LocationToNorth = alchemistHut;
            townSquare.LocationToSouth = home;
            townSquare.LocationToEast = guardPost;
            townSquare.LocationToWest = farmhouse;
            farmhouse.LocationToEast = townSquare;
            farmhouse.LocationToWest = farmersField;
            farmersField.LocationToEast = farmhouse;
            alchemistHut.LocationToSouth = townSquare;
            alchemistHut.LocationToNorth = alchemistsGarden;
            alchemistsGarden.LocationToSouth = alchemistHut;
            guardPost.LocationToEast = bridge;
            guardPost.LocationToWest = townSquare;
            bridge.LocationToWest = guardPost;
            bridge.LocationToEast = spiderField;
            spiderField.LocationToWest = bridge;
            // Add the locations to the static list
            Locations.Add(home);
            Locations.Add(townSquare);
            Locations.Add(guardPost);
            Locations.Add(alchemistHut);
            Locations.Add(alchemistsGarden);
            Locations.Add(farmhouse);
            Locations.Add(farmersField);
            Locations.Add(bridge);
            Locations.Add(spiderField);
        }
        public static Item ItemByID(int id)
        {
            foreach(Item item in Items)
            {
                if(item.ID == id)
                {
                    return item;
                }
            }
            return null;
        }
        public static Monster MonsterByID(int id)
        {
            foreach(Monster monster in Monsters)
            {
                if(monster.ID == id)
                {
                    return monster;
                }
            }
            return null;
        }
        public static Quest QuestByID(int id)
        {
            foreach(Quest quest in Quests)
            {
                if(quest.ID == id)
                {
                    return quest;
                }
            }
            return null;
        }
        public static Location LocationByID(int id)
        {
            foreach(Location location in Locations)
            {
                if(location.ID == id)
                {
                    return location;
                }
            }
            return null;
        }
    }
}

 

Source code for this lesson

Source code on GitHub

Source code on Dropbox

 

Next Lesson: Lesson 12.1 – Add the remaining UI controls

Previous lesson: Lesson 10.1 – Creating collections of objects

All lessons: Learn C# by Building a Simple RPG Index

160 Comments

  1. Jason
    Jason March 19, 2015

    Hi Scott, After I copy-paste the World class information, I get the error: The type or namespace name ‘RewardItem’ could not be found (are you missing a using directive or an assembly reference?).

    On line 95:
    clearAlchemistGarden.RewardItem = ItemByID(ITEM_ID_HEALING_POTION);

    On line 105:
    clearFarmersField.RewardItem = ItemByID(ITEM_ID_ADVENTURER_PASS);

    Did I miss something?

    Thanks!

    • Scott Lilly
      Scott Lilly March 19, 2015

      Hi Jason,

      The first thing I can think of is that maybe there is a problem in your Quest.cs class. Edit it, and make sure it has this property:

      public Item RewardItem { get; set; }

      Make sure it is spelled exactly the same, and uses matching upper and lower-case.

      Please let me know if that fixes it, or not. If it doesn’t, we can dig a little deeper.

      • Jason
        Jason March 19, 2015

        That worked!! I must’ve missed that. Thank you very much for your help!

      • Colt Eaves
        Colt Eaves June 3, 2016

        Scott thank you for this series and please keep going!
        I have posted your source code as well and am getting the error:

        “Cannot implicitly convert type ‘Engine.Item’ to ‘int’ line 95
        “Cannot implicitly convert type ‘Engine.Item’ to ‘int’ line 105

        I did update my Item. class in the way you helped Jason and still no fix!

          • Colt Eaves
            Colt Eaves June 6, 2016

            Thank you Scott for replying! I have been waiting for someone to do what you have done with this lesson series and I want to continue but my code is stuck giving me the error I spoke about above! Here is my Dropbox link to my file folder. Thank you so much 🙂
            LINK REMOVED FOR PRIVACY
            -Colt

          • Scott Lilly
            Scott Lilly June 6, 2016

            You’re welcome, Colt.

            The problem is in your Quest class. The datatype of the RewardItem property needs to be “Item”, instead of “int”, so it can hold the Item object returned by ItemByID.

            Please let me know if that doesn’t fix it.

          • Logan Cody
            Logan Cody January 7, 2017

            Heya Scott, i am also having problems with the world.cs, so if you could explain to me whats wrong and how i can fix it once you’ve seen it would be great!

          • Scott Lilly
            Scott Lilly January 7, 2017

            Hi Logan,

            Can you tell me what the problems are? If you are getting error messages, what do they say (the full error message will help)? Also, it might help if you can upload the world.cs file to https://gist.github.com/.

    • Joe D
      Joe D October 22, 2015

      why use a “foreach” instead of “for”?

       

      • Scott Lilly
        Scott Lilly October 23, 2015

        If you use a “foreach”, you don’t need to worry about “off-by-one” errors that are easy to make when using a for loop. For example, someone using a “for” loop may not remember that the index of first element is “0”, not “1”. Or, they may forget that the max index value is “Count – 1”, and use “Count”.

        This is a more common way of doing things nowadays, since the introduction of LINQ and the growing popularity of functional languages like F#, Haskell, Lisp, etc.

  2. Simon
    Simon May 21, 2015

    Hi. Do not understand what the foreach methods are for when being passed in as a variable. Thanks

    • Scott Lilly
      Scott Lilly May 22, 2015

      Think of a variable (or property) as a box we use to hold information. With properties, we put something into the box with a “set”, and we retrieve it with a “get”.

      Each variable/property has a name (so we can easily find the correct box we’re looking for). It also has a “datatype”, which tells us what kind of information we can put in the box.

      Many times, the box holds a single value. For example, in the Player class, we have a CurrentHitPoints property. It only ever holds a single number.

      However, these boxes can also hold a collection/list of values. Like putting a bunch of smaller boxes inside one big box.

      In the World class, when we first start up, we fill up some variables (Items, Monsters, Quests, Locations) with collections of the possible items, monsters, quests, and locations that exist in the game world. This happens in the “Populate” functions.

      When we want to retrieve a certain object from a collection, we have the “ByID” functions. These functions use a “foreach” to check each item in the collection, until it finds the one we are looking for (its ID matches the ID we are looking for). Kind of like opening up a big box and checking each of the smaller boxes until we find the one we are looking for. When it finds the object with the ID we are looking for, it returns the object to the part of the program that called the “ByID” function.

      • Simon
        Simon May 29, 2015

        Thank you. To me it seems you must be a genius in planning to know how to structure a program before you start programming. No way could I create anything remotely similar on my own as this program

        • Scott Lilly
          Scott Lilly June 3, 2015

          After you become familiar with the concepts of programming, it starts to get easier. Programming is just like many other things: When you first start learning, everything is new and can be a bit confusing. As you practice, you can start doing the basic things automatically.

          If you’re having trouble seeing how a whole program works, I like to imagine the program as a physical factory, with a conveyor belt moving the program’s “objects” around. When you write something to the database, it’s like storing it in the warehouse. When you read something from a database, it’s like pulling it from inventory. When you validate your data, it’s like a quality assurance person checking that the object is good. It sometimes even helps to put Post-It notes on the wall for each step in the program. Then, you can visually follow your data from step to step.

  3. Simon
    Simon May 29, 2015

    Hi. I’m having a little trouble getting my head around how the locations are linked together.

    Thank you

    • Scott Lilly
      Scott Lilly June 3, 2015

      Hi Simon,

      When you look at the PopulateLocation method, in the World class, the first thing I do is create all the individual locations as variables. Each location has four properties that can hold the variables for locations in each direction (North, South, East, and West).

      There is a line that says “home.LocationToNorth = townSquare;”. So, when we look at the variable for the “home” location, if we look at its “LocationToNorth” property, we see it holds the variable for the “townSquare” location. A few lines later, we have “townSquare.LocationToSouth = home;”. This is where we say that the “LocationToSouth” for the “townSquare” variable is the “home” variable. We don’t build a big map object in this game. Instead, we have each location object know what other location objects are next to it.

  4. Simon
    Simon June 14, 2015

    Thank you for this guide and your explanations in the comments. This guide helped me pass my assignment after I failed it the first time

    • Scott Lilly
      Scott Lilly June 18, 2015

      You’re welcome. Sometimes programming can be a bit difficult to get started learning. I hope you’ll be able to continue improving your programming skills.

  5. Scott
    Scott September 13, 2015

    Hey Scott after I copied and past the code I get 17 errors saying “Error    4    ‘Engine.Location’ does not contain a definition for ‘LocationToEast’ and no extension method ‘LocationToEast’ accepting a first argument of type ‘Engine.Location’ could be found (are you missing a using directive or an assembly reference?)    E:\Game\Engine\World.cs    147    24    Engine” Not Quite sure whats going on. Hoping you can help!

    • Scott Lilly
      Scott Lilly September 13, 2015

      It sounds like the Location class does not have the LocationToEast, LocationToNorth, etc. properties. When the World class tries to set those values (in lines 138-161), that’s where the error is happening.

      Double-check lesson 09.1, and make sure you have the changes to the Location and Quest classes that are shown in the GitHub source for that lesson.

      If that doesn’t fix the problem, let me know and we can investigate further.

  6. Joe D
    Joe D October 22, 2015

    Instead of “alchemistHut.QuestAvailableHere = QuestByID(QUEST_ID_CLEAR_ALCHEMIST_GARDEN)”

    could you use “alchemistHut.QuestAvailableHere = QUEST_ID_CLEAR_ALCHEMIST_GARDEN” and avoid the “foreach” loops altogether?

    and the same for the other “foreach”  loops?

    • Scott Lilly
      Scott Lilly October 23, 2015

      The datatype of the the QuestAvailableHere property is Quest, so we need to store a Quest object in it.

      The method that uses the “foreach” goes through the World’s collection of quests, finds the one with the matching ID, and returns the Quest object we want. We could have made the property an integer, named it something like QuestID, stored the QUEST_ID_WHATEVER in that property, and then looked up the quest later (when the player moves to the location).

      It’s a personal preference choice as to which way to do it. I went with storing the Quest object in the property, so we don’t need to look it up each time the player moves to the location.

  7. Bryan Baker
    Bryan Baker February 25, 2016

    Hey Scott,
    I’m running into the error:

    “The name ‘ItemByID’ does not exist in the current context”

    Did I accidentally miss, delete, or overwrite something without knowing? Thanks!

    • Scott Lilly
      Scott Lilly February 25, 2016

      Make sure the World class, and the ItemByID function are both “public static”. If they are, can you upload a copy of your World.cs class to https://gist.github.com/, and send me the link to it? Then I can try to find the problem.

  8. Edmund
    Edmund April 13, 2016

    Hey, I’ve been really enjoying this tutorial so far. The hardest part of programming for me is how to structure it, so this has been really helpful. I do have a few questions, however.

    Firstly, what is the reason for the whole ID system? Would it not be easier to create the different items publicly without adding them to a list – eg, wepSword = new Weapon(args) – and then add them directly? eg clearFarmersField.RewardItem = wepSword. Is this just for compartmentalisation? Or does it have other advantages?

    I’m also curious why you chose to implement the map in this way. Why did you not use a 2D array to create an actual grid where you add the tiles, and then the LocationToNorth etc can be generated programmatically.

    Thanks in advance.

    • Scott Lilly
      Scott Lilly April 13, 2016

      This gets a little bit into some advanced details, so let me know if my explanation isn’t clear.

      For this game, we don’t use individual game items (objects). We only need a way to get the information for a particular type of item.

      For example, in some games, each “rusty sword” could have unique enchantments (spells, gems, etc.). Or, it could wear out as it is used. In that type of game, we would need to instantiate a new object for each individual rusty sword, because each one could have different values for those other properties. However, in this game, all rusty swords are exactly the same – the same name, does the same amount of damage, etc. So, we only really need one instance of a rusty sword object, and a place where we can get its property values – since they will all be exactly the same. That’s why we instantiate each type of object once, and put it in the static World class. When we want to get the details for a rusty sword, we can use the ID as a way to find it in the list.

      When we get to part of the program where we fight a monster, you’ll notice that we look up the monster object and make a copy (new instance) of it. That’s because we need to have an unique monster to fight. As we fight each instance of the monster, its hit points will decrease. Each instance of a monster will also have different loot items. We could remove the Monsters list from the World class, and create the monsters with an object factory, but that gets more advanced than I wanted to demonstrate at this point.

      For the locations, in the larger open-source game engine I’m working on, I use the 2D method. Each location has X and Y coordinates, and the surrounding locations are determined by looking for locations with nearby coordinates. I just used this method because it was the way I thought of when I was writing the code. Sometimes there are advantages to doing it one way, sometimes different ways are roughly equal (or have have different positive and negatives that are roughly equal).

  9. Edmund
    Edmund April 14, 2016

    Thanks, your explanation is very clear and helpful. I have learn a lot about C# and programming in general with this tutorial. Thank you again!

  10. Jami
    Jami April 17, 2016

    Hi Scott,

    Good Day! Love the tutorial. Could you help me out with my errors?

    In private static void PopulateItems()

    I got an error

    (constant) int World.ITEM_ID_RUSTY_SWORD =1

    Argument 1: cannot convert from ‘Engine.Weapon’ to ‘Engine.Item’

    Could you please help out?

    Thanks

    • Scott Lilly
      Scott Lilly April 17, 2016

      Hello Jami,

      It sounds like the Weapon class is not inheriting from the Item class. So, the program will not realize that a Weapon is a type of Item.

      Check lesson 07.1 through lesson 08.2, to make sure the classes use the correct inheritance.

      If that does not fix the problem, please let me know.

  11. Montell
    Montell April 17, 2016

    I have a question regarding the

    Monster rat = new Monster(MONSTER_ID_RAT, “Rat”, 5, 3, 10, 3, 3);
    rat.LootTable.Add(new LootItem(ItemByID(ITEM_ID_RAT_TAIL), 75, false));
    rat.LootTable.Add(new LootItem(ItemByID(ITEM_ID_PIECE_OF_FUR), 75, true));

    Monster snake = new Monster(MONSTER_ID_SNAKE, “Snake”, 5, 3, 10, 3, 3);
    snake.LootTable.Add(new LootItem(ItemByID(ITEM_ID_SNAKE_FANG), 75, false));
    snake.LootTable.Add(new LootItem(ItemByID(ITEM_ID_SNAKESKIN), 75, true));

    Monster giantSpider = new Monster(MONSTER_ID_GIANT_SPIDER, “Giant spider”, 20, 5, 40, 10, 10);
    giantSpider.LootTable.Add(new LootItem(ItemByID(ITEM_ID_SPIDER_FANG), 75, true));
    giantSpider.LootTable.Add(new LootItem(ItemByID(ITEM_ID_SPIDER_SILK), 25, false));

    The 5, 5, and 20 for the Monsters are still showing errors. Why would this happen?

    • Scott Lilly
      Scott Lilly April 17, 2016

      Make sure the Monster class matches the code from Lesson 10.1. The most common error, when creating a new object with parameters, is if the parameters in the constructor do not match the parameters used when you try to instantiate the objects.

      If that does not help you find the problem, please let me know and we can check for other possible problems.

      • Montell
        Montell April 17, 2016

        I just checked over my Monster class and I had MaximumDamage = maximumHitPoints and I have string namePlural instead of int maximumDamage. That got rid of all 4 of my errors. Thank you!

  12. Jacob
    Jacob May 2, 2016

    I just found this website and I’ve been doing all of the lessons to lesson 11 this whole night. This is awesome, thank you so much for creating this. I can imagine it took great time and effort to create.

    It’s very helpful to see how and when classes are created, inheritance is used, and so on, in a real project – instead of simply studying how they work. I plan to do all the lessons to get some feeling for programming a game, and then move on to Unity. Thanks again.

    • Scott Lilly
      Scott Lilly May 2, 2016

      You’re welcome. It does seem easier to learn, when you are using the knowledge to build something.

  13. Kdm2233
    Kdm2233 August 16, 2016

    Hi Scott,

    I’m loving this game tutorial so far.  Thanks for all the time and effort you’ve put forth into this.

    I’m hoping you are still checking out the comments for this as I’ve been unable to solve this question so far using the C# books I have and googling as well.

    What I’m struggling understanding is you create new list of type ‘Item’ on line 11:

    —  public static readonly List<Item> Items = new List<Item>();  —

    You then add a new object of type ‘Weapon’ to the list on line 54:

    —  Items.Add(new Weapon(ITEM_ID_RUSTY_SWORD, Rusty sword, Rusty swords, 0, 5));  —

    then further add a new object of type ‘HealingPotion’ to the list on line 60:

    —  Items.Add(new HealingPotion(ITEM_ID_HEALING_POTION, Healing potion, Healing potions, 5));  —

    My Questions are:

    How are we able to add objects to this list that are not of type ‘Item’?  While the ‘Weapon’ class inherits properties from the ‘Item’ class, as this is a list of type ‘Item’ I would think only ‘Item’ objects would be allowed (or type ‘Weapon’ without the extra properties not found in type ‘Item’)
    How are we able to add more than one type of object to this single list?  As this list is of type ‘Item’ I would think there would be a conflict having both objects of type ‘Weapon’ and type ‘HealingPotion’ as they are two distinct object types?

    I figure it has something to do with them both inheriting from the base ‘Item’ class.  However it seems as though this shouldn’t work as while they are inheriting properties from the same class, they are two different object types.

    Thanks for the help.

     

    • Scott Lilly
      Scott Lilly August 17, 2016

      Hello,

      You are thinking correctly. This works because the Weapon and HealingPotion classes inherit from the Item class.

      So, a Weapon object has two types/datatypes: Weapon and Item. It is a Weapon, and it is an Item. That means we can use a Rusty Sword (a Weapon object) any place we can use a Weapon object, PLUS any place we can use an Item object.

      We can add it to a list of Item objects because it is an Item. It’s a special type of Item, one that is also a Weapon. But, at its base, it is still an Item.

      Please tell me if you still have any questions about this.

      • Kdm2233
        Kdm2233 August 17, 2016

        Hi Scott,

        Thanks for the help! It’s making more sense and I think I get it. Also thanks to your help I was able to find a few more articles relating to derived classes being put in lists of base classes.

        I guess I was just thinking as it’s a list of type ‘Item’, that the extensions provided by a ‘Weapon’ object wouldn’t be allowed.

        i.e. the list was expecting Item(a,b,c) but weapons are (essentially) Item(a,b,c,d). So I figured the ‘d’ wouldn’t be allowed as the list was only expecting ‘a’,’b’,& ‘c’.

        • Scott Lilly
          Scott Lilly August 17, 2016

          You’re welcome.

          Something to be aware of is, when you get a weapon object by calling World.ItemByID(), the object will be returned as an Item – that’s the return datatype for ItemByID(). The Item object will need to be “cast” to a Weapon datatype, for you to be able to use the extra properties/functions that only exist in the Weapon sub-class (the “d”, using your example). You’ll see how that works in a future lesson, when you start to build the combat system.

  14. Gerrit
    Gerrit August 26, 2016

    Hello, great tutorial so far,
    understand way more in a few hours than my teacher teached me over a whole year..

    i received some Errors after copy your world.cs and dont know how to handle them.
    Following Errors:

    ‘LootItem’ does not contain a constructor that takes 3 arguments
    ‘QuestCompletionItem’ does not contain a constructor that takes 2 arguments
    Inconsistent accessibility: property type ‘List<InventoryItem>’ is less accessible than property ‘Player.Inventory’
    Inconsistent accessibility: property type ‘List<PlayerQuest>’ is less accessible than property ‘Player.Quest’

    Hope you can help me with Solutions,

    Best regards
    Gerrit

    • Scott Lilly
      Scott Lilly August 26, 2016

      Thank you.

      For the “Inconsistent accessibility” errors, that means the “scope” of the datatype (class, in this case) of the property, is less visible than the scope of the property. If a property is “public”, but the class of its datatype is not public, you would see this error.

      Double-check your classes with the classes in Lesson 10.1 – especially these classes: https://gist.github.com/ScottLilly/161a5812ae4843563d6b

      Please tell me if that does not fix the problem.

      • Gerrit
        Gerrit August 29, 2016

        Thanks! Exactly what i was looking for!
        Works again, time to move Forward (:

  15. Quan
    Quan August 30, 2016

    Hello AD,

    Please reveal the code directly on this site.

    I am working, I cannot access it.

    • Scott Lilly
      Scott Lilly August 31, 2016

      Hello,

      I added the code in an expandable section. To view it, click on the text “Click to view source code”.

      You can also try to download the source code for all the lessons here.

      Tomorrow, I will look at the other lessons. I believe there are more lessons where you need to download the source code. I will add the “expand” to those lessons.

      Please tell me if you cannot get the source code now.

  16. Dylan
    Dylan October 11, 2016

    Lesson 10 “I think Im getting this. Yeah! I know what I’m doing!

    Lesson 11 “Nope! Bed time. Thought I had it. Don’t.”

    • Scott Lilly
      Scott Lilly October 12, 2016

      If a good night of sleep didn’t help (it often does, for me), can you tell me what part of statics is not clear? Maybe if I explain it differently, that might help.

  17. DaveG
    DaveG November 17, 2016

    Hi Scott,

    Great tutorial series…

    I have found this lesson a step up in difficulty from all the others so far and have several questions…

    In the constructor:

    static World()

    Why is this not declared as private or public (which we did for constructors in other classes)?

    …and the IDs – like another question above I do not fully understand why they are required even after your reply to it.

    I understand you need to find a monster in the list but couldn’t that be done via its name rather than have an extra property of ID (and same with locations etc):

    e.g.

    private static void PopulateLocations()
    {
    Location alchemistsGarden = new Location( “Alchemist’s Garden”, “Many plants are growing here.”);
    alchemistsGarden.MonsterLivingHere = MonsterByName(“Rat”);
    }

    public static Monster MonsterByName(string name)
    {
    foreach (Monster monster in Monsters)
    {
    if (monster.Name == name)
    {
    return monster;
    }
    }

    return null;
    }

    Would the above work?

    Do you use the ID so that the variable name you declare for the ID e.g.

    public const int MONSTER_ID_RAT = 1;

    …will be picked up by intellisense (whereas “Rat” will not) and thus not mis-typed?

    • Scott Lilly
      Scott Lilly November 17, 2016

      Hi Dave,

      Normally, we use scope on a class constructor to permit a second class to instantiate an object of the first class – or use a limited scope, to prevent a second class from instantiating this class.

      However, a static class “constructor” is a special situation. We don’t need to declare a scope (public, private, etc.) because no other classes can ever instantiate the static class. The “constructor” is only ever run by the .NET runtime, automatically, the first time anything is accessed in the class. This “constructor” cannot be called. So, we don’t need to declare an additional scope here.

      You could use the monster name, and not have an ID property. However, it’s common to build classes like this with an ID value. We want a value that will never change, and there are several reasons a name value might change.

      If you decide to change the name of the “rat” monster to “Evil rat”, you won’t need to make any other changes – if your program uses an ID as the “key” value. You might decide to make a multi-lingual version of this game, using a “resource file” to set the monster name for different languages (English, Spanish, etc.). But, they can all use the same ID value – and the rest of your code won’t need to change.

      Or, if you built a database for a sales app. You might have a Customer table (to hold the customer name, address, etc.) and an Order table (to hold all the orders of your customers). If you use the customer name as the key, to relate between the two tables, and the customer changes their name, you would need to update the Order table with the new name. If you only had two tables in the database, this is easy. However, as your programs become more complex, it would be easy to miss something that needs to be updated.

      That is the main reason for using an ID, instead of the name – a consistent, unique identifying value. However, you are also right that, with the constant, we gain the use of IntelliSense. That’s another thing to help us as we write the program.

      • DaveG
        DaveG November 18, 2016

        Many thanks Scott, that was a great explanation and I can now see why the ID is so useful 🙂

  18. DaveG
    DaveG November 17, 2016

    Hi again Scott,

    Another question…

    I am still struggling to get my head around how some of the things work in C# (and programming in general).

    i.e. when you instatiate a new Monster:

    Monster rat = new Monster(1, “Rat”, 5, 3, 10, 3, 3);

    Has this created a new Monster object stored in a variable called “rat”?

    …and if so (and was in scope), when creating a Location, why then can you not say:

    Location alchemistsGarden = new Location(“Alchemist’s Garden”, “Many plants are growing here.”);
    alchemistsGarden.MonsterLivingHere = rat;

    My mind is a bit blown trying to understand how it all works.

    …I have already gone through all the beginner C# lessons on Microsoft Virtual Academy before starting your series and while I can follow why something works and why the code you have used works, I cannot get my head around WHY something is done a certain way. …so I would be lost if trying to write my own application like SuperAdventure without just copying! I like to understand why I am doing something and a few alternatives that also work to really understand! 🙂

    I guess this is probably rather difficult for you to answer (i.e. help me understand) by messaging, but if you feel you can provide further insight it would be greatly appreciated 🙂

     

    Many thanks,

    Dave

    • Scott Lilly
      Scott Lilly November 17, 2016

      You could do that, if the “rat” variable was in scope for the function where you instantiate the “alchemistsGarden” location. But then, we would ultimately end up instantiating all our objects (items, monsters, quests, locations) inside one function – because the location object needs the monster object, which needs the item objects.

      You normally don’t want to have one big function that does several different tasks. That makes it more difficult to track down problems. So, we broke this down into a few functions.

      There isn’t an exact science to when you should break a function into a smaller function. There are guidelines, such as “move sections of code that are exact duplicates into their own function”, or “a function should never be longer than 25 lines, so you can see the complete function on the screen (without scrolling)”. There are also tools that give you metrics about your code, such as “cyclomatic complexity”. But, there aren’t any definitive answers.

      I’m working on new guides for refactoring tools, techniques, and code quality tools. These might give you a better idea of “why” you might perform a task one way, and not another.

      If you have more questions, please let me know.

      • DaveG
        DaveG November 18, 2016

        Hi Scott,
        Many thanks. That helps a bit but there are still some things I don’t understand.
        I am trying different things to try and get my head round how it all works!

        If I were to instantiate a Monster at the class level as below, then in the method I get an error:
        An object reference is required for the non-static field, method, or property ‘Engine.World.rat’.
        e.g.

        public static class World
        {
        Monster rat = new Monster(1, “Rat”, 5, 3, 10, 3, 3);

        static World()
        {
        PopulateLocations();
        }

        private static void PopulateLocations()
        {
        Location alchemistsGarden = new Location(5, “Alchemist’s Garden”, “Many plants are growing here.”);
        alchemistsGarden.MonsterLivingHere = rat;
        }

        }

        Whereas if the instantiation of the Monster in within the PopulateLocations() method, there are no errors. I can see why this 2nd example works but not why the first example does not!
        i.e. if it is a scope issue, since the Monster instantiation is at class level, then wouldn’t it be available inside any method?
        Furthermore, if I add “static” in front of the Monster instantiation the error disappears, but this does not make sense to me as it is a new object and thus not “static”.

        …and trying something else:
        If all the code were just within the class rather than in methods, I get further errors (4 errors), e.g.

        public static class World
        {
        Monster rat = new Monster(1, “Rat”, 5, 3, 10, 3, 3);
        Location alchemistsGarden = new Location(5, “Alchemist’s Garden”, “Many plants are growing here.”);
        alchemistsGarden.MonsterLivingHere = rat;
        }

        …but I don’t understand why the same code works within a method.

        In the MS tutorials I went through we did lots of code outside methods and putting it in a method was just a way to re use code.

        I am clearly not grasping something fundamental, so if you can help me understand, that would be greatly appreciated.

        Cheers,
        Dave

        • DaveG
          DaveG November 18, 2016

          Sorry, I just realised that in the MS tutorials we were always working within the default static void Main method, so of course the code has to be in a method.
          …so please ignore the 2nd part of my previous message, i.e. the part after “…and trying something else:”.

          However, the first few questions still stand.
          i.e. In the MS tutorials we declared class level variables outside of any methods.
          Must the instantiation of objects always be within a method and hence the error I was getting?

          Cheers,
          Dave

          • Scott Lilly
            Scott Lilly November 20, 2016

            Static resources (variables, methods, etc.) are available without instantiating an object. Non-static resources are only available when you instantiate an object that contains them.

            In your first example, since we haven’t instantiated a World object (which, we can’t ever do, because the class is static), the static functions can only use other static resources (variables, functions), and any objects it creates inside the function (these are effectively also static, because they are instantiated inside the static function). That is why the class-level “rat” variable is not available inside the static functions, unless you declare it static, or instantiate it inside the static function.

            Inside the PopulateItems function, we instantiate items, and add them to the static _items variable. Because _items is static, the objects inside it are available to other the static functions.

            By the way, the constants are also available to the static functions, because they are available without instantiating an object.

            Does that make it clearer?

          • DaveG
            DaveG November 21, 2016

            Hi Scott,
            There was no link to reply to your reply below, so replying here! 🙂
            …also the “visual” reply tab is not working when replying so using the “text” only tab.
            I tried on different browsers but same on all. :Z

            …anyway just wanted to say thanks – yes your explanation has made a few things much clearer for me.

            Really enjoying the tutorials 🙂

          • Scott Lilly
            Scott Lilly November 21, 2016

            Great! Let me know if you have any other questions.

  19. Trey
    Trey November 19, 2016

    Hi Scott!
    This is a great tutorial series and it has really helped me learn different ways to approach a problem. However, one issue i am having is that the static World class always seems to break when I run the World.ItemByID(ITEM_ID_RUSTY_SWORD,1) in the start of the game. The error i get is “The type initializer for ‘RPG_Engine.World’ threw an exception.” and the inner exception is “Object reference not set to instance of an object.”

    I looked back through the code and everything in the chapter to see if i missed a step and I couldn’t find anything. I don’t see why the object would NEED to be instantiated because it is a static class. Any help here would be great!

Leave a Reply

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