Making An Enemy Avoid Being Shot


motorollin

Member
Joined
Jul 31, 2007
Messages
163
I need to code some AI for one of my enemies so that it moves towards the player but moves to avoid bullets. Here's what I'm thinking:

1. Iterate through vector "bullets" to find closest bullet
2. Use the velocities and co-ordinates to predict a collision
3. No collision predicted -> Move towards player and return
4. Collision predicted
5. Search 90, 180, 270 and 360 degrees relative to the bullet's trajectory for other bullets or edge of playfield
6. No safe path detected -> Give up. Move towards player even if bullet in the way, and return
7. Safe path detected -> Move towards safe path and return

Any thoughts on this? Is there a more efficient method?
 
I would do it at a somewhat earlier point. Don't bother the individual shots, just try to stay out of the player's shot line.
Except your shots are really slow, in that case your approach would be better.

In general, don't really bother about the details. I mean, the perfect enemy AI is boring anyway. Let them make mistakes, you have less to think and the game will be more fun.
 
The game I am writing is a Geometry Wars clone, therefore Robotron style controls. So the player could fire a shot in one direction and then in another, which may not be the same directions in which the player is travelling. Also the player could be moving and shooting at the same time, which means any given bullet could be outside of the player's current 8 shot lines. So the enemy really needs to examine the bullets themselves, not the player, in order to avoid getting shot. I see your point about allowing the enemies to make mistakes. But using the player's shot lines to avoid bullets which are already on the screen would be too ineffective - unless I misunderstand your method.
 
Ok, right.

Then draw an imaginary line for each shot. The lines start at the shot's current position and end where the shot will end (at the next wall maybe?).
Advise your enemies to not cross those invisible lines. If an enemy has a line through him he tries to move sideways using the normal vector of AB where A is the player's and B is the enemy's position, additionally to the movement toward the player of course.
But you really ought to relax those rules a bit. If you really do a "predict and prevent" AI the player will get frustrated soon because he won't be able to hit the enemies. An option would be to introduce a sight radius, e.g. the enemy only handles bullets inside its limited angle of vision, so if, for example, a bullet hits him from behind he can't see it.
 
Simon Parzer said:
Then draw an imaginary line for each shot. The lines start at the shot's current position and end where the shot will end (at the next wall maybe?).

Would that line be an actual object which is created, or just calculated using maths at each Enemy.move()?

Simon Parzer said:
Advise your enemies to not cross those invisible lines. If an enemy has a line through him he tries to move sideways using the normal vector of AB where A is the player's and B is the enemy's position, additionally to the movement toward the player of course.

And I suppose once the enemy has been told "you can't move in that direction because it would mean crossing in to the path of a bullet" or "you need to get out of the way of the bullet path you are already in", I guess it will just test a different direction to see if that one is safe?

Simon Parzer said:
But you really ought to relax those rules a bit. If you really do a "predict and prevent" AI the player will get frustrated soon because he won't be able to hit the enemies.

Well in GridWars that is exactly what happens. In the hardest mode it is extremely hard to hit them. The best tactics I have found are for the player to move perpendicular to the enemy, constantly shooting towards it (so the shots are perpendicular to the player's movement). This effectively creates a wall of bullets emanating from the player. Because the player can move faster than the enemy, the wall will eventually overtake the enemy and leave it no path to escape. Speeding up or slowing down the speed of the player, the bullets and the enemy will tweak the difficulty.

Simon Parzer said:
An option would be to introduce a sight radius, e.g. the enemy only handles bullets inside its limited angle of vision, so if, for example, a bullet hits him from behind he can't see it.

It would be impossible to get "behind" it since it always moves towards the player.

I have got the BlitzMax source code for GridWars so I'll have a look through and see how the avoidance works in that.
 
Last edited by a moderator:
I would really have tried a simpler game than GridWars to get to grips with some of the concepts you are coming up against... But I admire your courage here... :)

Good luck with the project anyhow.
 
PokeParadox said:
I would really have tried a simpler game than GridWars to get to grips with some of the concepts you are coming up against...

Actually I think the difficulty curve of recreating GridWars has been ok. There are lots of simple things to get stuck in to: loading images, clipping sprites, blitting images, handling input, moving sprites, detecting collision. Learning all of these things has been great fun as well as a good introduction to coding in C++. Fortunately I have a lot of experience coding in PHP which is similar syntax to C, so it hasn't been too difficult. The rest of the things I need to learn to complete this game can all be worked out using logic and bouncing ideas of others (=you guys ;) ). I know I'll get there.

PokeParadox said:
But I admire your courage here... :)

Good luck with the project anyhow.

Thanks :D

I've hand-written some code which I think might handle the bullet-avoiding AI. I haven't got my player shooting yet so I can't test it, but I'll type it up and post it to get some opinions.
 
Last edited by a moderator:
Ok, here's what I'm thinking:

CODE


class Bullet
{
...
private:
Vector<SDL_Rect> points;
int x, y, xVel, yVel;
...
}


Bullet::Move()
{
points.clear();
int pointx=x;
int pointy=y;

//For each point on the bullet's trajectory to the edge of the playfield
while ( pointx >= 0 || pointy >= 0 || pointx <= LEVEL_WIDTH || pointy <= LEVEL_HEIGHT )
{
//Add a point in the points array
points.push_back();
points[points.back()].x = pointx;
points[points.back()].y = pointy;

//Next point
pointx += xVel;
pointy += yVel;
}
}

