3

I asked a question about structuring a Tower Defense game a while back, and ended up using the Flyweight pattern for my towers. Here's what my TowerType looks now (I'm using Python but looking for generic architectural answers):

class TowerType:

    def __init__(self, name, image, attack_speed, damage,
                 splash_radius, range, cost, upgrade, purchasable):
        self.name = name
        self.image = image
        ... # etc

And I actually load these types dynamically from a JSON file:

[
    {
        "name": "Arrow Tower 1",
        "image": "images/arrow.png",
        "attack_speed": 1.5,
        "damage": 10,
        "splash_radius": 0,
        "range": 80,
        "cost": 80,
        "upgrade": "Arrow Tower 2",
        "purchasable": true
    },
    {
        "name": "Arrow Tower 2",
         ...
    },
    ...
]

Now my Tower class itself doesn't do much other than seeks targets and attacks them:

class Tower(Entity):

    def __init__(self, type_, x=0, y=0):
        self.type = type_
        self.x = x
        self.y = y
        self._attack_timer = self.type.attack_speed

    def update(self, dt, game):  # This is called from the main/game loop
        self._attack_timer += dt
        time_between_attacks = 1 / self.type.attack_speed * 1000
        if self._attack_timer >= time_between_attacks:
            self._attack_timer -= time_between_attacks
            self.attack(game.monsters)

    def find_target(self, monsters):
        for monster in monsters:
            if distance(self.position, monster.position) <= self.type.range:
                return monster
        return None

    def attack(self, monsters):
        target = self.find_target(monsters)
        if target is None:
            return
        if self.type.splash_radius == 0:
            target.health -= self.type.damage
            return
        # Splash damage!
        for monster in monsters:
            dist = distance(monster.position, target.position)
            if dist > self.type.splash_radius:
                continue
            monster.health -= self.type.damage * (1 - dist / self.type.splash_radius)

The problem is, my towers just damage the enemy minions immediately. What I want is some kind of projectiles that fly from my tower to the enemy monster. I'm looking for help on how to structure these projectiles into my system:

  1. Should I move the damage and splash_radius from TowerType to ProjectileType, or should I just copy it from a Tower to a Projectile?
  2. Do I therefore even want a ProjectileType class, or should I just create random Projectiles on the fly?
  3. Should I attempt to put the projectiles in the JSON too? Should they be in a separate JSON file or should I put them inside of the corresponding towers?
  4. Something I've missed?
3

Should I move the damage and splash_radius from TowerType to ProjectileType, or should I just copy it from a Tower to a Projectile?

I'd avoid flyweights for projectiles unless you can anticipate that projectiles don't need the flexibility of having more data becoming unique and will share a boatload of unchanging data. However, even then you might avoid flyweights because unless your projectiles are really fancy, you generally don't need to store that much shared data into one to justify the indirection unless your TD is much different from most I've encountered.

They're a bit different from towers in a TD game since towers might store a whole lot of information that's worth sharing from one tower to the next, and it also makes it easier to reason about the game logic when you are centralizing the tower type meta-data and aren't interleaving the shared data with the unique data in one class. Your game revolves around towers with interesting properties, so to speak. The projectiles are just short-lived elements and something you can describe through the tower metadata/metafunctionality itself.

Do I therefore even want a ProjectileType class, or should I just create random Projectiles on the fly?

I like a ProjectileSystem in this case. It's kind of a bulky design but it can be responsible for animating the projectiles and making them seek the nearest enemy or whatever you want. That's my love for ECS talking and I'm not suggesting an entity-component system for a TD, but I find it useful to think about the "systems" my game needs which tend to be a broader idea than "objects". It makes it easier to reason about everything that's going on and when.

When your towers fire projectiles, you can add them to a place where the projectile system can get at them and it can be responsible for doing the rest. And I think it's fine to just create and describe their properties on the fly. You get a more flexible solution that way, like you might have a tower that fires projectiles with randomized damage or status effects each time it shoots or something to this effect. That becomes awkward if you use flyweights for projectiles.

Also some things might be easier to describe with polymorphic functions rather than data, like the above scenario where a projectile might cause random status effects upon hitting an enemy. That kinda ties to my previous answer where I suggested inheriting from MetaTower or TowerType for polymorphism with overridable functions, since sometimes it's easier to describe what you want towers or projectiles to do with overridden functions rather than data fields.

As another example, you might make like a tower which fires from a machine gun at enemies nearby, but a slow-firing sniper at the top of the tower which fires slowly at distance enemies... so it's like, "pewpewpewpewpewpewpewpew... bam! pewpewpewpewpewpewpewpew..... bam!" ... with the sniper projectile doing way more damage but the tower firing them much more slowly. And it's hard to get such flexibility of behavior for one specific type of tower by trying to describe it all with just properties/data as opposed to functionality/code. For that you might just want to be able to override an attack method in your red towers or something like this.

From a game design standpoint I think it's good to have towers and/or projectiles that can have very non-uniform attacks like this, since a lot of the fun and challenge of TDs is figuring out the optimal towers to use and when. When you make towers less uniform in their attacks, it makes it harder to figure that out.

Should I attempt to put the projectiles in the JSON too? Should they be in a separate JSON file or should I put them inside of the corresponding towers?

Tough question since it really depends to me on how complex your design revolving around projectiles/particles will be. If in doubt, I'd rather start out underkilling a problem than overkilling it. It's easier to make a solution go from simple to more sophisticated than from overly sophisticated to simpler.

  • 1
    Very, very good points you have there mate, this helped me a lot to get my thoughts together! As you suggested, I will not be using the Flyweight pattern for projectiles, instead I'll use a projectile system which I've never heard of or thought of before this, but it just feels good already. Thank you very much for your thoughts and ideas sir :) – Markus Meskanen Dec 9 '17 at 16:34
  • For the system side, it's just a way of organizing and reasoning about the codebase/engine. I find it easier to think about it if it's like, TowerSystem, ProjectileSystem, EnemySystem, SpriteSystem. There might be like dozens of things making up the game, but the way of looking at it as systems each responsible for doing a specific job by looping through all things the specific system is interested in makes it easier to think about and rationalize it. Entity-component systems as used often in AAA games revolve around this type of organization. – user204677 Dec 9 '17 at 16:46
  • The systems don't manage one "object". They manage many, so a projectile system would be responsible for making all the projectiles in the entire game do what they need to do. Individual particles wouldn't bother with any complex logic like finding the nearest enemy; the systems do that. – user204677 Dec 9 '17 at 16:47
  • 1
    If you're ever interested, and I don't recommend using an ECS for a TD (total overkill), here's an example of how a section of an ECS is organized: python-utilities.readthedocs.io/en/latest/_images/… – user204677 Dec 9 '17 at 16:50
  • 1
    Yeah thank you very much for your efforts, you've been a great help :) – Markus Meskanen Dec 9 '17 at 16:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.