
If you want to write a Role Playing Game, but don’t know how to program, or just want to learn how to program in C#, then you’re at right the place.
These lessons will take you from a complete beginner, to being an author of a Role Playing Game, for free.
This isn’t the world’s greatest game. In fact, it’s very short and kind of ugly.
However, as you create it, you’ll learn the most common C# programming practices and techniques. Then, if you want, you can improve the game, adding more features and your own special touch to it.
NOTE: If you already know the basics of C# programming (classes, properties, functions, “if” statements, etc.), you might want to look at the newer “Build a C#/WPF RPG” lessons. The code in those lessons is more like how I would write a “real” professional program – using better design and architecture.
SECTIONS
Lesson 00.1 – What is in these lessons?
Lesson 00.2 – General information about programming in C#
Lesson 00.3 – The parts of Visual Studio
Lesson 01.1 – Defining classes and objects for the game
Lesson 02.1 – Installing Visual Studio Community Edition
Lesson 02.2 – Building the solution for the game
Lesson 03.1 – Building the first screen
Lesson 04.1 – Creating the Player class and its properties
Lesson 05.1 – Creating objects from classes
Lesson 06.1 – Creating the remaining classes
Lesson 07.1 – Inheritance and base classes
Lesson 08.1 – Setting properties with a class constructor
Lesson 08.2 – Using class constructors with derived classes
Lesson 09.1 – Using your classes as datatypes
Lesson 10.1 – Creating collections of objects
Lesson 11.1 – Using a static class
Lesson 12.1 – Add the remaining UI controls
Lesson 13.1 – Functions, procedures, and methods
Lesson 13.2 – Creating functions to handle user input
Lesson 15.1 – Getting random numbers for the game
Lesson 16.1 – Writing the function to move the player
Lesson 16.2 – Refactoring the player movement function
Lesson 16.3 – Functions to use weapons and potions
Lesson 17.1 – Running the game on another computer
Lesson 18.1 – Future enhancements for the game
Bonus lessons (enhancements to the game)
Lesson 19.1 – Scroll to the bottom of a rich text box
Lesson 19.2 – Use a calculated value for a property
Lesson 19.3 – Clean up the source code by converting foreach to LINQ
Lesson 19.4 – Saving and loading the player information
Lesson 19.5 – Changing dropdown default values
Lesson 19.6 – Increase maximum hit points when the player gains a level
Improving SuperAdventure’s code quality by refactoring
Lesson 20.1 – Refactoring the SuperAdventure program
Lesson 20.2 – Binding a custom object’s properties to UI controls
Lesson 20.3 – Binding list properties to datagridviews
Lesson 20.4 – Binding child list properties to a combobox
Lesson 20.5 – Moving the game logic functions from the UI project to the Engine project
Adding a vendor to locations (with buying and selling items)
Lesson 21.0 – Plans for adding a vendor to locations
Lesson 21.1 – Adding a price to game items
Lesson 21.2 – Create the vendor class and add it to locations
Lesson 21.3 – Add a button and create its eventhandler in code, without the UI design screen
Lesson 21.4 – Completing the trading screen
Use SQL to save and restore player’s game data
Lesson 22.1 – Installing MS SQL Server on your computer
Lesson 22.2 – Creating database tables from classes
Lesson 22.3 – Creating the SQL to save and load the saved game data
Creating a console UI for SuperAdventure
Lesson 23.1 – Creating a console front-end for the game
Final refactoring (cleanup) of the SuperAdventure source code
Lesson 24.1 – Make the SuperAdventure source code easier to understand and modify
New game features
Lesson 25.1 – Select a random monster at a location
Lesson 26.1 Displaying a World Map
Lesson 26.2 – Hiding Unvisited Locations on the World Map
Bug Fixes
Lesson 99.1 – Preventing duplicate quests
Lesson 99.2 – Setting CurrentWeapon when the player has multiple weapons
Hello Scott,
I could finish reading until Lesson 20.4, and saw that you explain solving current weapon issue in the further lessons.
Maybe, I should wait and continue reading in stead of writing you immediately :).
Now, I found out that, you should have inserted _currentMonster datavariable also in xml files (both in writing and reading), otherwise, when you start playing from a location where a monster exists, your xml file returns data values without a monster.
I hope I do not disturb you with such messages, you can consider me as if I am doing my homework properly.
BR,
No problem, Can 🙂
There are probably a few more things in the game that need to be improved. Many people do “test-driven development”, to ensure the program always works – in every condition. That would help us find errors, and fix them immediately. I did not do that for these lessons, because test-driven development is more advanced. If you want to continue learning programming, that would be a good thing to learn, and to apply to this program.
I would really appreciate if someone could provide the entire solution with all the files necessary to run the game. That way I can see it working before working on the source code.
The final version of the solution is on GitHub at: https://github.com/ScottLilly/SuperAdventure
Hi Scott,
It’s me again Can.
On lesson 22, I have some reminders for your future edition of your publication, where I eoncountered few exceptions. In PlayerDataMapper.CreateFromDatabase() method, since SqlDataReader reader is not closed, after it is instantiated for savedgame, it should not be created again for quest and inventory. Either we should create reader at the beginning of the method or we should close reader everytime we create it.
Also, in case of any exception, we return our player null, but it should be inside the exception {} brackets. However, you put it outside the exception (see pp:222, line 114)
As I mentioned, these comments are only for a clearer publication for the future.
A good news is I’ve finished Windows form application, now I’ll start WPF version.
BR,
Hi Can,
Thank you for sharing that. That is fixed on the website, but needs to be updated in the PDF version. I will try to fix the PDF this weekend.
Good luck on the WPF version!
Hi Scott, first of all huge thanks for writing this tutorial as it’s been a massive help in my learning of C# and programming applications in Windows Forms. I have a few questions to ask of you which I’m hoping you can answer… even though I know you’re probably quite busy! Firstly I keep getting an unhandled exception error to do with the weapons combobox whenever I try and run the game… I’ve come up with a temporary fix which allows me to run the program and test for other errors etc. however the combobox then just displays ‘engine.weapon’ which obviously isn’t the best when trying to select a weapon since they all show the same name! Was also wondering if it’d be at all possible to implement a sort of localised login system where a user’s username and password are stored in an Xml document and are used to access a specific player’s stats and progress in the game… I thought this would be a nice feature and although I’m currently trying to figure out how to implement this myself I am finding it quite hard with my current knowledge of C# and I’m not really sure where to start even though I kind of know what I’m meant to do if that makes sense? Anyway sorry for rambling on a bit there but I certainly look forward to your reply!
Naoise
P.S. Happy to send you any of my source code if you could provide an email address or something to send it to (although I have fiddled with it a bit to match my own style, sorry!)
Hello Naoise,
You’re welcome! If you can upload your solution (including all the directories under it, and the files in them) to GitHub or Dropbox, I can look at the it. The weapon combobox should be a quick fix (I think I know where the problem would be). It might not be able to think about the system until the weekend.
Thanks for your speedy reply Scott,
Here’s a link to the dropbox folder where I’ve uploaded all the solution files I hope you required: https://www.dropbox.com/s/rovb5c64ujd6vzw/Naoise%27s%20Adventure.rar?dl=0
Another small issue I had which I forgot to mention at first is one I’m having with quests. Currently when a quest has been completed the user is able to receive the quest infinitely (however they’re not able to complete it again)… maybe I’ve missed something in my code here but I’ve had a look and couldn’t find anything obvious so perhaps you’d know of a fix? Once again really appreciate all the work you put into this project and its incredibly useful to have you so engaged with us and ready to lend a helping hand!
Naoise
For the weapon combobox problem, look at lines 255 and 256 of SuperAdventure.cs. The DisplayMember and ValueMember properties need to be set to the exact name of the properties in your Weapon/Item class. If you change them to “_name” and “_id”, the combobox should display correctly. Make sure you check the DisplayMember and ValueMember lines in the other functions that populate comboboxes.
For the quests, the “if” on line 95 checks if the player has the quest and it is not completed. If so, it tries to complete the quest. If both of those conditions are not true, it runs the code in the “else” after line 128. So, if the player has the quest and has completed it, the “else” code will run – including line 152, which gives the player the quest again. To help you practice debugging, and fixing code, I won’t tell you exactly what to do in this reply. If you try fixing it yourself, and have trouble, let me know. I just want you to try on your own first.
I’ll think about the multi-player login this weekend, and give you some clues on starting that.
Here is the code for a new constructor for SuperAdventure.cs that will let the players select from different saved game files. https://gist.github.com/ScottLilly/6b30876092fc3442f2c76ae022104c84
When the player starts the game, it will ask if they want to load a saved game. If they do, they can select the saved game file they want to use. If not, the game will create a new player. This uses a MessageBox, to ask if the game should load a saved game, and the OpenFileDialog class, to let the user select the saved game file to use.
You will need to modify the naoiseRPG_FormClosing() function, to do something similar, to let the players save their games to different files. That will need to use the SaveFileDialog class.
Let me know if you try this and have any questions.
Hi Scott,
This site was super helpful! I’m just learning C# with 0 prior programming experience under my belt and I felt these tutorials were for me a great way to learn the concepts. Small question in regards to the leveling portion. Since we are pulling the current level from the amount of experienced earned, what would be the best way to set a max level the player can reach?
Thanks again.
Dave
Thank you Dave,
In the “getter” for Level, I would change the code to calculate the level before returning it. If the calculated level is higher than the maximum level, return the maximum level. For example, if the maximum level is 10, you could do this:
public int Level
{
get
{
int level = ((ExperiencePoints / 100) + 1);
if (level > 10)
{
return 10;
}
else
{
return level;
}
}
}
You could also use Math.Min() to return the lowest number of the calculated level and the maximum level, like this:
public int Level
{
get
{
return Math.Min(10, (ExperiencePoints / 100) + 1);
}
}
That could be even shorter, by making the property an “expression-bodied property”:
public int Level => Math.Min(10, (ExperiencePoints / 100) + 1);
Thanks so much!
You’re welcome!
Where is everything populated? I remember doing it but not where it is and I can’t find it.
Thank you!
Nevermind, I found it in the World class in the Engine project 😛
No problem 🙂
Every time I use a healing potion, it works fine except for when I have more than 5 health points. Instead of giving my maximum of 10 hit points it changes to just 5 hit points. As far as I can tell, the code is ok. In the SuperAdventure.cs, the player is declared
_player = new Player(10, 10, 20, 0, 1);
so the maximum hit points is set as 10. And in my btnUsePotion_Click method I have the following lines
_player.CurrentHitPoints = (_player.CurrentHitPoints + potion.AmountToHeal);
// CurrentHitPoints cannot exceed player’s MaximumHitPoints
if (_player.CurrentHitPoints > _player.MaximumHitPoints)
{
_player.CurrentHitPoints = _player.MaximumHitPoints;
}
So it should all work just fine. 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?
LINK REMOVED FOR PRIVACY
Hi Jennifer,
I used the debugger, to watch the variables. It looks like the code is working.
Here is what I saw:
1. The player was fighting a rat, and had 7 hit points remaining
2. The player drank the potion
3. The player’s CurrentHitPoints was set to 12 (7 + 5) on line 412
4. The player’s CurrentHitPoints was greater than their MaximumHitPoints (line 415). So, the program ran line 417
5. On line 417, the player’s CurrentHitPoints was set to their MaximumHitPoints
6. On line 436, the monster attacked the player, doing damage (which was subtracted from the player’s CurrenHitPoints)
What you might have seen is the monster doing 5 points of damage – making it look like a problem with the healing potion code.
Does that make sense?
That makes sense, sorry. Thanks. Also, is the spider supposed to do so much damage it can kill you all at once?
You’re welcome. The giant spider is a powerful monster. If you want to make it weaker, you could change its maximum damage in the PopulateMonsters() function, in the World class.
Hey Scott, so I’m trying to add a feature to the game where there is “dialogue” when you enter a location with a quest, but I have this line of code, that when I test the game, keeps getting an index out of range exception error and I don’t know why.
Here is my code. https://github.com/JenniferE55/SuperAdventure/tree/questDialogue . The line causing the problem is 126 in the SuperAdventure.cs class. If you could help me out, that would be great!
To fix this, there are two things to do. First, change line 30 of Quest.cs to
QuestNotFinishedDialogues = questNotFinishedDialogues.ToList();
In the current World.cs code, the “questNotFinishedDialogues.Clear();” lines clear out the QuestNotFinishedDialogues properties in the Quest. That’s because the variable questNotFinishedDialogues is being used “by reference”, and not “by value” (more information on this). The Quest objects are all pointing to the same list, which is cleared out on line 134 of World.cs.
Adding the “.ToList()” makes a new list in memory. Then, that new list is assigned to the QuestNotFinishedDialogues property. When you call “questNotFinishedDialogues.Clear();” in World.cs, the new list (which the property is using) is not cleared.
It also looks like the random number generator sometimes has a problem, due to floating-point rounding. I changed line 30 of RandomNumberGenerator.cs, to [hopefully] prevent this problem. Use this new line:
return Math.Max(Math.Min((int)(minimumValue + randomValueInRange), maximumValue), minimumValue);
Let me know if that doesn’t work, or if you have questions.
I made both changes you told me, and it’s still giving me the same error every time I come back to a location where I have an unfinished quest. Thank you!
I’ve uploaded my code for the changes at: https://gist.github.com/ScottLilly/3921233171904b6e68c97ca36922231b
Can you try that and see if it fixes the errors? If it doesn’t, can you change line 125-126 of SuperAdventure.cs to this:
List dialogues = newLocation.QuestAvailableHere.QuestNotFinishedDialogues;
int index = RandomNumberGenerator.NumberBetween(0, dialogues.Count()-1);
rtbMessages.Text += dialogues[index]+Environment.NewLine;
Then, set a breakpoint on the last of those lines (where you get the dialog text from the list) and see what values you have for “index” and for “dialogues.Count”?
Hi. I stumbled upon your site by accident and am really interested in following your tutorial, but I hit a wall. I use Ubuntu and am using VS Code. I have no idea of how to start a windows form application in it, searched everywhere and nothing. Is there a way to do it or am I searching in vain…? And if not, can I still follow the lessons only typing the scripts? Thank you very much.
It’s been a long time since I’ve used Linux, but I don’t think you can use these lessons with VS Code. You might be able to use the WPF lessons (https://www.scottlilly.com/build-a-cwpf-rpg/), if VS Code has a front-end project for XAML – but that might also have problems.
Can you create a virtual machine, with Windows installed? That’s the only thing I know that will definitely work.
That fixed it actually! Thank you!!!
You’re welcome!
I just can’t describe how much more logical your pdf book is than other texts I’ve looked at. This is such a great book for an admin that needs to cross over to programming. Amongst other problems, other books have different projects each chapter but there is something about having a single project all the way though – in relation to knowledge nothing can be skipped, put out of order or assumed. Very very good read. And the short chapters are ideal for not getting overwhelmed and lost in pages of what seems at the time pointless theory for when your first starting out.
Thank you. That’s how I like to learn things (each lesson building on the previous lessons). So, that’s how I tried to build this. I’m glad to hear you liked it.
Awesome Tutorial. Makes learning fundamentals very easy!! Very much recommended..
Thank you!
Hi Scott,
its me again, so after the help with the chance of spawning monsters; I been adding in more items and monsters; I thought of changing the players starting stats and items so i tried that. It seems that i broke the player, and i reverted the changes and it seems that the player is still broken. You think you can take a look at my code again and tell me what went wrong.
LINK REMOVED FOR PRIVACY
the only thing I changed was it receiving a singular string to tell me what class to pick but I taken that off for now.
When I ran it, I had a problem with the “_player” variable being null in GAME.cs. But, that might be because I don’t have your database or saved game file, and was trying to create a default player object. The problem I had was on line 35, of GAME.cs. You instantiate a player object, but that is not assigned to the “_player” variable. So, I see a “null reference” error when the rest of the program tries to get values from “_player”.
When I changed line 35 to “_player = Player.CreateDefaultPlayer();”, it looks like the game is working for me. You could also have a problem if you changed your database structure, or XML saved game file format, for the changes you made, but didn’t change them back when you reverted the changes.
If the change I made doesn’t fix your game, try setting a debug breakpoint in PlayerDataMapper.CreateFromDatabase() and Player.createplayerforxmlstring(), and use F10 to step through it to see exactly which line has an error.
Let me know if that does not fix the problem for you.
oh, i forgot to make it _player = player.CreatedefaultPlayer(); well that was the bug and didn’t realized it. I really haven’t work with Database yet, i guess i just kept over looking it.
Hey!
This project has been fun to follow and learn from, thank you for this!
I have been trying to figure out a way to make a message when you level up, “Congratulations! You have reached level ” but have not been successful. Since OnPropertyChanged(“Level”); is in public int ExperiencePoints, the message pops up every time I get experience points, whether it be from monsters or quests. I tried to do something similar in public int Level and creating a new int, but no success. How would I go on about getting this to work the way I want it to?
You’re welcome!
I do that in the WPF version of these lessons – in lesson 10.5. The classes are a little different, but the important parts are the OnLeveledUp event and SetLevelAnMaximumHitPoints() function in the Player class, along with subscribing to the event in the GameSession class in the CurrentPlayer setter and the OnCurrentPlayerLeveledUp() function (which would be handled in the SuperAdventure.cs class in these lessons).
Let me know if you try that and have any problems.
Hello,
Any future plans to create this tutorial however in a VS2017 Mac version? Im starting to get my feet wet with C# and would LOVE to follow along on your tutorial however its very difficult to do on the VS2017 mac version.
Thanks!
Hi Charles,
I haven’t had any plans to create a version for Mac – mostly because I don’t own a Mac, and haven’t used VS 2017 for Mac. I’ll check with a friend of mine who also does Apple development and see if he has any ideas. But, I’ve been spending most of my time lately trying to start a consulting company. I don’t know how much time (if any) I could put into making a new version.
Hi Scott,
After fixing that tiny mistake of making player = player.createdefault. I been trying to find out why my player can’t pick up potion or buy potion to use. note that they are getting them in the inventory but the cobpotion is not adding newly added potion to the inventory and when i use my last potion that i can use it become blank and using it again error since potion name is null. have any idea of where i could look to fix this bug.
It sounds like the UI is not getting the correct PropertyChanged notification for the Potions property.
Check at line 134 of the PlayerOnPropertyChanged function in SuperAdventure.cs. That is the code where the cboPotions DataSource is being updated. It should look like this:
if(propertyChangedEventArgs.PropertyName == "Potions")
{
cboPotions.DataSource = _player.Potions;
if(!_player.Potions.Any())
{
cboPotions.Visible = false;
btnUsePotion.Visible = false;
}
}
On line 134, make sure you have “==”, not “=”, and that “Potions” is the correct spelling and upper/lower-case combination.
Also check lines 565-568 of the Player class, to ensure they match the code below (especially the spelling and upper/lower-case):
if (item is HealingPotion)
{
OnPropertyChanged("Potions");
}
If that does not fix the problem, 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?
it was OnPropertyChanged(“Potion”) rather than “Potions”, thanks Scott I spent a long few hours trying to figure out what I done wrong.
You’re welcome!
I’m using Visual Studio 2017 Community and I can’t set a reference to the engine (Lesson 2 Step 6) and I’m stuck. I perused your older comments and someone else ran into the problem on 2015 and you asked them for a picture of the solution explorer I believe so I’ve got a screenshot ready
Screenshot
Going to include this following piece for anyone using CTRL+F
“Unable to add a reference to project ‘Engine’.
It looks like the Engine project was created as a UWP class library project. It needs to be a .NET Framework class library project.
Try deleting the current Engine project and adding it in as a “Class Library (.NET Framework)” project, under the “Windows Classic Desktop” projects. After you delete the Engine project, you might need to close Visual Studio and delete its files/directories, before you create a new project with the same name.
Let me know if that doesn’t solve the problem.
I’ve found the issue. I was using the PDF version of the course which doesn’t include the part about installing VS2017, so I looked a little ahead on the lesson plan’s outline on the site and noticed the section for installing Visual Studio 2017, and realized that I’d never installed the Data Storage and Processing workload when installing VS2017.
Great. I’m currently editing a new version of the PDF, and should have that ready soon.
Let me know if you encounter any other problems.