I am trying to make a basic "Spell system" for a game, and trying to come up with a good way to implement "spell behaviour" in a modular way. I have a slight problem where I want certain methods to only be usable by spells of certain "types". Code example:
public abstract class Spell
{
public abstract void DoStuff();
public void DoProjectileMovement()
{
//Handle moving spell
//Want to only allow calling from spells which implement "Projectile" interface
}
}
public interface Projectile
{
int projNum { get; set; }
float projSpeed { get; set; }
}
public class Fireball : Spell, Projectile
{
public int projNum { get; set; }
public float projSpeed { get; set; }
public override void DoStuff()
{
DoProjectileMovement();
}
}
As it is now, the DoProjectileMovement() method can't use properties from the Projectile interface, which I want it to be able to access. The obvious option seems to just pass it as a parameter:
public void DoProjectileMovement(Projectile proj)
{
//Handle moving spell
//Now it can access projectile properties through proj
}
But then when calling this method in Fireball, I have to do like this:
public override void DoStuff()
{
DoProjectileMovement(this);
}
Having the extra this
feels superfluous. I know that the Projectile interface will never be implemented by any other base class than Spell. And if it is implemented by Spell, I want to be able to call DoProjectileMovement(). An alternative is to have the DoProjectileMovement method have a default implementation in the Projectile interface, and somehow specifying that Projectile is guaranteed to also be a Spell. Is this possible?
Also, the approach of having a separate abstract class "ProjectileSpell" doesn't work, as I want to be able to use multiple different interfaces at a time. (For example, Fireball might also be "Exploding", and then I can't inherit from the two abstract classes "ProjectileSpell" and "ExplodingSpell" at the same time).
A few possibilities come to mind.
public abstract class Spell
{
public abstract void DoStuff();
}
public static class ProjectileExtensions
{
public static void DoProjectileMovement<TSpell>(this TSpell projectile)
where TSpell : Spell, Projectile
{
// Handle moving spell
// (You can access members of Spell and Projectile here)
}
}
public class Fireball : Spell, Projectile
{
public int projNum { get; set; }
public float projSpeed { get; set; }
public override void DoStuff()
{
this.DoProjectileMovement(); // 'this' qualifier is required
}
}
You can even write extension methods that require a combination of interfaces to be implemented.
public static class ProjectileExtensions
{
public static void DoExplodingProjectileMovement<TSpell>(this TSpell projectile)
where TSpell : Spell, Exploding, Projectile
{
// Handle explosive moving spell
// (You can access members of Spell, Exploding, and Projectile here)
}
}
This option requires that you introduce an ISpell
interface implemented by both Spell and Projectile.
public interface ISpell
{
void DoStuff();
}
public abstract class Spell : ISpell
{
public abstract void DoStuff();
}
public interface Projectile : ISpell
{
int projNum { get; set; }
float projSpeed { get; set; }
void DoProjectileMovement()
{
// Handle moving spell
// (You can access members of ISpell and Projectile here)
}
}
public class Fireball : Spell, Projectile
{
public int projNum { get; set; }
public float projSpeed { get; set; }
public override void DoStuff()
{
((Projectile)this).DoProjectileMovement(); // cast is required
}
}
Change Projectile from an interface to a class.
public class Projectile
{
private Spell Spell { get; }
public Projectile(Spell spell)
{
Spell = spell;
}
public int projNum { get; set; }
public float projSpeed { get; set; }
public void DoProjectileMovement()
{
// Handle moving spell
// (You can access members of Spell (via the Spell property) and Projectile here)
}
}
public class Fireball : Spell
{
private Projectile Projectile { get; }
public Fireball()
{
Projectile = new Projectile(this);
}
public int projNum { get; set; }
public float projSpeed { get; set; }
public override void DoStuff()
{
Projectile.DoProjectileMovement();
}
}