天天看点

游戏AI入门系列之可预测AI

本文为作者原创或翻译,转载请注明,不得移除此声明,不得用于商业用途。

作者:[email protected] 首发链接: http://blog.csdn.net/rellikt/archive/2010/09/05/5864844.aspx 说到游戏AI,总是让人感到那么的神秘与兴奋。那些编写良好的AI,往往可以通过不作弊或者少量作弊就能战胜大多数的玩家,在棋牌类游戏,解密类游戏中,AI甚至已经达到了不可战胜的地位。在图形学已经日趋成熟的今天,各种游戏的视觉效果往往已经让人心满意足,接下来能决定游戏成败的往往就游戏AI的好坏了。今天就让我们来了解一下游戏AI吧。 by rellikt

AI简介

AI全称Artificial Intelligence(人工智能),大多数情况下它指代一段写在计算机中的程序,这段代码使计算机的工作看上去能像人脑一样思考。你不需要把电脑AI想得有多神秘,在我编写这篇博客的时候,php编辑器对我做出的语法修改和格式调整就是AI的一种。可以说在现在生活中AI是无孔不入的。 人工智能顾名思义,当然是一种模拟人的思维,最终目标是和人一样能思考的东西。其实我们现在已经有很多类似的拟人机器人的程序在游戏中存在和应用。他们甚至能学习,探索,进化,生育。这是不是听上去很不可思议?事实上AI中很多算法,例如神经元网络算法,遗传算法,模糊逻辑,机器学习等就是为了模拟这些行为而生的。我这系列的博客就将为你慢慢解说上述AI算法。 言归正传,我们要设计一个游戏中的AI系统,我这里想提醒你最重要的一点就是拟人。几乎每一个好玩的游戏AI最大的特点就是模拟玩家,如果你的游戏AI看上去有点笨,那就是你还没把游戏AI摆到玩家的角度去进行思考,或者思考不足。如果你的游戏AI看上去太机灵了,那一定是你动用了一些只有电脑才能动用的作弊手段。而这任何一种倾向都不是游戏AI成功的标志,事实上现在大多数游戏都在试图用作弊来弥补算法上的不足,虽然这种做法在一开始会很有效,但是从长期来看,对于游戏性来说并不是好事。我一直在等待一款能够震撼现代游戏业界的,以AI取胜的游戏出现,我相信如果真有这么一款游戏出现,它的销量一定不会比Quake或者Unreal差。 by rellikt

可预测AI

我首先想告诉大家,我们在游戏中最长用到的AI并不是那些神秘兮兮的高级算法。就拿我做过的一个RPG游戏来说,图中小怪最多用的AI算法只是随机做一定范围的无规则运动,然后在玩家靠近的情况下,靠近玩家并对玩家进行攻击,这种算法在低级怪特别多的图里面是常用的。我们不可能为了低级怪写高级AI,游戏系统不需要有那么智能的低级怪,同时大量的高级AI占用的系统资源也是我们不希望的。 那这最简单的AI我们是怎么写的呢?最简单的我们先要一个基础的运动。运动就必须要定义速度,定义速度我们可以这么写: monster_speedX += velocity_x; monster_speedY += velocity_y; 这样一个简单的运动就定义好了。 by rellikt 然后是随机运动,我们可以在游戏主循环中这么写: //get a random value for speed. randVelocityX = rand() % 10; randVelocityY = rand() % 10; //add time to accumulator, if we accumulate for more than 5s then we clean up the accumulator //and change our velocity. accumulatorTime += deltaTime; if(accumulatorTime > 5) { monster_speedX += randVelocityX; monster_speedY += randVelocityY; accumulatorTime = 0; } 用上面这段代码我们就可以实现一个简单的每5s换一个方向的随机运动的AI,当然具体每个值的范围还需要我们再仔细确认一下。 最后让我们来实现追踪的算法,说到追踪,我们现在有必要做一些简单的定义: class Actor { // more definition float positionX; float positionY; float velocityX:  float velocityY; // more definition..... } 我们一般会给2D世界中的物体这些最基本的属性定义。这里的位置属性positionX和positionY往往会在一个范围中,比如方型地图就是MIN_X, MAX_X, MIN_Y, MIN_Y。而速度属性就会有 VelocityX * VelocityX + VelocityY * VelocityY < MAX_SPEED*MAX_SPEED的限制,注意尽量避免用sqrt这个会严重影响效率的运算。这里有时候在大量运算的时候为了效率考虑甚至直接使用-MAX_SPEEDX < VelocityX < MAX_SPEEDX和-MAX_SPEEDY < VelocityY < MAX_SPEEDY作为限制条件。这这个限制条件下,追踪的算法可以简单的写为: by rellikt if(player.positionX < monster.positionX) { monster.velocityX = -MAX_SPEEDX; } else if(player.posionX > monster.positionX) { monster.velocityX = MAX_SPEEDX; } else monster.velocityX = 0; if(player.positionX < monster.positionX) { monster.velocityX = -MAX_SPEEDX; } else if(player.posionX > monster.positionX) { monster.velocityX = MAX_SPEEDX; } else monster.velocityX = 0; 写到这里我们一个最简单的追踪算法就算完成了。但是为了让我们的游戏看起来更加真实,更加有趣,不妨在多加点拟人的东西。

