Press "Enter" to skip to content

Lesson 19.4 – Saving and loading the player information

Lesson objectives

At the end of this lesson, you will know…

  • The basics of XML (Extensible Markup Language)
  • How to save the player information to disk and reload it when the player restarts the game.
  • How to make prevent your program from crashing, if it reads in a bad file.
  • A very simple version of the “Factory” design pattern

 

Someone recently asked me how to save the player information for a game and reload it when they restart the game.

So, here’s a new lesson on how to do that.

 

NOTE: There is a lot happening in this lesson. But that’s what happens when you start to go beyond simple coding examples. In order to do one thing, you need to know how to do a couple other things. If you have problems understanding this lesson, take it one step at a time, until you are sure you know what is happening.

 

There are several different ways you could save data: in a database, a comma-separated value file, an XML file, etc.

We’re going to use an XML file. It doesn’t require installing anything extra, such as a database. It’s also easy to read, since an XML file includes the structure (definition) and the data.

 

Defining the XML file structure

It’s easier to understand XML with a sample. So, here is the format we’ll use to store the game’s data.

<Player>
    <Stats>
        <CurrentHitPoints>7</CurrentHitPoints>
        <MaximumHitPoints>10</MaximumHitPoints>
        <Gold>123</Gold>
        <ExperiencePoints>275</ExperiencePoints>
        <CurrentLocation>2</CurrentLocation>
    </Stats>
    <InventoryItems>
        <InventoryItem ID="1" Quantity="1" />
        <InventoryItem ID="2" Quantity="5" />
        <InventoryItem ID="7" Quantity="2" />
    </InventoryItems>
    <PlayerQuests>
        <PlayerQuest ID="1" IsCompleted="true" />
        <PlayerQuest ID="2" IsCompleted="false" />
    </PlayerQuests>
</Player>

 

What is XML?

Think of XML data as one long string, or file, with special formatting rules.

There are three main parts to XML: nodes, attributes, and values.

 

“Nodes”, or “tags”, are the names of values, kind of like a property in a class.

In our file, the nodes are: Player, Stats, CurrentHitPoints, Gold, ExperiencePoints, Currentlocation, InventoryItems, InventoryItem, PlayerQuests, and PlayerQuest.

A few thing you need to know about XML nodes:

  1. Nodes have a start and an end. Notice that there is <Player> node at the very beginning, and </Player> at the end. The “/” before the node name indicates that it is the end of the node.
  2. Node start and end names must match exactly. They are case-sensitive. So, you cannot start a node with “<NAME>” and end it with “</name>”.
  3. Node names cannot contain a space. So, we can have a node named “CurrentHitPoints”, but not one named “Current Hit Points”.
  4. Node starts and ends cannot cross. For example, you can have <A><B><C></C></B></A>, but you cannot have <A><B><C></B></C></A>. In the second example, the C node is started after the B node; however, the B node ends before the C node ends, which is not allowed in XML.
  5. Nodes can also be “self-closing”. Notice that the InventoryItem nodes don’t have a corresponding </InventoryItem>. The “end” of those nodes is done with the “/” just before the “>”.
  6. If you start a new node before the previous node ends (like the CurrentHitPoints node, being between the start and end of the Stats node) then it is called a “child node”. CurrentHitPoints is a child node of Stats, and Stats is a child node of Player.
  7. A node can have multiple child nodes, even with the same name. See how InventoryItems has multiple InventoryItem nodes.
  8. You can store data as “values”, like the 7 in CurrentHitPoints, or as “attributes”, like the ID and Quantity in the InventoryItems.

 

Notice in the InventoryItem nodes, we store the ID and Quantity as attributes. If you wanted to, you could store them as values in child nodes.

So, instead of this:

<InventoryItem ID="1" Quantity="1" />

You could have this:

<InventoryItem>
    <ID>1</ID>
    <Quantity>1</Quantity>
<InventoryItem>

It’s personal preference, which way you do it.

 

That’s enough information about XML for us to get started. There are some more rules you need to know about, if you want to do more work with XML. Check out the Wikipedia page on XML to learn about them.

One thing to watch out for is if you ever output user-entered data into XML. If the user enters a less-than sign “<“, XML thinks it is the beginning of a node – unless you follow some of the special techniques you’ll find on the Wikipedia page (look for CDATA).

 

Why we are writing our own serialization code, instead of using the built-in serialization

Sometimes you need to pass objects to something outside your program. The problem is that it doesn’t understand your program’s object, or class. One example is the file system, which understands strings (and a few other things).

One way to do pass an object is to use “serialization”.

There are built-in functions in the .Net framework to do serialization (converting from an object) and deserialization (converting back into an object).

However, these built-in functions can be a little complex to use, especially when you have an object that contains a list of other objects. You need to modify all your classes to know how to serialize/de-serialize themselves.

That’s a lot of work for a small game. So, we won’t be using that.

We’ll write our own functions to convert the Player object information to XML, and back from XML into a Player object.

By the way, there are several types of serialization. You may have also heard of JSON (JavaScript Object Notation), which is used to transfer object information in many web sites and web apps. It uses a different format for its data, but the concept is similar to XML.

 

OK, enough talk. Time to write some code.

 

Saving and reloading player information

Step 1: Open the SuperAdventure solution in Visual Studio and select the Player class to modify.

 

Step 2: At the top of the Player class, add this line to the “using” statement section, so we have access to the XML functions.

using System.Xml;

 

Step 3: Add this new ToXMLString() function to the Player class. This will take the Player information and create an XML string with all the Player’s current data.

