yujiri.xyz

Game design

Game design reflections on Spacestation Defense

last edited 2024-11-22

Spacestation Defense is a game I first conceived when I was maybe 16 (this would've been 2014). Originally, it was going to be a turn-based, cooperative board game about defending a spacestation (I insisted on spelling it without a space as a matter of style) from waves of enemy ships. You would send out probes to collect scrap from destroyed enemy ships and use it to build new station components, such as new turrets or shield generators, or ships of your own. There would also be cards, one time use powerful abilities the players could play, such as a card that would repair the station or one that would instantly destroy any single ship.

Every turn you'd have to assign an action for each of your things, a target for each turret, etc. There was also going to be a ton of detailed simulation for ships, like having limited fuel and needing to land in a hangar to replenish it, and needing a "factory" station component to build them.

There were 2 main kinds of turrets: lasers and missiles. Missile turrets would do far more damage, but would usually miss small targets; laser turrets had better accuracy and fired 3 small bolts each turn, so they could spread their damage out across many weak targets. Ships were divided the same way, into laser-shooting fighters and missile-shooting bombers.

There was also a power management system. The station had power generators, and each component needed to spend power to activate, so you had to ration it (like turning off missile turrets when the only things in view were small fighters that could be better dealt with by lasers).

At the time, I didn't have much game design wisdom. I was just looking for a cooperative game to play with my family, and while reading discussion of board games online, I got inspired for this theme.

However, there were several obstacles to it working as a physical board game:

I soon realized these and decided to make it as a video game with Pygame. But before I got far, I realized other problems with the game:

I think it was 2022 when I realized that both these problems could be solved by making the game real-time. I set out to make it again, this time in Raylib with Zig, and in 2023 I actually completed it. Sort of. I mean, it has some placeholder art and serious bugs and a janky tutorial and never got some of the content I planned, but you can play it now.

Spacestation Defense download page (old)

With being real-time came a lot of natural changes. Instead of accuracy being random, there's actual projectile mechanics: missiles have travel time and you can move your ships out of the way. Enemy ships (and yours when you aren't controlling them) automatically try to do this, but the algorithm is deliberately simple and fails in some cases, so that missiles aren't completely useless against enemy fighters, and so that manually controlling your ships still has some value. For lasers I just made them always hit.

I removed the fuel mechanic and factory components, and made it so you can build ships anywhere. Hangars remained as a place where ships could land for protection and repairs.

With this redesign, the game could be fun. I had some fun singleplayer games of it (unfortunately I never got to play it with anyone else). But it went through some major changes after becoming a real-time strategy game, and I'd like to tell the stories of those changes too.

Originally, every station component had separate controls. For example, engines (which could rotate the station, more engines would rotate it faster) had to be turned on and off individually. A friend of mine pointed out that this didn't add meaningfully to gameplay because there wasn't really a situation where turning on only some of the engines made sense. If you wanted to rotate a small angle, you could turn one on for a minute, or turn them all on for less time and get there faster, while spending the same total energy. So I ended up implementing global controls for engines that would turn them all on or off. Another benefit was that you didn't have to select an engine to use these controls.

Shield generators got a similar change. They used to each have their own shield meters and had two toggles: charging and projecting. The charging toggle would spend power to recharge the shields, and the projecting toggle would spend power to make them actually protect the station. Damage would be evenly distributed across all shield generators that were projecting. You wanted to spread the damage evenly to maximize the number that could be recharging at once (effectively maximizing the rate at which you could convert power to shields), but also, shields couldn't regenerate while they were taking damage, so you only ever wanted to have one projecting at a time. The optimal way to manage shields was to cycle which one was on to meet both these requirements. This was micro-intensive, which isn't necessarily a bad thing in a real time strategy game, but it was also repetitive and boring, since this was *always* the optimal way to manage shields.

So I eventually replaced their individual meters with a global shield meter and made it so shields could recharge while taking damage. And instead of being able to change which shield generators were projecting, you could set individual components to not be covered by the shields. This *did* create some interesting decisions, because if your shields are going to run out, you could use this to sacrifice some health of a component that's currently under attack to keep the shields up longer, to protect other components.

