When I created the original Flash game that Starcom: Nexus is based on back in 2008-ish, I’d been watching a lot of the new (at the time) Battlestar Galactica. I loved how the ship combat looked and still thinks it holds up really well today. One of the upgrades I added to the game was a point-defense system called “Havok” inspired by the clouds of explosive flak thrown up in the big capital ship fights. Of course, having to run in Flash, in 2008 era browsers, my flak didn’t look very much like Battlestar Galactica’s visuals.
Since Starcom: Nexus is a PC game and computers have come quite a ways since then, I’m currently working on an improved version of this frequently-requested feature for the next Early Access update.
Previously, I had a working version in a pre-alpha build, but took it out. It looked good, but the performance hit was too heavy.
The desired gameplay effect of Havok is an automatic system that passively protects the player’s ship with a directed “umbrella” of explosive flak. Any hostile ship or missile that passed through the flak would take damage from the numerous cluster explosions.
After watching a lot of Battlestar Galactica clips in slow-motion, I had a pretty good idea of how I wanted the effect to look.
Visually, I wanted to the player to see their ship firing off a barrage of small projectiles that explode with a single burst at a perimeter distance, then a moment later a cluster of mini-bomblets detonate around the burst.
Here’s the particle system for the burst and bomblets. The initial central burst a pre-rendered sprite sheet with random rotation:
And here’s a bunch of them in context:
As you can see, the player’s ship is firing a constant stream of tiny projectiles from its Havok modules that produce numerous instances of the above effect. The enemy fighters passing through the flak cloud take damage, some exploding in fireballs of their own, adding to the general fireworks. The flak clouds have a Rigidbody that inherits a small fraction of the speed of the projectile that produced them to give a subtle cue that the cloud is being emitted from the player’s ship. Both the projectiles and the burst particles are pooled objects, eliminating the very expensive instantiate calls to create them.
Calculating the impact of individual bomblets would obviously be crazy overkill– the game only needed to calculate some range of damage for ships roughly inside a flak cloud, not figure out which individual particle overlapped them. My original attempt was to have the clusters do a Physics.OverlapSphereNonAlloc() call to find targets to damage each FixedUpdate. Performance tip: always try to use the NonAlloc version of physics calls into an array that you reuse. This reduces garbage collection.
This worked “okay” but in heavy battles became a noticeable performance problem for a few reasons:
- Lots of small calls to the Physics system are expensive, even if you’re using layer masks to reduce the search space.
- If the Physics system reported a hit, a GetComponent call was required to actually deal the damage.
When I revisited this feature for the upcoming Beta 0.11, I changed the logic. The Havok AI already needs to look for “Health” components to target. It does this by doing a single, big, OverlapSphereNonAlloc every 0.25 to 0.33 seconds. (I’m using a custom scheduler that processes game tasks at the end of a frame. Game tasks give a range of time in which they can run, so the scheduler can wait a couple frames to avoid “busy” frames if possible.) This pass does the GetComponent call and filters the results into a List<Health> of target candidates, filtering out non-hostiles. Its search radius is pretty large because it needs to know about incoming threats that might be well outside the flak perimeter.
During each physics FixedUpdate, the Havok system iterates over each active burst and for each burst looks at each enemy found during the AI phase. If their (burstPosition – enemyPosition).sqrMagnitude is within the square of the burst radius, it deals damage. From previous tests I knew that Vector3.Distance is quite fast, but still about 25-50% slower than a sqrMagnitude comparison. Having a dozen clusters check against possibly several dozen targets may sound like a lot of operations, but in some situations a large number of very fast operations is better than a smaller number of slow operations. Sometimes you just have to test things out to figure out which is better.
Longer video of the effect:
Note: This feature isn’t yet in the current Early Access build or Beta branch, so as always with these features there may be issues uncovered during testing that require adjustment.