public string ToXmlString()
{
    XmlDocument playerData = new XmlDocument();
    // Create the top-level XML node
    XmlNode player = playerData.CreateElement("Player");
    playerData.AppendChild(player);
    // Create the "Stats" child node to hold the other player statistics nodes
    XmlNode stats = playerData.CreateElement("Stats");
    player.AppendChild(stats);
    // Create the child nodes for the "Stats" node
    XmlNode currentHitPoints = playerData.CreateElement("CurrentHitPoints");
    currentHitPoints.AppendChild(playerData.CreateTextNode(this.CurrentHitPoints.ToString()));
    stats.AppendChild(currentHitPoints);
    XmlNode maximumHitPoints = playerData.CreateElement("MaximumHitPoints");
    maximumHitPoints.AppendChild(playerData.CreateTextNode(this.MaximumHitPoints.ToString()));
    stats.AppendChild(maximumHitPoints);
    XmlNode gold = playerData.CreateElement("Gold");
    gold.AppendChild(playerData.CreateTextNode(this.Gold.ToString()));
    stats.AppendChild(gold);
    XmlNode experiencePoints = playerData.CreateElement("ExperiencePoints");
    experiencePoints.AppendChild(playerData.CreateTextNode(this.ExperiencePoints.ToString()));
    stats.AppendChild(experiencePoints);
    XmlNode currentLocation = playerData.CreateElement("CurrentLocation");
    currentLocation.AppendChild(playerData.CreateTextNode(this.CurrentLocation.ID.ToString()));
    stats.AppendChild(currentLocation);
    // Create the "InventoryItems" child node to hold each InventoryItem node
    XmlNode inventoryItems = playerData.CreateElement("InventoryItems");
    player.AppendChild(inventoryItems);
    // Create an "InventoryItem" node for each item in the player's inventory
    foreach(InventoryItem item in this.Inventory)
    {
        XmlNode inventoryItem = playerData.CreateElement("InventoryItem");
        XmlAttribute idAttribute = playerData.CreateAttribute("ID");
        idAttribute.Value = item.Details.ID.ToString();
        inventoryItem.Attributes.Append(idAttribute);
        XmlAttribute quantityAttribute = playerData.CreateAttribute("Quantity");
        quantityAttribute.Value = item.Quantity.ToString();
        inventoryItem.Attributes.Append(quantityAttribute);
        inventoryItems.AppendChild(inventoryItem);
    }
    // Create the "PlayerQuests" child node to hold each PlayerQuest node
    XmlNode playerQuests = playerData.CreateElement("PlayerQuests");
    player.AppendChild(playerQuests);
    // Create a "PlayerQuest" node for each quest the player has acquired
    foreach(PlayerQuest quest in this.Quests)
    {
        XmlNode playerQuest = playerData.CreateElement("PlayerQuest");
        XmlAttribute idAttribute = playerData.CreateAttribute("ID");
        idAttribute.Value = quest.Details.ID.ToString();
        playerQuest.Attributes.Append(idAttribute);
        XmlAttribute isCompletedAttribute = playerData.CreateAttribute("IsCompleted");
        isCompletedAttribute.Value = quest.IsCompleted.ToString();
        playerQuest.Attributes.Append(isCompletedAttribute);
        playerQuests.AppendChild(playerQuest);
    }
    return playerData.InnerXml; // The XML document, as a string, so we can save the data to disk
}

 

At the start of this function, we create the playerData XmlDocument. This object lets us add nodes in a safe way, so we can’t break the most common XML formatting rules.

Now that we have the XmlDocument, we’ll populate it with our player data.

First, we create the “Player” XmlNode and add it to the document.

Next, we create the “Stats” XmlNode and add it as a child node to the Player node. That means it will start, and end, between the start and end of the Player node tags <Player> and </Player>.

Then we create nodes for the CurrentHitPoints, MaximumHitPoints, Gold, ExperiencePoints, and CurrentLocation. These nodes hold data, so their only child nodes are the values added to them with the CreateTextNode function.

Notice that we don’t have a node to store the Level value. That’s because Level is always calculated from the ExperiencePoints.

These nodes are all child nodes of “Stats”. So, when we finish creating them, we use the AppendChild method on the Stats node.

Then we add the “InventoryItems” node, and “InventoryItem” nodes for all the items in the player’s inventory.

Notice that we are using the CreateAttribute method to add the values to the nodes. After creating the attribute, we set its value.

Finally, we add the “PlayerQuests” node and its “PlayerQuest” child nodes (if any exist)

Since we are writing the XML value to a file, we return the InnerXml property of the XmlDocument, which is all the XML as a string.

 

This is probably a good time to rebuild your solution, and see if there are any errors, before we go to the next step.

 

Step 4: The next step is to make a new constructor for the Player class that takes the XML data and creates a new Player object, with the values from it.

This is where we’ll use the “Factory” design pattern.

A design pattern is basically a general method, or technique, to do something. It’s good to learn the common ones, especially if you work with other programmers. Then, instead of giving a long explanation of what you’re doing, you can just say, “It’s a Singleton,” or, “Use a Decorator.” Then the other programmer will know what you’re doing.

So far, in this program, whenever we want an object, we use the constructor to create an instance of the class. We’re going to change that for the Player class. We’re going to use a factory to create the object for us.

Replace the constructor for the Player class with the code below (and add in the two new functions).

private Player(int currentHitPoints, int maximumHitPoints, int gold, int experiencePoints) : base(currentHitPoints, maximumHitPoints)
{
    Gold = gold;
    ExperiencePoints = experiencePoints;
    Inventory = new List<InventoryItem>();
    Quests = new List<PlayerQuest>();
}
public static Player CreateDefaultPlayer()
{
    Player player = new Player(10, 10, 20, 0);
    player.Inventory.Add(new InventoryItem(World.ItemByID(World.ITEM_ID_RUSTY_SWORD), 1));
    player.CurrentLocation = World.LocationByID(World.LOCATION_ID_HOME);
    return player;
}
public static Player CreatePlayerFromXmlString(string xmlPlayerData)
{
    try
    {
        XmlDocument playerData = new XmlDocument();
        playerData.LoadXml(xmlPlayerData);
        int currentHitPoints = Convert.ToInt32(playerData.SelectSingleNode("/Player/Stats/CurrentHitPoints").InnerText);
        int maximumHitPoints = Convert.ToInt32(playerData.SelectSingleNode("/Player/Stats/MaximumHitPoints").InnerText);
        int gold = Convert.ToInt32(playerData.SelectSingleNode("/Player/Stats/Gold").InnerText);
        int experiencePoints = Convert.ToInt32(playerData.SelectSingleNode("/Player/Stats/ExperiencePoints").InnerText);
        Player player = new Player(currentHitPoints, maximumHitPoints, gold, experiencePoints);
        int currentLocationID = Convert.ToInt32(playerData.SelectSingleNode("/Player/Stats/CurrentLocation").InnerText);
        player.CurrentLocation = World.LocationByID(currentLocationID);
        foreach(XmlNode node in playerData.SelectNodes("/Player/InventoryItems/InventoryItem"))
        {
            int id = Convert.ToInt32(node.Attributes["ID"].Value);
            int quantity = Convert.ToInt32(node.Attributes["Quantity"].Value);
            for(int i = 0; i < quantity; i++)
            {
                player.AddItemToInventory(World.ItemByID(id));
            }
        }
        foreach(XmlNode node in playerData.SelectNodes("/Player/PlayerQuests/PlayerQuest"))
        {
            int id = Convert.ToInt32(node.Attributes["ID"].Value);
            bool isCompleted = Convert.ToBoolean(node.Attributes["IsCompleted"].Value);
            PlayerQuest playerQuest = new PlayerQuest(World.QuestByID(id));
            playerQuest.IsCompleted = isCompleted;
            player.Quests.Add(playerQuest);
        }
        return player;
    }
    catch
    {
        // If there was an error with the XML data, return a default player object
        return Player.CreateDefaultPlayer();
    }
}

 

Notice that the constructor is now private. That means it can only be called by another function inside the Player class. We didn’t need to do this. However, since we are only going to use the other two methods to create a Player object, I made it private.

So now, if you want to create a new Player object, you need to either call the CreateDefaultPlayer function or the CreatePlayerFromXmlString function. These methods are public. So they can be accessed by the rest of the solution. Since they are static, you can call them directly from the class, without an object (you’ll see to how to do that in a minute).

The CreateDefaultPlayer function should look familiar. It’s basically the same thing that the SuperAdventure page does when it creates a new player.

The CreatePlayerFromXmlString function is where we take the XML string with the game’s data, read the values in it, and create a player object with the values from the saved game.

First, we load the string into an XmlDocument object.

To get the data that only has a single value, we use the SelectSingleNode function on the XmlDocument. We give it the XPath of the data (like “/Player/Stats/CurrentHitPoints”). The “InnerText” says to get the value. InnerText always returns a string, so we need to wrap all that with Convert.ToInt32, to get the number.

Think of XPath, and child nodes, kind of like directories/folders, with sub-directories/folders, on your hard disk.

For the nodes that can have multiple items (inventory items and player quests), we use the SelectNodes function on the XmlDocument. This gets every node that matches the XPath. For each node, we create an InventoryItem, or PlayerQuest, object, set its values, and add it to the player object.

Notice that this function has a “try” and a “catch” section. This is known as a “try catch block”. What happens here is that the program tries to run everything in the “try” section. If there is an error, it does whatever is in the “catch” section.

We have this here in case there is ever a problem with the file (like, if someone tries to modify it, and messes it up). Instead of the user seeing an error, the program will execute the “catch” block and create the default player for the game.

Try catch blocks are a very powerful tool in professional programming. Instead of having your program crash, you can have it deal with problems more gracefully.

 

Don’t try building your program now. Since we made the Player constructor private, we need to make a couple changes before we can build without errors.

 

Step 5: Next, we need to have the program write the player’s information to disk when they exit the program.

We’re going to do this by adding code to the “FormClosing” event of the program. Whenever the program is being closed (stopped), this function will run. It will take the player’s current information, using the new ToXMLString() method of the Player class, and write it to the disk, in a file named “PlayerData.XML”

Select the SuperAdventure.cs file, and open it in design mode (so you see the screen). In the lower-right corner of Visual Studio, in the Properties box, click on the lightning symbol.

Events

Now you see all the events that this form has. Scroll to the “FormClosing” event, and double-click on it.

FormClosingEvent

That should have taken you to the code for the SuperAdventure form and created a function named “SuperAdventure_FormClosing”. This is the function that will be run when you close the program.

 

NOTE: It is important to create the function this way. This method adds a line of code to another file that connects the function to the FormClosing event. If you only copy/paste the new SuperAdventure.cs code into your program, that line will not be created, and this function will not ever run.

 

Before we add the code for this function, we need to include a reference to the library that lets us write to, and read from, the disk. Go to the top of the page and add this line to the section with the other “using” statements:

using System.IO;

 

Scroll down to the section where we define our class-level variables _player and _currentMonster (this should be around line 20). Add this new line as class-level constant:

private const string PLAYER_DATA_FILE_NAME = "PlayerData.xml";

 

That is the file name where we will save the player’s data. We made it a constant, because it doesn’t change. And it’s a class-level variable, so we can use it in the function that creates the file and the function that loads the file. We need it visible in both places.

Go back to the SuperAdventure_FormClosing function and change it to this:

private void SuperAdventure_FormClosing(object sender, FormClosingEventArgs e)
{
    File.WriteAllText(PLAYER_DATA_FILE_NAME, _player.ToXmlString());
}

 

Now, when the program is closing, it will write the player’s data, in our XML format, to the PlayerData.xml file.

 

Step 6: Finally, when the game starts, we want to look for the PlayerData.xml file.

If it exists, we’ll read it and create the player object from its values. If it doesn’t exist, we’ll assume we are starting a new game and use the normal Player constructor to create a new Player object.

Change the “public SuperAdventure” function to this:

public SuperAdventure()
{
    InitializeComponent();
    if(File.Exists(PLAYER_DATA_FILE_NAME))
    {
        _player = Player.CreatePlayerFromXmlString(File.ReadAllText(PLAYER_DATA_FILE_NAME));
    }
    else
    {
        _player = Player.CreateDefaultPlayer();
    }
    MoveTo(_player.CurrentLocation);
    UpdatePlayerStats();
}

 

When the game starts, it will look for the PlayerData.xml file.

If the file exists, it will read it and create the Player object with the values in the file. If it doesn’t find the file, it will create the default Player object.

Now you can build the solution.

Try moving the player to a new location, then exit the game and restart it. You should see that the game starts with the player in the new location.

If you ever want to have the player start over, you can manually delete the PlayerData.xml file (it will be in the same directory as SuperAdventure.exe). If you want to get fancy, you could add a menu to the game and let the play save and restore multiple games, with different file names.

But, I’m going to stop for now. We’re already way over 2000 words in this lesson.

 

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

This was a big lesson, with some new concepts.

If you want to become a professional programmer, read about design patterns and learn about good practices to use with try catch blocks.

 

Source code for this lesson

Source code on GitHub

Source code on Dropbox

 

Next lesson: Lesson 19.5 – Changing dropdown default values

Previous lesson: Lesson 19.3 – Clean up the source code by converting foreach to LINQ

All lessons: Learn C# by Building a Simple RPG Index

