端午节放假总结了一下好久前写过的一些游戏引擎,其中NPC等游戏AI的实现无疑是最繁琐的部分,现在,给大家分享一下:
从一个简单的情景开始
怪物,是游戏中的一个基本概念。游戏中的单位分类,不外乎玩家、NPC、怪物这几种。其中,AI 一定是与三类实体都会产生交集的游戏模块之一。 以我们熟悉的任意一款游戏中的人形怪物为例,假设有一种怪物的 AI 需求是这样的:
大部分情况下,漫无目的巡逻。
玩家进入视野,锁定玩家为目标开始攻击。
Hp 低到一定程度,怪会想法设法逃跑,并说几句话。
我们以这个为模型,进行这篇文章之后的所有讨论。为了简化问题,以省去一些不必要的讨论,将文章的核心定位到人工智能上,这里需要注意几点的是:
不再考虑 entity 之间的消息传递机制,例如判断玩家进入视野,不再通过事件机制触发,而是通过该人形怪的轮询触发。
不再考虑 entity 的行为控制机制,简化这个 entity 的控制模型。不论是底层是基于 SteeringBehaviour 或者是瞬移,不论是异步驱的还是主循环轮询,都不在本文模型的讨论之列。
首先可以很容易抽象出来 IUnit:
public interface IUnit
{
void ChangeState(UnitStateEnum state);
void Patrol();
IUnit GetNearestTarget();
void LockTarget(IUnit unit);
float GetFleeBloodRate();
bool CanMove();
bool HpRateLessThan(float rate);
void Flee();
void Speak();
}
public interface IUnit
{
void ChangeState(UnitStateEnum state);
void Patrol();
IUnit GetNearestTarget();
void LockTarget(IUnit unit);
float GetFleeBloodRate();
bool CanMove();
bool HpRateLessThan(float rate);
void Flee();
void Speak();
}
然后,我们可以通过一个简单的有限状态机 (FSM) 来控制这个单位的行为。不同状态下,单位都具有不同的行为准则,以形成智能体。 具体来说,我们可以定义这样几种状态:
巡逻状态: 会执行巡逻,同时检查是否有敌对单位接近,接近的话进入战斗状态。
战斗状态: 会执行战斗,同时检查自己的血量是否达到逃跑线以下,达成检查了就会逃跑。
逃跑状态: 会逃跑,同时说一次话。
最原始的状态机的代码:
public interface IState<TState, TUnit> where TState : IConvertible
{
TState Enum { get; }
TUnit Self { get; }
void OnEnter();
void Drive();
void OnExit();
}