I think these are nice solutions if you are into design patterns. But it does seem to make the problem more difficult than it needs to be.
What is an item? A collection of characteristics. Designing a class for every possible characteristic is weird. My thinking is why? Moreover, why would you want to perform a type check for every characteristic of an item? This may be fine in some games, but suppose you were designing an arpg, See Here. This would become flat out inefficient.
Another approach could be to define specific items.
public class Item
{
public readonly string Name;
public readonly string Description;
public readonly string ID;
public readonly string Type;
public readonly float Value;
public readonly float Weight;
public readonly bool IsStackable;
private Item() { }
public Item(string name, string description, string id, string type, float value, float weight, bool isStackable)
{
Name = name;
Description = description;
ID = id;
Type = type;
Value = value;
Weight = weight;
IsStackable = isStackable;
}
}
This way you could have an item, such as a cup, which may not serve any purpose other than random loot/props that have value for the player to sell. You could then define item type such as equipment. Is stackable is something you will have to determine, considering that not all items are stackable, but you may want the previously mentioned cup to be stackable.
public class Equiptable : Item
{
public readonly SlotType Slot;
private List<Modifier> _Modifiers;
public IList<Modifier> Modifiers { get { return _Modifiers.AsReadOnly(); } }
public Equiptable(string name, string description, string id, string type, float value, float weight, bool isStackable, SlotType slot, List<Modifier> modifiers)
: base(name, description, id, type, value, weight, isStackable)
{
Slot = slot;
_Modifiers = modifiers;
}
public void AddModifier(Modifier modifier)
{
_Modifiers.Add(modifier);
}
public void RemoveModifier(Modifier modifier)
{
_Modifiers.Remove(modifier);
}
}
Here we define an equitable item. An equitable item is going to provide some benefit to the player - when equipped. An equitable is a collection of modifiers that provide some sort of modifier to the player when equipped.
So what is a modifier? Well it is just a name, value, and calculation. The calculation determines how the modifier will be applied. Such as a flat rate or percentage. They are just flags, and can be represented as an enum. Note, there may be more than one percent calculation, as mathematically percentages display unique properties when added together or applied individually.
/// <summary>
/// Represents a Modifier
/// </summary>
public class Modifier
{
#region readonly members
/// <summary>
/// This modifier's name
/// </summary>
public readonly string Name;
/// <summary>
/// This modifier's description
/// </summary>
public readonly string Description;
/// <summary>
/// This modifier's id
/// </summary>
public readonly string ID;
/// <summary>
/// This modifier's type
/// </summary>
public readonly string Type;
/// <summary>
/// This modifiers magnitudue
/// </summary>
public readonly float Magnitude;
/// <summary>
/// This modifier's calculations type
/// </summary>
public readonly CalculationFlag Calculation;
#endregion readonly members
#region constructors
/// <summary>
/// prevent public access to unititialized modifier
/// </summary>
private Modifier() { }
/// <summary>
/// Constructs a new modifier
/// </summary>
/// <param name="name">This modifier's name</param>
/// <param name="descrpition">This modifier's description</param>
/// <param name="id">This modifier's id</param>
/// <param name="type">This modifier's type</param>
/// <param name="mangnitude">This modifier's magnitude</param>
/// <param name="calculation">This modifier's calculation type</param>
public Modifier(string name, string descrpition, string id, string type, float mangnitude, CalculationFlag calculation)
{
//
// Initialize member variables
//
Name = name;
Description = descrpition;
ID = id;
Type = type;
Magnitude = mangnitude;
Calculation = calculation;
}
#endregion constructors
}
Forgive the comments, this is copied from active code. However, it may provide intuition as to what is going on. In addition to the values I had mentioned, the modifier is just a memory location with values. So, what do these modifiers actually do? How do they work? The type, as I have it will define the attribute/stat that it effects. It is a string to so I may define all my modifiers in a file loaded into the program. Many benefits to this, but the one I am interested in is the data driven programming aspect. I will not need to recompile to modify a modifier. Moreover, a nice thing about this is that I can define a normal item as a minimum and maximum damage modifier. The modifiers will apply to the character, when equipped, like any other. We can then have an attribute class to represent stats for a character.
Then we have the attribute
/// <summary>
/// Represents an Attribute
/// </summary>
public class Attrbute
{
#region public variables
/// <summary>
/// An action to take place when the base value is changed
/// </summary>
public Action OnBaseValueChange;
/// <summary>
/// An action to take place when the current value is changed
/// </summary>
public Action OnCurrentValueChange;
/// <summary>
/// This attribute's name
/// </summary>
public readonly string Name;
/// <summary>
/// This attribute's description
/// </summary>
public readonly string Description;
/// <summary>
/// This attribute's id
/// </summary>
public readonly string ID;
#endregion public variables
#region private variables
/// <summary>
/// The base value of this attribute
/// </summary>
private float _BaseValue;
/// <summary>
/// The current value of this attribute
/// </summary>
private float _CurrentValue;
/// <summary>
/// The modified state of this attribute
/// </summary>
private bool _IsModified;
/// <summary>
/// A collection of modifiers applied to this attribute
/// </summary>
private List<Modifier> _Modifiers;
#endregion private variables
#region public properties
/// <summary>
/// A read-only collection of modifiers applied to this attribute
/// </summary>
public IList<Modifier> Modifiers { get { return _Modifiers.AsReadOnly(); } }
/// <summary>
/// The base value of this attribute
/// </summary>
public float BaseValue
{
get
{
//return the base value
return _BaseValue;
}
set
{
//update the base value
_BaseValue = value;
//Update modified state
_IsModified = true;
if (OnBaseValueChange != null)
OnBaseValueChange();
}
}
/// <summary>
/// Returns the current value of this modifier
/// </summary>
public float CurrentValue
{
get
{
//update the current value if this attribute has been modified
if (_IsModified)
{
//get new current value
_CurrentValue = CalculateCurrentValue();
//update modified state to false
_IsModified = false;
if (OnCurrentValueChange != null)
OnCurrentValueChange();
}
//return the current value
return _CurrentValue;
}
}
#endregion public properties
#region constructors
/// <summary>
/// Prevents public access to instantiate an unitialized attribute
/// </summary>
private Attrbute() { }
/// <summary>
/// Constructs an instance of an attribute with an empty collection of modifiers
/// </summary>
/// <param name="name">The name of the attribute</param>
/// <param name="description">The description of the attribute</param>
/// <param name="id">The id of the attribute</param>
/// <param name="baseValue">The base value of the attribute</param>
public Attrbute(string name, string description, string id, float baseValue)
:this(name, description, id, baseValue, new List<Modifier>()) { } //TODO(stephen): Performance -> A new list with an initialze size?
/// <summary>
/// Constructs an attribute with a given collection of modifiers
/// </summary>
/// <param name="name">The name of the attribute</param>
/// <param name="description">The description of the attribute</param>
/// <param name="id">The id of the attribute</param>
/// <param name="baseValue">The base value of the attribute</param>
/// <param name="modifiers">The collection of modifiers for the attribute</param>
public Attrbute(string name, string description, string id, float baseValue, List<Modifier> modifiers)
{
//
//Initialize member variables
//
Name = name;
Description = description;
ID = id;
BaseValue = baseValue;
_Modifiers = modifiers;
}
#endregion constructors
#region public methods
/// <summary>
/// Adds a modifier to this attribute
/// </summary>
/// <param name="modifier">The attribute to remove</param>
public void AddModifier(Modifier modifier)
{
//Add modifier to collection
_Modifiers.Add(modifier);
//update modified state
_IsModified = true;
}
/// <summary>
/// Removes a modifier from this attribute
/// </summary>
/// <param name="modifier">The modifier to remove</param>
public void RemoveModifier(Modifier modifier)
{
//Remove modifier to collection
_Modifiers.Remove(modifier);
//update modified state
_IsModified = true;
}
#endregion public memthods
#region private properties
/// <summary>
/// Calculates the current value from all applied modifiers
/// </summary>
/// <returns></returns>
private float CalculateCurrentValue()
{
//initiliaze local variables
float flatRateValues = BaseValue;
float percentRateValue = 1.0f;
//iterate through all modifiers applied to this attribute
foreach (Modifier modifier in Modifiers)
{
//add magnitude to flat rate values
if (modifier.Calculation == CalculationFlag.Flat)
flatRateValues += modifier.Magnitude;
//add magnitude to percent rate values
else if (modifier.Calculation == CalculationFlag.Percent)
percentRateValue += modifier.Magnitude;
//calculation type not found, throw exception
else
throw new NotSupportedException("Attribute->CalculateCurrentValue() - CalcuationType -> "
+ modifier.Calculation + " Not Found!");
}
//return the calculated value
return flatRateValues * percentRateValue;
}
#endregion private variables
}
The attribute is relatively strait forward. We manage a collection of modifiers that apply to the attribute. The actions provide means of doing things when the attribute is modified, such as updating ui or some other attribute, such as max health.
Now all we need is a character that can equip equitables.
public class Character
{
private List<Attrbute> _Attributes;
private Dictionary<SlotType, Equiptable> _Equiptment;
public IList<Attrbute> Attributes { get { return _Attributes.AsReadOnly(); } }
public Character(List<Attrbute> attributes)
{
_Attributes = attributes;
_Equiptment = new Dictionary<SlotType, Equiptable>();
foreach (SlotType slot in System.Enum.GetValues(typeof(SlotType)))
{
_Equiptment.Add(slot, null);
}
}
public Attrbute FindAttribute(string name)
{
return _Attributes.Find(attribute => attribute.Name == name);
}
public void Equipt(Equiptable equiptable)
{
Equiptable currentEquipt = _Equiptment[equiptable.Slot];
_Equiptment[equiptable.Slot] = equiptable;
foreach (Modifier modifier in equiptable.Modifiers)
{
Attrbute attribute = FindAttribute(modifier.Type);
if (attribute != null)
attribute.AddModifier(modifier);
}
//add currentEquipt to inventory?
}
public void UnEquipt(SlotType slot)
{
Equiptable removed = _Equiptment[slot];
_Equiptment[slot] = null;
foreach (Modifier modifier in removed.Modifiers)
{
Attrbute attribute = FindAttribute(modifier.Type);
if (attribute != null)
attribute.RemoveModifier(modifier);
}
//add removed to inventory?
}
}
Equitable slots is an enum that you can define however you like, but int this example:
public enum SlotType
{
None = 0x0000,
Primary = 0x0001,
Secondary = 0x0002,
TwoHanded = 0x0004,
Ranged = 0x0008,
}
All of this is just a suggestion. Each game will handle things differently. For instance, you may not have props as items, such as a cup. Moreover, the desing patterns mentioned in this post may serve you well. It all comes down to how you want to manage your items. If you wanted crafting of some kind with raw resources, then you can define a class for that.