再一次问好!如所承诺的,在这里我与另一个很酷的AI实现。在我的 上一篇文章中,我介绍了gdxAI 框架,并试图解释如何使用Arrive行为。
在进入这个主题之前,我想感谢你向我演示这个令人敬畏的角色设计的Nyanla La Geekette nee-chan ^^:
我邀请大家访问她的博客,并记住您可以与您自己的艺术作品联系,或者在艺术页面上发表评论。
回到 指导行为(Steering Behaviors),今天我正在追求的行为,这让我终于可以添加暴徒的游戏。
我暂时从这个来源选择了这个眼睛的意大利面条怪物,因为它是FSM 和一个 伟大的老人的chibi混合物 。这是游戏中第一个暴徒,其第一个任务是追求小猫。
概观
你如何使一个实体跟随另一个实体?在阅读下面的推理之前,想一想。
可视化两个角色:暴徒(mob )和小猫(kitten)。暴徒应该尽快去小猫。在我们的欧几里德世界,两点之间的最短路径是一条直线。因此,暴徒应该沿着一条直线向小猫走去。
在LibGDX中,此路径由Rays描述,Rays是将起始点连接到目标点的向量。
但是,我们应该考虑到,像墙壁,岩石,洞穴一样可能会有障碍。我们列出规则:
- 如果我们把一个射线从暴民扔到小猫身上,它不会碰撞,那么暴徒可以直接向小猫走去。
1
2
3
4
|
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
;
}
|
暴徒将开始向最后的气味踪迹移动,直到下一个转向计算循环。然后,他会再次尝试到达小猫,重复计算。
最后的规则和规则摘要
只剩下一条规则:
- 如果暴徒不能通过直线或直线观察小猫,那么他将停止跟踪目标。
1
2
3
4
5
6
7
8
|
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广场,名为“瓦片”。每个瓦片都有自己的属性。例如,可以存在草地,你可以走路; 水砖,你可以在哪里潜水; 砂瓦,你会慢下来; 墙砖,你不能穿过; 孔瓷砖,你会落在哪里等
1
|
addCell
(
TileFactory
.
COLLECTION
.
grass
(
position
)
)
;
|
射线碰撞
对于那个小小的部分,对不起,但这将有助于解释如何管理冲突。
因为现在我们控制着世界各个地块,所以很容易就知道暴徒发射的射线是否遇到任何可碰撞的障碍物。使用Bresenham的算法,我们将获得一行列中存在的tile。然后,我们只是检查返回的列表中是否有可碰撞的tile:
1
2
3
4
5
|
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.
}
|
哪里:
1
2
|
//Pseudocode
RaycastCollisionDetector
<Vector2>
wallCollisionDetector
=
new
RayWallDetector
(
)
;
|
我实现了 RaycastCollisionDetector 接口,并使用了这种 Bresenham的算法实现。这是完整的结果。
把所有东西放在一起
现在我们可以从猎人的角度来看待所有的过程。
1
2
3
|
public
class
MonsterModel
extends
Automaton
{
private
Target
target
;
[
.
.
.
]
|
1
2
3
4
5
|
public
void
setTarget
(
SteerableAgent
target
)
{
this
.
target
=
new
Target
(
target
,
new
RayTargetSingle
(
this
)
)
;
setSteeringBehavior
(
BehaviorsFactory
.
COLLECTION
.
pursue
(
this
,
this
.
target
)
)
;
}
|
猎物是一个SteerableAgent,保持暂时的气味踪迹:
1
2
3
4
5
6
7
8
9
10
11
|
protected
void
changePosition
(
Vector2
newPosition
)
{
position
.
set
(
newPosition
)
;
[
.
.
.
]
if
(
pursuable
.
is
(
)
)
{
pursuable
.
addSmellTrailAt
(
newPosition
)
;
}
}
|
暴徒在循环中执行他的SteeringBehavior的计算方法:
1
2
3
4
5
|
@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了。暴徒正确地跟随小猫,但每当他遇到一堵墙时,他仍然停留着,不再跟随小猫了。
经过几次调试,我意识到,暴徒正在 进入 墙壁,显然,从那一刻起,他所有的光线都与同一堵墙相撞。因此,他不能再追求小猫了。
为了防止这种情况发生,暴徒必须躲避墙壁。这有点令人沮丧,但同时也是开始同时行动的好时机。我会尽量让暴徒追求小猫,同时避免墙壁。
如果您好奇,请注意我的下一篇文章!感谢您阅读^^请不要忘了分享,评论和订阅。再见!