In retrospect, I never really considered the idea that shields can't recharge while taking damage, I just cargo-culted it from other games I've played with shields, like Halo. But those games were all shooters.

At one point a friend suggested shield generators should *reduce* incoming damage as a multiplier rather than having a meter that blocks a certain amount and then needs to recharge. This was an interesting idea. It would simplify controls further and seemed to promise a fun dynamic: because station components would inevitably take some damage and there was no free way to repair them, they were inherently temporary. It would lead to a more chaotic game of constantly losing and replcaing components, as opposed to a game of keeping them all alive forever, and if you ever lost one, the game was going downhill. But that dynamic didn't really happen because you could build outward from the damaged component, covering it from enemy attacks.

This shield design also led to a scaling problem: a linear increase in the number of shield generators meant an exponential decrease in the fraction of damage they'd let through, which meant in late game your station would become basically invincible if you spammed shield generators. I made it so shield generators' effect tapered off at longer distances, but the scaling problem was still there, so I reverted to shields that block damage subtractively.

There was another late-game scaling issue which was already present and which I never solved: there was effectively a maximum number of turrets you could build, because their lines of fire had to be clear, and the station had limited surface area. Building outward could increase the surface area, but you needed a quadratic increase in size to get a linear increase in surface area, and you couldn't move components anyway, so old turrents would get covered by expansion and become useless.

In a game that largely inspired Spacestation Defense, a flash game called The Space Game, this wasn't a problem because nothing had collision or blocked lines of fire. Turrets could shoot past each other. But I didn't want to do this, because I think thinking about surface area and fields of view for your turrets made Spacestation Defense more interesting.

A possible solution could've been some way to upgrade turrets rather than adding more, or something that would make mass ships more viable in the late game. But I never explored these possibilities.

Speaking of ships, another problem I never solved had to do with the balance between turrets and ships. They were supposed to be an efficiency vs flexibility tradeoff: turrets are more efficient, but ships can move around, defend probes out in the open, and pick off isolated enemy ships before they get in range, so the station doesn't fight a whole wave at once. But ships were actually more efficient in terms of damage per cost. Their real downside was requiring micromanagement to not get killed, which limited how many you could use effectively. I don't think this tradeoff is as interesting as the one I intended to create.

Something that wasn't exactly a game design problem, but definitely a lesson I learned from the project, had to do with pathfinding and collision. In Spacestation Defense, everything is rectangular and can rotate, which meant calculating collision and pathing was extremely complex and CPU-intensive. Basically, every frame, for every ship that was moving, for every hypothetical position it was examining to find a path, it had to check if it would collide with any of the other ships or station components in play. This could mean millions of collision checks every frame, and caused a lot of lag (even though I was writing in a very efficient language and wasn't using any fancy graphics!). I eventually optimized this by having things ignore certain obstacles in situations where I knew they wouldn't be relevant (for example, pathing for enemy ships doesn't consider player stuff as obstacles, because if they're ever in its way, it'll just attack them instead of going around), and these optimizations helped enough to get lag under control, but I still wondered why this was so hard in the first place.

I learned about fancy solutions to reduce the time complexity of pathing, like dividing space into sections and pre-generating a list of all the entities in each, so that collision checks would only be done on things that were nearby anyway. But these things would be a headache to implement.

I think the main lesson I learned from this difficulty is that ships didn't need to have rectangular collision. They could've been treated as circles, which would be much easier to calculate since rotation doesn't matter; you could just check the distance between them and subtract their radiuses. Rotation is a nightmare for real-time collision detection. And unlike the fancy solutions, this would've been *easier* to implement.

Proxied content from gemini://yujiri.xyz/game-design/reflections-spacestation-defense.gmi

Gemini request details:

Original URL
gemini://yujiri.xyz/game-design/reflections-spacestation-defense.gmi
Status code
Success
Meta
text/gemini; lang=en
Proxied by
kineto

Be advised that no attempt was made to verify the remote SSL certificate.

What is Gemini?