Now that we added the code to save and load a game, let’s see if we can improve it.
The first thing I’d like to improve is the size of the save game file. We serialize all the objects’ properties, but only use a few of them.
So, let’s add an attribute to the properties we want the SerializeObject function to ignore.
Step 1: Modify \Engine\Models\GameItem.cs
The only property we need from this class is the ItemTypeID. We pass that value to the factory and it creates the GameItem object, with all the other values.
So, we’ll add the new “using Newtonsoft.Json;” directive on line 2 (so we can use classes from the Newtonsoft.Json NuGet package.
Then, we’ll add a “[JsonIgnore]” attribute before all the properties we don’t need – all of them except ItemTypeID. This way, when we call JsonConvert.SerializeObject in SaveGameService, those properties will not be in the string that is created.
This is what we’ll do for the other classes in this lesson.
GameItem.cs
using Engine.Actions;
using Newtonsoft.Json;
namespace Engine.Models
{
public class GameItem
{
public enum ItemCategory
{
Miscellaneous,
Weapon,
Consumable
}
[JsonIgnore]
public ItemCategory Category { get; }
public int ItemTypeID { get; }
[JsonIgnore]
public string Name { get; }
[JsonIgnore]
public int Price { get; }
[JsonIgnore]
public bool IsUnique { get; }
[JsonIgnore]
public IAction Action { get; set; }
public GameItem(ItemCategory category, int itemTypeID, string name, int price,
bool isUnique = false, IAction action = null)
{
Category = category;
ItemTypeID = itemTypeID;
Name = name;
Price = price;
IsUnique = isUnique;
Action = action;
}
public void PerformAction(LivingEntity actor, LivingEntity target)
{
Action?.Execute(actor, target);
}
public GameItem Clone()
{
return new GameItem(Category, ItemTypeID, Name, Price, IsUnique, Action);
}
}
}
Step 2: Modify \Engine\Models\Inventory.cs
Add “using Newtonsoft.Json;” and put a “[JsonIgnore]” attribute before: GroupedInventoryItems, Weapons, Consumables, and HasConsumable. All those properties will be populated when we add objects to the Items property.
Inventory.cs
using System.Collections.Generic;
using System.Linq;
using Engine.Services;
using Newtonsoft.Json;
namespace Engine.Models
{
public class Inventory
{
#region Backing variables
private readonly List<GameItem> _backingInventory =
new List<GameItem>();
private readonly List<GroupedInventoryItem> _backingGroupedInventoryItems =
new List<GroupedInventoryItem>();
#endregion
#region Properties
public IReadOnlyList<GameItem> Items => _backingInventory.AsReadOnly();
[JsonIgnore]
public IReadOnlyList<GroupedInventoryItem> GroupedInventory =>
_backingGroupedInventoryItems.AsReadOnly();
[JsonIgnore]
public IReadOnlyList<GameItem> Weapons =>
_backingInventory.ItemsThatAre(GameItem.ItemCategory.Weapon).AsReadOnly();
[JsonIgnore]
public IReadOnlyList<GameItem> Consumables =>
_backingInventory.ItemsThatAre(GameItem.ItemCategory.Consumable).AsReadOnly();
[JsonIgnore]
public bool HasConsumable => Consumables.Any();
#endregion
#region Constructors
public Inventory(IEnumerable<GameItem> items = null)
{
if(items == null)
{
return;
}
foreach(GameItem item in items)
{
_backingInventory.Add(item);
AddItemToGroupedInventory(item);
}
}
#endregion
#region Public functions
public bool HasAllTheseItems(IEnumerable<ItemQuantity> items)
{
return items.All(item => Items.Count(i => i.ItemTypeID == item.ItemID) >= item.Quantity);
}
#endregion
#region Private functions
// REFACTOR: Look for a better way to do this (extension method?)
private void AddItemToGroupedInventory(GameItem item)
{
if(item.IsUnique)
{
_backingGroupedInventoryItems.Add(new GroupedInventoryItem(item, 1));
}
else
{
if(_backingGroupedInventoryItems.All(gi => gi.Item.ItemTypeID != item.ItemTypeID))
{
_backingGroupedInventoryItems.Add(new GroupedInventoryItem(item, 0));
}
_backingGroupedInventoryItems.First(gi => gi.Item.ItemTypeID == item.ItemTypeID).Quantity++;
}
}
#endregion
}
}
Step 3: Modify \Engine\Models\LivingEntity.cs
Add “using Newtonsoft.Json;” and put a “[JsonIgnore]” attribute before: IsAlive and IsDead.
LivingEntity.cs
using System;
using System.Collections.Generic;
using Engine.Services;
using Newtonsoft.Json;
namespace Engine.Models
{
public abstract class LivingEntity : BaseNotificationClass
{
#region Properties
private string _name;
private int _dexterity;
private int _currentHitPoints;
private int _maximumHitPoints;
private int _gold;
private int _level;
private GameItem _currentWeapon;
private GameItem _currentConsumable;
private Inventory _inventory;
public string Name
{
get => _name;
private set
{
_name = value;
OnPropertyChanged();
}
}
public int Dexterity
{
get => _dexterity;
private set
{
_dexterity = value;
OnPropertyChanged();
}
}
public int CurrentHitPoints
{
get => _currentHitPoints;
private set
{
_currentHitPoints = value;
OnPropertyChanged();
}
}
public int MaximumHitPoints
{
get => _maximumHitPoints;
protected set
{
_maximumHitPoints = value;
OnPropertyChanged();
}
}
public int Gold
{
get => _gold;
private set
{
_gold = value;
OnPropertyChanged();
}
}
public int Level
{
get => _level;
protected set
{
_level = value;
OnPropertyChanged();
}
}
public Inventory Inventory
{
get => _inventory;
private set
{
_inventory = value;
OnPropertyChanged();
}
}
public GameItem CurrentWeapon
{
get => _currentWeapon;
set
{
if (_currentWeapon != null)
{
_currentWeapon.Action.OnActionPerformed -= RaiseActionPerformedEvent;
}
_currentWeapon = value;
if (_currentWeapon != null)
{
_currentWeapon.Action.OnActionPerformed += RaiseActionPerformedEvent;
}
OnPropertyChanged();
}
}
public GameItem CurrentConsumable
{
get => _currentConsumable;
set
{
if(_currentConsumable != null)
{
_currentConsumable.Action.OnActionPerformed -= RaiseActionPerformedEvent;
}
_currentConsumable = value;
if (_currentConsumable != null)
{
_currentConsumable.Action.OnActionPerformed += RaiseActionPerformedEvent;
}
OnPropertyChanged();
}
}
[JsonIgnore]
public bool IsAlive => CurrentHitPoints > 0;
[JsonIgnore]
public bool IsDead => !IsAlive;
#endregion
public event EventHandler<string> OnActionPerformed;
public event EventHandler OnKilled;
protected LivingEntity(string name, int maximumHitPoints, int currentHitPoints,
int dexterity, int gold, int level = 1)
{
Name = name;
Dexterity = dexterity;
MaximumHitPoints = maximumHitPoints;
CurrentHitPoints = currentHitPoints;
Gold = gold;
Level = level;
Inventory = new Inventory();
}
public void UseCurrentWeaponOn(LivingEntity target)
{
CurrentWeapon.PerformAction(this, target);
}
public void UseCurrentConsumable()
{
CurrentConsumable.PerformAction(this, this);
RemoveItemFromInventory(CurrentConsumable);
}
public void TakeDamage(int hitPointsOfDamage)
{
CurrentHitPoints -= hitPointsOfDamage;
if(IsDead)
{
CurrentHitPoints = 0;
RaiseOnKilledEvent();
}
}
public void Heal(int hitPointsToHeal)
{
CurrentHitPoints += hitPointsToHeal;
if(CurrentHitPoints > MaximumHitPoints)
{
CurrentHitPoints = MaximumHitPoints;
}
}
public void CompletelyHeal()
{
CurrentHitPoints = MaximumHitPoints;
}
public void ReceiveGold(int amountOfGold)
{
Gold += amountOfGold;
}
public void SpendGold(int amountOfGold)
{
if(amountOfGold > Gold)
{
throw new ArgumentOutOfRangeException($"{Name} only has {Gold} gold, and cannot spend {amountOfGold} gold");
}
Gold -= amountOfGold;
}
public void AddItemToInventory(GameItem item)
{
Inventory = Inventory.AddItem(item);
}
public void RemoveItemFromInventory(GameItem item)
{
Inventory = Inventory.RemoveItem(item);
}
public void RemoveItemsFromInventory(IEnumerable<ItemQuantity> itemQuantities)
{
Inventory = Inventory.RemoveItems(itemQuantities);
}
#region Private functions
private void RaiseOnKilledEvent()
{
OnKilled?.Invoke(this, new System.EventArgs());
}
private void RaiseActionPerformedEvent(object sender, string result)
{
OnActionPerformed?.Invoke(this, result);
}
#endregion
}
}
Step 4: Modify \Engine\Models\Location.cs
Add “using Newtonsoft.Json;” and put a “[JsonIgnore]” attribute before: Name, Description, ImageName, QuestsAvailableHere, MonstersHere, and TraderHere.
Location.cs
using System.Collections.Generic;
using System.Linq;
using Engine.Factories;
using Newtonsoft.Json;
namespace Engine.Models
{
public class Location
{
public int XCoordinate { get; }
public int YCoordinate { get; }
[JsonIgnore]
public string Name { get; }
[JsonIgnore]
public string Description { get; }
[JsonIgnore]
public string ImageName { get; }
[JsonIgnore]
public List<Quest> QuestsAvailableHere { get; } = new List<Quest>();
[JsonIgnore]
public List<MonsterEncounter> MonstersHere { get; } =
new List<MonsterEncounter>();
[JsonIgnore]
public Trader TraderHere { get; set; }
public Location(int xCoordinate, int yCoordinate, string name, string description, string imageName)
{
XCoordinate = xCoordinate;
YCoordinate = yCoordinate;
Name = name;
Description = description;
ImageName = imageName;
}
public void AddMonster(int monsterID, int chanceOfEncountering)
{
if(MonstersHere.Exists(m => m.MonsterID == monsterID))
{
// This monster has already been added to this location.
// So, overwrite the ChanceOfEncountering with the new number.
MonstersHere.First(m => m.MonsterID == monsterID)
.ChanceOfEncountering = chanceOfEncountering;
}
else
{
// This monster is not already at this location, so add it.
MonstersHere.Add(new MonsterEncounter(monsterID, chanceOfEncountering));
}
}
public Monster GetMonster()
{
if(!MonstersHere.Any())
{
return null;
}
// Total the percentages of all monsters at this location.
int totalChances = MonstersHere.Sum(m => m.ChanceOfEncountering);
// Select a random number between 1 and the total (in case the total chances is not 100).
int randomNumber = RandomNumberGenerator.NumberBetween(1, totalChances);
// Loop through the monster list,
// adding the monster's percentage chance of appearing to the runningTotal variable.
// When the random number is lower than the runningTotal,
// that is the monster to return.
int runningTotal = 0;
foreach(MonsterEncounter monsterEncounter in MonstersHere)
{
runningTotal += monsterEncounter.ChanceOfEncountering;
if(randomNumber <= runningTotal)
{
return MonsterFactory.GetMonster(monsterEncounter.MonsterID);
}
}
// If there was a problem, return the last monster in the list.
return MonsterFactory.GetMonster(MonstersHere.Last().MonsterID);
}
}
}
Step 5: Modify \Engine\Models\Quest.cs
Add “using Newtonsoft.Json;” and put a “[JsonIgnore]” attribute before: Name, Description, ItemsToComplete, RewardExperiencePoints, RewardGold, RewardItems, and ToolTipContents.
Quest.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
namespace Engine.Models
{
public class Quest
{
public int ID { get; }
[JsonIgnore]
public string Name { get; }
[JsonIgnore]
public string Description { get; }
[JsonIgnore]
public List<ItemQuantity> ItemsToComplete { get; }
[JsonIgnore]
public int RewardExperiencePoints { get; }
[JsonIgnore]
public int RewardGold { get; }
[JsonIgnore]
public List<ItemQuantity> RewardItems { get; }
[JsonIgnore]
public string ToolTipContents =>
Description + Environment.NewLine + Environment.NewLine +
"Items to complete the quest" + Environment.NewLine +
"===========================" + Environment.NewLine +
string.Join(Environment.NewLine, ItemsToComplete.Select(i => i.QuantityItemDescription)) +
Environment.NewLine + Environment.NewLine +
"Rewards\r\n" +
"===========================" + Environment.NewLine +
$"{RewardExperiencePoints} experience points" + Environment.NewLine +
$"{RewardGold} gold pieces" + Environment.NewLine +
string.Join(Environment.NewLine, RewardItems.Select(i => i.QuantityItemDescription));
public Quest(int id, string name, string description, List<ItemQuantity> itemsToComplete,
int rewardExperiencePoints, int rewardGold, List<ItemQuantity> rewardItems)
{
ID = id;
Name = name;
Description = description;
ItemsToComplete = itemsToComplete;
RewardExperiencePoints = rewardExperiencePoints;
RewardGold = rewardGold;
RewardItems = rewardItems;
}
}
}
Step 6: Modify \Engine\Models\Recipe.cs
Add “using Newtonsoft.Json;” and put a “[JsonIgnore]” attribute before: Name, Ingredients, OutputItems, and ToolTipContents.
Recipe.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
namespace Engine.Models
{
public class Recipe
{
public int ID { get; }
[JsonIgnore]
public string Name { get; }
[JsonIgnore]
public List<ItemQuantity> Ingredients { get; } = new List<ItemQuantity>();
[JsonIgnore]
public List<ItemQuantity> OutputItems { get; } = new List<ItemQuantity>();
[JsonIgnore]
public string ToolTipContents =>
"Ingredients" + Environment.NewLine +
"===========" + Environment.NewLine +
string.Join(Environment.NewLine, Ingredients.Select(i => i.QuantityItemDescription)) +
Environment.NewLine + Environment.NewLine +
"Creates" + Environment.NewLine +
"===========" + Environment.NewLine +
string.Join(Environment.NewLine, OutputItems.Select(i => i.QuantityItemDescription));
public Recipe(int id, string name)
{
ID = id;
Name = name;
}
public void AddIngredient(int itemID, int quantity)
{
if(!Ingredients.Any(x => x.ItemID == itemID))
{
Ingredients.Add(new ItemQuantity(itemID, quantity));
}
}
public void AddOutputItem(int itemID, int quantity)
{
if(!OutputItems.Any(x => x.ItemID == itemID))
{
OutputItems.Add(new ItemQuantity(itemID, quantity));
}
}
}
}
Step 7: Modify \Engine\Models\GameSession.cs
Add “using Newtonsoft.Json;” and put a “[JsonIgnore]” attribute before: CurrentWorld, CurrentMonster, CurrentTrader, HasLocationToNorth, HasLocationToEast, HasLocationToSouth, HasLocationToWest, HasMonster, and HasTrader.
GameSession.cs
using System.Linq;
using Engine.Factories;
using Engine.Models;
using Engine.Services;
using Newtonsoft.Json;
namespace Engine.ViewModels
{
public class GameSession : BaseNotificationClass
{
private readonly MessageBroker _messageBroker = MessageBroker.GetInstance();
#region Properties
private Player _currentPlayer;
private Location _currentLocation;
private Battle _currentBattle;
private Monster _currentMonster;
private Trader _currentTrader;
public string Version { get; } = "0.1.000";
[JsonIgnore]
public World CurrentWorld { get; }
public Player CurrentPlayer
{
get => _currentPlayer;
set
{
if(_currentPlayer != null)
{
_currentPlayer.OnLeveledUp -= OnCurrentPlayerLeveledUp;
_currentPlayer.OnKilled -= OnPlayerKilled;
}
_currentPlayer = value;
if(_currentPlayer != null)
{
_currentPlayer.OnLeveledUp += OnCurrentPlayerLeveledUp;
_currentPlayer.OnKilled += OnPlayerKilled;
}
}
}
public Location CurrentLocation
{
get => _currentLocation;
set
{
_currentLocation = value;
OnPropertyChanged();
OnPropertyChanged(nameof(HasLocationToNorth));
OnPropertyChanged(nameof(HasLocationToEast));
OnPropertyChanged(nameof(HasLocationToWest));
OnPropertyChanged(nameof(HasLocationToSouth));
CompleteQuestsAtLocation();
GivePlayerQuestsAtLocation();
CurrentMonster = CurrentLocation.GetMonster();
CurrentTrader = CurrentLocation.TraderHere;
}
}
[JsonIgnore]
public Monster CurrentMonster
{
get => _currentMonster;
set
{
if(_currentBattle != null)
{
_currentBattle.OnCombatVictory -= OnCurrentMonsterKilled;
_currentBattle.Dispose();
}
_currentMonster = value;
if(_currentMonster != null)
{
_currentBattle = new Battle(CurrentPlayer, CurrentMonster);
_currentBattle.OnCombatVictory += OnCurrentMonsterKilled;
}
OnPropertyChanged();
OnPropertyChanged(nameof(HasMonster));
}
}
[JsonIgnore]
public Trader CurrentTrader
{
get => _currentTrader;
set
{
_currentTrader = value;
OnPropertyChanged();
OnPropertyChanged(nameof(HasTrader));
}
}
[JsonIgnore]
public bool HasLocationToNorth =>
CurrentWorld.LocationAt(CurrentLocation.XCoordinate, CurrentLocation.YCoordinate + 1) != null;
[JsonIgnore]
public bool HasLocationToEast =>
CurrentWorld.LocationAt(CurrentLocation.XCoordinate + 1, CurrentLocation.YCoordinate) != null;
[JsonIgnore]
public bool HasLocationToSouth =>
CurrentWorld.LocationAt(CurrentLocation.XCoordinate, CurrentLocation.YCoordinate - 1) != null;
[JsonIgnore]
public bool HasLocationToWest =>
CurrentWorld.LocationAt(CurrentLocation.XCoordinate - 1, CurrentLocation.YCoordinate) != null;
[JsonIgnore]
public bool HasMonster => CurrentMonster != null;
[JsonIgnore]
public bool HasTrader => CurrentTrader != null;
#endregion
public GameSession()
{
CurrentWorld = WorldFactory.CreateWorld();
int dexterity = RandomNumberGenerator.NumberBetween(3, 18);
CurrentPlayer = new Player("Scott", "Fighter", 0, 10, 10, dexterity, 1000000);
if (!CurrentPlayer.Inventory.Weapons.Any())
{
CurrentPlayer.AddItemToInventory(ItemFactory.CreateGameItem(1001));
}
CurrentPlayer.AddItemToInventory(ItemFactory.CreateGameItem(2001));
CurrentPlayer.LearnRecipe(RecipeFactory.RecipeByID(1));
CurrentPlayer.AddItemToInventory(ItemFactory.CreateGameItem(3001));
CurrentPlayer.AddItemToInventory(ItemFactory.CreateGameItem(3002));
CurrentPlayer.AddItemToInventory(ItemFactory.CreateGameItem(3003));
CurrentLocation = CurrentWorld.LocationAt(0, 0);
}
public GameSession(Player player, int xCoordinate, int yCoordinate)
{
CurrentWorld = WorldFactory.CreateWorld();
CurrentPlayer = player;
CurrentLocation = CurrentWorld.LocationAt(xCoordinate, yCoordinate);
}
public void MoveNorth()
{
if(HasLocationToNorth)
{
CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate, CurrentLocation.YCoordinate + 1);
}
}
public void MoveEast()
{
if(HasLocationToEast)
{
CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate + 1, CurrentLocation.YCoordinate);
}
}
public void MoveSouth()
{
if(HasLocationToSouth)
{
CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate, CurrentLocation.YCoordinate - 1);
}
}
public void MoveWest()
{
if(HasLocationToWest)
{
CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate - 1, CurrentLocation.YCoordinate);
}
}
private void CompleteQuestsAtLocation()
{
foreach(Quest quest in CurrentLocation.QuestsAvailableHere)
{
QuestStatus questToComplete =
CurrentPlayer.Quests.FirstOrDefault(q => q.PlayerQuest.ID == quest.ID &&
!q.IsCompleted);
if(questToComplete != null)
{
if(CurrentPlayer.Inventory.HasAllTheseItems(quest.ItemsToComplete))
{
CurrentPlayer.RemoveItemsFromInventory(quest.ItemsToComplete);
_messageBroker.RaiseMessage("");
_messageBroker.RaiseMessage($"You completed the '{quest.Name}' quest");
// Give the player the quest rewards
_messageBroker.RaiseMessage($"You receive {quest.RewardExperiencePoints} experience points");
CurrentPlayer.AddExperience(quest.RewardExperiencePoints);
_messageBroker.RaiseMessage($"You receive {quest.RewardGold} gold");
CurrentPlayer.ReceiveGold(quest.RewardGold);
foreach(ItemQuantity itemQuantity in quest.RewardItems)
{
GameItem rewardItem = ItemFactory.CreateGameItem(itemQuantity.ItemID);
_messageBroker.RaiseMessage($"You receive a {rewardItem.Name}");
CurrentPlayer.AddItemToInventory(rewardItem);
}
// Mark the Quest as completed
questToComplete.IsCompleted = true;
}
}
}
}
private void GivePlayerQuestsAtLocation()
{
foreach(Quest quest in CurrentLocation.QuestsAvailableHere)
{
if(!CurrentPlayer.Quests.Any(q => q.PlayerQuest.ID == quest.ID))
{
CurrentPlayer.Quests.Add(new QuestStatus(quest));
_messageBroker.RaiseMessage("");
_messageBroker.RaiseMessage($"You receive the '{quest.Name}' quest");
_messageBroker.RaiseMessage(quest.Description);
_messageBroker.RaiseMessage("Return with:");
foreach(ItemQuantity itemQuantity in quest.ItemsToComplete)
{
_messageBroker
.RaiseMessage($" {itemQuantity.Quantity} {ItemFactory.CreateGameItem(itemQuantity.ItemID).Name}");
}
_messageBroker.RaiseMessage("And you will receive:");
_messageBroker.RaiseMessage($" {quest.RewardExperiencePoints} experience points");
_messageBroker.RaiseMessage($" {quest.RewardGold} gold");
foreach(ItemQuantity itemQuantity in quest.RewardItems)
{
_messageBroker
.RaiseMessage($" {itemQuantity.Quantity} {ItemFactory.CreateGameItem(itemQuantity.ItemID).Name}");
}
}
}
}
public void AttackCurrentMonster()
{
_currentBattle.AttackOpponent();
}
public void UseCurrentConsumable()
{
if(CurrentPlayer.CurrentConsumable != null)
{
CurrentPlayer.UseCurrentConsumable();
}
}
public void CraftItemUsing(Recipe recipe)
{
if(CurrentPlayer.Inventory.HasAllTheseItems(recipe.Ingredients))
{
CurrentPlayer.RemoveItemsFromInventory(recipe.Ingredients);
foreach(ItemQuantity itemQuantity in recipe.OutputItems)
{
for(int i = 0; i < itemQuantity.Quantity; i++)
{
GameItem outputItem = ItemFactory.CreateGameItem(itemQuantity.ItemID);
CurrentPlayer.AddItemToInventory(outputItem);
_messageBroker.RaiseMessage($"You craft 1 {outputItem.Name}");
}
}
}
else
{
_messageBroker.RaiseMessage("You do not have the required ingredients:");
foreach(ItemQuantity itemQuantity in recipe.Ingredients)
{
_messageBroker
.RaiseMessage($" {itemQuantity.Quantity} {ItemFactory.ItemName(itemQuantity.ItemID)}");
}
}
}
private void OnPlayerKilled(object sender, System.EventArgs e)
{
_messageBroker.RaiseMessage("");
_messageBroker.RaiseMessage("You have been killed.");
CurrentLocation = CurrentWorld.LocationAt(0, -1);
CurrentPlayer.CompletelyHeal();
}
private void OnCurrentMonsterKilled(object sender, System.EventArgs eventArgs)
{
// Get another monster to fight
CurrentMonster = CurrentLocation.GetMonster();
}
private void OnCurrentPlayerLeveledUp(object sender, System.EventArgs eventArgs)
{
_messageBroker.RaiseMessage($"You are now level {CurrentPlayer.Level}!");
}
}
}
Step 8: Test the changes
Open the game, move around, then exit the game. Re-open the game and check that the player is in the last location.
Even though we changed the format of the save game file, we don’t need to give its format a new version number. This is because we didn’t change any of the properties that were used in our function that deserializes the save game data.
NEXT LESSON: Lesson 17.3: Add a menu to save and load the game state
PREVIOUS LESSON: Lesson 17.1: Saving and loading game state