拟人化的可预测AI

首先我们知道人的极限速度是恒定的,也就是说如果定义速度向量(x,y)那么这个向量的模是有最大值的,这也就是我前面提到的那个最初的速度限定表达式的来源。我们一般可以使用下面的算法来实现这点: vector2f direction = vector2f(player.positionX - monster.positionX,  player.positionY - monster.positionY); vector2f normalizedDirection = vector2f.normalize(direction); vector2f monsterSpeed = speedMax * normalizedDirection; 上面的vector2f是一些算法库中常见的数据结构,它会为我们完成一些我们需要的向量运算。这里我不想再展开讨论关于线性代数的一些内容。 另外一点就是对于突然的变速或者变向人是会有反应时间的,并不能认为人能瞬间反应,这里可以引入反应时间reactionTime。这样做的话可以使玩家能够有可能通过一下变速操作躲开monster 这里实现的时候可以把前面算出的monsterSpeed压入一个队列中,然后通过合适的队列长度来映射reactionTime,实现需要的反应时间。 另外将变速等AI指令封装成一个个消息让AI agent来处理也是一个常见的AI编程模型。关于消息处理等编程模型,有兴趣的读者可以去参阅一些MFC的代码。by rellikt 最后在靠近猎物的时候,我们可以使monster因为兴奋做出一些偏移或者减速,使玩家有更多的操作空间,这也是很有趣的。事实上我们如果对这个概念进行一下拓展,很容易就会想到给AI设置一些State来实现行为逻辑的控制。这就会牵涉到我们将要在下期博客中谈到的AI状态机的建立了。 下面是从一段小程序中摘抄来的一段简单的AI追踪的代码,有兴趣的读者可以做一下参考。在写一些脚本AI的时候也许我们需要的就是这些了。 // compute vector toward player float vx = player_x - mines[index].varsI[INDEX_WORLD_X]; float vy = player_y - mines[index].varsI[INDEX_WORLD_Y]; // normalize vector (sorta :) float length = Fast_Distance_2D(vx,vy); // only track if reasonable close if (length < MIN_MINE_TRACKING_DIST)   {   vx=MINE_TRACKING_RATE*vx/length;    vy=MINE_TRACKING_RATE*vy/length;   // add velocity vector to current velocity   mines[index].xv+=vx;   mines[index].yv+=vy;   // add a little noise   if ((rand()%10)==1)   {   vx = RAND_RANGE(-1,1);   vy = RAND_RANGE(-1,1);   mines[index].xv+=vx; mines[index].yv+=vy;   }// end if   // test velocity vector of mines   length = Fast_Distance_2D(mines[index].xv, mines[index].yv);   // test for velocity overflow and slow   if (length > MAX_MINE_VELOCITY)   {   // slow down   mines[index].xv*=0.75;   mines[index].yv*=0.75;   } // end if   } // end if else   {   // add a random velocity component   if ((rand()%30)==1)   {   vx = RAND_RANGE(-2,2);   vy = RAND_RANGE(-2,2);   // add velocity vector to current velocity   mines[index].xv+=vx;   mines[index].yv+=vy;   // test velocity vector of mines   length = Fast_Distance_2D(mines[index].xv, mines[index].yv);  // test for velocity overflow and slow    if (length > MAX_MINE_VELOCITY) { // slow down mines[index].xv*=0.75; mines[index].yv*=0.75; } // end if   } // end if } // end else 追踪问题是一个很复杂的AI问题,关于这方面的算法有很多,如果你想让自己的AI agent能在复杂的地形上进行追踪,那么可能你会 需要pathfinding的算法。如果你想让自己AI agent能有加速度,减速度,力矩旋转,碰撞检测等更真实的物理效果,你可能会需要一个好用的物理库。这些部分已经超出了可预测AI的范畴,这里就不做太多拓展了。上面那个例子对于我们现在的可预测AI来已经差不多够用了 , 在后面的篇幅中也许我会提到更多的关于追踪的AI算法。 我这里最想提醒大家注意的一点其实还是拟人化的AI。往往就是那 些拟人化的AI细节决定了一个游戏AI的好坏。

结论

以上就是可预测AI的一个基本概念。对我们程序来说这部分的工作更注重的细节,有很多细节都是通过认真观察生活,观察其他成名游戏得来的。事实上可预测AI的制作,在游戏制作领域往往是更偏重Design的活。作为一个AI程序员我们会把更多的精力放在建模和一些复杂而且有效的算法上面。 下一章我想会介绍一些最基本的AI状态机的建立和决策树方面的知识。好吧,今天就先说到这里。

继续阅读