06/16/2020
人工智能AI
基础介绍
最近在做关于3D坦克大战的AI, 我们打算做一个简单的AI坦克,这个坦克只有巡逻和战斗状态
巡逻状态
坦克移动路线
简单的行动路线,给定一个移动距离,前后移动这个坦克。
坦克可视范围(半径和角度)
通常坦克的扫视范围是根据炮台的朝向画圆,半径和扫过的角度可以自由决定,如果敌方坦克进入这个范围,AI坦克将会发现它。那么如何检测其他坦克进入AI坦克的扫视范围呢?
已知量
- 其他坦克的位置
- 自身坦克的位置
- 自身坦克炮台的朝向和扫视角度(弧度制表示)
struct AiTank
{
Vector3 position;
Vector3 orientation; //Ai炮台的朝向
float scanRadius = 10.0f;
float scanRadian = 3.14/8; //弧度制表示 正负 180度/8
};
判断依据
- 两坦克的位置求距离公式与扫视半径比较
- AI坦克的炮台的正面朝向和AI到目标坦克的夹角与扫视角度比较
- 点乘可以计算两个向量的夹角,并判断是否在扫射角度里还是角度外
- 点乘为正,锐角,0为直角,负数为钝角,值越大表示锐角越小
- 注意:点乘是不分先后顺序的,即
//伪代码
bool findEnemy(const AiTank& aiTank,const Tank& target){
Vector3 pointToTarget = target.position - aiTank.position;
float distance = Vector3Length(pointToTarget);
if(distance <= scanRadius) // 距离判断,在扫视半径的圆内
{
float cosValue = dot(pointToTarget,aiTank.orientation);
float radian = arccos(cosValue /(Vector3Length(pointToTarget)*Vector3Length(aiTank.orientation)));
if(radian < scanRadian && radian > -scanRadian)
{
return true;
}
}
return false;
}
起初使用if-else
class AiTank
{
public:
enum class State = {PATROL,BATTLE}; //巡逻,战斗两个状态
State mCurrentState = State::PATROL;
void update(float dt)
{
if(mCurrentState == State::PATROL)
{
moveForWardBack(dt,5.0f);
if(findEnemy())
{
mCurrentState = State::BATTLE;
}
}else if(mCurrentState == State::BATTLE)
{
move(dt);
fire();
}
//other state
}
};
状态设计模式
class State
{
public:
virtual void execute(AiTank* aiTank) = 0;
};
class Patrol:public State
{
public:
void execute(AiTank* aiTank)override
{
if(aiTank->findEnemy())
{
aiTank->changeState(new Battle());
}else
{
aiTank->patrolForWardBack();
}
}
};
class Battle:public State
{
public:
void execute(AiTank* aiTank)override
{
aiTank->battle();
}
};
class AiTank
{
public:
void update()
{
mCurrentState->execute(this);
}
void changeState(const State* newState)
{
delete mCurrentState;
mCurrentState = newState;
}
private:
State* mCurrentState;
};
总结
状态控制AI坦克什么时候改变状态和做特定的行为,同时State类可以方便扩展出其他状态。每一个游戏的智能体的状态是作为一个唯一的类实现的,并且每个智能体拥有一个指针指向它的当前状态的实例。智能体也实现了一个changeState成员函数,无论何时需要状态变换时可以被调用来促成状态变换。决定任何状态变换的逻辑包含在每个State类中。
进一步State基类
每个状态有相关的进入和退出动作比较好,允许程序员编写只执行一次的逻辑,(Enter和Exit函数)
class State
{
public:
virtual ~State(){}
virtual void enter(AiTank* aiTank) = 0;
virtual void execute(AiTank* aiTank) = 0;
virtual void exit(AiTank* aiTank) = 0;
};
void AiTank::changeState(State* newState)
{
assert(mCurrentState && newState);
mCurrentState->exit(this);
mCurrentState = newState;
mCurrentState->enter(this);
}
状态子类可以设计为单例模式,有利于共享给所有的AI坦克中
单例模式确保一个对象智能实例化一次,并且时全局可访问的
class Patrol:public State
{
public:
static Patrol* getInstance();
void enter(AiTank* aiTank)override;
void execute(AiTank* aiTank)override;
void exit(AiTank* aiTank)override;
private:
Patrol(){}
};
Patrol* Patrol::getInstance()
{
static Patrol patrol;
return &patrol;
}
State状态模式可重用(模板类)
template<class EntityType>
class State
{
public:
virtual ~State(){}
virtual void enter(EntityType* entityType) = 0;
virtual void execute(EntityType* entityType) = 0;
virtual void exit(EntityType* entityType) = 0;
};
游戏人工智能编程实例精粹第二章介绍了本文的状态设计模式