This weekend was mostly spent readying things up to move to another apartment over the next couple weeks — not a drastic relocation, just moving from one suburb of North Dallas to another — and my home office is a bit of a mess right now. By which I mean, I have to jump over several boxes to get to my chair. Probably a literal fire hazard. That sort of thing.
I’ve been working on enemy designs recently, mostly on paper but also prototyping in the game as I’m able. My goal is to have about 15-20 unique enemies types in Gunmetal Arcadia Zero, plus some variations on these (palettes swaps with alternate abilities), plus bosses and minibosses. It’s entirely possible that will be overscoped and I’ll have to scale it way down, but why not aim high, right? If you’re curious, I chose those numbers by looking at the bestiaries and rogues galleries of a number of NES games in the same vein, especially Castlevania.
I’m starting by decoupling the visual design of enemies from their functional abilities so I can focus entirely on gameplay for now. I just played through Symphony of the Night again last week, and I’ve been trying to identify interesting ideas that I can adapt to Gunmetal. One of these came in the form of the bone-throwing skeletons found throughout that game.
For months now, I’ve had a pending task to implement aimed shots, and I finally began prototyping that this week. The goal is for an enemy to be able to hit a moving target (typically the player) with a lobbed projectile, given some constraints on how fast the projectile can move. This is a fairly common problem in game dev and one that I’ve solved in various forms before, but in this case, the solution was a little more complicated than I anticipated. But before I get into the details, let’s step back and look at solutions to similar problems in the same vein.
Hitting a stationary starget with a linear projectile is trivial. We take the vector from the shooter to the target, normalize it, and multiply it by our projectile speed.
Hitting a moving target with a linear projectile is slightly more complicated, but still not bad. Assuming the speed of the projectile is constant, we know how far it will have traveled in any direction at time t. Assuming our target maintains its current velocity over time, we can solve for any point (or points) at which the target is this same distance from our shooter. (I like to visualize this as a line-sphere intersection test, but with the added condition that the radius of the sphere increases linearly over time.) This turns out to be a quadratic equation, so we may have up to two valid (non-imaginary) results, and we may choose any result with a positive time t value.
When we introduce acceleration due to gravity, things get a little trickier. If our target is stationary and we want to hit it with a projectile that follows a non-linear path affected by gravity, we can represent this path as a parabola that intersects the positions of our shooter and target and solve for the angle of elevation given our desired projectile speed.
So now we want to put both of these together and hit a moving target with a projectile that is affected by gravity. And this is where things get really hairy. Due to the additional complexity of modeling acceleration, however, this actually turns out to be a fourth-order, or quartic, function. This means it may have as many as four results.
Now, I’m not aware of any equivalent to the quadratic formula for solving quartic functions, so once I realized this was a fourth-order problem (and verified that with some internet searches), I had to decide whether it worth pursuing an exact solution. I spent an hour or two poking around at it before deciding it was not. Instead, David suggested I separate the vertical and horizontal motion of the projectile, fix one of these, and solve for the other, and that’s what I’ve ended up doing. The downside to this is that the magnitude of our velocity vector will vary depending on input conditions. The upside is it’s much more easily solved and for the sort of game I’m making, probably no one will notice the difference anyway.
My first attempt was to fix the horizontal speed of the projectile and alter its vertical velocity to hit the target. This works, but it feels a little wonky in practice, especially when the vertical velocity is near zero, and the projectile appears to have been dropped rather than thrown or fired. I got far better results when I fixed the initial vertical velocity and altered the horizontal speed to hit the target. This guarantees that the projectile will always spawn with a good amount of speed, it will always reach a certain height, and generally it just feels more tangible and physical.
If we disregard the player’s velocity, this becomes a problem of hitting a stationary target. This is a little easier to solve, and still a pretty good gameplay experience. If we account for the player’s velocity, then we end up with the case where the projectile will always hit the player unless they change their course after it’s been fired. I’m not yet sure how I feel about this. I feel like it’s somewhere between challenging and unfairly difficult or frustratingly artificially calculated. So what I’ve done instead is allow this to be tuned by solving for a variable amount of the player’s velocity. At zero, this becomes a stationary target solution and the player will never be hit as long as they keep moving. At one, this becomes a moving target solution and the player will always be hit unless they change course. It feels like somewhere in the middle will probably be a sweet spot, where projectiles are not totally predictable one way or the other, but can be avoided in most circumstances simply be staying aware of the playing field.
Of course, we don’t want enemies to be able to bullseye the player from halfway across the map, so it’s important to include some constraints on what valid solutions may be. In this case, I’m clamping the horizontal speed to some maximum value. If the player is too far away and can’t be reached without exceeding this value, then we just lob the projectile as far as we can and don’t worry about it.
In the future, I may tie some of these aspects into other AI systems, like whether the enemy is aware of the player, whether they’re facing the player, and so on, but for a quick prototype, I feel like this is off to a pretty good start.