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.
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
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
Hey Scott.
What is your oppinion about making 4 static classes (monsters, items, quests and locations) instead of one world class?
For bigger games it looks easyer to read. (lets pretend databases dont exsist) 🙂
You could do that, but I like the idea of having a single static class that holds the complete “world”.
To make World.cs smaller, I might create four factory classes (MonsterFactory, ItemFactory, etc.). Those factory classes would each have one static function – like “GetAllMonsters()”. That function would create a list of all monster objects and return that list. The World class would call those functions, instead of calling the current “Populate” functions inside World.cs, and use the returned lists to populate the static list in World.cs.
But, this is one of those things that would mostly depend on the programmer’s personal preference.
I see.
Thnx
I appreciate all these lessons, your updates, and replies to all the questions. I can tell you are very passionate and dedicated about teaching and programming seeing that you STILL answer questions and reply to them. It’s an amazing feat to say the least really. But I had a general question about this lesson particularly. The code that you had us copy from Github is huge and feels like a giant skip between the lessons. I know it cost a heck of a lot of lessons to cover all those but why did you decide that this is the way to continue on with the lessons?
Also, I’m still very confused about the constructor class and I’ve been checking lots of places to get an answer but they all sound similar to me and are still very difficult for me to grasp. Could you kindly let me understand what constructor is like I’m 5 years old in one or two sentences? Doesn’t need to be precise or professional I just really want to get a feel of understanding the constructor function.
Thank you!
This isn’t one or two sentences, but, I hope it will help you.
Remember one of the earlier lessons about classes and objects. A class is a definition of what is in an object. So, a class is like a blank form you need to fill out to open a bank account. You don’t actually have the bank account until you fill out the form and the bank creates the account – like creating/instantiating an object from a class.
A constructor is just a function used to create/instantiate an object from a class. So, when you want to create a Player object (for example), you call the Player class constructor. If the Player class does not have a constructor function, you instantiate it by saying “new Player();”. This is called “using the default constructor”, because it is the method to use for all non-static classes that do not have a constructor function.
Sometimes the programmer wants to control creating instances of a class – usually because they want to ensure the object has some values filled in when it is constructed. So, the programmer would create a constructor function for the class. When you want to instantiate a Player object, you need to pass in values for the parameters that are in the constructor. For example, if the programmer wants all Player objects to have have a name, they could create a constructor function like this:
public Player(string name)
{
Name = name; // Set the "Name" property to the value passed in as the "name" parameter
}
It is possible for a class to have more than one constructor function, if the programmer wants to provide different ways to instantiate an object for the class.
Static classes are a little different. They cannot be instantiated – never. They are classes that always exist in the project. You can use their static functions and static properties without ever instantiating an object.
For static classes, you can define a function that is run the first time any of its functions or properties is used. In the World class, this is the function that starts with “static World()”. This function runs the first time any other part of the program tries to use any of the static functions, like ItemByID, MonsterByID. This function populates the static lists, which are used by the static functions. It only needs to run the first time because the static lists remain populated, for the next time one of the static functions is called.
Does that make sense?
I have been struggling to learn the exact definition of static classes and you just explained it perfectly and easily. Hands down the best tutorial for beginners.
I am transitioning from VBA to C#, and this tutorial has been great so far in helping me understand the basics of C# while also learning how to use Visual Studio.
I am curious, in this lesson, would it be possible to replace the “public static Thing ThingByID(int id)” with something like “public static ??? ByID(int id, ??? classType)” with a switch that would determine the appropriate list to look in based on the classType passed?
Maybe the complexity of doing something like that would outweigh the benefits of removing the duplication found in those methods.
Hi Byron,
You could do that with generics. You would probably want to implement the “repository design pattern”. You can see a sample way to do that here: https://cpratt.co/truly-generic-repository/.
This solution usually involves more complexity than I like. However, there are several ORMs (Object-relational mapping) libraries that do this when they hide the database access. You setup a data repository, make calls to it, and it manages all the read/write/update/delete commands for the database. StackOverflow shows a very basic implementation here: https://stackoverflow.com/questions/6543436/nhibernate-repository.
in the World.cs lines 95 and 105 the ItemByID(Item_ID_ItemName) has the red squiggly line and says cannot implicitly convert type ‘Engine.Item’ to ‘int’
I’m not sure where I messed up.
Hi Anthony,
Double-check your classes with the ones in Lesson 10.1. I think the problem might be in the Quest class. It sounds like you might have:
public int RewardItem { get; set; }
but, the datatype for the RewardItem property needs to be “Item”, not “int”. So, the line would look like:
public Item RewardItem { get; set; }
Let me know if that does not fix the errors.
Heey Scott,
First of all nice course!
I have the same problem like Anthony Gibson.
But when i change the public int RewardItem { get; set; }
to public Item RewardItem { get; set; }
It dont solve the problem.
I also checked the other classes but cannot find the problem.
Thanks Nicky!
I saw your other comment on Lesson 16.1 about the errors you’re seeing. Can you upload your solution (including the directories under it, and all the files in those directories) to GitHub or Dropbox, so I can look at it?
Hi Scott,
Love the guide, it is such a great way to learn core coding concepts.
One question I have though – what is the purpose of ItemByID, MonsterByID etc.
I can’t wrap my head around why we need them.
Thank you
Specifically, why do we use ItemByID when instantiating the Monster loot tables?
Eg.
rat.LootTable.Add(new LootItem(ItemByID(ITEM_ID_RAT_TAIL), 75, false));
Why not just
rat.LootTable.Add(new LootItem(ITEM_ID_RAT_TAIL), 75, false));
Hi Andrew,
The way I created the LootItem class, we are storing an “Item” object in the “Details” property. This way, when the player defeats a monster, we can immediately give the player the Item object. We can also see the Name and Price of the Item object – because it is an instantiated object, with all the object’s properties populated.
We could have written it so we only store the Item’s ID value in LootItem. Then, when the player defeats a monster, we would instantiate the Item (probably by calling ItemByID) and give it to the player. If we only stored the ID, and we ever wanted to add logic for the player to “pickpocket” a monster, we would not be able to see the item’s Name – because we only have the ID stored. So, we would need to write another function to lookup an item’s Name by its ID.
The program can be made to work either way. I just prefer to store instantiated objects in properties, so we have all its other properties available all the time.
Let me know if that wasn’t clear, or you still have questions.
Hi Scott,
Awesome course. Made me better understand many concepts simply because it speaks in language and references I understand – games.
I also have troubles understanding why would we need
rat.LootTable.Add(new LootItem(ItemByID(ITEM_ID_RAT_TAIL), 75, false));
instead of
rat.LootTable.Add(new LootItem(ITEM_ID_RAT_TAIL), 75, false));
based on your response I now think that I know why, but I’m not exactly certain that my understanding is correct.
The first example (the one used in the code) allows us to pass all properties of ITEM_ID_RAT_TAIL like it’s ID, name and name plural, because it takes ITEM_ID_RAT_TAIL from list of items.
If we’d use second example we would only be passing the ITEM_ID_RAT_TAIL id without name and name plural, because it doesn’t search for ITEM_ID_RAT_TAIL from the list but just takes the public const int ITEM_ID_RAT_TAIL, correct?
I hope this is understandable even if it’s not correct 😀
Thank you for your effort. You should consider publishing these tutorials in a book. You’ve got a talent for teaching. If there’s a way to donate you somehow, please let me know. I’d like to do that.
Thank you, Rafal
You are very close. The slight difference is that in the first one, we aren’t just passing in all the values (ID, Name, and NamePlural). We’re passing in a complete “Item” object, which has those values in its properties: ID, Name, and NamePlural.
One of the students created a PDF of the lessons. You can get download it here: Learn C# by Building a Simple RPG.
I don’t have a way to accept donations, but I do plan to create paid courses. I’m currently working on on for using C# with SQL Server. You can follow my Twitter (https://twitter.com/scottlilly). When I do release something, I’ll announce it there.
Hi Scott
Hope you are well. Fantastic Course, has really helped me understand the concepts of c# in a fun interactive way. I run the program after every code change which gives me exposure to error codes and helps me identify where i have gone wrong. I copied the code from github for the world class and think i may have spotted a typo on line 93 and line 103. The new QuestCompletionItem is missing a s
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));
Hi Dee,
I’m glad to hear you enjoyed the lessons.
The “new QuestCompletionItem(ItemByID(ITEM_ID_RAT_TAIL), 3)” (without the “s”) is correct. In that part of the code, we are instantiating a new QuestCompletionItem object (which needs to match the name of the QuestionCompletionItem class. Then, we add that QuestionCompletionItem into the QuestionCompletionItems property. Notice that the property name has the “s”, but its datatype is a collection of QuestCompletionItem (without the “s”) objects.
Does that make sense?
Hi Scott
Thanks for your swift response. I have just realised it was an error by me. I named the class QuestionCompletionItems (with the ‘s’) so i was getting the red lines under the instantiating of the new object. There shouldnt be an issue me just renaming the class and all the elements the old name referenced?
You’re welcome. You should be able to rename the class and references to it. If you rename the file, make sure you also change the line inside the class to “public class QuestCompletionItem”. Renaming the file doesn’t automatically change this line inside the file.
Well.. This went from good to information overload levels of confusion. Guess I’ll just start over and work my way back up.
It took me a little time to understand the idea of static classes. Now, I like to think of them like a utility – electricity, water, the internet, etc. Static classes (and static functions) are like services that are available to all the other parts of the program, without needing to create an “instance” of them.
Hey, I was just wondering if certain parts of the code (Specifically the naming of other classes) are supposed to be underlined red, it has the error ‘Class Name’
Until a reply comes through im just going to continue through the tutorial but i thought i would post this just in case.
Thank You!
There shouldn’t be anything underlined in red – except when you’re in the middle of a lesson, making changes.
Can you upload your solution (including the directories under it, and all the files in those directories) to GitHub or Dropbox, so I can look at it?
Hi Scott!
If you’re still responding to quarrels in this section then I need help with the following case:
Error CS7036 which says that “There is no argument given that corresponds to the required formal parameter ‘rewardItem’ of Quest.Quest(int, string, string, int, int, Item)”
I tried solving it but it eventually led me to a dead end.
Please help!
Can you upload your solution (including the directories under it, and all the files in those directories) to GitHub or Dropbox, so I can look at it?
Hi, Im learning a lot thanks to you, You are amazing, but I have a little question. The last part of the code, the “foreach” stuff, by the way I understand they arent necessary (its for leting it more cleaner) but how it wold be if I dont use this part how it makes the code more cleaner? (Sorry my bad english, Im brazilian and thanks again for the lessons).
Hello,
Because we have those functions, in the other classes we can get an object in one line, by writing code like “Quest questToGet = World.QuestByID(7);” (for example). If we did not have those functions, instead of that one line, we would need to write the loop in all the other classes (maybe 10 lines of code each time).
Does that answer the question?
Yes I really appreciate that you take your time to answear me. I get a book about C# at my college library to learn a bit more now and with your explanation I na really seeing how the code work, thanks again. One more thing if (later) I want to add some images and sound to my game, its possible? or I have to look something else?
We add some images to the game in ln lessons 26.1 and 26.2. I don’t have any lessons for adding sound to the game.
Hello.
Thank you very much for making this tutorial and its been great so far. Now this part of the tutorial was the hardest by far. There are a lot of things that the code has and I don’t really get why we use them
In the constructor we just wrote 4 methods but after that we closed with “}” and then after that we use private static void. What is a void and why do we use it?
After that there is a ton of code that I really didn’t get anything, and I don’t think that it was covered on the previous lessons.
Hello,
Static classes work differently from other classes. This is something that took me a while to understand.
You don’t create an “instance” of a static class, like we do with our other classes, when we say “Player player = new Player();”. A static class is just a container for code (properties and functions) that are available everywhere in the program. We can use this World class’ methods from any place in the program.
But, we do want to have some values available (Items, Monsters, etc.) when we call the World functions (like ItemByID, MonsterByID, etc.). We could do that by having the function check if the list it needs is empty, and populating it if it needed to. But, C# has a way to run some code the first time any function is used in a static class. You just need to create a function that says “static World()” (for the World class). This function will be run the first time any function or property is used in the World class. And in this function, we populate the lists used by the other functions.
So, the first time the program calls a function in the World class (let’s say it calls the LocationByID function first), the program will see there is a “static World()” function to run, and it will run it. That “static World()” function runs four other functions that populate the Items, Monsters, Quests, and Locations lists. When those functions are done running, the prorgam will go back to the LocationByID function, which tries to find a Location in the Locations list variable. Because that variable was just populated, it can find the Location.
If you still have trouble understanding it, look at this guide to using the debugger in Visual Studio. Set breakpoints at the first line of the “static World()” function – “PopulateItems();”. Then set breakpoints at the first lines of the ItemByID(), MonsterByID(), QuestByID(), and LocationByID() functions. Run the program and use F11 when you hit the first breakpoint. That will show you the order the code is run in, and might help you understand it a little better.
Hey Scott,
Why did you use ItembyID, MonsterbyID, etc. Couldn’t you have just used “Item_id_snake_fang” instead of adding ItembyID at the start.
Also, I don’t understand the foreach loops for ItembyId, MonsterbyID, so can you explain them line by line of code?
The World class is a static class. So, it always exists and can be called by any other part of the program.
So, in the “constructor” on lines 44-50, we call functions that populate lists of things in the world – locations, monsters, quests, and items.
When another part of the program wants to know about something in the World, we have these functions that look in the lists, find the object the other function is looking for (by matching the ID value), and returns that to the function.
For example, let’s say the player is at location #3, and wants to to move to location #2. The program needs to display the location #2’s information in the UI. So, it will call the LocationByID function and pass in the number 2 (the ID of the location it wants to display). The LocationByID function starts looking through the list of locations on line 216 – the “foreach” function looks at the “Locations” list. It grabs the first location object from the Locations list and stores it in the “location” variable. Then, on line 218, it checks if the location’s ID matches the ID value it’s looking for (2). If it matches, the function found the Location object it was looking for and returns it from the function, for the calling code to use. If the ID values do not match, the function will get the next Location object from the Locations list, put it into the “location” variable, and check if its ID matches.
The function will do that until it finds a Location with a matching ID, or until there are no more Location object in the Locations list. If it gets to the end of the list, the function goes to line 224 and returns “null” – basically saying none of the Location objects in the Locations list has a matching ID.
The other functions work the same way, but for the different game objects (monster, items, and quests). Does that makes sense?
What are the ToNorth, ToSouth, etc. functions in the code, can you explain?
Thanks
Those are properties of the Location class. They hold another Location object, or are empty (null).
So, in this program, after we create the Location objects, we “connect” them by setting these properties. For example, we create the “home” variable that is the Location object for the player’s home. We want the “townSquare” Location to be North of the player’s home, so we have “home.LocationToNorth = townSquare;”. We also go to “twonSquare” variable and set “townSquare.LocationToSouth = home;”. That way, when we look at the “townSquare” object, we know if we go South, the player should be at the “home” Location.
Let me know if that wasn’t clear.
Hello Scott,
Thank you for this guide, it’s helped a lot. For the ItemByID, MonsterByID, etc., i noticed that when you write the method, you write “public static Item ItemByID(int id).” the first “Item” written looks like it is calling to the Item.cs file, but I can’t seem to tell why? Wouldn’t it just search in the list that is contained in the World class? If I have a concept twisted around, please let me know.
Appreciated,
Andrew.
Hi Andrew,
The “Item” on the line is the function’s return datatype – the type of object it will return. And, for this function, it is returning an object that is an instance of our Item.cs class.
By the way, if a function doesn’t return anything, the datatype would be replaced with “void”.
Does that makes sense?
Hey Scott, how are you? I noticed you’ve assigned names to each element of the Locations, Quests and, Monsters Lists, but you didn’t do it for Items. Each element in Items has no name. Is there a reason for that?
One other question, place focus on right side of the = sign on following line of code:
spiderField.MonsterLivingHere = MonsterByID(MONSTER_ID_GIANT_SPIDER);
Why would you resort to using methods like MonsterByID, LocationByID, etc. as seen throughout this file? Wouldn’t it be possible to write:
spiderField.MonsterLivingHere = giantSpider;
?
Hi Joana,
For the lines like:
spiderField.MonsterLivingHere = MonsterByID(MONSTER_ID_GIANT_SPIDER);
We are taking whatever we get from the MonsterByID function and immediately storing it in the spiderField.MonsterLivingHere property.
In the functions like PopulateMonsters, PopulateQuests, and PopulateLocations, we store the objects in a variable so we can do some more things to those objects before we add them to our lists. For example:
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));
Monsters.Add(rat);
We instantiate (create) a new monster object and store it in the “rat” variable. Then we add items to the rat’s LootTable. When we are finished modifying the “rat” variable, we add it to the list of Monsters.
Those variables are places to temporarily store the object while we work with it. When we set a Location’s MonsterLivingHere, we don’t need to do any work to the Monster object, so we don’t need a variable. We could put the Monster into a variable, the set the MonsterLivingHere property to that variable. That would also work. But, it’s usually a good idea to keep your functions small and not have unnecessary lines or variables.
Let me know if that was not clear, and if you have any other questions.
Hello Scott,
I am having errors in this code.
Error CS7036
There is no argument given that corresponds to the required parameter ‘rewarditem’ of ‘Quest.Quest
(int, string, string, int, int, Item)’
Hi Darsh,
Can you check the Quest class, and make sure it looks like the code in step 4 of https://scottlilly.com/learn-c-by-building-a-simple-rpg-index/lesson-10-1-creating-collections-of-objects/?
If that doesn’t fix the problem, 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?
If you haven’t used GitHub before, here is some information (and a video) on how to upload your solution to GitHub and share it with me. https://codingwithscott.com/how-to-connect-visual-studio-community-edition-2022-to-github/
I saw your screenshots in the gist, but need to see the complete files for the classes.