初入社会戏人生(五)-游戏中的AI

06/16/2020

基础介绍

最近在做关于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为直角,负数为钝角,值越大表示锐角越小
    • 注意:点乘是不分先后顺序的,即 a b = b a a \cdot b = b \cdot a
//伪代码
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;

};

游戏人工智能编程实例精粹第二章介绍了本文的状态设计模式

猜你喜欢

转载自blog.csdn.net/weixin_44200074/article/details/106794372