Lesson Objectives
At the end of this lesson, you will know…
- How to store groups of objects in one property or variable, and read them later.
How to handle groups of objects
You may have noticed that there are properties missing from some of our classes. When outlining the program, we mentioned that the player will be able to have items in their inventory. The player will have quests. Quests have items that need to be turned in, to complete them. Monsters have items that can be looted from them.
We have a Quest class, and an Item class, but we don’t have any place to store them in the Player or Monster classes. We also need a way to store more than one item, since a Player will probably have more than one item in their inventory and could have more than one quest.
We do this with lists, or collections.
But before we create these list properties, we’re going to need a place to store some additional information for the items and quests.
For example, with the items in the Player’s inventory, we need to store the quantity of each items they have. It’s similar to what we’ll need to do with the Quest class – store the quantity of each item that is needed to complete the quest.
For the player’s quests, we also need a place to record whether or not the player has completed the quest.
In the Monster class, we want a list of the items that can be “looted” from the monster. This is called the “loot table”. We need to store the item, the percentage of times it is “dropped”, and if the item is a default loot item – in case the random number generator says that none of the loot items were dropped.
There are different ways to do this, but I’m going to show you a way that uses some additional classes.
So, let’s create these new classes, then add them as “list” properties to the existing Player, Monster, and Quest classes.
NOTE: In the video, the Quest class is missing the RewardItem property from the last lesson (shown at 6:52). You should keep that property in your class.
Step 1: Start Visual Studio Express 2013 for Desktop, and open the solution.
Step 2: Create these four new classes by right-clicking on the Engine project and selecting “Add”, then “Class…”. The four classes to create are named:
InventoryItem.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Engine { public class InventoryItem { public Item Details { get; set; } public int Quantity { get; set; } public InventoryItem(Item details, int quantity) { Details = details; Quantity = quantity; } } }
LootItem.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Engine { public class LootItem { public Item Details { get; set; } public int DropPercentage { get; set; } public bool IsDefaultItem { get; set; } public LootItem(Item details, int dropPercentage, bool isDefaultItem) { Details = details; DropPercentage = dropPercentage; IsDefaultItem = isDefaultItem; } } }
PlayerQuest.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Engine { public class PlayerQuest { public Quest Details { get; set; } public bool IsCompleted { get; set; } public PlayerQuest(Quest details) { Details = details; IsCompleted = false; } } }
QuestCompletionItem.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Engine { public class QuestCompletionItem { public Item Details { get; set; } public int Quantity { get; set; } public QuestCompletionItem(Item details, int quantity) { Details = details; Quantity = quantity; } } }
NOTE: We use a new datatype for two of these classes – “bool”. This stands for “Boolean” and is used to store a value of “true” or “false”. For example, in the PlayerQuest class, the “IsCompleted” property will store a value of “false”, until the player finishes the quest. Then we’ll store “true” in it.
Step 3: Now that we have the new classes, we can create properties for them in the Player, Quest, and Monster classes.
Edit the Player class, by double-clicking on it in the Engine project.
Look at line two of the Player class. In order to use lists, one variable or property to hold a collection of objects that are the same class, we need to have this line included. This is where your program will find everything it needs, in order to work with collections.
Add these two properties to the Player class:
public List<InventoryItem> Inventory { get; set; } public List<PlayerQuest> Quests { get; set; }
Now you have two new properties that can hold lists containing InventoryItem and PlayerQuest objects.
Then, in the constructor code, add these new lines:
Inventory = new List<InventoryItem>(); Quests = new List<PlayerQuest>();
These two lines set the value of the new properties to empty lists. If we didn’t do this, the value for those properties would be “null” – nothing. By setting them to an empty list, we can add items to them later, because you can add objects to an empty list, but you can’t add object to nothing (null).
Player.cs (complete, with changes)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Engine { public class Player : LivingCreature { public int Gold { get; set; } public int ExperiencePoints { get; set; } public int Level { get; set; } public List<InventoryItem> Inventory { get; set; } public List<PlayerQuest> Quests { get; set; } public Player(int currentHitPoints, int maximumHitPoints, int gold, int experiencePoints, int level) : base(currentHitPoints, maximumHitPoints) { Gold = gold; ExperiencePoints = experiencePoints; Level = level; Inventory = new List<InventoryItem>(); Quests = new List<PlayerQuest>(); } } }
Step 4: Edit the Quest class. Add this new property:
public List<QuestCompletionItem> QuestCompletionItems { get; set; }
In the constructor, add this new line, so the QuestCompletionItems list will be ready to have objects added to it:
QuestCompletionItems = new List<QuestCompletionItem>();
Quest.cs (complete, with changes)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Engine { public class Quest { public int ID { get; set; } public string Name { get; set; } public string Description { get; set; } public int RewardExperiencePoints { get; set; } public int RewardGold { get; set; } public List<QuestCompletionItem> QuestCompletionItems { get; set; } public Item RewardItem { get; set; } public Quest(int id, string name, string description, int rewardExperiencePoints, int rewardGold) { ID = id; Name = name; Description = description; RewardExperiencePoints = rewardExperiencePoints; RewardGold = rewardGold; QuestCompletionItems = new List<QuestCompletionItem>(); } } }
Step 5: Edit the Monster class, by adding this property:
public List<LootItem> LootTable { get; set; }
And add this to the constructor, so the new property isn’t “null”:
LootTable = new List<LootItem>();
NOTE 1: We didn’t need to set the string, integer, and Boolean properties to a default value because those datatypes have a built-in default value. However, Lists are null (non-existent), until you set them to an empty list (a new List object, with no values in it yet).
NOTE 2: We didn’t create new parameters in the constructor to pass in values for these new properties. We could have, but we are going to populate them a little differently – a way that I think is a little easier for someone new to programming.
Monster.cs (complete, with changes)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Engine { public class Monster : LivingCreature { public int ID { get; set; } public string Name { get; set; } public int MaximumDamage { get; set; } public int RewardExperiencePoints { get; set; } public int RewardGold { get; set; } public List<LootItem> LootTable { get; set; } public Monster(int id, string name, int maximumDamage, int rewardExperiencePoints, int rewardGold, int currentHitPoints, int maximumHitPoints) : base(currentHitPoints, maximumHitPoints) { ID = id; Name = name; MaximumDamage = maximumDamage; RewardExperiencePoints = rewardExperiencePoints; RewardGold = rewardGold; LootTable = new List<LootItem>(); } } }
Summary
Almost every program I’ve written has included some type of list of collection. In the real world, we usually work with several items (e.g., display all the deposits into, and payments from, a bank account).
We’ll show how to add to, and work with, the objects in these lists very soon.
Source code for this lesson
Source code on GitHub (new classes)
Source code on GitHub (updated classes)
Source code on Dropbox (new classes)
Source code on Dropbox (updated classes)
Next lesson: Lesson 11.1 – Using a static class
Previous lesson: Lesson 09.1 – Using your classes as datatypes
All lessons: Learn C# by Building a Simple RPG Index
Hello
I’ve started reading your tutorial for a while already and there is only 1 thing i’d have to say with all theses classes appearing, folders are a great thing to organize the project lol.
Thanks for this awesome tutorial. It have been much easyier to understand how those things work with the details your are giving to us.
Peace and nice work!
Hello (and thank you),
Yes, organizing classes is extremely important. Some of the large programs I’ve worked on have over 100 projects, thousands of classes, and over a million lines of source code.
Honestly i’ve been saying that mainly because i didn’t see you talking about it even though there is quite a few classes already.
Well, i guess it’s a bit of my own fault for not exploring Visual Studio a bit more too lol.
Because this is a small program, that was originally only going to be the code from the first 16 lessons, I didn’t create folders for the classes. For the WPF version, I created the folders that I would normally use.
Hey, just got upto this part of the tutorial but now starting to get errors that I can’t seem to see the problem to. Things Like
Error CS0051 Inconsistent accessibility: parameter type ‘Item’ is less accessible than method ‘InventoryItem.InventoryItem(Item, int)’ Engine
and that’s for most of the new classes just made.
Hi Dylan,
Check your classes and properties, to ensure they have “public”. From that message, I’m guessing that your Item class says “class Item”, instead of “public class Item”.
Please let me know if that does not fix the errors.
Thanks for your great lessons.
Could you tell me how I can get weapon MaximumDamage inside that loop?
foreach (InventoryItem inventoryItem in _player.Inventory)
{
if (inventoryItem.Details is Weapon)
{
// Weapon MaximumDamage?
}
}
You’re welcome.
In the foreach loop, the datatype of the “inventoryItem” variable is declared as “InventoryItem”. So, it is treated as an InventoryItem object – which does not have MinimumDamage or MaximumDamage properties. However, for InventoryItems that are also Weapons, we can “cast” it to Weapon – which has those properties.
You could do it one of these two ways:
Weapon itemCastAsWeapon = inventoryItem as Weapon;
int maximumDamage = itemCastAsWeapon.MaximumDamage;
or:
int maximumDamage = (inventoryItem as Weapon).MaximumDamage;
Another way to cast objects is
Weapon itemCastAsWeapon = (Weapon)inventoryItem;
However, with that technique, if the cast fails (when the object is not also a Weapon object), you get an error. When you use “inventoryItem as Weapon”, if inventoryItem is not also a Weapon, it will result in a “null” – and it will not cause an error on the line that performs the cast.
Let me know if that doesn’t work, or if you have any questions.
Hello, im not sure if your still active on this lesson but im confuded on what you mean by
NOTE: In the video, the Quest class is missing the RewardItem property from the last lesson (shown at 6:52). You should keep that property in your class.
Hello Joe,
In the source code on this page, the Quest class has a RewardItem property on line 17. If you watch the video, that line is missing from the Quest class. Some people noticed the property was missing in the video, and wondered if the video was correct or the source code was correct – whether or not Quest needs the RewardItem property. It does need it. So, you need to use the Quest.cs code from this page (not what you see in the video).
Is that clear?
Hey there I would just like to thank you for this awesome guide! I’m so happy I found this! Hope you’re having a great day, wish you all the best! – Skafti from Iceland
You’re welcome, Skafti! I’m happy to hear you enjoy the guide.
Hello Scott(Mr Lilly), thanks for this tutorial. Your said on this chapter some that “There are different ways to do this,…” meaning there could be other ways to store information about Player Quest for instance or Loot Item. What could be those other ways to do that, because in OOP, they say “nouns” (like you pointed out) and “nouns-phrase” become classes. If it is easy to identify “LootItem” from the scenario, but not the same to identify other “nouns-phrases” like Player Quest, what could be the other ways, why not just create collection/list of Quests in the Player Class and so and so on. Thank for your answer
Hello Kevin,
This is one of the parts of programming that requires “abstract thinking”. We start with the simple classes that relate to the nouns. But, many programs will require other classes that are not so simple.
A Quest object (for example) will hold the information about a quest – its name, what must be done to complete the quest, what the rewards are, etc. But, the program also needs to track the “state” of a Quest object (has the Player completed the Quest?) For a simple game, with one Player, we could just add a Completed property to the Quest class, and the Player could have a property that contains Quest objects, instead of PlayerQuest objects. When the Player receives a Quest, we instantiate the Quest object and give it to the Player.
But, if we were writing a multi-player game, it might be more complex. In the game world, each quest would only have one Quest object. When a Player receives a Quest (let’s use the “kill snakes” quest for this example), each Player would only have a reference to that one “kill snakes” quest object. Then, if we needed to change something about the Quest object (for example, the reward), we would only need to change the one “kill snakes” Quest object that every Player has a reference to (instead of looking at each Player, checking if they have the Quest, and updating their Quest object’s reward if they have not completed the Quest). This is what we are doing in our game. Our World have one instance of each Quest object, and all the Players (in our game, it is only the one Player) have a PlayerQuest object that has a reference to the World’s single “kill snakes” Quest object.
Does that help answer your question?
Is there a reason why we’re using Lists instead of other types that can store multiple things like arrays or dictionary(not sure if C# has this)?
I generally use Lists so I can easily use LINQ. To me, LINQ lets you write code that sounds similar to normal language (for example, if I was working with a list named “items”, I could write “items.Where(item => item.Value > 10).OrderBy(item => item.Name)”
Dictionaries are good when I have objects with a unique value that I can use for the “key”. ANd I do use them in other programs, But, because this is a beginner’s guide, I wanted to keep it simple and just use one type of collection class.
So i’ve been following along this far and i may have missed it but why are we doing this?
public LootItem(Item details, int dropPercentage, bool isDefaultItem)
{
Details = details;
DropPercentage = dropPercentage;
IsDefaultItem = isDefaultItem;
}
Why name it one thing then go and name it the same thing with lowercase?
Hi Caleb,
I’m sorry it took so long to respond. C# is case-sensitive. So, “details” is different from “Details”. In this case, “details” is the name we use for the parameter – the temporary value being passed into the LootItem constructor. “Details” is the name of the property – the permanent place where we hold the value in the object.
Most C# programmers follow this pattern. Constructor parameters are the same name as the property they are put into, except the parameter is camel-cased (starts with a lower-case letter) and the property is Pascal-cased (starts with an upper-case letter). https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/capitalization-conventions#capitalizing-compound-words-and-common-terms
Hi there, I have just began following this series – great tutorial so far.
I was wondering, how come when we added the Item RewardItem property in the Quest Class, we don’t then add it as part of the constructor like we have all other properties in the series so far? e.g. Item rewardItem in the public Quest() parameter section and then assign it – RewardItem = rewardItem in the {} bit.
Thanks
You could do it by accepting a list parameter in the constructor. But, when you instantiate a Quest object, you’d need to either 1) create a list of reward item objects before the Quest object instantiation and pass that variable into the Quest constructor, or 2) instantiate the reward item list inside your instantiation of the Quest object – probably using collection initializers.
I couldn’t think of a clear way to describe that to beginners (since this is still one of the earlier lessons), so I used the simpler way of not populating the reward items through the constructor. But, if you’re comfortable with doing that, you can definitely use that technique.
Hi Scott, thanks for your reply, I’m not sure if i’m getting more confused now, as I don’t think the RewardItem is a list? I thought it was a variable of type Item, so basically an object with 3 properties = an ID a name and a name plural.
There is a list/collection with the name called QuestCompletionItems and within this list we will be adding the QuestCompletionItem objects to it – so ultimately we will have a list of objects that contain two properties, 1. another object named Details, containing an ID, Name and NamePlural. 2. The quantity of this particular item.
Just thinking through the code again and I believe I may have just twigged what is going on now! Is the RewardItem an actual physical item that may be given to the player for successfully completing a quest? – e.g. the Healing Potion. This would then be added to the players bag of inventory items they currently are carrying (another collection/list in coding terms). Therefore, the reason it is not part of the constructor is because not all quests will reward the player with a RewardItem and so this will be set to the default (I guess null? or Empty?), yet all quests will reward the player with gold and experience points?, hence why they are part of the constructor.
And the QuestCompletionItems list, is a list of the items the player must collect/find in order to complete the quest itself, therefore each time you set up a new quest, you need to define the items/quantity the player needs to find, and then I take it later on we will be comparing what is in this list to what the player has in their inventory list.
Sorry for the long post, it helps me digest things better if I write out my thoughts!
I do have one more query though, I may be overthinking it! As I was working my way back through the logic, I started thinking, why don’t the LootItem, QuestCompletionItem and InventoryItem just inherit the Item as a base class? Then you wouldn’t have to add the Item Details property, you could just add it to the constructor and then use the : base() bit to inherit it. But then I thought, no that can’t be right as if you wanted to add a Weapon to your Inventory list in the Player object. How would you pass the min/max damage properties to it. (Am I right in thinking that?)
Then, I thought about it the other way around, if you had a Weapons in your inventory, how would you then know what the min/max damage is, as you only add an Item variable called Details within it – but again, I think I have twigged it out – – so to confirm I fully understand, the InventoryItem is not really a physical item like a sword or a pistol etc, it is more like a noted down piece of info, such as a till receipt from a shop. Each line on the receipt provides you with info (Details object) and then the quantity you bought. e.g. Swords = 2, Pistol = 3. Then, the physical receipt itself is the actual list/collection.
Therefore to add an item to the list you need to 1. First make an actual sword object using the Weapon class as the template. 2. Create a entry to go into the list, so create a new InventoryItem. 3. Add the entry to the list InventoryItems that belongs to the Player.
Now, referring back to the InventoryItem, when you use the constructor to create this entry, you would actually add a Weapon type variable (the sword object we created earlier) for the Item Details property. The min and max values as part of the swords properties will be accessible to read via this Details property because the swords Weapon constructor has been inherited from the Item class. So regardless that the property in the InventoryItem class is defined as an Item type, the actual item you add can be an Item type or any class that has the Item type as its base – the inherited classes (Weapon, HealingPotion etc) – and all the properties ID, Name, NamePlural from the Item class and any additional ones from the inherited class will all be stored within the Details variable.
Phew! Apologies again – spent an hour writing all this out! But hopefully what I have said is right!! Please let me know if I am correct! 🙂
Hi Pezz,
I’m sorry. I answered your last question while I was on a short break from my client project and was thinking of the QuestCompletionItems – not the RewardItem. I’ll take a look at your latest comment tonight, when I can give it my full attention, and hopefully answer it correctly this time. 🙂
Hi Pezz,
Yes, you are correct about the purpose of QuestCompletionItems and RewardItem. We could make the RewardItem an optional parameter (I think we have some classes with optional parameters later). But, since we don’t have it as a constructor parameter, we can have quests that only reward the player with experience points and/or gold.
And you understand how the InventoryItem, QuestCompletionItem, and LootItem classes work. They are like a shopping list: 2 boxes of cereal, 1 carton of milk, 1 pound of ground beef. By having the full Item object, we have access to the full details (properties) of the Item.
This is an important concept to know, and it takes a little while for many programmers understand. But, it sounds like you’ve got it. 🙂
Let me know if you have any other questions.