So this is my snippet of code for setting a weapon’s stats to a script that handles all of the attacking. For the most part, it looks fine (Although I suppose stats like the damage and attack speed could be consolidated into the parent class), but I feel like there is a way I could make this a lot cleaner.
Looks plenty good, probably not worth worrying about it unless you need to rework it anyway and see a good opportunity to improve it.
Just a small suggestion, if you have an ItemStats subclass for every WeaponClass, you could just switch on the object to avoid needing an explicit cast:
switch (weaponInfo.itemStats)
{
case MeleeItemStats meleeItemStats:
// ...
break;
case RangedItemStats rangedItemStats:
// ...
break;
case MagicItemStats magicItemStats:
// ...
break;
}
Oops, will make sure next time to do that lol.
Otherwise, thanks for the help! Didn’t know you could use switch statements like that.
Even better, if you have interfaces for that:
public interface IHaveAttackSpeed {
float attackSpeed;
}
public interface IHaveWeaponDamage {
float damageMin;
float damageMax;
}
public interface IHaveAttackRange {
float attackRange;
}
public class MeleeItemStats : IHaveAttackSpeed, IHaveAttackRange,IHaveWeaponDamage {}
public class RangedItemStats : IHaveAttackSpeed {}
public class MagicItemStats : IHaveAttackSpeed, IHaveAttackRange, IHaveWeaponDamage {}
if (weaponInfo.itemStats is IHaveAttackSpeed speedStats) {
weaponAttackHandler.setAttackSpeed(speedStats.attackSpeed);
}
if (weaponInfo.itemStats is IHaveAttackRange rangeStats) {
weaponAttackHandler.setAttackRange(rangeStats.attackRange);
}
if (weaponInfo.itemStats is IHaveWeaponDamage damageStats) {
weaponAttackHandler.setWeaponDamage(damageStats.damageMin, damageStats.damageMax);
}
This way you dont care about the actual implementation and can easily add new stuff (as long as you dont need new interfaces, every new classes using at least on of those interfaces will work without further code change)
Edit: use if instead of switch ![]()
Ah, I think this is what I was looking for. I was looking into using generics, but I don’t think that was really the proper way to go about it.
It’s not quite clear where the shown code is actually located. It seems your “weaponInfo.itemStats” is some sort of base class and you have different implementations with different stats. In such a case I would probably go for a polymorphic approach since each concrete type knows what kind of stats it has and wants / needs to apply. So when you have a virtual “Apply” method in your base class, you could simplifiy your code to something like:
weaponInfo.itemStats.ApplyTo(weaponAttackHandler);
So each concrete class could override the virtual method and implement the instance specific logic. Maybe you even have something that is the same for all classes (that is already defined in the base class, maybe the AttackSpeed?) so you could implement that in the base method so when overriding the method you simply call the base implementation. Well, just simple OOP.
Interfaces can be great, depending on the usecase. Note that a switch case will always only hit a single case, the first one that matches. So if the object implements several interfaces, you would only ever set a single value. Your pattern would make more sense as separate if statements
if (weaponInfo.itemStats is IHaveAttackSpeed speedStats)
weaponAttackHandler.setAttackSpeed(speedStats.attackSpeed);
if (weaponInfo.itemStats is IHaveAttackRange rangeStats)
weaponAttackHandler.setAttackRange(rangeStats.attackRange);
if (weaponInfo.itemStats is IHaveWeaponDamage damageStats)
weaponAttackHandler.setWeaponDamage(damageStats.damageMin, damageStats.damageMax);
Yep, fixed it up with some virtual/override functions, and I cut it down all to this.
ItemStats newItem = weaponInventory[i].GetComponent<WeaponInfo>().itemStats;
WeaponInfo weaponInfo = weaponInventory[i].GetComponent<WeaponInfo>();
weaponAttackHandler.setWeaponClass(newItem.weaponClass);
weaponAttackHandler.setWeaponTip(weaponInfo.weaponTip);
weaponAttackHandler.setProjectile(weaponInfo.projectile);
weaponAttackHandler.setAttackRange(newItem.getAttackRange());
weaponAttackHandler.setAttackSpeed(newItem.getAttackSpeed());
weaponAttackHandler.setWeaponDamage(newItem.getDamageMin(), newItem.getDamageMax());
Gave up on the interface method, because I had 0 clue how to work those with variables, but at least for functions, I think I might find a use later in my project.
Why not invert the relationship and avoid the switch all together?
public class ItemStats
{
public virtual void SyncStats(WeaponAttackHandler weaponAttackHandler) {}
}
public class MeleeItemStats : ItemStats
{
public float attackSpeed;
public float attackRange;
public float damageMin;
public float damageMax;
public override void SyncStats(WeaponAttackHandler weaponAttackHandler)
{
weaponAttackHandler.setAttackSpeed(attackSpeed);
weaponAttackHandler.setAttackRange(attackRange);
weaponAttackHandler.setWeaponDamage(damageMin, damageMax);
}
}
public class RangedItemStats : ItemStats
{
public float attackSpeed;
public override void SyncStats(WeaponAttackHandler weaponAttackHandler)
{
weaponAttackHandler.setAttackSpeed(attackSpeed);
}
}
public class MagicItemStats : ItemStats
{
public float attackSpeed;
public float attackRange;
public float damageMin;
public float damageMax;
public override void SyncStats(WeaponAttackHandler weaponAttackHandler)
{
weaponAttackHandler.setAttackSpeed(attackSpeed);
weaponAttackHandler.setAttackRange(attackRange);
weaponAttackHandler.setWeaponDamage(damageMin, damageMax);
}
}
//your original post context
weaponInfo.itemStats.SyncStats(weaponAttackHandler);
Now if you need to add more ItemStat types the code that consumes it doesn’t need to care about it. The new ItemStats class just implements the necessary methods to make it work.
You know… use inheritance for what it’s intended for.
Note…this could also be abstracted into an interface to decouple inheritance hierarchies:
public class IItemStats
{
void SyncStats(WeaponAttackHandler weaponAttackHandler);
}
public class MeleeItemStats : IItemStats
{
public float attackSpeed;
public float attackRange;
public float damageMin;
public float damageMax;
public void SyncStats(WeaponAttackHandler weaponAttackHandler)
{
weaponAttackHandler.setAttackSpeed(attackSpeed);
weaponAttackHandler.setAttackRange(attackRange);
weaponAttackHandler.setWeaponDamage(damageMin, damageMax);
}
}
public class RangedItemStats : IItemStats
{
public float attackSpeed;
public void SyncStats(WeaponAttackHandler weaponAttackHandler)
{
weaponAttackHandler.setAttackSpeed(attackSpeed);
}
}
public class MagicItemStats : IItemStats
{
public float attackSpeed;
public float attackRange;
public float damageMin;
public float damageMax;
public void SyncStats(WeaponAttackHandler weaponAttackHandler)
{
weaponAttackHandler.setAttackSpeed(attackSpeed);
weaponAttackHandler.setAttackRange(attackRange);
weaponAttackHandler.setWeaponDamage(damageMin, damageMax);
}
}
//your original post context
weaponInfo.itemStats.SyncStats(weaponAttackHandler);
edit: Just noticed this is what Bunny83 said too.
