Press "Enter" to skip to content

Learn C# by Building a Simple RPG

Giant Spider!
Mandatory Giant Spider!

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.



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 14.1 – Variables

Lesson 14.2 – If statements

Lesson 14.3 – Foreach loops

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


Share your personal, expanded versions of SuperAdventure here!


  1. Can İpek
    Can İpek October 29, 2017

    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.


    • Scott Lilly
      Scott Lilly October 29, 2017

      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.

  2. Simon
    Simon November 5, 2017

    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.

  3. Can İpek
    Can İpek November 7, 2017

    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.


    • Scott Lilly
      Scott Lilly November 9, 2017

      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!

  4. Naoise Stewart
    Naoise Stewart December 4, 2017

    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!


    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!)

    • Scott Lilly
      Scott Lilly December 4, 2017

      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.

  5. Naoise Stewart
    Naoise Stewart December 5, 2017

    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:

    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!


    • Scott Lilly
      Scott Lilly December 7, 2017

      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.

    • Scott Lilly
      Scott Lilly December 12, 2017

      Here is the code for a new constructor for SuperAdventure.cs that will let the players select from different saved game files.

      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.

  6. Dave
    Dave December 9, 2017

    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.


    • Scott Lilly
      Scott Lilly December 10, 2017

      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
      int level = ((ExperiencePoints / 100) + 1);

      if (level > 10)
      return 10;
      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
      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);

  7. Jennifer Rutherford
    Jennifer Rutherford January 9, 2018

    Where is everything populated? I remember doing it but not where it is and I can’t find it.
    Thank you!

    • Jennifer Rutherford
      Jennifer Rutherford January 9, 2018

      Nevermind, I found it in the World class in the Engine project 😛

  8. Jennifer Rutherford
    Jennifer Rutherford January 9, 2018

    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?

    • Scott Lilly
      Scott Lilly January 10, 2018

      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?

  9. Jennifer Rutherford
    Jennifer Rutherford January 10, 2018


    • Scott Lilly
      Scott Lilly January 12, 2018

      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?

      • Jennifer Rutherford
        Jennifer Rutherford January 12, 2018

        That makes sense, sorry. Thanks. Also, is the spider supposed to do so much damage it can kill you all at once?

        • Scott Lilly
          Scott Lilly January 12, 2018

          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.

  10. Jennifer Rutherford
    Jennifer Rutherford February 26, 2018

    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. . The line causing the problem is 126 in the SuperAdventure.cs class. If you could help me out, that would be great!

    • Scott Lilly
      Scott Lilly February 27, 2018

      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.

      • Jennifer Rutherford
        Jennifer Rutherford March 5, 2018

        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!

        • Scott Lilly
          Scott Lilly March 6, 2018

          I’ve uploaded my code for the changes at:

          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”?

  11. Helena
    Helena March 6, 2018

    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.

    • Scott Lilly
      Scott Lilly March 6, 2018

      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 (, 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.

  12. Jennifer Rutherford
    Jennifer Rutherford March 7, 2018

    That fixed it actually! Thank you!!!

  13. Chris
    Chris March 10, 2018

    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.

    • Scott Lilly
      Scott Lilly March 10, 2018

      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.

  14. Pujan Bajra
    Pujan Bajra March 11, 2018

    Awesome Tutorial. Makes learning fundamentals very easy!! Very much recommended..

  15. William Nichols
    William Nichols March 14, 2018

    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.
    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.

    • Scott Lilly
      Scott Lilly March 14, 2018

      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.

      • William Nichols
        William Nichols March 15, 2018

        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.

  16. Tom
    Tom March 22, 2018


    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?

    • Scott Lilly
      Scott Lilly March 22, 2018

      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.

  17. Charles
    Charles March 22, 2018


    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.


    • Scott Lilly
      Scott Lilly March 23, 2018

      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.

  18. William Nichols
    William Nichols March 26, 2018

    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.

    • Scott Lilly
      Scott Lilly March 26, 2018

      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;

      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)

      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?

      • William Nichols
        William Nichols March 27, 2018

        it was OnPropertyChanged(“Potion”) rather than “Potions”, thanks Scott I spent a long few hours trying to figure out what I done wrong.

  19. Bill
    Bill April 5, 2018

    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


    Going to include this following piece for anyone using CTRL+F
    “Unable to add a reference to project ‘Engine’.

    • Scott Lilly
      Scott Lilly April 5, 2018

      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.

  20. Bill
    Bill April 5, 2018

    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.

    • Scott Lilly
      Scott Lilly April 5, 2018

      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.

Leave a Reply

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