Advertisement

Getting started with AI

Started by January 19, 2012 07:01 PM
0 comments, last by TheGoozah 12 years, 8 months ago
So far Ive just done games with very simple (i.e. basically none) space invader style shooters, platforms, etc. I'm looking now to make a more indepth space shooter, with the ships able to perform a number of tasks, work in groups, and show some measure of situational awareness (e.g. bomber wing not running down flak alley cause that sensor platform at the end looked like an easy target).

I found lots of bits and pieces around (e.g. leading a target, pathfinding, etc) but nothing to really bring it together to create a viable implementation.


Ive come up with somthing that I think might have potential, but not really sure if its a remotely good idea, or how to implement situational awarness or more advanced maneuvers (things like formations, evading incoming projectiles, collision avoidence, etc).

The basic idea is to split the problem into global (player would be counted as being here), group and unit level AI's. Groups may have subgroups. Right now I'm looking at the group and unit level.


For each ship I was thinking of splitting the AI into several independent parts. The main/maneuvering AI flies the ship, and may pick out specfic targets (or be told them by the group level, depending on the specific commands). The weapons AI handles the ships fixed weapons, taking opportunity shots at the main target, and perhaps using guided weapons and such against other opponents in range. Each turret also runs an AI that selects targets (with priority given to the main target were possible) and shoots at them. Most of the weapon/turret ai's/commands would be never ending, and would most likly have defaults set in the ships design (I suspect they will be 99% common code, with some differences in the algorthimns used for target selection.

Some main commands would be simply things like move to a specfic position, while other more complex ones (like attacking some target) might draw on a range of other commands to use as sub commands (e.g. the move commands).

Group commands would be slightly more general, like defend somthing, attack some other group, and will hand out suitable commands to its units and subgroups based on their strengths and weaknesses as well as there current status.

The global ai would then deal with deciding what to build/buy, and giving the top most groups tasks (stuff like defend base, attack emeny position, etc) and watch there progress (e.g. perhaps order a retreat if things look bad).

In the Ship object itself, Ive only put some very basic stuff, and some common bits I felt would be useful across the ships current "commands" (e.g. a ship-wide hold fire flag, primary target, etc).
[source lang="cpp"]
struct BaseCmdState
{
/**Used by Ship::update to accelerate/deaccelerate.*/
float targetSpeed;
/**Lower level commands wont excede or change this.*/
float maxSpeed;
/**Used by Ship::update to turn. Nearly every command will change this.
*/
float targetDir;

Vector2F targetPos;
/**Object to move towards, away, etc.*/
Ship *targetObj;
/**Object to attack.*/
Ship *attackObj;

bool holdFire;
};
void clearCmdState(BaseCmdState *state);
void setCmdStateStop()
{
BaseCmdState &c = getCmdState();
c.targetSpeed = 0;
c.targetDir = getDir();
}

BaseCmdState &getCmdState(){return cmdState;}
void setCmdState(const BaseCmdState &state){cmdState=state;}
ai::ShipCommand *getCmd(){return cmd;}
void setCmd(ai::ShipCommand *cmd, bool deleteOld=true);

