A tactical squad-based survival strategy game based in a thoroughly stylised pixel-perfect steampunk setting. Plan fights, rest at camps, juggle dwindling resources, and journey to the relative safety of bunkers, all while on the run from a swarm of vicious and heavily armed mutated rats.

Post news Report RSS Devlog #70: a story about parallel enemy movement

New devlog entry for Shardpunk. I am rambling about the challenges I've encountered when implementing parallel enemy movement. Gif-heavy.

Posted by on

Howdy! I checked all the dates and it seems that Digital Dragons competition has its new deadline set to 1st of July. It means I have a few weeks to polish the game in its current state and release a new demo.

I will have to hide the shelter phase though, as it is still not ready.

Now, several weeks of development sound nice - but I have to remember that I am only able to work on this during evenings and/or early in the mornings. This does not give me a lot of time in the end.

Anyways, I decided to squeeze in one extra game combat feature which is about reducing the enemy turn duration. Sure, there is already a possibility to speed up enemy movement by holding the space button but I knew that more could be done to make the gameplay more convenient.

So: there is a lot of melee enemies in the game. And they run a lot, as they need to reach player characters in order to hit them, obviously. And sadly, melee enemies were not making moves in parallel.

Previously, each enemy had to finish their move before the next one started.
That was taking too much time.


There had to be a way to speed this up. I decided to use a similar approach to the one XCOM 2 had when dealing with Lost (zombie-like creatures, coming in large numbers): melee enemies will all move in the same time, and only when all the movement is finished, each of them who has a possibility performs a melee attack.

To make stuff more readable, I decided that such move-then-attack batches will be run consecutively for each set of melee enemies for each pod (spawn point). Enemies originating from a single pod tend to be closer together, so the player will see where they move.

So I made the change. I will skip all the performance improvement challenges I had to face (as pathfinding had to be calculated quicker). Anyway, after I made the change a number of other issues appeared:

Issue #1: The camera movement

If more than one enemy is moving at once, which one should be followed by the camera? I thought about not moving the camera at all, as that's what I do when an enemy pod gets triggered for the first time. The thing is that enemies in a triggered pod have 50% of their original action points (as the triggering phase is only there to make sure they get in position) so they were not traveling long distances, meaning that even after the move they would not go outside of camera's viewport. Here it was not the case.

I decided that the camera will focus on the first melee enemy in a pod, and it looked good.

Issue #2: overwatch reaction shots

So yeah, the camera was following the enemies nicely. But then, player characters could have overwatch enabled. And as multiple enemies were moving at once, more than one reaction fire could take place. This caused a few visual issues.

The first one was related with a visual "feature" that I've added the other day, about friendlies reacting to another friendly shot with a "dodge" animation:

One character shoots, others duck.


It turned out it wasn't looking good when multiple reaction shots were being taken: a character who was shooting was also performing that "dodge" thing.

In the end, I made sure that a "friendly dodge" animation cannot happen during the parallel movement phase.

To be fair, the same issue was occurring when using the shotgun's AOE attack (I've increased its range, by the way): a character would be stuck in the "dodge" animation instead of getting correctly killed by friendly fire.

A friendly character has died. Everyone else has gained 5 stress points because of that.
Unfortunately, he's stuck in another animation.


Issue #3: camera behavior during overwatch

With the camera following a single melee enemy, the player was not always able to see who is doing a reaction shot. So I fixed that as well: for the duration of a reaction shot, the camera stopped following the first enemy and focused on both the enemy being targetted by reaction fire and the character performing the attack.

Sounds good? Well, not really! It might be that more than one player character was firing a reaction shot. The camera couldn't be in both places at once (and note that each character might stand in a completely different place on a map) so in some cases, the player was still unable to see who is shooting (and whether they hit the target or not).

As an ultimate solution, I made sure that reaction shots are not displayed in parallel - they are queued instead.

So, the script for the parallel melee enemies movement is as follows:

1. All melee enemies start their movement, the camera starts following the 1st enemy only.
2. All reaction shots get displayed one after another. For each reaction shot the camera focuses on the target and the attacker.
3. If there are any melee enemies alive, they perform their melee attacks.

In this example, no enemy has reached its destination. Just look at this 37% hit!


Also, a bonus: characters can have a "multi-overwatch" skill, that allows them to shoot more than one reaction shot during the enemy turn. Without the queuing approach for reaction shots, it looked silly:


After all the fixes, it finally looks good:


Here's how all of this works if there are no overwatch reaction shots:


Pretty faster than the original approach, isn't it?

Anyways, that's it for this entry! I have three more weeks before the Digital Dragons submission, so theoretically I could squeeze in one major change into the game. Right now I will focus on some playtesting though.

Thanks for reading, and see you in two weeks!

Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: