Lesson objectives
At the end of this lesson, you will know how to…
- Set the default selected item in a dropdown
- Prepare to make a change to an existing program
A reader mentioned a problem they saw when the player has more than one weapon. When the player moves to a new location, the dropdown for weapons always selects the first weapon in the list.
If the player wants to fight with a different weapon, the reader wanted to have the dropdown always select that weapon – unless the player manually changed weapons again.
Here is how to do that.
Planning
Before you change an existing program, it’s good to think about everything it will affect.
This seems like a simple change. However, we should make some additional changes when we do this.
For example, store the currently selected weapon in the save game file. This way, when the player restarts the game, they will have their last weapon selected.
If we think a little more, we realize that the player may already have a saved game file, without a value for the currently selected weapon in it. So, in the code to read the currently selected weapon from the save game file, we need to deal with a file that does not have that value in it.
This is the type of thing to watch out for when you modify a program.
Saving and reloading player information
Step 1: Open the SuperAdventure solution in Visual Studio and select the Player class to modify.
Add a new property to store the player’s current weapon. I added this under the “CurrentLocation” property (although you could add it anywhere outside of a function):
public Weapon CurrentWeapon { get; set; }
Step 2: Open the code file for the SuperAdventure.cs form.
In the lesson where we save and restore the game state in a file, we connected a function to the FormClosing event. This function is an “event handler”, it “handles” the closing “event”. In that lesson, we let Visual Studio connect the event to the function automatically. For this lesson, we need to connect the function manually. You’ll see why in a minute.
Add this function to SuperAdventure.cs:
private void cboWeapons_SelectedIndexChanged(object sender, EventArgs e) { _player.CurrentWeapon = (Weapon)cboWeapons.SelectedItem; }
This gets the selected item from the cboWeapons dropdown and saves it in the player’s CurrentWeapon property.
Notice that we have “(Weapon)” in front of “cboWeapons.SelectedItem”. That’s because a dropdown can hold different types of objects, and cboWeapons.SelectedItem could be any datatype. By adding “(Weapon)” in front of it, we are “casting” it to the Weapon datatype.
We can do this because the items in the dropdown are Weapon objects, so the casting will work.
We need to do this, because the CurrentWeapon property only holds Weapon objects. It can’t take a generic SelectedItem object.
Step 3: Now we need to change the code that populates the cboWeapons dropdown. We want it to select the player’s CurrentWeapon (if they have one) and connect to the cboWeapons_SelectedIndexChanged function (for when the player changes the value in the dropdown list).
Find the “UpdateWeaponsListInUI” function and change it to the code below:
private void UpdateWeaponListInUI() { List<Weapon> weapons = new List<Weapon>(); foreach(InventoryItem inventoryItem in _player.Inventory) { if(inventoryItem.Details is Weapon) { if(inventoryItem.Quantity > 0) { weapons.Add((Weapon)inventoryItem.Details); } } } if(weapons.Count == 0) { // The player doesn't have any weapons, so hide the weapon combobox and "Use" button cboWeapons.Visible = false; btnUseWeapon.Visible = false; } else { cboWeapons.SelectedIndexChanged -= cboWeapons_SelectedIndexChanged; cboWeapons.DataSource = weapons; cboWeapons.SelectedIndexChanged += cboWeapons_SelectedIndexChanged; cboWeapons.DisplayMember = "Name"; cboWeapons.ValueMember = "ID"; if(_player.CurrentWeapon != null) { cboWeapons.SelectedItem = _player.CurrentWeapon; } else { cboWeapons.SelectedIndex = 0; } } }
Look at the lines before and after where we set the cboWeapons.DataSource. These lines connect, or disconnect, a function to the SelectedIndexChanged event of cboWeapons.
In the line before setting the datasource, we remove the function connected to the dropdown’s “SelectedIndexChanged event (the line with the “-=”). That’s because when you set the DataSource property of a dropdown, it automatically calls the function connected to the SelectedIndexChanged event. We don’t want that to happen. We only want that event called when the player manually changes the value.
After the DataSource is set, we add the event handler function back to the SelectedIndexChanged event (with the “+=”). This way, the function will run when the player changes the value.
At the end of this function, we have code to check if the player has a value in their CurrentWeapon property. If they do, we set the dropdown’s SelectedItem to that weapon. If the player doesn’t have a CurrentWeapon, we default to the first one.
Step 4: Now we need to update the save game and load game functions. Go back to editing the Player.cs file.
Find the “ToXmlString” function and add this code after the section for saving the current location:
if(CurrentWeapon != null) { XmlNode currentWeapon = playerData.CreateElement("CurrentWeapon"); currentWeapon.AppendChild(playerData.CreateTextNode(this.CurrentWeapon.ID.ToString())); stats.AppendChild(currentWeapon); }
This will save the ID of the player’s currently selected weapon (if they have one).
Next, find the “CreatePlayerFromXmlString” function and add this code after the lines to set the CurrentLocation:
if(playerData.SelectSingleNode("/Player/Stats/CurrentWeapon") != null) { int currentWeaponID = Convert.ToInt32(playerData.SelectSingleNode("/Player/Stats/CurrentWeapon").InnerText); player.CurrentWeapon = (Weapon)World.ItemByID(currentWeaponID); }
This will load the player’s currently selected weapon, if one exists in the save game file.
Now, we’re finished. When the player changes their active weapon, that value will stay set when they move to a new location, or if they exit and resume the game. It makes the game a little easier for the player, which is always a good thing to do with your programs.
Check your work
Build the solution and make sure there are no errors in the “output” box at the bottom of Visual Studio. If you see any problems, double-check the changes you made in this lesson.
Summary
The important thing from this lesson is that you need to think before making a change to an existing program. What else could your change affect?
This is why some programmers create unit tests and use continuous integration. With those, you can make a change and quickly know if it broke anything.
Source code for this lesson
Next lesson: Lesson 19.6 – Increase maximum hit points when the player gains a level
Previous lesson: Lesson 19.4 – Saving and loading the player information
All lessons: Learn C# by Building a Simple RPG Index
Thank you very much for the help 🙂
You’re welcome. And thanks for letting me know about that broken link. As soon as I fixed it, my Internet went down, so I couldn’t reply then. 🙁
Hey! great tutorial! are you planning on showing us how to add armor, magic, merchants, etc…? Its a fun and enjoyable way to learn C#.
Thank you. I’ve just restarted working on a project for a version of the gam that can have many more features. The current source code (very early stuff) is here: https://github.com/ScottLilly/ScottsOpenSourceRPG. If you follow it on GitHub, you’ll see the updates as it gets more features. I’ll probably write a few tutorials on how I do some of the things in this game, but not a complete step-by-step guide.
So I started getting 5 errors on this part.
4 are from the 4 movement/location changing buttons
Error CS1061 ‘object’ does not contain a definition for ‘LocationToEast’ and no extension method ‘LocationToEast’ accepting a first argument of type ‘object’ could be found (are you missing a using directive or an assembly reference?)
The 5th is from the a different section of the SuperAdventure.cs
Error CS1061 ‘IEnumerable<PlayerQuest>’ does not contain a definition for ‘Add’ and no extension method ‘Add’ accepting a first argument of type ‘IEnumerable<PlayerQuest>’ could be found (are you missing a using directive or an assembly reference?)
Any help would be great, thanks in advance as I have loved working on this project.
These are the first things I would check. If they do not solve the problem, can you upload your solution (all files) to GitHub, or Dropbox, and send me the link to it, so I can investigate some more?
In the Location, class, make sure the LocationTo properties are public, like this:
public Location LocationToNorth { get; set; }
public Location LocationToEast { get; set; }
public Location LocationToSouth { get; set; }
public Location LocationToWest { get; set; }
For the problem with “IEnumerable“, check the Player class. Make sure you have this line in the “using” statements area
using System.Collections.Generic;
… that the Quests property is defined this way:
public List Quests { get; set; }
… and that you have this line in the constructor, to initialize the Quests property:
Quests = new List();
LINK REMOVED, FOR YOUR PRIVACY
Uploaded to Dropbox sir.
I checked and all those seemed to be setup that way to me.
It looks like there are several problems in the Player class. It should look like the code here: https://gist.github.com/ScottLilly/4a0c6ed8e55b89bbd789.
The error for the locations properties is happening because the Player.CurrentLocation is an internal field, with an “object” datatype, in your solution. It needs to be a property that is a Location datatype. The datatypes for the Quests and Inventory properties are IEnumerable, in your solution. Those need to be List datatype, instead of IEnumerable. List has an Add property on it (to add items to the list). IEnumerable does not implement an Add method – which is why you are getting those errors.
Let me know if fixing the Player class does not solve the problems.
I have copied the Player.cs and SuperAdventure.cs from that location before.
I apologize but I am not sure how you are suggesting I fix these. I have followed the instructions as I thought by the directions on each chapter so I am not sure why it is doing something different from what you are saying it should.
I just noticed that there is a second Player class in your solution. There is one in the Engine project (where it should be) and another one in the SuperAdventure project. The one in the SuperAdventure project is the one with the problems. And, the UI is using that Player class.
If you delete the Player.cs file in the SuperAdventure project, the game should use the Engine project’s Player class – which looks like it has the correct code.
That did fix it, thank you sir.
You’re welcome.
Hello again, Mr. Lilly.
After completing the base tutorial, I’ve made several additions (abilities, Strength and Defense stats, UI changes that let the user see monster statistics at a glance, encounter rates, etc…) in the idea of greatly expanding on the concept, and I’ve learned a lot. During playtesting, the most requested feature to be added was to have the game remember their weapon selection. Luckily for me, you already have a tutorial on the matter! I tried to implement it on my own, but apparently there are a lot of shortcomings of comboboxes that I am unaware of, which shows how far I have to go. My unsuccessful solution was very close to yours, though.
It does the job fantastically. There is something I’d like to change about it, however. When a battle begins, it briefly shows the default selection in the combobox before changing over to the stored selection. It’s a small issue, I know, but it irks me, and I feel that making this change will make for a more immersive experience. Can you point me in the right direction?
Have you included the changes in Lesson 20.4? If you did, and you still see the delay, can you upload a copy of your current source code, for me to examine?
That fixed it! I hadn’t gotten that far into the lessons, as anything past lesson 18 I considered beyond the base tutorial. It works perfectly for weapons, and it should work perfectly after I expand on items and add more consumable types. I still have to get it to work for abilities (they use their own combobox and, the way the class is written, can’t be implemented the exact same way as weapons and potions), but I’m sure I can figure it out sooner or later.
Thank you again, Mr. Lilly. Your website continues to be a great source of knowledge and practice.
You’re welcome!
Hi,
where do the player exactly can obtain the second weapon, if I’m not mistaken, the weapon “Club” is in world.cs, but it’s not a drop from any monster neither reward from any quest in the game. Only way how to obtain that weapon is manually “give it” to the player. Am I right or I just overlooked something ?
And if I may, I have one off-topic question about screen resolution. I just wonder, how to do it, if I want to make own align of controls based on screen resolution, like bigger controls in higher resolution and smaller controls in lower resolution to keep same look of the game or beware the display issues, like controls supposed to be in one line, but because of small resolution one of them gets to the second line or something like that.
Is there a method/function or something how to make universal layout that work in all screen resolution or the game form needs to be done in every screen resolution ?
Hi Martin,
In lesson 26.1, I add a line in the SuperAdventure constructor to give the player a club as a default weapon. But, you can add it (or any other items you want to create) as a loot item from a monster, or a reward item for a new quest. After you complete the lessons, you can modify the game to whatever locations, monsters, and items you want – to make the game your own.
If you want to change the size of the form, and keep the layout of the controls looking good, you need to look at anchoring and docking controls. You can see more about how those work here: https://www.techrepublic.com/article/manage-winform-controls-using-the-anchor-and-dock-properties/.
Hi,
I will look into that and as always, thank you very much.