Fusion turrets - strategies and flyweights

On a side note, it's been two weeks since my last post? That's awful. I feel like I've had a lot to talk about, just haven't had the time to write an article.

The goal

I've been working on fusion a good amount lately, and one of the major pieces of development work is the introduction of 3 new turret types - lasers, missiles and machineguns. This is in addition to the original turret, which has now been renamed the cannon. Each turret type has some advantages and disadvantages:

  • Cannons are the all around decent turret. They are accurate, have good range and decent damage.
  • Lasers are weaker than cannons, but make up for it with incredible range and perfect accuracy. They are often the first turret to open fire on the incoming enemies.
  • Missiles have a very strong punch and splash damage, but a slow rate of fire and are the most likely to miss their target.
  • Machineguns have very high damage output, but very low range. They can chew through invaders quickly, but by the time they open fire, the invaders are just about ready to fire on the generators.

To implement these weapons, I needed to make some changes to js/Bullet.js to support multiple types of bullets that have their own independent logic and parameters, without having to duplicate large portions of the code. Always wanting to find ways to implement design patterns, I began to think of which patterns to use.

Patterns

The strategy pattern was an obvious one. Each turret will have a strategy that dictates how it creates bullets, and each bullet will have a corresponding strategy that dictates how it moves and interacts with enemies.

A less obvious choice was the flyweight pattern. This is a pattern I'm familiar with but have never had a chance to use. Back when I was working on wsilent, I looked into using the flyweight pattern for bullets but never quite reached the point where I was doing it. Now that Fusion has reached the point where different weapon types are being created, the flyweight pattern came back.

Turret.weapon.cannon = {
  firespeed : 0.75,
  range     : 400
};
Turret.weapon.cannon.init = function(turret) {
  turret.gun = new VisualGameObject(
    turret.image,
    turret.x,
    turret.y,
    2
  );
  turret.gun.sprite.initFrames(7,5);
  turret.gun.sprite.setFrame(0,1);
  g_GameObjectManager.addGameObject(turret.gun);
};
Turret.weapon.cannon.shoot = function(turret) {
  var bullet = new Bullet(
    Bullet.cannon,
    turret.x + turret.sprite.width / 2,
    turret.y + turret.sprite.height / 2,
    turret.angle,
    -1
  );
 
  turret.gun.sprite = AnimatedSprite(turret.gun.sprite, [2,3,4,5], 0.5);
 
  g_GameObjectManager.addGameObject(bullet);
};

This sample code shows the code for the cannon turret. It is both a strategy and a flyweight and is structured to let me add new weapons fairly easily. By passing the turret object over to all strategy method calls, I let the strategy become a flyweight - only one instance of the turret strategy will ever exist. Anytime a cannon turret wants to fire, it calls it's strategy's fire method, passes itself over as an object, and the flyweight strategy handles the rest.

Using a strategy lets me isolate the logic of the turret types from an instance of a turret and change turret types at runtime (that feature doesn't exist yet, but the strategy pattern will let me do it). Using a flyweight pattern reduces the overhead of each turret since I don't need to create a new instance of each strategy for every turret.

Problems

Of course, the problem that is now introduced can be classified as a "parallel inheretience hierarchy" bad code smell. Each time I make a new weapon type, I need to implement it both as a turret strategy and as a bullet strategy, as well as eventually a player strategy since the player will have multiple weapons in time.

I don't yet know the exact solution to this, since it can't always be assumed that cannon turrets fire only cannon bullets, or that cannon bullets only fire from cannon turrets. So simply combining the bullets and turrets into a single object that has both the interface of a turret strategy and a bullet strategy may not be the ideal solution.

On that last note - I'm not too worried about ideal solutions. I've been writing javascript based games for all of 5 months now. Always worrying about ideal solutions is what killed wsilent and dovetail. I knew I was doing something wrong but didn't have the experience to know how to do it right. Now that I'm fighting the urge to always do things correcty, I'm making very good progress. And with that progress comes the experience to know how to do it right next time.