//cpp
void Ship::update()
{
if(cmd)
{
cmd->update();
if(cmd->isComplete())
{
delete cmd;
cmd = 0;
setCmdStateStop();
}
}
//Todo update mainWeaponsCmd, turret commands
//Todo2 implement weapons
//Todo3 then implement weapon/turrent commands

//Turn, accelerate, and update position
lastPos = pos;
lastDir = dir;

if(dir != cmdState.targetDir)
{
dir = turnTowards(dir, cmdState.targetDir, type->turnRate);
}
if(speed != cmdState.targetSpeed)
{
assert(cmdState.targetSpeed <= cmdState.maxSpeed);
assert(cmdState.targetSpeed <= type->maxSpeed);
//todo, make somthing like turnTowards
float d = cmdState.targetSpeed - speed;
if(abs(d) <= type->acceleration)speed = cmdState.targetSpeed;
else if(d >= 0)speed += type->acceleration;
else speed -= type->acceleration;
}

vel.magDir(speed, dir);//for now at least, rule is ships only move forward

pos += vel;
//Same for weapons and turrets
//Weapons each have a bool fire setby mainWeaponsCmd (target most likly
//state.attackObj, but homing for example might draw from a wider range
//of targets, perhaps anti-missile missiles?)
//Turrets have a targetDir and boolean fire each set by there own turretCmd
//priority is state.attackObj

//Will need to update weapon/turret positions since pos&dir may have changed
}
[/source]
The command objects are based on this. weapon/turrets may get different base classes. Plan to add events to help inform commands of local events rather than poll it (e.g. criticalDamage, incomingFire (non homing), incomingMissile, etc)
[source lang="cpp"] class ShipCommand
{
public:
ShipCommand(Ship *ship)
: ship(ship)
{}
virtual ~ShipCommand(){}
virtual bool isComplete()const=0;
virtual void update()=0;
protected:
Ship *const ship;
};
[/source]
Various commands then build on each other.
[source lang="cpp"]
/**@brief Move to a position.
*
* Moves to within targetRange of targetPos.
*
* Uses targetPos, targetSpeed and targetDir basic state members.
* maxSpeed is respected, and not changed.
*/
class MoveToPosInSector : public ShipCommand
{
public:
MoveToPosInSector(Ship *ship,
const Vector2F &targetPos, float targetRange,
bool slowCorners, bool stopAtPos)
: ShipCommand(ship)
, targetRange(targetRange)
, slowCorners(slowCorners), stopAtPos(stopAtPos)
{
status = SCS_MOVING_TO_POS;
Ship::BaseCmdState &state = ship->getCmdState();
state.targetSpeed = state.maxSpeed;
state.targetPos = targetPos;
}
virtual void update()
{
if(status != SCS_COMPLETED)
{
Ship::BaseCmdState &state = ship->getCmdState();
//vector from ship to target
Vector2F d = state.targetPos - ship->getPos();
float distSqr = d.magSqr();
//within range, end command
if(distSqr <= targetRange*targetRange)
{
//starting stopping now in attempt to end targetRange from
//targetPos
if(stopAtPos)
{
state.targetSpeed = 0;
state.targetDir = ship->getDir();
}
else
{
state.targetDir = d.dir();
}
status = SCS_COMPLETED;
}
else
{
//turn towards target
state.targetDir = d.dir();

float stoppingDist = ship->calcStoppingDistance() + targetRange;
if(stopAtPos && distSqr <= stoppingDist*stoppingDist)
{
//starting stopping now to prevent overrun
state.targetSpeed = 0;
}
else
{
if(slowCorners)
{
//If sharp corner, slow down
float dir = ship->getDir();
float deltaDir = wrapAngle(dir - state.targetDir);
if(abs(deltaDir) >= math::pi/2)
state.targetSpeed = 0;
else
state.targetSpeed = state.maxSpeed;
}
//full speed ahead
else state.targetSpeed = state.maxSpeed;
}
}
}
}
private:
float targetRange;
bool slowCorners, stopAtPos;
Status status;
};
[/source]
A higher level command that uses a few others.
[source lang="cpp"]
/**@brief Attack a near by target using bombing runs. Carries on as long
* as possible.
*
* Clears maxSpeed state to max physical speed. Changes targetSpeed,
* targetDir, targetPos. targetObj and attackObj set to target.
*/
class BomberAttack : public ShipCommand
{
public:
BomberAttack(Ship *ship, Ship *target)
: ShipCommand(ship, CMD_TURN_TOWARDS)
{
calcDistances();
status = S_MOVING_TO_RANGE;

sub = new MoveToObjInSector(
ship, target, startRunDist,
false, false);

ship->getCmdState().targetObj = target;
ship->getCmdState().attackObj = target;
ship->getCmdState().targetSpeed = ship->getType()->maxSpeed;
}
virtual void update()
{
if(!isComplete())
{
Ship *target = ship->getCmdState().targetObj;
if(!target->isAlive())
{
status = S_TARGET_DEAD;
ship->setCmdStateStop();
}
else if(ship->getSector() != target->getSector())
{
status = S_DIFF_SECTORS;
ship->setCmdStateStop();
}
else if(status == S_MOVING_TO_RANGE)
{
sub->update();
if(sub->isComplete())
{
status = S_BOMBING_RUN;
delete sub;
//going for a more erratic path than regular move, but while still keeping with a few degrees of the target
sub = new BombingRun(
ship, target, endRunDist);
}
}
else if(status == S_BOMBING_RUN)
{
sub->update();
if(sub->isComplete())
{
status = S_MOVING_AWAY;
delete sub;

Vector2F dp = target->getPos() - ship->getPos();
float dir = dp.dir();
dir +=
(rand()%2 == 0 ? 1 : -1) *
(0.5f + (rand()/(float)RAND_MAX)*1.0f);
Vector2F moveVec = Vector2F::fromMagDir(breakOffDist, dir);
sub = new MoveToPosInSector(
ship, moveVec+ship->getPos(), 100, false, false);
}
}
else
{
assert(status == S_MOVING_AWAY);
sub->update();
if(sub->isComplete())
{
status = S_MOVING_TO_RANGE;
delete sub;
sub = new MoveToObjInSector(
ship, target, startRunDist,
false, false);
}
}
}
}
enum Status
{
S_DIFF_SECTORS,
S_TARGET_DEAD,
S_MOVING_TO_RANGE,
S_BOMBING_RUN,
S_MOVING_AWAY
};
Status getStatus()const{return status;}
virtual bool isComplete()const
{
return status == S_DIFF_SECTORS || status == S_TARGET_DEAD;
}
private:
Status status;
void calcDistances()
{
//todo when have weapons etc etc
startRunDist = 1500;
endRunDist = 500;
breakOffDist = 2000;
}

/**Distance at which to start bombing run.*/
float startRunDist;
/**Distance at which to end bombing run.*/
float endRunDist;
/**Distance to fly after bombing run, before starting over.*/
float breakOffDist;

//BombingRun *bombingRun;
//MoveToPosInSector *movePos;
//MoveToObjInSector *moveObj;
ShipCommand *sub;
};
[/source]
(code not complete or tested, just looking at ideas. Memory managment etc not really considered, but at the least Ship likly to become reference counted in some form)
Hey,

i'm rather new in game development and I'm working on my second large project (student).
I'll have to program some "basic" AI soon and when I looked at your code I saw an interesting
thing. Your commands have their own class. This is a very interesting fact for me.
Why did you choose to create a command class? Isn't it more interesting to work with
a command manager, which have several states and function to handle everything. And
let the command manager work togheter with a unit manager for example.
Not sure... but that was my first idea/concept. I def want to get into your choice of doing it like that.

Cheers!

This topic is closed to new replies.

Advertisement