【翻译馆】Unity3D中的敌人AI系统开发
原文链接:https://medium.com/otavio-henrique/simple-enemy-ai-system-for-unity3d-ed22f389974c
Unity3D中的一个简单敌人AI系统
译者: 张华栋(wcby) 审校:王磊(未来的未来)
在这篇文章中,我将解释我是如何为我在技术学院的最后一个项目开发了一个简单的AI系统的。这个简单的AI系统的想法是用一个简单的脚本,让敌人在战场上随机进行移动,追逐和攻击玩家。
我开发的AI脚本用于几款游戏,尤其是在stealth游戏中。当游戏开始的时候,AI系统会为敌人选择一个随机的目的地,并让敌人往这个方向进行行走,当敌人靠近选定的随机目的地的时候,AI系统会选择另一个随机目的地,然后再次开始整个过程。如果当敌人正朝着一个点行走的时候看到了玩家,那么他就会开始追逐玩家,如果他追到了这个玩家,他就会攻击这个玩家。
在这个例子中,我将使用Unity 5游戏引擎进行开发,但是您可以轻松地进行更改并在其他引擎中使用,比如像是 Unreal引擎。
我不会在这篇文章中或是编程逻辑中解释Unity的基本功能,如果您有任何问题,请阅读参考链接,或是在这篇文章的下方评论区或是在我的Twitter上询问我。
第一步: 构建场景
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
在这个例子中,我使用3D块构建了一个简单的迷宫,并将右上角的敌人(蓝色球体)和左下角(红色球体)的玩家进行定位。
我们的玩家是一个简单的FPSController(你可以在Unity Standard Assets上找到这个脚本),在这里阅读更多信息。
第二步:创建导航网格
对于我们的敌人如何在场景中行走,最简单的方法是在场景中创建导航网格,并在敌人的组件上添加导航网格代理(3D球体)。
“导航系统可以让你创建可以在游戏世界中智能移动的角色,需要使用从场景几何体自动创建的导航网格。”
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
请注意你的导航网格,这个导航网格必须绕过所有的墙壁,否则你的敌人将无法正常行走。
这个简单的步骤将使接下来的事情变得简单。
第三步:我们的脚本
要开始编写脚本,我们需要使用Random.range函数声明和定义我们的随机目标,并获取navMeshAgent组件。 在此之后,我们的代码如下所示:
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
public int currentRandomPoint;
private NavMeshAgent navMesh;
void Start () {
currentRandomPoint = Random.Range(0, randomPoints.Length);
navMesh = transform.GetComponent<NavMeshAgent>();
}
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
如果你将使用annimator组件来对敌人做动画,你应该在start函数里面进行声明和使用,就像是下面这样:
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
protected Animator animator;
animator = GetComponent<Animator>();
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
在update函数中,我们需要做的第一件事是计算玩家距离和随机点距离,为此我们使用了Vector3。
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
playerDist
= Vector3.Distance(Player.transform.position, transform.position);
randomPointDist = Vector3.Distance(randomPoints[currentRandomPoint].
transform.position, transform.position)
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
并且声明一个RaycastHit,这个raycasthit将会确保我们的敌人看不到墙后面的玩家。
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
RaycastHit hit;
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
你可以在这里阅读有关Physics.Raycast的信息。
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
if (Physics.Raycast (transform.position, direction, out hit, 1000)
&&
playerDist < perceptionDistance ) {
if (hit.collider.gameObject.CompareTag("Player")) {
seeingPlayer = true;
} else {
seeingPlayer = false;
}
}
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
如果我们的玩家的距离大于感知距离的时候,敌人会选择继续行走。
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
if (
playerDist > perceptionDistance)
walk();
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
而我们的行走函数基本上是在设置navMesh的加速度,速度和目的地,就像是下面这样:
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
void walk () {
if (chasing == false) {
navMesh.acceleration = 1;
navMesh.speed = walkVelocity;
navMesh.destination =
randomPoints[currentRandomPoint].position;
} else if (chasing == true) {
chaseTime = true;
}
}
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
现在有了RaycastHit,玩家距离和随机点距离在我们的脚本中是下面这样的:
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
橙色虚粗线是我们的脚本计算的玩家距离,蓝色虚线是我们的脚本计算的随机点距离,绿色箭头是我们的RaycastHit。
下面是我们的敌人所具有的其他三种行为:
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
void look () {
navMesh.speed = 0;
transform.LookAt (Player);
}
void chase () {
navMesh.acceleration = 5;
navMesh.speed = chaseVelocity;
navMesh.destination = Player.position;
}
void attack () {
navMesh.acceleration = 0;
navMesh.speed = 0;
attacking = true;
}
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
如果你正在使用animator和动画,你可以在这里调用你的动画变量,就像下面这个例子这样:
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
animator.SetFloat("Speed", 0.5f);
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
接下来的步骤是进行大量的检查来攻击,追逐,停止追逐和所有其他的东西,我不会对这些东西做详细的说明,但如果你有疑问,你可以联系我,如上所述那样。
这就是接下来的检查的具体实现:
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
if (
playerDist <= perceptionDistance && playerDist > chaseDistance) {
if (seeingPlayer == true)
look();
else
walk();
}
if (
playerDist <= chaseDistance && playerDist > attackDistance) {
if (seeingPlayer == true) {
chase();
chasing = true;
}else {
walk();
}
}
if (playerDist <= attackDistance && seeingPlayer == true)
attack();
if (randomPointDist <= 8) {
currentRandomPoint = Random.Range(0, randomPoints.Length);
walk();
}
if (chaseTime == true)
chaseStopwatch += Time.deltaTime;
if (chaseStopwatch >= 5 && seeingPlayer == false) {
chaseTime = false;
chaseStopwatch = 0;
chasing = false;
}
if (attacking == true)
attackingStopwatch += Time.deltaTime;
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
这是攻击检测的实现:
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
if (attackingStopwatch >= attackTime && playerDist <=
attackDistance) {
attacking = true;
attackingStopwatch = 0;
if (attackingStopwatch >= attackTime && playerDist
> attackDistance) {
attacking = false;
attackingStopwatch = 0;
}
}
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
如果你想对玩家造成伤害,你应该添加如下内容:
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
playerLife = playerLife - enemyDamage;
if (playerLife <= 5) {
Application.LoadLevel(GameOverSceneName);
} else if (attackingStopwatch >= attackTime &&
playerDist > attackDistance) {
attacking = false;
attackingStopwatch = 0;
}
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
我真的推荐让playerLife称为玩家脚本的一个变量。
最后,如果你想要的是你的玩家可以攻击敌人,你可以添加刚体组件给你的敌人,并做下面这些事情:
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
void OnCollisionEnter (Collision col) {
if(col.transform.tag == "Bullet"){
enemyLife -= 10;
if (enemyLife <= 0) {
Destroy (gameObject);
}
}
}
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
现在我们完成了我们简单的人工智能脚本,你可以用它来进行简单的游戏,学习和改进,几个月后我会发布第二部分来解释如何改进这个脚本,添加视野、声音等等。
您可以在我的github上查看完整的脚本/项目,我对项目/代码的质量感到抱歉,我写这个的时候才年仅16岁。
地址在https://github.com/OtavioHenrique/simpleAI。
如果你有什么疑问的话,请在我的Twitter或Linkedin上询问我。
谢谢你们,希望能够很快再见到你们。
【版权声明】
原文作者未做权利声明,视为共享知识产权进入公共领域,自动获得授权。