Lesson 10.3: Refactoring – Encapsulating LivingEntity Properties (Hit Points and Gold)

One principle of Object-Oriented Programming is “encapsulation”.

With encapsulation, you don’t allow one class to directly change property values in another class. Instead, you have functions that control changing a property’s value. This way, you can prevent the property from being set to a bad value. You can also run additional code, if we need to.

We already do this in the AddItemToInventory and RemoveItemFromInventory function in LivingEntity – although, we don’t make it mandatory. In this lesson, we’re going to encapsulate changing a LivingEntity’s hit points and gold.

 

 

 

Lesson Steps

Step 1: Modify Engine\Models\LivingEntity.cs

First, we’ll make the setters for Name, CurrentHitPoints, MaximumHitPoints, and Gold to be private (lines 20, 30, 40, and 50). This way, their values can only change within other functions inside the LivingEntity class.

This will cause many errors, because we currently change those properties in many other classes.

Next, we’ll add a new Boolean property on line 64, “IsDead”. This is an “expression-body property” that returns the result of a computation.

For this property, the computation will check if CurrentHitPoints are less than, or equal to, zero. If so, the computation returns “true”. If CurrentHitPoints is greater than 0, the computation returns “false”.

We don’t need the IsDead property. I just think it is a little clearer than always checking “if(CurrentHitPoints <= 0)”.

 

On line 68, we add a new event “OnKilled”. The GameSession class is going to subscribe to (listen to) this event. When a LivingEntity (Player. Monster, or Trader) is killed, LivingEntity will raise this event and notify any subscribers.

 

Because we changed Name, MaximumHitPoints, CurrentHitPoints, and Gold to have private setters, we can only set their value inside LivingEntity. So, we are going to pass the values for those properties into the constructor and set them there. Those changes are on lines 70 through 75.

 

On lines 81 through 120 we have several new functions. Other classes will use these functions to change a LivingEntity’s Gold or CurrentHitPoints value. We also added some extra logic in some of these functions. For example:

TakeDamage() checks if the player is dead, after taking damage. If so, it sets their CurrentHitPoints to 0 (in case CurrentHitPoints became negative) and calls the new RaiseOnKilledEvent() function.

Heal() ensures CurrentHitPoints can never be greater than MaximumHitPoints.

SpendGold() throw an exception if we ever try to spend more gold than the LivingEntity has.

 

Finally, on line 167, we have the RaiseOnKilledEvent function. TakeDamage() calls this if the LivingEntity’s CurrentHitPoints drops to zero or less, to notify other classes subscribed to the OnKilled event.

 

LivingEntity.cs

 

 

Step 2: Update Engine\Models\Player.cs, Monster.cs, and Trader.cs

Because we added new parameters to the LivingEntity constructor, we need to update its child classes to pass in values for those parameters.

The Trader doesn’t really need hit points or gold (currently). So, we pass in large hard-coded values of 9999 for these parameters.

 

Player.cs

Monster.cs

 

 

Trader.cs

 

 

Step 3: Modify Engine\ViewModels\GameSession.cs

The first thing we need to fix is the GameSession constructor. This is where we instantiate the CurrentPlayer object (line 121) – which now needs to have its property values passed in as constructor parameters.

 

Next, we’ll have GameSession subscribe to the OnKilled event for the CurrentPlayer and CurrentMonster.

To do this for CurrentPlayer, we need to change it to use a backing variable, because we need to add logic into its setter.

When an object subscribes to another object’s event, you also need to handle unsubscribing to it. This helps the program get rid of old objects in memory.

When we set the CurrentPlayer property, we want to unsubscribe from the previous _currentPlayer’s OnKilled event – if _currentPlayer already has a value. This is what’s happening on lines 27 through 30. The “-=” unsubscribes from an event. The “OnCurrentPlayerKilled” function is the function that will run if the Player object raises an OnKilled event (the RaiseOnKilledEvent function we added to LivingEntity.cs).

After unsubscribing, we set the backing variable to the new value passed to the setter on line 32.

Now that we’ve changed the backing variable to the new object for CurrentPlayer, we want to subscribe to its OnKilled event, if the value wasn’t null. This is on lines 34 through 37.

We don’t really need this for CurrentPlayer, because we only set the value once. But, it could be useful in the future.

 

We do need to use this technique for the CurrentMonster property – because we frequently set that property to a new object. The code for this is on lines 67 through 80.

When the CurrentMonster raises an OnKilled event, GameSession will run the OnCurrentMonsterKilled function.

 

Next, we need to change the AttackCurrentMonster function on line 245. Instead of having this function check if the monster or player is killed, we will let the event-handling functions deal with that.

So now, this function only needs to handle applying damage to the player or monster, and instantiating a new monster (if the current monster was killed).

 

I did have to change the order of some of the RaiseMessage lines. Before, they were after applying damage to the player and monster. However, with the new event-handling functions, this could lead to a problem.

For example, if lines 282 and 283 were reversed (their previous order), the player could take damage and be killed. The event-handling code moves the player to their home, if they are killed. Because they are at a new location, we try to get the monster at that location. But there is no monster at the player’s home. So, CurrentMonster gets set to null.

This would cause an error with the RaiseMessage that is now on line 282. There is no “CurrentMonster”, so we can’t display CurrentMonster.Name.

So now, we have the RaiseMessage lines before we apply any changes to an object.

 

On lines 288 through 295, we have the new OnCurrentPlayerKilled function. This runs when the CurrentPlayer.OnKilled event is raised. It displays a message, moves the player to their home, and completely heals them.

 

On lines 297 through 313, we have the new OnCurrentMonsterKilled function – for when CurrentMonster.OnKilled is raised. This now holds the code to give the player their loot from defeating the monster.

 

GameSession.cs

 

 

Step 4: Modify WPFUI\TradeScreen.xaml.cs

Now that the LivingEntity Gold property is encapsulated, we need to change how we add and subtract gold from the player when they sell and buy items with a trader.

Change line 26 to use the ReceiveGold function. Then, change line 41 to use the SpendGold function.

 

TradeScreen.xaml.cs

 

 

Step 5: Test the game

Because we’re refactoring, the program should run exactly the same as it did before. We’re just improving the structure of the code – making it easier for us to work with.

 

Return to main page

Leave a Reply

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