Green::canmove( int pointx, int pointy )
{
//For each bullet
for ( int i = 0; i < bullets.size(); i++ )
{
//For each point on the bullet's trajectory
for ( int j = 0; j < bullets.points.size(); j++ )
{
//Get two known distances between the target point
//and the point on the bullet's trajectory
int side1 = abs( pointx - bullets.points[j].x );
int side2 = abs( pointy - bullets.points[j].y );

//Calculate the missing side to get the actual distance
//between the target point and the point on the
//bullet's trajectory. And I told my maths teacher I
//would never use Pythagorus...
int distance = sqrt( ( side1*side1 ) + ( side2*side2 ) );

//The bullet's trajectory is too close to the target point,
//so don't allow the enemy to move to this position
if ( distance < 10 ) return false;
}
}

//Target point is safe to move to
return true;
}

Green::move()
{
//Check each movement direction, starting with the optimal
//direction (moving towards the player). Each subsequent check
//is 45 degrees further in an anti-clockwise direction, until a
//safe path is found. If no safe path is found, the enemy just
//moves towards the player anyway

if ( canmove( x+xVel, y+yVel ) x+=xVel; y+=yVel; return;
if ( canmove( x+xVel, y ) x+=xVel; return;
if ( canmove( x+xVel, y-yVel ) x+=xVel; y-=yVel; return;
if ( canmove( x, y-yVel ) y-=yVel; return;
if ( canmove( x-xVel, y-yVel ) x-=xVel; y-=yVel; return;
if ( canmove( x-xVel, y ) x-=xVel; return;
if ( canmove( x-xVel, y+yVel ) x-=xVel; y+=yVel; return;
if ( canmove( x, y+yVel ) y+=yVel; return;
}




This doesn't look terribly efficient to me, but it's the only way I can think of doing it. Any corrections or suggestions appreciated!
 
You don't want to use || (or) when you mean && (and).

A guideline for the future - You shouldn't use a member var-name like 'x', since you're begging for trouble down the road. It is generally wiser to use x, y, i, j, k and other 'tiny' var-names for incidentals ("for x = ..."); use a useful variable name as a member var-name so you don't use it by accident.. its more clear. Coudl even have a struct for coordinate pairs, and then have..

coord_pair_t m_coords; // m_coords.x and m_coords.y

*shrug*

Just cruising theough your thread without reading it ;)

(I'm not a fan of asking on how best to do every little thing before trying it. Just try it, do it, do your best, then ask for help. Otherwise you're coding-by-proxy :p)

jeff
 
and

CODE


if ( canmove( x+xVel, y+yVel ) x+=xVel; y+=yVel; return;
if ( canmove( x+xVel, y ) x+=xVel; return;
etc etc




should definately be

CODE


if ( canmove( x+xVel, y+yVel ))
{
x+=xVel;
y+=yVel;
return;
}


if ( canmove( x+xVel, y ))
{
x+=xVel;
return;
}
etc etc
 
If you're going to go there, it really should be ...

if ( move ( xstuff, ystuff ) ) {
return;
}

ie: Never restate the same thing twice .. the method can check, and perform it, all at once, returnign true/false. Unless you want to separate it of course.

Course, I'm anal, I don't beleive a unit shoudl know abouts its own movement half the time. A unit should be able to produce things like speed and movement type, but the actual movement should be controlled by a world or physics object or the like.

ie:

playfield.move ( unit, motion_vector )

Or somesuch.

But again, I've not read any of this :)

(ie: OO is about localizing the info .. so all movement stuff goes to the object, along with everythign else with the object. But I prefer to have4 the object return the useful descriptions and such .. let the object know what needs to be drawn about it, what kinds of motion it needs, and so forth. But then a motion handler does the movement (the playfield object, say), while a render object does the rendering. So a render object can ask another object for its description, say. So keep any given object a master of everything about it, but not necessarily doing it .. else you end up with motion code in every object, when it shoudl just be a motion _description_.)

Theorycraft ftw.

jeff
 
skeezix said:
If you're going to go there, it really should be ...

if ( move ( xstuff, ystuff ) ) {
return;
}

Actually the point I was raising was that:

if ( canmove( x+xVel, y+yVel ) x+=xVel; y+=yVel; return;

Is the same same as

if ( canmove( x+xVel, y+yVel ))
{
x+=xVel;
}
y+=yVel;
return;

Which definately isn't what he intended.

But yes having a move method would be more oo, although the if statements would then just be shifted inside the enemy class (agreed, where they belong), the bug would still be there.
 
Last edited by a moderator:
Ah, nice one.

As a rule, peopel should _always_ use braces and never rely on "if ..." without them. It sort of annoys me to have that inconsistency in the language (one of many really). Always braces. Never optional.

Why.. stuff like that. Hard to see at 5am debug sessions :p

But more to point..

I once found a piece of code "if ( .. ) function();"; took me awhile (too long really, doh) to sort out that "function" was a #define macro (should have been FUNCTION .. all uppercase for #define's!). Being a macro, it was multiline and thus when expanded the if only covered the first bit. Because it looked like a function call, I assumed it would work. I got to rage, since it was other peopels code and I always use braces :)

Aren't we all elitist.. :p

jeff
 
It is possible to get some compilers to flag that kind of "bad coding". Eg the ARM compiler at work when given this code:

void main (void)
{
if (1) 0;
}

gives the following output:

Error (main.c:3) the statement forming the body of an if, else if, else, while, do ... while, or for statement shall always be enclosed in braces
Warning (main.c:3) expression has no effect
Error (main.c:3) all non-null statements shall have a side-effect

Although useful, it makes it incredibly difficult to take code from someone else not using the above compiler, and incorporate it into a project which does.
 
Looking for a thorough read on the subject, "Programming Game AI by Example". written by Mat Buckland published by Wordware, is rather excellent.

Multiline macros are a sign of evil. As far as syntax goes code what you mean and mean what you code. I only brace as much as required to make it clear to the compiler my intention. For humans, I prefer matching columns, braces always on lines by themselves and lots of whitespace.
 
Back
Top