再一次问好!如所承诺的,在这里我与另一个很酷的AI实现。在我的 上一篇文章中,我介绍了gdxAI 框架,并试图解释如何使用Arrive行为。
在进入这个主题之前,我想感谢你向我演示这个令人敬畏的角色设计的Nyanla
La Geekette nee-chan ^^:

我邀请大家访问她的博客,并记住您可以与您自己的艺术作品联系,或者在艺术页面上发表评论。
回到 指导行为(Steering
Behaviors),今天我正在追求的行为,这让我终于可以添加暴徒的游戏。
我暂时从这个来源选择了这个眼睛的意大利面条怪物,因为它是FSM 和一个 伟大的老人的chibi混合物 。这是游戏中第一个暴徒,其第一个任务是追求小猫。
嗨!
概观
你如何使一个实体跟随另一个实体?在阅读下面的推理之前,想一想。
可视化两个角色:暴徒(mob )和小猫(kitten)。暴徒应该尽快去小猫。在我们的欧几里德世界,两点之间的最短路径是一条直线。因此,暴徒应该沿着一条直线向小猫走去。

在LibGDX中,此路径由Rays描述,Rays是将起始点连接到目标点的向量。
但是,我们应该考虑到,像墙壁,岩石,洞穴一样可能会有障碍。我们列出规则:
- 如果我们把一个射线从暴民扔到小猫身上,它不会碰撞,那么暴徒可以直接向小猫走去。
|
private boolean CanMoveStraightForward()
{
return !collides(target.get().getPosition().x, target.get().getPosition().y);
}
|
- 如果射线碰撞,那么暴民应该寻找一条替代路径来达到目标。我发现最好的解决方案是这个,基于作者所说的“气味痕迹”。
闻到痕迹
定义
简而言之,这样的算法就是这样一种想法,即当目标在走路时在地面上留下臭味。如果小猫隐藏在墙上,那么暴徒就不能直接看到他,而是在小猫身上检查小猫留下的气味,然后到最后一个可见的踪迹。

随着时间的流逝,气味 消失了,所以最终暴徒会失去踪迹。

履行
在考虑猎人之前,想想猎物。每一个潜在的目标都必须是一个 可执行的( Steerable) 代理人(在我的上一篇文章中解释),并由一个可追溯的领域组成。
可追求的(Pursuable )是能够存储和提供访问 SmellTrails的方法的类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
public
class
Pursuable
implements
IPursuable{
protected
SmellTrails
smellTrails;
public
Pursuable(boolean
pursuable)
{
smellTrails
=
new
SmellTrails();
isPursuable
=
pursuable;
}
@Override
public
void
setSmellTrails(SmellTrails
smellTrails)
{
this.smellTrails
=
smellTrails;
}
@Override
public
Iterator<SmellTrail>
getSmellTrailsIterator()
{
return
smellTrails.getIterator();
}
@Override
public
void
addSmellTrailAt(Vector2
position)
{
smellTrails.add(position);
}
@Override
public
void
updateSmellTrail(float
delta)
{
smellTrails.update(delta);
}
}
|
SmellTrails 类保留路径的数组,并处理路径上的更改。例如,它将删除太旧的路径以便无法检测到:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public class SmellTrails {
private ArrayDeque<SmellTrail> path;
private float smellTrailDecayTime = 20f;
[...]
public void update (float delta)
{
SmellTrail smellTrail;
Iterator<SmellTrail> iterator = path.descendingIterator();
while (iterator.hasNext())
{
smellTrail = iterator.next();
smellTrail.duration += delta;
if (smellTrail.duration > smellTrailDecayTime)
{
iterator.remove();
}
}
}
}
|
恢复最后一条规则,“如果雷射相撞,那么暴民应该寻找一条替代路径来达到目标”,暴徒会看着目标留下的气味,并把这个位置定为他的新目标:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
private
boolean
CanMoveToLastSmellTrail()
{
Iterator<SmellTrail>
iterator
=
target.get().getPursuable().getSmellTrailsIterator();
while
(iterator.hasNext())
{
SmellTrail
smellTrail
=
iterator.next();
if
(!collides(smellTrail.center.x,
smellTrail.center.y))
{
target.setAlternativeTargetCoords(smellTrail.center);
return
true;
}
}
return
false;
}
|
暴徒将开始向最后的气味踪迹移动,直到下一个转向计算循环。然后,他会再次尝试到达小猫,重复计算。
最后的规则和规则摘要
只剩下一条规则:
- 如果暴徒不能通过直线或直线观察小猫,那么他将停止跟踪目标。
|
if(CanMoveStraightForward() || CanMoveToLastSmellTrail())
{
realSteering = accelerateTowardsTheTarget(accelerationVector);
}
else
{
realSteering = accelerationVector.setZero();
}
|
总结如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@Override
protected
SteeringAcceleration<Vector2>
calculateRealSteering
(SteeringAcceleration<Vector2>
accelerationVector)
{
SteeringAcceleration<Vector2>
realSteering;
if(CanMoveStraightForward())
{
realSteering
=
accelerateTowardsTheTarget(accelerationVector,
target.get().getPosition());
}
else
if
(CanMoveToLastSmellTrail())
{
realSteering
=
accelerateTowardsTheTarget(accelerationVector,
target.getAlternativeTargetCoords());
}
else
{
realSteering
=
accelerationVector.setZero();
}
return
realSteering;
}
|
美丽新世界
如果暴徒所画的射线与障碍物相撞,暴徒应该实行不同的行为。但是,我怎么能真正知道射线是否与障碍物碰撞?
为了解决这个问题我创建了一个瓦片管理器。
游戏的世界已经彻底改变,现在比以往更加活跃和危险!
现在,世界由TiledMap组成。地面分为32X32广场,名为“瓦片”。每个瓦片都有自己的属性。例如,可以存在草地,你可以走路; 水砖,你可以在哪里潜水; 砂瓦,你会慢下来; 墙砖,你不能穿过; 孔瓷砖,你会落在哪里等
平铺地图示例
这个工厂为我创建不同类型的瓷砖,以后我可以添加到地图中:
|
addCell(TileFactory.COLLECTION.grass(position));
|
射线碰撞
对于那个小小的部分,对不起,但这将有助于解释如何管理冲突。
因为现在我们控制着世界各个地块,所以很容易就知道暴徒发射的射线是否遇到任何可碰撞的障碍物。使用Bresenham的算法,我们将获得一行列中存在的tile。然后,我们只是检查返回的列表中是否有可碰撞的tile:
|
|
private
boolean
collides(float
x,
float
y)
{
Ray<Vector2>
ray
=
target.getRayConfig().updateTarget(x,
y);
return
(wallCollisionDetector.collides(ray));
//This
line will return true if the ray encounters collidable tiles in its way.
}
|
哪里:
|
//Pseudocode
RaycastCollisionDetector<Vector2> wallCollisionDetector = new RayWallDetector();
|
我实现了 RaycastCollisionDetector 接口,并使用了这种 Bresenham的算法实现。这是完整的结果。
把所有东西放在一起
现在我们可以从猎人的角度来看待所有的过程。
暴徒是一个具有目标的自动机:
|
|
public
class
MonsterModel
extends
Automaton
{
private
Target
target;
[...]
|
2.设定目标时,他也会获得追求(Pursue )行为:
|
public void setTarget(SteerableAgent target)
{
this.target = new Target(target, new RayTargetSingle(this));
setSteeringBehavior(BehaviorsFactory.COLLECTION.pursue(this, this.target));
}
|
猎物是一个SteerableAgent,保持暂时的气味踪迹:
|
|
protected
void
changePosition(Vector2
newPosition)
{
position.set(newPosition);
[...]
if
(pursuable.is())
{
pursuable.addSmellTrailAt(newPosition);
}
}
|
暴徒在循环中执行他的SteeringBehavior的计算方法:
|
@Override public void calculateSteering(float delta)
{
steeringBehavior.calculateSteering(steeringOutput);
[...]
}
|
5. 追求行为的calculateSteering方法计算是否有可用路径,如果没有可用路径,则会停止。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@Override
protected
SteeringAcceleration<Vector2>
calculateRealSteering
(SteeringAcceleration<Vector2>
accelerationVector)
{
SteeringAcceleration<Vector2>
realSteering;
if(CanMoveStraightForward())
{
realSteering
=
accelerateTowardsTheTarget(accelerationVector,
target.get().getPosition());
}
else
if
(CanMoveToLastSmellTrail())
{
realSteering
=
accelerateTowardsTheTarget(accelerationVector,
target.getAlternativeTargetCoords());
}
else
{
realSteering
=
accelerationVector.setZero();
}
return
realSteering;
}
|
这是它的工作原理。您可以在此提交中阅读完整版本。
墙排斥
说实话,我在执行过程中遇到了一个问题,而且游戏看起来有点儿bug了。暴徒正确地跟随小猫,但每当他遇到一堵墙时,他仍然停留着,不再跟随小猫了。
经过几次调试,我意识到,暴徒正在 进入 墙壁,显然,从那一刻起,他所有的光线都与同一堵墙相撞。因此,他不能再追求小猫了。
为了防止这种情况发生,暴徒必须躲避墙壁。这有点令人沮丧,但同时也是开始同时行动的好时机。我会尽量让暴徒追求小猫,同时避免墙壁。
如果您好奇,请注意我的下一篇文章!感谢您阅读^^请不要忘了分享,评论和订阅。再见!
文章出处:http://fightingkitten.webcindario.com/?p=480