AI与游戏——吃豆人(4)方法综述

来源:互联网 发布:ezzy付强 知乎 编辑:程序博客网 时间:2024/06/05 01:06

这一部分先提一下一些基本的目前广泛用于游戏中的AI算法。这里最好现有点机器学习相关知识,不然可能会不知所云。后面提到具体算法我会尽量列出一些参考文献,例子也继续在吃豆人上面举例。

目前主流方法主要有,专门的行为表达方法例如有限状态机,行为树,和一些基于单元的AI。
有限状态机FSM不用多说,就是一个状态在某种条件下为迁移至另一种状态,这些状态和条件都是之前预设好的,下图是FSM在吃豆人的一个例子,FSM后面就不再说了,其他的算法在后面会继续提到。树查找算法指的是搜索行为函数的空间,然后见了可能动作顺序的树,常见的有Alpha-Beta和蒙特卡洛搜索树。演化计算指的是基于群体的全局最优求解,例如遗传算法,演化策略,粒子群最佳化演算法。监督学习指的是学习已有的数据集模型,数据集是有一些数据实例与相应的目标值组成,最常见的是神经网络。增强学习则主要是解决动作序列与奖励与惩罚的关系,不同于监督学习的值。无监督学习也是需要数据集,只不过数据集不再需要目标值,常见的有k均值,层次聚类等。还有一些其他的典型算法如TD-learning(时间差学习法)及其一个注明特例Q-learning。目前,神经演化算法和带有人工神经网络的TD学习是最受欢迎的AI算法。
有限状态机

那么如何在游戏中使用这些算法呢,首先要做的就是表述。没错,就是要将当前的游戏状态以及要执行的动作表达成数字才能用这些算法进行计算,例如FSM就是用图表示,行为树是用树来表示。这也是游戏AI第一步也是最难得一步之一。好的表述也可以带来很好的实验与实践效果,游戏AI特征的质量的问题也是需要注重解决的问题。

这里在介绍一个东西,效用(Unity),Unity是一个度量,通常可以被看做是一个函数,用于帮助算法去决定好的路线。为了达到这个目的,效用函数采集查找并收集空间中“有好处”的信息,对当时的情景进行大致的评估。例如,在象棋对局中,如果走的这一步会是“将”面临危险那么这一步的unity函数就会为0,而如果下一步可以让对面的“将”处于危险,那么这时的unity函数就会很大。所以unity函数的作用就是要最大化利益,最小化风险与错误。这些类似于增强学习的奖励与惩罚。所谓的学习过程,就是最大化Unity的过程。

下面介绍一下特定行为编程,摆阔之前提到的FSM,以及行为树BT(如下图所示),行为树跟FSM很相似,都是在有限的状态转换下执行任务,只是行为树的模块行更佳:如果设计的好的话,行为树可以通过简单任务的组合来执行复杂的行为。其最大的不同是,BT是行为的组合而不是状态,BT也更容易设计,测试与debug。BT很成功的运用在光环2,生化奇兵等游戏。

行为树例子

行为树包括以下几部分:
1、序列(蓝色矩形):一个孩子节点成功执行,那么将按照箭头的顺序继续执行,当所有孩子节点执行完后,父节点才算执行成功,否则序列执行失败。
2、选择(红色矩阵及其子节点):选择包括两种,基于概率的与基于优先级的,图中可以看做是基于概率的,每个动作都有概率执行,在AI学习的过程便是通过调节各个分支概率的大小(类似蒙特卡洛树)。也可以看做基于优先级的,数字越大优先级越高,当高优先级的无法执行时,执行次优先级的。
3、装饰(紫色六边形):装饰的作用是增强某个子节点的行为。例子中的装饰器就是一个循环,使任务一直射击直至对方血量为零。

对于FSM,吃豆人源码中给出的一个控制器startpacman就是一个FSM的例子。那么如何将BT应用于我们之前提到的吃豆人游戏呢?下图就是一个很好的应用。这个行为树用于控制吃豆人的寻找豆子的行为,其中的选择部分是基于优先级的,优先没有魔鬼的,然后是有豆子的,最后才是,没有豆子的,找到豆子之后如果没有魔鬼则一直吃下一个豆子。

吃豆人的行为树

那么这个方法会比之前的Startman方法好吗,好多少呢?首先这里的的寻找方法看起来比之前更细粒度一些,那么我们就来实现一下看看效果怎么样。

public MOVE findPath(Game game, int current)    {        boolean withPill;        ArrayList GhostIndexes = new ArrayList();        ArrayList<JunctionData> roundFromJunctions=nodes[current].closestJunctions;        ArrayList<Integer> targets=new ArrayList<Integer>();        int[] pills=game.getPillIndices();        int[] powerPills=game.getPowerPillIndices();        for(int w=0;w<pills.length;w++)                 //check which pills are available            if(game.isPillStillAvailable(w))                targets.add(pills[w]);        for(int w=0;w<powerPills.length;w++)            //check with power pills are available            if(game.isPowerPillStillAvailable(w))                targets.add(powerPills[w]);        for(Constants.GHOST ghost : Constants.GHOST.values())            GhostIndexes.add(game.getGhostCurrentNodeIndex(ghost));        for(int i = 0; i<roundFromJunctions.size(); i++)        {            withPill = false;            for (int j = 0; j < roundFromJunctions.get(i).path.length; j++)            {                if (GhostIndexes.contains(roundFromJunctions.get(i).path[j]))                    return game.getNextMoveAwayFromTarget(game.getPacmanCurrentNodeIndex(), roundFromJunctions.get(i).path[0], Constants.DM.PATH);                if (targets.contains(roundFromJunctions.get(i).path[j]))                    withPill = true;            }            if(withPill)                return game.getNextMoveTowardsTarget(game.getPacmanCurrentNodeIndex(), roundFromJunctions.get(i).path[0], Constants.DM.PATH);        }        int[] targetsArray=new int[targets.size()];     //convert from ArrayList to array        for(int i=0;i<targetsArray.length;i++)            targetsArray[i]=targets.get(i);        //return the next direction once the closest target has been identified        return game.getNextMoveTowardsTarget(current,game.getClosestNodeIndexFromNodeIndex(current,targetsArray, Constants.DM.PATH), Constants.DM.PATH);    }

代码如上所示,这里是基于之前startman控制器以及找周围junction的方法做,只是将原本startman中的第三部之前添加上图中的步骤,先找pacman的周围的岔路,然后看到各个岔路的路径上有没有魔鬼,有的话就原理这个junction,有豆子的话就靠近这个junction,都没有就向地图中最近的豆子靠近。那么结果如何呢,这里我分别让两种方法进行100次,看两种方法的平均得分。

第一种方法(原startman):4014.9
第二种方法(用行为树):3326.7

看来方法并没有什么优化,原因可能是这个地图的“走廊”太短,所以行为树方法并没有起到什么作用。不过这只是一个小尝试,后面还有更多更好的算法值得去尝试。

阅读全文
0 0