In this lesson, we’ll make changes to let us create different types of actions that we can assign to different items.
Step 1: Create the interface file Engine\Actions\IAction.cs
To create an interface, right-click on the Actions folder in Solution Explorer. Select Add -> New Item -> Visual C# Items -> Code -> Interface. Name the interface IAction.cs (that first letter is an upper-case “i”). Interfaces are not required to start with a capital “I” – but it’s a common practice.
Currently, the datatype of the GameItem.Action property is “AttackWithWeapon” – our only action class. However, we want to create other actions for different GameItems.
All the action classes will have two things in common:
- An Execute function that accepts actor and target parameters
- An OnActionPerformed event to report the action’s results
In this situation, “interface” is not related to a user interface. Instead, it is like a contract, or set of requirements. It says, “all action classes must have these properties, events, and functions”.
We can use this interface as a datatype. We can store any object that implements an interface into a variable/property/parameter that uses the interface as its datatype.
So, instead of having an Action property whose datatype is “AttackWithWeapon”, we’ll set its datatype to “IAction” – the interface that defines what an action class needs to have and do.
On lines 8 and 9, we add the two things that must exist in every action class: the OnActionPerformed event and the Execute function. Notice that they do not have a scope. That’s because everything defined in an interface must be public – at least, in C# interfaces.
IAction.cs
using System;
using Engine.Models;
namespace Engine.Actions
{
public interface IAction
{
event EventHandler<string> OnActionPerformed;
void Execute(LivingEntity actor, LivingEntity target);
}
}
Step 2: Modify Engine\Actions\AttackWithWeapon.cs
On line 6, add “: IAction”, to the class. This declares that the AttackWithWeapon class implements all the properties/events/functions listed in the IAction interface – which it already does.
Now, just like with a base class, the datatype of any AttackWithWeapon object is either “AttackWithWeapon” or “IAction”.
AttackWithWeapon.cs
using System;
using Engine.Models;
namespace Engine.Actions
{
public class AttackWithWeapon : IAction
{
private readonly GameItem _weapon;
private readonly int _maximumDamage;
private readonly int _minimumDamage;
public event EventHandler<string> OnActionPerformed;
public AttackWithWeapon(GameItem weapon, int minimumDamage, int maximumDamage)
{
if(weapon.Category != GameItem.ItemCategory.Weapon)
{
throw new ArgumentException($"{weapon.Name} is not a weapon");
}
if(_minimumDamage < 0)
{
throw new ArgumentException("minimumDamage must be 0 or larger");
}
if(_maximumDamage < _minimumDamage)
{
throw new ArgumentException("maximumDamage must be >= minimumDamage");
}
_weapon = weapon;
_minimumDamage = minimumDamage;
_maximumDamage = maximumDamage;
}
public void Execute(LivingEntity actor, LivingEntity target)
{
int damage = RandomNumberGenerator.NumberBetween(_minimumDamage, _maximumDamage);
if(damage == 0)
{
ReportResult($"You missed the {target.Name.ToLower()}.");
}
else
{
ReportResult($"You hit the {target.Name.ToLower()} for {damage} points.");
target.TakeDamage(damage);
}
}
private void ReportResult(string result)
{
OnActionPerformed?.Invoke(this, result);
}
}
}
Step 3: Modify Engine\Models\GameItem.cs
Change the Action property’s datatype to “IAction” (line 18) and change the datatype of the constructor’s “action” parameter to “IAction”.
This means we can pass in any object that implements the IAction interface and store it in the Action property. For weapons, we’ll pass in AttackWithWeapon. When we add healing objects, we’ll create a new Heal action class that implements IAction, and we can pass in a Heal action object.
GameItem.cs
using Engine.Actions;
namespace Engine.Models
{
public class GameItem
{
public enum ItemCategory
{
Miscellaneous,
Weapon
}
public ItemCategory Category { get; }
public int ItemTypeID { get; }
public string Name { get; }
public int Price { get; }
public bool IsUnique { get; }
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 4: Run the unit tests and game, to ensure it still works.
NEXT LESSON: Lesson 12.4: Letting the Monster use AttackWithWeapon
PREVIOUS LESSON: Lesson 12.2: Creating the AttackWithWeapon command
Heloo Scott. First i wanted to tell you how grateful I am for this course. Even though alot of things in this i barely understand. I hope i can became a dev soon. As of time of writing this comment. Iam in requalification program so we will see.
Anyway this is a first time i encountered a problem that i dont know how to solve and even when i think have the same code as you. In item factory in function BuildWeapon, I have an error where on this line
weapon.Action = new AttackWithWeapon(weapon, minimumDamage, maximumDamage);
the error says : “Severity Code Description Project File Line Suppression State Details
Error (active) CS0266 Cannot implicitly convert type ‘Engine.Models.AttackWithWeapon’ to ‘Engine.Actions.IAction’. An explicit conversion exists (are you missing a cast?)
”
I do think thought that possible fix is to simply define the type as such
weapon.Action = (IAction)new AttackWithWeapon(weapon, minimumDamage, maximumDamage);
It could be an different type of net. As i have started this project on net.8
I hope this helps someone in the future 🙂
ps. link to my git – > https://github.com/Fasimedes?tab=repositories
Ok so i have an update for this. And i feel like an idiot. So i wont mention how many things i have said wrong in my previous post, only just one. That i fixed the problem.
It was caused by the fact that i have not deleted the action class so AttackWithWeapon was trying to access that old class which ofc didnt implement the IAction interface.
Anyway i think i will go ponder meaning of life and my own stupidity now. See yall 🙂
Hey Peter,
I hope you see this, and if you don’t, I hope it’s still helpful for other readers. But, I hope people don’t feel “stupid” when they try these lessons and have a problem or make a mistake. You’re doing something new, and the computer is very unforgiving. Miss one semi-colon, or one closing curly brace, and the whole program could break. Usually, the error messages are helpful, but sometimes they aren’t. I’ve been programming for over 40 years and still have errors and get stuck at times. As you continue programming, you’ll build up a big memory bank of problems you encountered. That memory bank will help you find and fix problems quicker, and eventually make it so you almost never make that error.
But, I’m glad you were able to get to the root cause of the problem and fix it. Now you’re a little more experienced, and a little smarter, and can continue forward to make newer, exciting mistakes. 🙂
Okay, so I’m not going to lie—I was a little tilted when I was writing this. But I still wanted other folks taking this course to see it because, in my opinion, bashing your head and struggling like this is a normal part of learning programming. The important thing is to never give up, because that’s when all the effort will be for nothing. I hope to use this codebase to eventually turn it into my own game, and hopefully, because of it, land a Junior position or internship.
Thank you for your mentorship! 🙂