For the various types of Ships, Projectiles, and Weapons, I need to set up variables to make them behave differently in the game. The Setup methods basically involve a parameter with a type and depending on this type, we set the correct settings using a switch statement.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
public Weapon(WeaponType inputWeaponType, ObjectManager manager, Vector3 inputPositionOffset, float inputDirectionOffset, bool Swap)
{
objectManager = manager;
BarrelNext = new[] { 0 };
IsBullet = true;
BarrelPositions = null;
DirectionOffset = inputDirectionOffset;
TurretInputPosition = inputPositionOffset;
this.Type = inputWeaponType;
switch (inputWeaponType)
{
#region Misc Weapons
case WeaponType.FastTorpedo:
MinStartVelocity = 450;
MaxStartVelocity = 450;
InitialDirectionRandomness = 0f;
ProjectileType = ProjectileType.Torpedo;
DetectRotate = .4f;
RotateRange = .4f;
Rotation = 0f;
BarrelDelay = 2000;
DetectRange = 9000;
psFire = new[]
{
new ParticleEmitSettings(objectManager._game.smokePlumeParticles, 20)
{
directionMult = 30,
directionPosOffset = 20,
velocityMultiplier = .5f
}
};
IsBullet = false;
break;
case WeaponType.FastHeavyIonCannon:
InitialDirectionRandomness = 0;
isLaser = true;
laserType = LaserType.FastHeavyIonCannon;
//RotateSpeed = .075f;
//RotateRange = .7f;
//DetectRotate = .7f;
Rotation = 0f;
BarrelDelay = 500;
DetectRange = 5000;
MuzzleYChange = -5;
break;
//.. ~1000 more lines ..
#endregion
}
DetectRangeSquared = DetectRange * DetectRange;
} |
As you can see, this can get quite complicated to maintain and update because of how many case conditions and setup statements are on the page.
We can separate these similar setup statements by using a design pattern called Strategy Design Pattern.
This involves:
- a Context
- a Strategy
- and multiple Concrete Strategies.
Let’s start out with a Strategy. It’s an interface which describes what your concrete strategies will all hold in common. In this case, I want to be able to pass in my object manager and the weapon we are trying to set up.
|
1 2 3 4 |
public interface IWeaponSetupStrategy
{
void Setup(ObjectManager objectManager, Weapon weapon);
} |
We can then create a Concrete Strategy implementing the Strategy interface. This class sets up all the necessary variables.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public class FastTorpedo : IWeaponSetupStrategy
{
public void Setup(ObjectManager objectManager, Weapon weapon)
{
weapon.MinStartVelocity = 450;
weapon.MaxStartVelocity = 450;
weapon.InitialDirectionRandomness = 0f;
weapon.ProjectileType = ProjectileType.Torpedo;
weapon.DetectRotate = .4f;
weapon.RotateRange = .4f;
weapon.Rotation = 0f;
weapon.BarrelDelay = 2000;
weapon.DetectRange = 9000;
weapon.psFire = new[]
{
new ParticleEmitSettings(objectManager._game.smokePlumeParticles, 20)
{
directionMult = 30,
directionPosOffset = 20,
velocityMultiplier = .5f
}
};
weapon.IsBullet = false;
}
} |
The Context is where we create a static array of these strategies for later use. Since it is static, we won’t have to re-instantiate the Concrete Strategies whenever we want to use it.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
public class WeaponContext
{
public static IWeaponSetupStrategy[] Strategies;
static WeaponContext()
{
Strategies = new IWeaponSetupStrategy[Enum.GetValues(typeof(WeaponType)).Length];
Strategies[(int)WeaponType.FastTorpedo] = new FastTorpedo();
// add more strategies here...
}
} |
Let’s go back to the Weapon class and hook the strategies in.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public Weapon(WeaponType inputWeaponType, ObjectManager manager, Vector3 inputPositionOffset, float inputDirectionOffset, bool Swap)
{
objectManager = manager;
BarrelNext = new[] { 0 };
IsBullet = true;
BarrelPositions = null;
DirectionOffset = inputDirectionOffset;
TurretInputPosition = inputPositionOffset;
this.Type = inputWeaponType;
WeaponContext.Strategies[(int)inputWeaponType].Setup(manager, this);
DetectRangeSquared = DetectRange * DetectRange;
} |
The constructor is much shorter and we can separate out weapon setups into separate files.