Having trouble with using composition to structure Enemy and Player code

Started by
3 comments, last by Stocke 4 years, 5 months ago

So I've been trying to use OOP where I need polymorphic behavior. I know that a lot of people have problems with it but it has worked out so far for me. I've tried to avoid using inheritance too much and tried to use composition to make bigger classes out of smaller ones.

This is my situation. I have an attack class like


abstract Class Attack{
	abstract void execute(vector3 Dir);
}

And melee and  ranged attack classes like


class MeleeAttack{
	override void execute(vector3 Dir){//do stuff}
}
class ProjectileAttack{
	override void execute(vector3 Dir){//spawn stuff}
}

I need player specific attack behaviour so I have a player attack class that I use for the player.


class PlayerAttack{//does not inherit attack since I need more parameters in execute
	Attack meleeOrRangedAttack;
	void execute(vector3 Dir, random params....){
		//do meleeOrRangedAttack while obeying player rules
		dir = calculateDirTBasedOnInputOrSomething()
		meleeOrRangedAttack.execute(dir)
	}
}

And a different enemy Attack Class because enemies need to target a player.


class EnemyAttack{
	Player target;
	Attack meleeOrRangedAttack;
	override void execute(target){
		//do meleeOrRangedAttack while targetting player
		dir = calculateDirToTarget()
		meleeOrRangedAttack(dir)
	}
}

This works well and I can reuse melee or ranged attacks both for players and enemies.

But say I need a homing Attack for enemies. If I make it a subclass of attack then I can't just override execute(). I need some way to pass in a target. Is there a better way to structure this code while keeping composition(not writing separate enemy homing attack, player homing attack classes)?

Advertisement
51 minutes ago, Stocke said:

If I make it a subclass of attack then I can't just override execute()

Why?

This is the purpose of OOP and fits into your design, the problem is your parameter. I would have not created a Player/Enemy attack class but a class to determine a legal target. This way you are more flexible and don't need subclasses for enemy and player, instead you have subclasses for the target and are free to provide any specialized code there.


abstract class Target
{
    public abstract Vector3 GetDirection(); 
}
abstract class Attack
{
	public abstract void Execute(Target target);
}

And then create specific implementations for your Attack and the Target. This way you are flexible about having another kind of targeting, for example a spread damage attack one day.

By the way, ther eis nothing wrong about using interfaces and you should use them unless you put generic code into your abstract class (that you don't here). If the abstract class is just abstract to force implementation of functions/ properties without any further functionality, use an interface instead

13 minutes ago, Shaarigan said:

Why?

This is the purpose of OOP and fits into your design, the problem is your parameter. I would have not created a Player/Enemy attack class but a class to determine a legal target. This way you are more flexible and don't need subclasses for enemy and player, instead you have subclasses for the target and are free to provide any specialized code there.



abstract class Target
{
    public abstract Vector3 GetDirection(); 
}
abstract class Attack
{
	public abstract void Execute(Target target);
}

And then create specific implementations for your Attack and the Target. This way you are flexible about having another kind of targeting, for example a spread damage attack one day.

By the way, ther eis nothing wrong about using interfaces and you should use them unless you put generic code into your abstract class (that you don't here). If the abstract class is just abstract to force implementation of functions/ properties without any further functionality, use an interface instead

Having a separate target class will probably be an improvement. Previously I had the attack classes forget about direction altogether and rely on the facing direction (which was handled in player and enemy classes). But the problem is that some attacks might not work with a direction vector. An enemy might need to calculate projectile velocity based on player position and other attacks don't want to mess with direction.  I can't just pass in a target vector for all cases. 

As for interfaces, Unity's serialization is broken . If I want assignable references in the editor, I need to use abstract classes. Otherwise I would have used interfaces. Clearly the code needs refactoring. I'm just not sure what direction it should go.

So according to Shaarigan's suggestion I delegated the targetting to TargettableAttack class that takes in a target and delegates to a attack class after calculating directions. For homing attacks I override that behavior by as necessary in a HomingTargettableAttack class.

 

This topic is closed to new replies.

Advertisement