A Probabilistic Shot Accuracy Model for FPS Enemy AI

I’m currently working on an FPS called DEBASER. In this game, until very recently in development, the enemies had 100% accuracy when shooting, so long as you were visible by the time they pulled the trigger. Obviously, this is both unrealistic and makes the game unreasonably hard. After some playtesters complained, I began searching for a solution. For simplicity, I decided that I would use a probabilistic model to determine if the enemy misses or hits to player according to RNG.

The Variables

As a starting point, I figured that a good model for shot accuracy should take two variables into account: distance and speed of the target. If you were trying to shoot something, you would much rather it be close to you and not moving, than far away and zig-zagging.

An immediate problem with this model is that speed can be misleading. It’s difficult to hit a target running around you in a circle, but it’s easy if they’re running directly towards you. This is because you only have to account for movement that changes the angle you must shoot at. It follows that we would be better suited to consider the angular velocity of the target relative to the shooter.

Above is a basic diagram showing how we get the angular velocity of the player relative to the enemy. Note that v is the tangential velocity, which is found by projecting the velocity vector of the player onto the plane whose normal is the facing vector of the enemy:

Vp = player’s velocity vector, Fe = face vector of the enemy
Angular velocity as a function of tangential velocity v and radius r

The Difficulty Model

As an intermediate to the Accuracy model, let’s first model “difficulty.” Difficulty (D) will be a number from 0 to infinity, where 0 means it is 100% likely that the enemy will hit the player and infinity means it is 0% likely that the enemy will succeed. Difficulty should increase as range and angular velocity increase, like so:

To get an intuition for how this function behaves, let’s graph it with D on the y axis and r on the x axis. v will raise and lower.

Notice that the difficulty starts at 0 when the distance is zero. Intuitively, this makes sense. However, the function is asymptotic: it converges to a number, not infinity. This makes no sense, as difficulty should always be rising as distance increases. The reason for this convergence is that, as you get farther away, angular velocity shrinks so fast that it cancels out the growth of r. The solution to this is to add a “helper” constant (c) to the angular velocity at all times. This will ensure that it always approaches infinity.

Above, you can see the effect that increasing the helper constant has on the limits of the Difficulty model.

With that, our Difficulty model is complete:

The Accuracy Model

Our final accuracy model should represent the probability from 0 to 1 that the shot will be accurate. Because Difficulty can be anywhere from 0 to infinity, we should start by establishing two more constants to confine our results: L = the difficulty below which accuracy is 100%. U is the difficulty above which accuracy is 0%.

Above we see L and U in the context of the current difficulty model. To create an accuracy model, we need to form a piecewise function. The behavior when D is above and below L and U has already been specified above, but what about in-between? We don’t want this function to have any gaps in it, so what we can do is interpolate from 1 to 0 based on how far D has traveled throughout the region between U and L. The math behind this interpolation gets a bit messy but the overall concept is simple. The resulting Accuracy model is as follows:

Accounting for Human Stupidity

Humans are bad at probability (well, maybe it’s just me…); as I play-tested this model, I noticed that the AI was still very accurate. At 50% accuracy they were still very often taking me out on the first try. In a deliberate departure from realism, I added another constant, h, which I’ve affectionately named the Constant of Human Stupidity. The final model simply applies h as an exponent to A. Along with making it easier to avoid getting shot, this constant has the effect of making the model look more exponential for higher values of h. Below is the result of raising and lowering h.

The Full Model

Tangential velocity of player relative to enemy
Angular velocity as a function of tangential velocity and radius
Difficulty score, from 0 to infinity
Accuracy, as a probability from 0 to 1

Click below to play with the model on Desmos.

The Ping Pong Modulo Function

Ping Pong Paddle On Table Vector Art & Graphics | freevector.com

Earlier today I had to spend a whole 20 minutes coming up with this function. It’s like a modulo function, but instead of looping back to zero every cycle, it transitions back down to zero. It cycles every 2(n-1) elements. There’s probably a proper name for it, but I can’t figure it out.

In case anyone else would rather google it than spend time figuring it out, here it is:

def ping_pong_mod(i, n):
    cycle = 2 * (n - 1)
    i = i % cycle
    if i >= n:
        return cycle - i
    return i

I hope this is helpful to a fellow lazy person in the future.