查看: 2731|回复: 1
收起左侧

[已翻译] 使用群体行为创建一个冰球游戏的AI(四):防守

[复制链接]

[已翻译] 使用群体行为创建一个冰球游戏的AI(四):防守[复制链接]

初末 发表于 2017-12-25 14:53:01 [显示全部楼层] |只看大图 回帖奖励 |倒序浏览 |阅读模式 回复:  1 浏览:  2731
翻译:张华栋(wcby)      审校:陈敬凤(nunu)
这部分教程是系列教程“用群体行为和有限状态机来编写冰球游戏”的最后一部分。在这个部分里,我们将提高我们运动员的人工智能来让他们能够在面对对手进攻的时候防守自己的球门。我们还会让我们的运动员在防守的时候执行一些进攻战术,这样他们就可以恢复对冰球的控制权并终止对方的进攻。
关于防守AI的一些想法
在像曲棍球这样的竞技游戏里,防守过程不仅仅是冲到球队的得分区域以防止对手能够得分。防止对手得分只是防守过程涉及的很多任务之一。
如果一支球队只是采用阻止对手得分的策略,所有的本方运动员只会成为对手前进途中的障碍物。对手将继续移动,试图找到一条路径突破防守。这将消耗对手的一些时间,但最终对手还是会得分。
防守过程其实是防守行动和进攻行动的混合。最好的终止对手进攻的方法,是在防守的时候发动攻击行动,而终止对方进攻是防守的目的。这听起来有点混乱,但它在实际中运转的非常良好。 在防守过程中的运动员应该向他的目标移动,试图去阻止对手得分。在这个过程中,他必须尝试从控制着冰球的对手那里偷到冰球的控制权,或者当冰球在对手之间传递的时候拦截冰球。这正是这个教程将尝试实现的:防守战术与攻击行动。
融合防守动作和攻击动作
为了实现这种带着一定攻击行动的防守行为,我们将添加两个新的状态到人工智能的有限状态机里面:
DSC0000.png 用一个基于堆栈的有限状态机来表示攻击和防守全过程。。。
防守(defend)状态将是防守过程的基石。在防守状态下,运动员将向冰球场本方的区域移动,总是试图恢复对冰球的控制权来终止对手的进攻。
巡逻(patrol)状态将防御过程变得完整。这将避免当运动员到达他们在冰球比赛场的防守位置以后只是站着静止不动。这种状态将保持运动员一直在一个小的区域里面移动和巡逻,这将产生一个更有说服力的结果。
先理解什么是防守状态
防守(defend)状态是基于一个非常简单的想法。当这个状态被激活的时候,每个运动员将向他们在冰球比赛场的初始位置移动。我们已经使用了这个位置,由theAthlete类的mInitialPosition属性描述。它是在实现这个系列教程第一部分的准备比赛(prepareForMatch)状态的时候实现的。 当运动员在向他在冰球比赛场的初始位置移动的同时,如果他离对面控球的运动员足够近的话,他也会对控球运动员执行一些攻击动作。举个例子来说,如果运动员在移动的过程中发现对方球队的带头人(也就是控制着冰球的运动员)就在他的附近,那么防守(defend)状态就会被一些其他更合适的状态取代,比如偷球(stealPuck)状态。
由于运动员在进攻的时候往往倾向于分布在整个冰球比赛场上,当他们切换到防守(defend),开始返回他们在冰球比赛场的初始位置的时候,他们将覆盖一个非常显著的区域,确保形成一个非常有说服力的防守模式。
DSC0001.gif 运动员在返回他们在冰球比赛场的初始位置的过程中执行攻击行动。
有些运动员在返回他们在冰球比赛场的初始位置的过程中不会遇到控球的对方运动员,所以他们就只是向在冰球比赛场的初始位置移动。但是另外一些运动员会在这个过程中与一些感兴趣的对手离的比较接近,而哪些是感兴趣的对手?就比如说对方球队的带头人(控球的对方运动员)。
实现防守状态
防守状态将有4个状态跳转:
DSC0002.png 用来描述整个防守过程的有限状态机里面的防守状态和它的状态跳转。
其中的三个状态跳转,球队控制着冰球(team has the puck)、接近对方的控球运动员(close to opponent leader)和冰球处于自由状态没有控制者(puckhas no owner)是和攻击行动相关的。这三个状态挑跳转将负责运动员在往防守位置移动的时候适时地表现出攻击动作。当运动员最终到达他在冰球比赛场的初始位置以后“到达位置”(in position)状态跳转将会被触发。
实现防守(defend)状态的第一步是让运动员往他们在冰球比赛场的初始位置移动。因为要在到达终点的时候减速,到达行为(arrive steeringbehaviorhttp://gamedevelopment.tutsplus.com/tutorials/understanding-steering-behaviors-flee-and-arrival--gamedev-1303)用来做这个事情将非常合适:
[size=1em]
[size=1em]1

[size=1em]2

[size=1em]3

[size=1em]4

[size=1em]5

[size=1em]6

[size=1em]7

[size=1em]8

[size=1em]9

[size=1em]10

[size=1em]11

[size=1em]12

[size=1em]13

[size=1em]14

[size=1em]15

[size=1em]16

[size=1em]17

[size=1em]18

[size=1em]19

[size=1em]20

[size=1em]21

[size=1em]22

[size=1em]23

[size=1em]24

[size=1em]25

[size=1em]26

[size=1em]27

[size=1em]28

[size=1em]29

[size=1em]30

[size=1em]31

[size=1em]32

[size=1em]33

[size=1em]34

[size=1em][size=1em]class Athlete {
[size=1em]    // (...)
[size=1em]   
[size=1em]    private function defend() :void {
[size=1em]        var aPuckOwner :Athlete = getPuckOwner();
[size=1em]         
[size=1em]        // Move towards the initial position, arriving there smoothly.
[size=1em]        mBoid.steering = mBoid.steering + mBoid.arrive(mInitialPosition);
[size=1em]         
[size=1em]        // Does the puck has an owner?
[size=1em]        if (aPuckOwner != null) {
[size=1em]            // Yeah, it has. Who has it?
[size=1em]            if (doesMyTeamHasThePuck()) {
[size=1em]                // My team has the puck, time to stop defending and start attacking!
[size=1em]                mBrain.popState();
[size=1em]                mBrain.pushState(attack);
[size=1em]                  
[size=1em]            } else if (Utils.distance(aPuckOwner, this) < 150) {
[size=1em]                // An opponent has the puck and he is close to us!
[size=1em]                // Let's try to steal the puck from him.
[size=1em]                mBrain.popState();
[size=1em]                mBrain.pushState(stealPuck);
[size=1em]            }
[size=1em]        } else {
[size=1em]            // No, the puck has no owner, it is running in the rink.
[size=1em]            // There is no point to keep defending the goal, because nobody has the puck.
[size=1em]            // Let's switch to 'pursuePuck' and try to get the puck to our team.
[size=1em]            mBrain.popState();
[size=1em]            mBrain.pushState(pursuePuck);
[size=1em]        }
[size=1em]    }
[size=1em]      
[size=1em]    // (...)
[size=1em]}



[/table]当运动员的防守(defend)状态被激活的时候,到达行为会创建一个力来推动运动员向他在冰球比赛场的初始位置(mInitialPosition
)移动。代码中计算了到达力以后,我们会运行一系列测试来检测冰球的控制权以及大概的对手位置,将防守(defend)状态从有限状态机的堆栈中弹出,然后根据当时的情形压入一个新的状态。
如果冰球目前没有控制者,还处于自由状态,那么它可能是在冰球比赛场上自由的移动。在这种情况下,追球(pursuePuck)状态会被压入运动员的有限状态机的堆栈中(在上面代码的第29行)。如果冰球现在被某一方控制,那么对这一方来说防守过程已经结束了,现在是时候发起进攻了(上面代码的第16行)。如果冰球现在是被对手一方控制并且控球的运动员还离人工智能控制的运动员很近,那么偷球(stealPuck)状态将被压入运动员的有限状态机的堆栈中(在上面代码的第22行)。
结果就是团队能够进行有效的防守,有效的追球并且会在适当的时候去偷取对方控制的冰球控制权。下面是对当前的防守实现进行一个说明:
在区域巡逻
当前实现的防守行为是可以接受的,但是如果它能做一些调整的话就会更有说服力了。如果你分析之前的演示,你会注意到运动员在防守的时候,一旦到达他们在冰球比赛场的初始位置以后就会停下来站着不动。
如果运动员在返回到他们在冰球比赛场的初始位置的过程没有遇到任何对手,那么他将在原地不动,直到控制着冰球的对方运动员从他身边经过或者本方团队重新夺回对冰球的控制权。
我们可以添加一个巡逻(patrol)状态来提升防守时候的表现,当运动员到达他在冰球比赛场的初始位置以后,如果还是处于防守状态,那么防守状态会跳转到巡逻状态:
DSC0003.png 用来描述整个防守过程的有限状态机里面的巡逻状态和它的状态跳转。

巡逻(patrol)状态实现起来是非常简单的。当这个状态被激活的时候,它会让运动员在短时间内随机的四处移动,这将在视觉上模拟运动员试图防守冰球比赛场一个点时候的预期行为。
当运动员与他的初始位置之间的距离是大于某个值的时候,举个例子来说,不妨假设这个值为10,巡逻状态会从有限状态机的堆栈弹出来并压入防守状态。那么防守状态会驱动运动员返回他们在冰球比赛场的初始位置,一旦当他们到达初始位置以后,如果他们还是处于防守状态,那么巡逻状态会再次压入有限状态机的堆栈,整个过程就会一直循环:

DSC0004.gif 巡逻(patrol)状态的示例。
巡逻(patrol)状态所要求的随机运动模式可以很轻易的用徘徊群体行为(wander steeringbehavior)来实现。巡逻(patrol)状态的实现如下:
[size=1em]
[size=1em]1

[size=1em]2

[size=1em]3

[size=1em]4

[size=1em]5

[size=1em]6

[size=1em]7

[size=1em]8

[size=1em]9

[size=1em]10

[size=1em]11

[size=1em]12

[size=1em]13

[size=1em]14

[size=1em]15

[size=1em]16

[size=1em]17

[size=1em][size=1em]class Athlete {
[size=1em]    // (...)
[size=1em]   
[size=1em]    private function patrol() :void {
[size=1em]        mBoid.steering = mBoid.steering + mBoid.wander();
[size=1em]         
[size=1em]        // Am I too far away from my initial position?
[size=1em]        if (Utils.distance(mInitialPosition, this) > 10) {
[size=1em]            // Yeah, I am. It's time to stop patrolling and go back to
[size=1em]            // my initial position.
[size=1em]            mBrain.popState();
[size=1em]            mBrain.pushState(defend);
[size=1em]        }
[size=1em]    }
[size=1em]      
[size=1em]    // (...)
[size=1em]}


这里的距离检测(上面代码的第8行)将确保运动员是在一个围绕他初始位置的很小的区域里面巡逻而不会是完全离开他初始的防守位置。
使用巡逻(patrol)状态的结果就是得到了一个更有说服力的行为:
将前面实现的内容都结合在一起
在之前教程的偷球(stealPuck)状态的实现中,存在一种情况运动员应该从偷球(stealPuck)状态切换到防守(defend)状态。当时由于当时没有实现防守(defend)状态,所以没有做这个状态跳转。
如果运动员目前正在试图偷取冰球的控制权(也就是位于偷球(stealPuck)状态),如果控球的对方运动员离他已经很远了,那么还去试图偷取冰球的控制权是毫无意义的。在这种情况下最好的选择是弹出偷球(stealPuck)状态,然后在状态机中压入防守(defend)状态,让更加靠近对方控球运动员的队友去尝试偷球。
所以偷球(stealPuck)状态必须要修改一下(在代码的第28和29行),这样才能允许运动员在上面说的情况下跳转到防守(defend)状态。
[size=1em][table=98%,none]
[size=1em]1

[size=1em]2

[size=1em]3

[size=1em]4

[size=1em]5

[size=1em]6

[size=1em]7

[size=1em]8

[size=1em]9

[size=1em]10

[size=1em]11

[size=1em]12

[size=1em]13

[size=1em]14

[size=1em]15

[size=1em]16

[size=1em]17

[size=1em]18

[size=1em]19

[size=1em]20

[size=1em]21

[size=1em]22

[size=1em]23

[size=1em]24

[size=1em]25

[size=1em]26

[size=1em]27

[size=1em]28

[size=1em]29

[size=1em]30

[size=1em]31

[size=1em]32

[size=1em]33

[size=1em]34

[size=1em]35

[size=1em]36

[size=1em]37

[size=1em]38

[size=1em]39

[size=1em]40

[size=1em]41

[size=1em]42

[size=1em][size=1em]class Athlete {
[size=1em]    // (...)
[size=1em]   
[size=1em]    private function stealPuck() :void {
[size=1em]        // Does the puck has any owner?
[size=1em]        if (getPuckOwner() != null) {
[size=1em]            // Yeah, it has, but who has it?
[size=1em]            if (doesMyTeamHasThePuck()) {
[size=1em]                // My team has the puck, so it's time to stop trying to steal
[size=1em]                // the puck and start attacking.
[size=1em]                mBrain.popState();
[size=1em]                mBrain.pushState(attack);
[size=1em]            } else {
[size=1em]                // An opponent has the puck.
[size=1em]                var aOpponentLeader :Athlete = getPuckOwner();
[size=1em]                  
[size=1em]                // Is the opponent with the puck close to me?
[size=1em]                if (Utils.distance(aOpponentLeader, this) < 150) {
[size=1em]                    // Yeah, he is close! Let's pursue him while mantaining a certain
[size=1em]                    // separation from the others to avoid that everybody will ocuppy the same
[size=1em]                    // position in the pursuit.
[size=1em]                    mBoid.steering = mBoid.steering + mBoid.pursuit(aOpponentLeader.boid);
[size=1em]                    mBoid.steering = mBoid.steering + mBoid.separation(50);
[size=1em]                     
[size=1em]                } else {
[size=1em]                    // No, he is too far away. Let's switch to 'defend' and hope
[size=1em]                    // someone closer to the puck can steal it for us.
[size=1em]                    mBrain.popState();
[size=1em]                    mBrain.pushState(defend);
[size=1em]                }
[size=1em]            }
[size=1em]        } else {
[size=1em]            // The puck has no owner, it is probably running freely in the rink.
[size=1em]            // There is no point to keep trying to steal it, so let's finish the 'stealPuck' state
[size=1em]            // and switch to 'pursuePuck'.
[size=1em]            mBrain.popState();
[size=1em]            mBrain.pushState(pursuePuck);
[size=1em]        }
[size=1em]    }
[size=1em]      
[size=1em]    // (...)
[size=1em]}



对偷球(stealPuck)状态更新以后,运动员现在能够组织进攻和防守战术了,这就使得由人工智能控制的2个队伍能够互相比赛。
结果如下面的demo所示:

总结
在这部分教程中,我们给运动员实现了一个防守策略,让他们用来面对对手来防守自己的球门。然后,我们通过添加了一些攻击动作来提升防守状态,比如防守的时候会试图去偷进攻者的冰球,这些让防守策略更加自然和让人信服。
我们还通过添加一个非常简单但是很强大的状态-巡逻状态来提高防守行为。这样做的目的是为了防止运动员在进行防守的时候站在原地不动。
有了防御部分的人工智能以后,我们已经给我们的冰球游戏构建了一个完整的人工智能系统!

参考文献:
关于作者

+1
2724°C
1
  • anmie7723
过: 他们
因分享而快乐,学习以自强!
anmie7723 发表于 2017-12-26 08:27:55 显示全部楼层
路过看看 感谢分享
因分享而快乐,学习以自强!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

VR/AR版块|Unity3d|Unreal4|新手报道|小黑屋|站点地图|沪ICP备14023207号-9|【泰斗社区】-专注互联网游戏和应用的开发者平台 ( 沪ICP备14023207号-9 )|网站地图

© 2001-2013 Comsenz Inc.  Powered by Discuz! X3.4

1
QQ