经过前面28节的不断完善,主角已经具备了MMORPG游戏中的大多数功能;但是其他精灵例如怪物等暂时还是无法行动的,上一节中它们无辜的充当了主角的肉靶子,它们开始呐喊:上帝呀,请给予我们灵魂与智慧吧!其实灵魂早就有了,就是精灵的生命线程。那么该如何赋予精灵智慧呢?
精灵智慧的实现其实就是为精灵赋予AI(人工智能)。完整的游戏引擎或多或少都必须拥有一定的AI,例如棋牌类型游戏有着它们独特出牌AI,射击类型游戏同样也拥有相应的计算AI等等。AI的类型在不同类型游戏中有着不同的体现与定位,可深可浅,甚至无限扩充的可能。在MMORPG类型的游戏中则以“追踪者”为最基础的精灵用AI,同时也是游戏中使用率高达80%以上的AI类型。何谓“追踪者”?简单的说就是当怪物发现敌对玩家或是其他敌对精灵时即会锁定目标并向其位置移动,一旦对方进入自己的攻击范围后即刻发起物理或魔法攻击;同时,如果对方跑开,自己会继续追击且循环重复前面过程,就好比一个幽灵缠着你。这段描述大家是否有种四曾相似的感觉?没错,“追踪者”AI其实就是上一节中主角战斗功能模块实现的最直接体现。下面我将为大家讲解如何通过代码实现怪物的“追踪者”人工智能。
游戏AI设计同样是对游戏引擎框架合理性的重要考验,大家回想一下,主角与怪物均为QXSpirit类型控件,通过前面的章节我们已经实现了主角大多数的功能,如移动、攻击、追击、伤害等等;同样的,我们是否可以将用于实现这些功能的方法为其他所有精灵通用?这是肯定的。比如障碍物预测使用的方法WillCollide(),直线移动方法StraightMoveTo(),寻路移动方法AstarMoveTo(),判断移动到达目的地方法ArriveTarget()等等,这些方法在前面章节中均是无参数的,里面的处理均以Leader为主元素进行相应的属性设置及修改。此时我们不妨为这些方法均添加一个QXSpirit spirit参数,然后将方法里的Leader全部改成spirit;如此,在除法AI后,所有的精灵对象就都可以重用这些方法了:
举个例子,好比原先的ArriveTarget()方法写法如下:
private bool ArriveTarget() {
return (storyboard != null && storyboard.GetCurrentProgress(Leader) == 1) ? true : false;
}
改成AI通用的则为:
private bool ArriveTarget(QXSpirit spirit) {
return (Super.storyboard.ContainsKey(spirit.Name) && SpiritStoryBoard(spirit.Name).GetCurrentProgress(spirit) == 1) ? true : false;
}
不光是多了个参数,同时大家是否有注意到我将storyboard定义成了一个字典以存放所有精灵的移动动画板:
public static Dictionary<string, Storyboard> storyboard = new Dictionary<string, Storyboard>();
每个精灵在移动的时候会首先判断以自身名字(spirit.Name)为键名的Storyboard键值是否存在,如果不存在则创建一个新的,接着在此Storyboard上执行移动属性动画,例如直线移动方法改为:
private void StraightMoveTo(QXSpirit spirit, Point p) {
……
if (!Super.storyboard.ContainsKey(spirit.Name)) { Super.storyboard.Add(spirit.Name, new Storyboard()); }
……
}
使用字典来管理所有精灵的移动动画板即方便又高效,我们只需要通过Lambda即可以轻松获取指定键名的Storyboard:
private Storyboard SpiritStoryBoard(string key) {
return Super.storyboard.Single(X => X.Key == key).Value;
}
同样的也可以在每次移动时这样创建新的Storyboard:
private void NewSpiritStoryboard(string key) {
if (!Super.storyboard.ContainsKey(key)) {
Super.storyboard.Add(key, new Storyboard());
}
}
额外的,我们也可以使用类似的方法在精灵停止跑动时或死亡时移除掉对应的Storyboard以释放资源。
以创建精灵对象AI为目的的重构完成后,接下来需要进行具体功能的实现—如何启动AI?
从原理上我们可以这样设定:每个怪物精灵赋予一个索敌范围(视线距离)SeekRange,当玩家进入这个区域后他就会将攻击对象锁定为玩家并向其发起攻击;该索敌范围同样为以怪物精灵X、Y坐标为圆心,以视线距离为半径的圆,判断方法可以复用上一节中的InCircle():
大家在玩网游的时候是否注意到,玩家引怪容易,甩怪却需要更长一些的时间与距离。难道我们还得多设定一个OutRange来定义摆脱怪物视线的距离?这里其实有个小技巧,仅仅SeekRange一个参数就够了。我们可以将怪物索敌方法放在游戏的辅助线程的异步方法中,这样怪物每间隔1秒进行1次扫描,因此主角在与怪物距离小于SeekRange时即会引上怪,而只有当主角与怪物的距离大于SeekRange并持续1秒后才能甩掉怪物,这样就间接的产生了两个距离:索敌距离与摆脱距离:
private void RefreshFace(){
……
if (spirit.VLife != 0
&& Super.InCircle(Leader.X, Leader.Y, spirit.X, spirit.Y, spirit.SeekRange)
&& IsOpposition(spirit, Leader)) {
spirit.LockObject = Leader.Name;
} else {
spirit.LockObject = null;
}
……
}
当怪物视线范围内发现主角时,锁定它为攻击目标;一旦视线范围内无法找到主角且持续1秒以上则取消攻击锁定,此时怪物会停止追踪原地站立。
至此就全部完成了。“追踪者”AI的实现其实也不过如此,下面是三张运行测试图:
怪物们已经具备了索敌与战斗AI,尽管是简单的智慧,但这在MMORPG中占据着80%AI份额呢,嘿嘿,小有成就吧?仅仅的砍杀或许太过单调了一些,华丽即将登场,下一节我将为大家展示一场魔法盛宴,敬请期待。
出处:http://alamiye010.cnblogs.com/
教程目录及源码下载:点击进入(欢迎加入WPF/Silverlight小组 WPF/Silverlight博客团队)
本文版权归作者和博客园共有,欢迎转载。但未经作者同意必须保留此段声明,且在文章页面显著位置给出原文连接,否则保留追究法律责任的权利。