Game AI move towards player while avoiding an obstacle

I'm trying to move enemy towards player, while enemy also tries to avoid an obstacle if it's in its path. Here's a simple illustration of what I want to do:

What I've tried:

• Making circular collision around the obstacle. It works but that makes the enemy impossible to hit.

• I think going with an A* pathfinding algorithm might be a bit overkill especially since it's designed for multiple obstacles, where in my case there is only the mouse and wall collision concerned.

• Moving the enemy towards the player, while also moving away from the obstacle at a slower speed. This works better because you can actually hit the enemy, but also slows down enemy if obstacle if it's in its path.

My questions:

1. Is there a better way to go about doing this?

2. Anything you would've done differently with my code to either improve or optimize?

Here's the code I have currently in my game logic and a fully-working example on jsFiddle.

// Calculate vector between player and target
var toPlayerX = playerPosX - enemyPosX;
var toPlayerY = playerPosY - enemyPosY;

// Calculate vector between mouse and target
var toMouseX = mousePosX - enemyPosX;
var toMouseY = mousePosY - enemyPosY;

// Calculate distance between player and enemy, mouse and enemy
var toPlayerLength = Math.sqrt(toPlayerX * toPlayerX + toPlayerY * toPlayerY);
var toMouseLength = Math.sqrt(toMouseX * toMouseX + toMouseY * toMouseY);

// Normalize vector player
toPlayerX = toPlayerX / toPlayerLength;
toPlayerY = toPlayerY / toPlayerLength;

// Normalize vector mouse
toMouseX = toMouseX / toMouseLength;
toMouseY = toMouseY / toMouseLength;

// Move enemy torwards player
enemyPosX += toPlayerX * speed;
enemyPosY += toPlayerY * speed;

// Move enemy away from obstacle (a bit slower than towards player)
enemyPosX -= toMouseX * (speed * 0.4);
enemyPosY -= toMouseY * (speed * 0.4);

• "Making circular collision around the obstacle. It works but that makes the enemy impossible to hit." Just to clarify: is the mouse the obstacle? Is the player trying to hit the enemy with the mouse? Apr 21, 2018 at 0:35
• @JibbSmart Yes! Apr 21, 2018 at 0:43

There's a few questions that are very relevant to the potential answer given.

What is the desired behavior?

It's clear you want the enemy to avoid the obstacle/mouse, but how? Is it a zombie ambling towards a human? A fast moving jet flying towards a location? Is it an abstract shape moving towards another abstract shape? Is it a ghost avoiding a proton-pack-style beam?

The nature of the enemy and the obstacle matters when determining how it avoids the obstacle. An animal or zombie might stop or even reverse direction. A trained soldier might make attempts to dodge or predict pathing. A jet, car, or other entity with velocity could only make course corrections. A ghost might be able to simply disappear or teleport. A normal person might try moving/dodging or retreating, but be more sluggish than others (utilizing JibbSmart's idea)

The kind of entity it is determines the method by which it would avoid the obstacle.

What is the desired difficulty?

You could easily make an entity that could, quite literally, never be touched with the mouse. You could also make an entity that merely bee-lined and didn't avoid the mouse at all. It's clear you want something in the middle, but the level of difficulty matters.

Can the enemy speed up? Does the enemy have knowledge of past obstacle location? Can the enemy predict future locations? If this is a very simple entity with no ability to predict or alter tactics, are there other planned means to introduce varying levels of difficulty? Is it a possibility to have different "types" of enemies that utilize different methods of obstacle avoidance?

This information is important because, with no ability to predict the obstacle or alter speed, it will inevitably be slower when avoiding the obstacle/mouse than it would be moving directly towards the player. There's no way to avoid an obstacle slowing the enemy down.

The solution

If, however, all you want to do is merely avoid the obstacle with present-state knowledge, but do so in a reasonable way that provides difficulty but not impossibility, you do not want to simply add an "away from mouse" vector to the "towards player" vector. Rather, what you want is to change the angle of approach.

To do this, you'll need to do the following:

1. Calculate the angle to the goal/player
2. Calculate the angle and distance to the obstacle/mouse
3. Determine if the angle and distance to the obstacle is in the enemy's "danger zone" (is it a threat?)
4. If the obstacle is a threat, steer away from it in whichever direction is closer to the angle to the goal/player

The angle and distance of the "danger zone" and the angle chosen to steer around the obstacle are going to be determined when you are balancing difficulty.

The math goes something like this:

angleToPlayer = atan(toPlayerY, toPlayerX)
angleToMouse = atan(toMouseY, toMouseX)
angleToMove = angleToPlayer

angleToMove += courseCorrectionAngle
}

enemyPosX += cos(enemyAngle) * speed
enemyPosY += sin(enemyAngle) * speed


Arctangent helps you find the angle to the relevant objects using the x and y coordinates Cosine determines the normalized x distance to travel, while Sine determines the normalized y distance to travel

• Hey, thanks for the detailed answer. Do you mean something along the lines of Jason Stackhouse's code in his answer? Apr 21, 2018 at 15:29
• No, that answer doesn't make sense to me. You do, however, use arctangent to determine the angle. You then use sin and cos to determine the x and y positioning. I can amend the answer to include that information. Apr 21, 2018 at 16:15
• Works a little better than my initial code. I'm just assuming you meant atan2. jsfiddle.net/512rctxp/86 Apr 21, 2018 at 17:02
• For your specific implementation, sure, atan2. But the link you posted doesn't really utilize what I put above. You only change the enemy position once, after you determine the proper angle. It's up to you and your balancing to figure out how you determine the proper angle, but once determined, use cos and sin to determine the position/vector of travel. Apr 21, 2018 at 18:45
• Your code makes a lot of sense, but Math.atan() can only reference one argument. This confuses me so much. Apr 21, 2018 at 20:34

One thing that makes shooting moving targets really interesting is that we as humans have slow reaction times. When we hit a moving target, it's because we've successfully estimated where that target will be after the span of our reaction time has passed and met the target there with our aimer.

For this enemy to avoid the mouse but still be hittable, you could give it a delayed reaction time. Have it pathfind through (or perform collision with) an old version of the current state.

If you keep a ring buffer of recent states (it doesn't have to be every frame if that's too much -- it could interpolate between a few states), you can have the enemy look back in time say half a second (or whatever works best for the difficulty you're going for) and give it circular collision with that old state. This'll require the player to quickly move over the enemy and fire where they anticipate the enemy will be in order to beat its reaction time.

Also, you could have the invisible collision circle around the mouse shrink when it moves quickly or changes direction, and grow over time back to full size when it's relatively still or moving consistently. This might have the enemy appear to keep a wide berth from the mouse when its position is obvious, but force it to make only small moves to avoid it when the mouse moves quickly.