54 Comments

  1. Dancer
    Dancer July 26, 2015

    How could one encrypt data, so it would not be visible (readable) to player?

    • Scott Lilly
      Scott Lilly July 27, 2015

      You could encrypt the XML string before you write it to the file. When you read in the data, you couldn’t use the LoadXml(). You’d need to read in the file with a method like this https://msdn.microsoft.com/en-us/library/ezwyzy7b.aspx, then decrypt it before loading it into the XML document.

      If you don’t need a super secure level of encryption (you just want to prevent average players from modifying the data), you could use a Base64 conversion on the string, like shown here: https://msdn.microsoft.com/en-us/library/dhx0d524%28v=vs.110%29.aspx. If you need something more secure, you want to look at the System.Security.Cryptography namespace in the .Net framework. You can make your data more secure, but there is more work involved.

  2. EvossaN
    EvossaN July 29, 2015

    Uhm, I’m having a problem here, my game is saving the PlayerData.xml once i close it, it generates the file, but when i open then file i just start a new game, yet when debuging it the game goes through

    if (File.Exists(PLAYER_DATA_FILE_NAME))
    {
    _player = Player.CreatePlayerFromXmlString(File.ReadAllText(PLAYER_DATA_FILE_NAME));
    }

    and it shows the “PlayerData.xml” file on it, i’m going to look better onto this but help would be appreciated, also can you make a tutorial to show us how to add Merchants? and maybe armor/spells

     

    • Scott Lilly
      Scott Lilly July 29, 2015

      The first thing I’d do is verify that the PlayerData.xml is properly-formed XML. If it isn’t, that will cause an error when the program tries to read it. You can do this by opening the PlayerData.XML file in a browser. If should look something like this:

      If it’s not properly-formed XML, the browser will give you an error message. Then you’d need to double-check the code in Player.ToXmlString() method.

      If that looks good, I’d modify the “catch” part of Player.CreatePlayerFromXmlString(), to this:

      catch(Exception ex)
      {
      string errorMessage = ex.Message;

      // If there was an error with the XML data, return a default player object
      return Player.CreateDefaultPlayer();
      }

      Then, set a breakpoint on the line where you assign a value to the errorMessage variable. I suspect the program is getting an error reading the file, causing it to start with the default (beginning) game information. If this happens, you can examine the “ex” variable to find out exactly which line has the error. That should get you right on the cause of the error. But, let me know if it doesn’t.

      I’m also finally getting back into working on an expanded version of the game. I’ve been busy moving back to the US, getting an apartment/job/etc. But things have finally settled down, and I’m getting some free time again.

      • Andy Gronow
        Andy Gronow November 3, 2017

        Hi, I finished adding the source code from this lesson to my program and there aren’t any obvious errors, but when I load the game back up by running the program, it doesn’t seem to load any XML data, it just starts back at the beginning with default values as if the XML file doesn’t exist. I tried running the exe version of the progra in the debug folder and saw an XML file there, but the game still doesn’t save. I checked in the XML file and it seems like it stores default values like the beginning of the game instead of the values I would currently have when closing the form. I can’t fathom why the XML isn’t working properly. Do you know where I may have gone wrong? Also, how would I go about sending my code to you if you needed it for identifying the problem?

        Regards, Andy

        • Scott Lilly
          Scott Lilly November 5, 2017

          Hi Andy,

          If the program isn’t updating the saved game data, you could start by setting a debug breakpoint in the SuperAdventure_FormClosing() function in SuperAdventure.cs. It you aren’t familiar with how to use the debugger, here is some information on using it. My guess is that the function is not running, or it is, but it has an error in the Player.ToXmlString() function. If you step through the code, one line at a time when it’s running, you should see which line has the error.

          If you can’t find the problem that way, you can upload your solution (including the directories under it, and all the files in those directories) to GitHub or Dropbox. Then I can look at it.

  3. Kyle
    Kyle September 4, 2015

    Hey so Im trying to make mine a little fancier and cant seem to get it to work. When you my game it prompts the user to either select to load the game or choose a new one. If they choose load game, it just loads the game back to where you left off, when you hit new game, it takes the user to a screen where they can input there name and hit start. What I want it to do is when you hit new game, to completely start a new game, even if there is a saved game on file. Any ideas on how to make this work?

    • Kyle
      Kyle September 4, 2015

      Also I cant seem to get it to save the player name working how it is now. Im pretty sure Ive added all the code in the needed spots, but when you leave the game, the name you set does not reappear. Any idea why this is happening?

      • Scott Lilly
        Scott Lilly September 5, 2015

        With XML, it’s important to make sure you write and read the data in the exact same place (XPath), with the exact same name – even matching the upper/lower-case of the node and attribute names. I’d check the XML file, after you save the game, to make sure it’s writing the data where you expect it to. If that looks good, I’d put a debugger breakpoint in the Player.CreatePlayerFromXmlString() method, and watch what happens when you read in the XML file. Make sure it reads it in fine and take a look at the XmlDocument.

        If you can’t find the problem that way, please post the Player.CreatePlayerFromXmlString() and Player.ToXmlString() source code to https://gist.github.com/, and I’ll take a look at it.

    • Scott Lilly
      Scott Lilly September 5, 2015

      How do you ask the player if they want to start a new game or re-load the last one?

      The two ways I’m thinking of would be to either have a MessageBox pop-up and ask them during the startup of the SuperAdventure UI form, or have a separate UI form that starts up, then calls the SuperAdventure form. The change would depend on how you’re doing it. If you can post the code online somewhere, like https://gist.github.com/, and send me the link, I can take a look at it and give you a specific answer.

      • Kyle
        Kyle September 8, 2015

        Sorry it took me so long to reply! Yes the way I have it is when you click the launch to run the game, a windows form called StartScreen pops up that has two buttons, one to load the game and one to start a new game. If you click the load game, I want it to load the saved game if there is one, if not, display a message box saying “There is no saved file.”. The new game button takes you to another form called NewGame which lets you input your name, and click the start button to take you to the game. I have included the necessary files to work with to solve this in git hub at this link: https://gist.github.com/anonymous/efdf6bff5fa52a73cc8a.

        I have been fiddling around trying to get it to work but so far have nothing good to show. The code posted does not work correctly, as of now the load game just takes you to the game and the new game button, takes you too the new game, but will not start a new game if there is one saved already. Any ideas how to make this work the way I want it to?

        • Scott Lilly
          Scott Lilly September 9, 2015

          OK. What you need to do is get your answer from the NewGame screen to the Adventure screen. Here is a way to do that.

          In Adventure.cs, change line 22 to this:

          public Adventure(bool startNewGame)

          Then, in StartScreen.cs, change line 26 to:

          Adventure loadGame = new Adventure(false);

          And change line 37 of NewGame.cs to:

          Adventure newGame = new Adventure(true);

          Now, the constructor of Adventure.cs will know if it needs to start a new game or not. You’ll need to change the conditions on lines 26 through 33 of Adventure.cs, to use the “startNewGame” variable to decide whether or not to load the existing game (if it exists), or to start a new game.

          You can do the same type of thing to pass in the player name from NewGame.cs. Add a second parameter to the Adventure.cs contructor. You’ll need to pass in an empty string for the name parameter, from line 26 of StartScreen.cs, and you’ll just ignore that, since you’ll get the player’s name from the XML file.

          Let me know if that needs clarification, or if you have any trouble adding that to your game.

          • Kyle
            Kyle September 10, 2015

            Yes that worked! It now will load and save correctly. There is one thing that I still cant get to work though. For some reason it will not save the players name. I changed the way you get it from the new game form to what you said to, and I’m pretty sure I edited all the XML methods to include a name field, but maybe I missed something.
            I put a link to those files, if you want to take a look at it. Again thanks for all your help already!

            https://gist.github.com/anonymous/9569502ca3f3bffc8670

          • Scott Lilly
            Scott Lilly September 10, 2015

            You are close. So I’ll give you a hint, instead of telling you exactly what to change.

            In Adventure.cs, on lines 34 and 40, you create a new _player object. Where are you setting the PlayerName property on that _player object?

          • Kyle
            Kyle September 16, 2015

            Thank you for all your help getting this to work. Now I can start working on adding more. One thing Im trying to implement is a picture box that changes an image based on your location. Now since I have roughly 200 locations, at the moment, I dont set each location to have a picture. Say for instance There is 20 open spaces that all use the same picture. Rather than set each location to have the same picture, I have it set to change the picture when you first come into contact with that area. This saves a lot of time and space for coding however there is a problem with it. If you exit the game, and load it again, the image that was there will not be, unless it has code to set the image. So for all those locations that dont have a set image method, they appear blank when reloading the game. Soooooo after all that my question is, can you write an image to an xml file to save it? And if so how? I have tried a way to get it to work, by simply just adding another field to the player object and putting it in the XML function, but kept running into error after error. Any idea how to make this possible but functional?

          • Scott Lilly
            Scott Lilly September 16, 2015

            You’re welcome.

            Personally, I’d set the image for each location (in a new property of the Location class). That’s probably the simplest solution, and simple solutions usually pay off as you make more changes later.

            If you don’t want to do that, I’d create a new property in the Player class for CurrentImage, to hold the name of the image to load. In the SuperAdventure.MoveTo() function, after the line “_player.CurrentLocation = newLocation;”, I’d add this:

            if(newLocation.ImageName != null)
            {
            _player.CurrentImage = newLocation.ImageName;
            }

            // Add code to load _player.CurrentImage into the picture box here

            Then I’d change the Player ToXmlString() and CreatePlayerFromXmlString() functions to save and retrieve the Player.CurrentImage value.

            I think that should work. Since you call MoveTo(), in SuperAdventure.cs, after creating the _player object from the saved game, it won’t overwrite the _player.CurrentImage value for locations without a CurrentImage value.

          • Andy
            Andy October 26, 2017

            Hello Scott,

            Based on Kyle’s post I am trying to solve following problem:
            1. User starts the game and InitialWindow with two buttons (NewGame and LoadGame) appears.
            2. No matter which button the users clicks, a main “Game” window opens. Subsequently I want to close the InitialWindow, not just hide it.

            My idea is to close the InitialWindow from “SuperAdventure_FormClosing” method.
            SuperAdventure.cs:

            namespace SuperAdventure
            {
                public partial class InitialWindow : Form
                {
                    SuperAdventure Game;
                    public static InitialWindow _InitialWindow;
                    public InitialWindow()
                    {
                        InitializeComponent();
                        _InitialWindow = this;
                    }
                    private void btnNewGame_Click(object sender, EventArgs e)
                    {
                        Game = new SuperAdventure(true);
                        Game.Show();
                        //this.Hide();
                    }
                    private void btnLoadGame_Click(object sender, EventArgs e)
                    {
                        Game = new SuperAdventure(false);
                        Game.Show();
                        //this.Hide();
                    }
                }
            }

            Method “SuperAdventure_FormClosing” in SuperAdventure.cs

            private void SuperAdventure_FormClosing(object sender, FormClosingEventArgs e)
            {
                string playerData = _Player.ToXMLString();
                File.WriteAllText(PLAYER_DATA_FILE_NAME, playerData);
                InitialWindow._InitialWindow.Close();
            }

            It seems it works as expected. But I would like to know whether it’s a good solution for my problem and whether there is any better way to do it.

             

          • Scott Lilly
            Scott Lilly October 29, 2017

            In your eventhandlers for the initial form, try replacing the commented-out “//this.Hide();” lines with “this.Close();”. I believe that should work. If it doesn’t, your solution is also a good way to handle closing the initial form.

  4. David
    David November 13, 2015

    When I got to the point where I had to change “public SuperAdventure” function (at the end of the lesson), the last line of the new function, which is “UpdatePlayerStats();” is underlined with red line in my code and says ” it does not exists in the current context” what could be the problem? I think I followed the descriptions exactly, and before step4 I rebuilt the solution and worked well!

    BTW: enjoyed the whole lesson, successfully made the game working, learned a lot of new things! 🙂 I want to create similar game with little graphics and with hunger/thirst in the near future! 🙂 (traveling costs hunger and thirst, and player needs food and drink, kind of survival)

    Thanks for your help and for the GREAT tutorials! – David

    • Scott Lilly
      Scott Lilly November 13, 2015

      Hi David. Thanks for your comment!

      It sounds like the UpdatePlayerStats() function is missing from SuperAdventure.cs. The code for it is from Step 5 of Lesson 19.2, or just add this into SuperAdventure.cs:

      private void UpdatePlayerStats()
      {
      // Refresh player information and inventory controls
      lblHitPoints.Text = _player.CurrentHitPoints.ToString();
      lblGold.Text = _player.Gold.ToString();
      lblExperience.Text = _player.ExperiencePoints.ToString();
      lblLevel.Text = _player.Level.ToString();
      }

      I’ll review the lessons this weekend, in case that function is missing from the source code on GitHub or in Dropbox. It was a little difficult keeping the code synchronized everywhere.

      FYI: In December, I’m going to restart work on an open-source RPG engine with a lot more features (and cleaner code). Since you want to create more games, you might want to check it out at: https://github.com/ScottLilly/ScottsOpenSourceRPG

  5. Toby
    Toby December 4, 2015

    Hi Scott,

    I just finished going through the excellent Super Adventure RPG builder tutorial, i cant tell you how interesting and helpful i have found this!

    I was wondering if you had any plans for showing how we could adapt this to start storing the data in a simple database rather than XML file output? or if you have any good suggested reading on the topic of basics around setting up your first database?

    Thanks

     

    Toby

      • Toby
        Toby December 8, 2015

        Brilliant thanks Scott that would be excellent, this is all sooooo helpful!

  6. Matthew
    Matthew December 4, 2015

    First off, want to say love the guide and good work on it.

    Disclaimer: I didn’t copy and paste and code because I am writing the application on a different computer than the guide.

    So, everything works as normal. However, when I start the game in debugging mode and move around/attack monsters/etc. and then quit and reopen it. It doesn’t load the PlayerData.xml. Instead it creates a new default player. But the PlayerData.xml file is still being created and written to with updated information. Do you know what could be causing this?

    Also, when I am playing the game and finish the first quest, clearing the alchemist’s garden, The rtbMessage display shows that I’ve received the rewardItem, however it shows it as Engine.HealthPotion. Do you know what could be causing this??

     

    Thank you

    • Matthew
      Matthew December 4, 2015

      To add onto my previous question about the Engine.HealthPotion. When I get the adventurer’s pass, it is also displayed in the rtbMessage area as Engine.Item.

      • Scott Lilly
        Scott Lilly December 4, 2015

        In case you checked my response right after I posted it, I mistakenly pointed at a similar solution for datagrids, and not the richtextbox (I’m working on a new lesson for databinding to datagrids, and that’s stuck in my brain right now). I’ve fixed it to answer the question for the richtextbox control.

    • Scott Lilly
      Scott Lilly December 4, 2015

      Thank you Matthew!

      Check the code that gives the player the quest reward, and make sure it says:

      rtbMessages.Text += newLocation.QuestAvailableHere.RewardItem.Name + Environment.NewLine;

      I suspect it has this in there right now:

      rtbMessages.Text += newLocation.QuestAvailableHere.RewardItem + Environment.NewLine;

      Notice that the second line only has “RewardItem”, and not “RewardItem.Name”. That’s the most likely reason it would show the object/class name, and not the “Name” property of the object.

      Not loading the PlayerData.xml file might be trickier to track down. I made a video to show you how to use the debugger (link below), if you want to try finding the problem on your own. You’d want to put a breakpoint in the constructor of the SuperAdventure.cs class, on this line:

      if(File.Exists(PLAYER_DATA_FILE_NAME))

      Then, you can watch what is happening in your program. I suspect there may be a typo in either the ToXmlString() or CreatePlayerFromXmlString() function of the Player class. XML is very picky, and one little typo will cause an error.

      If you’re still stuck, let me know and we can dig into it some more.

      “Live Coding” example of debugging a C# program

  7. Lucas
    Lucas January 25, 2016

    I am up to date with the tutorial atm and the latest part thats up is https://www.scottlilly.com/learn-c-by-building-a-simple-rpg-index/lesson-21-4-completing-the-trading-screen/ .

    And Everything works exept saving with xml. I dont know what happend really but I had not done any of the “making the code easier” parts but I now have becuase I wanted to add the vendor which I now have and it works. its just that I must have done something wrong and I cant figure out why it wont create the PlayerData.xml file anymore.

    PS: one thing I dont understand either is the “UNSELLABLE_ITEM_PRICE” I dont know what to create with it. sorry if its obvius and I missed it. But I just created a new int in the world.cs thats named “UNSELLABLE_ITEM_PRICE“.

    thx in advance!

    • Scott Lilly
      Scott Lilly January 25, 2016

      Usually, if there is a problem with XML, it is because the node names are different between when they were written, and when they are being read. If the name is mis-typed in one place, that will cause an error – and we do not do error-checking in this project. Can you upload your Player.cs file somewhere (Dropbox, Google Drive, Gist)? Then I can look at it and see if I can find the problem.

      For UNSELLABLE_ITEM_PRICE, did you add this line: public const int UNSELLABLE_ITEM_PRICE = -1; to the World class, in step 4 of Lesson 21.1? A variable, with a descriptive name, will prevent you from asking yourself, “Why do some items have a price of -1?”, when you make changes in the future.

      Please let me know if that answered your question.

  8. Lucas
    Lucas January 26, 2016

    I managed to figure it out! For some reason the close function was not in the event handler thing. So I just had to press the little arrow in the event box and choose a function/method that the program would be executing when it closed^^

    Thx again for all the help and keep up with the great tutorials! I absolutely love them!

  9. J
    J May 19, 2016

    Hey Scott!

    First off, sorry for bothering you so much.

    After reading your explanation and example code for saving and rebuilding the player from XML I wanted to try and manually rebuild your code myself, understanding everything one line at a time with no copy pasting (just reading your code again when I got stuck).

    When writing the CreatePlayerFromXmlString I did the same thing as you did except didnt in-line the collection of xmlnodes because it seemed easier for me like this (because in my mind it seems as if you are running the selectnodes to get the collection after every loop cycle over and over again, while not inlining means only getting the collection once):

    XmlNodeList nodes = playerData.SelectNodes(“/Player/InventoryItems/InventoryItem”);
    foreach (XmlNode node in nodes)
    {
    // read ID and Quantity
    // Create new InventoryItem object
    // add to inventory
    }

    So as you can see I comment what I want to do first and then work through my list of whatever it is I want to do. Then I read your code to check if I was correct, but I saw that you merely used the AddItemToInventory method to loop using the Quantity property to determine the amount of loop cycles. And you didnt have to create an inventoryitem object here because the AddItemToInventory method takes care of that.

    Now the code isn’t hard to understand for me having some experience as a developer. Understanding code that someone else wrote is the easiest thing to learn from.

    The thing that is hard for me to understand (and this is my real actual question) is how you thought of this design in advance!

    When you designed this game, did you think in advance like “well my method is gonna take an item object and I’m going to let that method ‘convert’ it to an inventoryitem rather than adding inventoryitems directly”. Do you design the entire game on paper in advance? Or do you get these insights while going along the way? I probably wouldnt have thought of this in advance (probably creating new InventoryItems in the loadfromxml method, resulting in bad coding practices). Were you able to code like this when you were starting out, or did you learn it from experience (so that I may be able to write code like that later on as well)?!

    Sorry for the wall of text , I just get these thoughts when coding and having fun and I realise there’s still alot for me to learn. This tutorial is awesome 🙂

    • Scott Lilly
      Scott Lilly May 19, 2016

      Hi J!

      It’s no bother 🙂

      When I came up with the idea for the game, I didn’t do much designing. I only thought of the basic functionality I wanted in the game (player moves to locations, gets quests, fights monsters, receives loot, etc.) That gave me the main “business” classes – Player, Location, etc.

      Then, I built the program in the same order as the lessons. For each feature I added, I’d follow this order:
      • Figure out what feature I want to add (and any sub-features it needs)
      • Come up with a rough plan (like what you did with the comments)
      • Write the code for the feature
      • Test that it worked
      • Clean up the code after everything works (I didn’t do that for all these lessons, but that’s what I do in my “real-life” programs.

      What you did, with writing comments, is something many programmers have done for decades. It’s called pseudo-coding. You write down a plan for the functionality to want to do, see if there is anything missing from the plan, then write the code.

      If you started the pseudo-code at a little higher level, it might have looked like this:
      • For each inventory node
      o Read the value from the XML file
      o Add the item to the player’s inventory

      Then, you might remember that you already have a function that adds items to the player’s inventory. So, you only need to pass the XML values to that function, instead of writing something new.

      Or, you could have “refactored” after adding each function. That way, if you wrote the inventory-adding logic inside the loop, you would review your code afterwards. That’s when you might notice the duplicated code, and change the loop to call the already-existing function.

      Much of my decisions for how to write a feature is based on experience. After you do some technique hundreds of times, you just “know” how/when/where to use that technique again. But, there are a couple ways you can gain this type of experience faster.

      Learn design patterns. These are standard solutions to common problems. When you learn these, and work on programs, you’ll start to see places where you should implement those patterns (although, don’t make the mistake of forcing design patterns into places where they aren’t needed). I’m creating a couple videos per month on C# design patterns, but if you want to learn them faster, check out “Head First Design Patterns”. The code samples in the book are in Java, but it’s close enough to C# that you probably won’t have any problems understanding them.

      You could also do programming “katas”. Try to re-write a feature a different way (after saving your current version, so you can go back to it). If a function has a lot of “if” statements, try to re-write it without any “if”s. That might lead you to learning about polymorphism – which will make you familiar another technique. Try to eliminate base classes. That might lead to learning about “composition over inheritance”.

      The important thing is to try new techniques, see where they are good, and see where they are bad. That will give you “more tools in your toolbox”.

  10. Jim
    Jim August 10, 2016

    Hello.

    I ran into a problem. While writing/loading from the XML file works just fine, I wonder what I could do to make the code slimmer.

    For example, this is a part of my “Save” method:

    XmlNode Strength = playerData.CreateElement(“Strength“);
    Strength.AppendChild(playerData.CreateTextNode(p.Strength.ToString()));
    stats.AppendChild(Strength);

    XmlNode Speed = playerData.CreateElement(“Speed“);
    Speed.AppendChild(playerData.CreateTextNode(p.Speed.ToString()));
    stats.AppendChild(Speed);

    XmlNode Dexterity = playerData.CreateElement(“Dexterity“);
    Dexterity.AppendChild(playerData.CreateTextNode(p.Dexterity.ToString()));
    stats.AppendChild(Dexterity);

    …………………………

    As you can see these lines are nearly identical excluding the attribute names. My RPG has many stats as well as other information so the code is quite long as I resort to copy-pasting. Is there any way to fix it?

    • Scott Lilly
      Scott Lilly August 10, 2016

      Hi Jim,

      Cleaning repetitive code is definitely a good idea.

      I’ll give you a hint, so you can practice a little “thinking like a programmer”. If you want to see a completed solution, you can look at the Player class from lesson 24, which has refactoring done for this.

      Those repeated lines of code:
      1. Create an XmlNode with the attribute name
      2, Add the attribute’s value to the node’s text
      3. Add the attribute’s XmlNode to the parent XmlNode (playerData)

      You want to create a function that does all that, but takes parameters for the values – so you can use the new function for each attribute. In this case, it needs: the attribute name, the attribute value, and the parent XmlNode. The function will need to do the same thing the current code does, except it will use the parameters it receives.

      It’s easiest to write the function and only use it for one attribute, to start. When it works for that one attribute, change the code for the other attributes. This way, if you have a problem, there is a very small area to look (only the code that has changed).

      Please let me know if you have any questions about this.

  11. Josh
    Josh June 12, 2017

    + $exception
    {System.FormatException: Input string was not in a correct format. at System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
    at System.Number.ParseInt32(String s, NumberStyles style, NumberFormatInfo info)
    at System.Convert.ToInt32(String value)
    at Engine.Player.CreatePlayerFromXmlString(String xmlPlayerData) in f:\Development\Projects\SuperAdventure\Engine\Player.cs:line 71} System.Exception {System.FormatException}

    I used your above breakpoint recommendation, since I was able to establish for both my girlfriend’s and my own project that my code will write, but not read; however the above code is what i got. Referencing the line I have pointed to

    foreach(XmlNode node in playerData.SelectNodes(“/Player/PlayerQuests/PlayerQuest”))
    {
    This is the broken line >> int id = Convert.ToInt32(node.Attributes[“ID”].Value);
    bool isCompleted = Convert.ToBoolean(node.Attributes[“IsCompleted”].Value);

    PlayerQuest playerQuest = new PlayerQuest(World.QuestByID(id));
    playerQuest.IsCompleted = isCompleted;

    player.Quests.Add(playerQuest);
    }

    My code is identical to yours, so it seems like it should be converting properly but… It clearly isn’t. Since the issue is the same from both of us, without having copied each others code, I a worried the issue is larger than I’m understanding

    • Josh
      Josh June 12, 2017

      To add to my confusion… Provided I do not move more than one ‘location’ away from home (I can stay at home, move to town square, or move to Blacksmith, which I created directly to the south of home) data is saved, but the second I reach the alchemists garden, or farm house the next time I try and load SuperAdventure the above comments exception is triggered

    • Scott Lilly
      Scott Lilly June 12, 2017

      Hi Josh,

      I’ll tell you some more debugging steps. If they don’t help, can you upload your solution (and the files in the folders underneath it – including the PlayerData.xml file) to GitHub or Dropbox?

      Now that you know which line the error happens on, we can start to think of what might cause that error.

      The error happens when the program tries to parse the ID attribute value into an integer. So, I would look at the XML file and see if there is an unexpected, or missing, value. For example, is the value in the ID attribute blank/missing, or not a valid number? Or, is the ID attribute missing, or misnamed. Because XML is case-sensitive, an attribute of “Id” (with a lower-case “d”) would be considered a different attribute from “ID” (with an upper-case attribute). If any of those is the problem, then the Player.ToXmlString() function might need to be fixed.

      Please tell me if that helps you find the source of the problem, or if you want me to look at the source code.

      • Josh
        Josh June 12, 2017

        Alright I resolved two issues, the first made all my error codes go away, which was even more frustrating, the following bit of script
        XmlAttribute idAttribute = playerData.CreateAttribute(“ID”);
        idAttribute.Value = item.Details.ID.ToString();
        inventoryItem.Attributes.Append(idAttribute);

        XmlAttribute quantityAttribute = playerData.CreateAttribute(“Quantity”);
        quantityAttribute.Value = item.Quantity.ToString();
        inventoryItem.Attributes.Append(quantityAttribute);

        was written in such a way that the quantity, not the ID was being written to the XML, for quantity AND ID.

        After I fixed that I was still having issues with the game breaking if I moved toof ar. It turns out that was because whenever I was accepting a quest I had ALSO copied that section in to override the quest section with the same value for PlayerQuest and IsCompleted in the XML.

        I think I just tried to write out too much code, too late at night and skipped around lines without noticing

        Sorry for bothering you, but you sent me in the exact right direction to figure my issue out, and now hopefully I can work out my girlfriends in the same manner!

        your tutorial has been absolutely invaluable; Once I complete this (with all your bonus goodies to add) I hope I’m able to find more stuff from you, because you rock!

        • Scott Lilly
          Scott Lilly June 13, 2017

          Great! Figuring out how to track down a bug, and fix it, is a major part of programming – unfortunately 🙂

          Let me know if you need help finding the source of the problem in your girlfriend’s code.

          • Josh
            Josh June 15, 2017

            LINK REMOVED FOR PRIVACY

            I gave it two days of going over her code after work.She did her camel casing/pascal casing differently than your tutorial, so I’m half tempted to scrap it and just copy my project over to her.

            I did the breakpoint fix in the ‘catch’ method to no avail, and have since removed it. It wasn’t catching any exceptions.

            I pored over her xml, but aside from different casing on variables I don’t see anything that would be pushing out nor grabbing the wrong data.

            as it stands her game boots up and runs just fine, saves data at shutdown, but always loads default values. I threw a rtbmessage on her load if/else to see if it was going to default that way, but it ‘says’ it’s loading her playerdata xml.

          • Scott Lilly
            Scott Lilly June 17, 2017

            There are two things that need to be fixed in the CreatePlayerFromXmlString function.

            First, after getting the currentLocationID value, it needs to be set on the Player object. So, after line 66, in the Player class, add this line:
            player.CurrentLocation = World.LocationByID(currentLocationID);

            Second, on line 68, the foreach is currently trying to loop through the child nodes of “/Player/Stats/CurrentLocation”. This is the foreach for the player’s inventory items, which needs to use the XML node “/Player/InventoryItems/InventoryItem”.

            Because of the problem with the inventory items, you might need to manually add a weapon to her PlayerData.xml file – or add temporarily a line like this to the CreatePlayerFromXmlString function:
            player.AddItemToInventory(World.ItemByID(World.ITEM_ID_CLUB));
            You could put that after the line that assigns the player.CurrentLocation, run the program, exit the program (so it saves the club in the PlayerData,xml file), then remove the line.

            Please tell me if you have any other questions.

  12. Anjelica
    Anjelica July 3, 2017

    It seems something is missing in my Visual Studio.

    The “using System.Xml;” is grey and it says that it is unnecessary, I suggest that there is a reference missing.

    Also I have a lot of errors: All “XmlDocument”, “XmlNode” and “XmlAttribute” are underlined.
    (“type- or namespacename is missing”)

    (The correction suggest to change it to “using.System.Xml.Linq” and “XDocument”/”XNode”/”XAttribute”. )

    Because I’m not very experienced with this I’m not sure about changing it, or where to find the right reference. Maybe I missed something at the installation process…

    I had the same error with the RNGCryptoServiceProvider and the “using System.Security.Cryptography;” in Lesson 15.1, so I took the easy-way-generator instead of the other one.

    Sorry for my stupid questions.

    • Scott Lilly
      Scott Lilly July 4, 2017

      I looked at your project, and I think I see where the problem is. In Visual Studio 2017, there is a new type of Class Library project. The Engine project should be the “.NET Framework” version, not the “.NET Standard” version. I converted the Engine project to .NET Framework, and uploaded it to Dropbox.

      Can you please download that (to a new directory, so you still have your original version, in case you still need it), and tell me if that fixes the errors?

      null

      • Justin
        Justin October 22, 2017

        I was having the same issue myself, as a matter of fact I am still having the issue. I’ve tried making sure its correct .Framework. It’s set to .NET Framework 4.6 tried adding the using.System.Xml.Linq to no avail. I am still learning this coding thing. Any idea what could be the problem?

        • Scott Lilly
          Scott Lilly October 24, 2017

          Justin,

          Did you add:
          using System.Xml.Linq;

          Or, did you add:
          using System.Xml;

          It should be the second one – without “.Linq”

          If that doesn’t fix the problem, can you upload your solution (including the folders under it, and the files in those folders) to GitHub or Dropbox, so I can look at it?

  13. Anjelica
    Anjelica July 4, 2017

    Yay, its all working now the way it should.
    Thank you very much for your help again.
    Next time I will look more careful what to choose and why.

    • Scott Lilly
      Scott Lilly July 4, 2017

      Cool!

      I updated the original lesson, where the projects are created in the solution. These lessons are three years old, and Microsoft has changed Visual Studio since I wrote them. So, there are probably several other people who did not know which “Class Library” project to choose.

  14. Anjelica
    Anjelica July 7, 2017

    That’s a good idea.

    Yes, it is kind of complicated when you look at Visual Studio the first time and don’t know what to choose and why, because there is so much and for a beginner it is not clear what the differences are.

    Personally, I didn’t wanted to study all this before trying to program anything. I’m more into learing by doing and try and error 🙂 It’s easier to remember things you have to correct or change then doing it once and finish it and go to another thing or lesson right away 😉

  15. Matt
    Matt December 20, 2018

    Hey Scott,

    I feel like I’m reviving this problem again, I’ve tried reading through these comments on the issue with XML, but I can’t for the life of me find the problem. I’ve looked over my work, even downloaded your completed game and compared. There’s something I’m missing and I just can’t spot it.

    I’m having the issue where my XML will save but will not load, I thought I understood that the error must be in “CreatePlayerFrom…” I’ve read it over many times now.

    I’ve uploaded my version to Git if you could please lead me in the right direction It would be greatly appreciated!

    https://github.com/Arcticflare/SuperAdventure

    • Scott Lilly
      Scott Lilly December 25, 2018

      Hi Matt,

      The problem is in the Player.ToXMLString() function. Line 195 writes this value to the CurrentLocation XML node:

      this.CurrentLocation.ToString()

      When you call .ToString() on an object, it will write the name of the object (unless you specifically create a ToString function that does something different). So, when the PlayerData.xml file is created, it looks like line 7 of PlayerData_ORIGINAL.xml here: https://gist.github.com/ScottLilly/c7eab205b98bf50681214edb0d9db644 – with a value of “Engine.Location” for the CurrentLocation node.

      If you change line 195 to write this:

      this.CurrentLocation.ID.ToString()

      The XML will write the ID of the current location object – which is what you’ll see in the PlayerData_FIXED.xml sample. The code that reads the XML, and tries to create a Player object, is expecting the location’s ID – which is why it fails when it sees “Engine.Location”.

      Let me know if that doesn’t work, or if it isn’t clear.

  16. Steve
    Steve April 18, 2023

    Hi Scott,

    I’m loving this tutorial series and have found it much more helpful when learning C# then some books I have been working through.

    My version seems to be working for the most part. When I close the game all the info properly saves to the XML file.

    When I re-open the game it doesn’t seem to pull from the xml and just creates a new default.

    When I debug it flags Player.cs “int currentHitPoints = Convert.ToInt32(playerData.SelectSingleNode(“Player/Stats/CurrentHitPoints”).InnerText);”
    I keep getting “Exception Thrown System.NullReferenceException: ‘Object reference not set to an instance of an object”

    It does this to the currentHitPoints and currentWeaponID from lesson 19.5.

    Then in the SuperAdventure.cs it flags “_player = Player.CreatePlayerFromXmlString(File.ReadAllText(PLAYER_DATA_FILE_NAME));

    Not sure why with that one but I figure it has something to do with my issue in Player.cs.

    What could be causing this?

    Thanks

    • Scott Lilly
      Scott Lilly April 19, 2023

      Hi Steve,

      My first thought is that there might be a typo in one of the node names probably “CurrentHitPoints”, if everything else is loading correctly. With XML, it’s important to make sure the upper and lower casing matches between the code that writes the node and the code that reads it. You should be able to open the game data XML file in Visual Studio or Notepad.

      If that doesn’t help 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/

Leave a Reply to David Cancel reply

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