在Unity中创建简单的C#AI

图片

几乎任何游戏都需要与用户互动的人工智能(AI),通常以对玩家不利的力量形式出现。 在某些情况下,AI应该帮助玩家,而在另一些情况下,AI应当与之战斗,但是计算机控制的所有角色都有一些相似之处。 根据项目的要求,AI可以使用简单或复杂的行为。 这些要求可能是与其他玩家进行外交交流,或者是在平台上来回徘徊。 尽管如此,有必要确保AI有效地完成其工作。

在这个项目中,我将演示一个非常简单的人工智能。 假设我们创建了一个游戏,其中玩家必须潜入敌方总部附近。 当监控摄像头注意到玩家时,会在附近制造敌人并在短时间内追赶该玩家。 这就是我们在项目中最简单的实现。 项目完成后,您将收到一个受控的玩家对象,一个用作敌方相机的圆圈,以及一个敌人的对象,当相机的对象告知其存在时,该对象会追赶玩家。

准备工作


首先,我们需要创建一个3D项目。 启动Unity之后,单击窗口顶部的New按钮,如图1所示。


图1:创建一个新项目

为您的AI项目命名,并确保它是3D项目。 在计算机上选择一个存储项目的位置之后,单击底部的Create Project按钮,如图2所示。


图2:项目设置屏幕

创建项目后,我们要做的第一件事是在“ 资产”窗口中设置文件夹以组织我们的工作。 右键单击Assets窗口,然后选择Create→Folder创建一个新文件夹。 将此文件夹命名为Materials 。 然后创建第二个文件夹,并将其命名为Scripts 。 图3显示了它的外观。


图3:创建一个新文件夹

完成所有这些操作后,“ 资产”窗口应类似于图4所示。


图4:“ 资产”窗口。

接下来,创建一个所有对象都将站立的地板。 在“ 层次结构”窗口中,选择“ 创建”→“ 3D对象”→“平面”以创建将用作地板的平面对象。


图5:创建平面对象。

命名此Floor对象,并将其X Scale值更改为7,将Z Scale值更改为3。之后,带有所选Floor对象的Inspector窗口应如图6所示。


图6:设置Floor对象的属性。

现在,我们需要为地板创建一个新的材质,以将其与将要放置在场景中的其余对象区分开。 在“ 资产”窗口的“ 材料”文件夹中,右键单击“ 资产”窗口,然后选择“ 创建”→“材料” ,以创建新材料


图7:创建新材料

完成后,将物料命名为Floor


图8: 地板材料。

在选择了“ 地板”的“ 检查器”窗口的顶部,选择一个颜色选择器。


图9:选择一个颜色选择器。

当然,您可以为地板选择任何颜色,但是在此示例中,我选择了红棕色,如图10所示。


图10:颜色选择器。

在“ 层次结构”窗口中选择“ 地板”对象,然后在“ 网格渲染器”组件中,选择“ 材质”旁边的小箭头。


图11:准备材料更改。

地板材料从“ 资源”窗口拖动到“ 检查器”窗口中“ 网格渲染器”组件的“ 元素0”字段。


图12:将Floor材质定义为Floor对象的材质。

完成了地板对象后,我们需要围绕墙壁区域进行创建,以使播放器不会掉落边缘。 返回创建→3D对象→平面以创建一个新平面。 我们称该墙为平面,并将其设置为与Floor相同的尺寸,即X Scale的值为7, Z Scale的值为3。然后再创建三个墙,选择一个对象,然后按Ctrl + D 3次。性别与表格中的数据一致。

职称
位置X
位置Y
位置z
旋转x
旋转z

-35
21
0
0
-90
墙(1)-1
11
-15
90
0
墙(2)
-1
11
13.5
-90
0
墙(3)
34
21
0
0
90

表1:所有Wall对象的位置和旋转。

完成所有这些操作后,您需要更改摄像头的位置,以使其从上方看向地面。 选择主摄像机对象,然后将“ Y位置”设置为30,将“ Z位置”设置为0,将“ X旋转”设置为80。


图13:设置相机对象。

场景已经准备好了,是时候创建玩家角色了。 在“ 层次结构”窗口中,单击创建→3D对象→球体以创建球体对象。 将该对象命名为Player ,然后单击“ 检查器”窗口底部的“ 添加组件”按钮。


图14:添加一个新组件。

现在找到刚体 。 之后,从列表中选择Rigidbody组件并将Rigidbody添加到Player对象。


图15:添加刚体组件。

接下来,您需要为玩家分配一个标签,该标签稍后会在代码中派上用场。 单击检查器窗口左上角的“ 标签”下拉菜单,然后选择“ 播放器”标签。


图16:定义新标签。

我们需要设置玩家的位置,使其不在地板对象下方。 在示例中,我将播放器放置在左上角,其中X位置等于26, Y位置等于1, Z位置等于-9。


图17:球员位置。

为了使将来的代码正常工作,我们当然需要将其附加到对象上。 返回到“ 层次结构”窗口,这次选择“ 创建”→“ 3D对象”→“多维数据集” 。 调用此Guard多维数据集,使用“ 检查器”窗口中的“ 添加组件”按钮向其中添加Rigidbody组件和NavMesh Agent组件。 接下来,将其放置在场景的左上角。 之后, Guard对象的Inspector窗口将如下所示:


图18:“ 检查器”窗口中的Guard对象。

这个对象应该位于这样的位置:


图19: Guard对象的位置。

最后,我们需要一个用作守卫对象“眼睛”的对象,它将通知守卫玩家正在触摸它。 上一次,进入“ 层次结构”窗口,然后选择“ 创建”→“ 3D对象”→“球体”以创建另一个球体对象。 将此对象命名为Looker 。 这次我们不需要添加任何其他组件。 但是,我们将调整对象的大小。 选择Looker之后 ,在“ 检查器”窗口中更改以下“ 变换”组件变量。

  • X缩放为9。
  • 以0.5 缩放Y。
  • Z标尺为9。

然后,将Looker放置在地板的中间上部,如图20所示。


图20:查找器的位置。

现在是为Looker提供独特材料的正确时机,这样您就可以避免使用它。 在“ 资源”窗口的“ 材料”文件夹中,右键单击并创建一个新材料。 将其命名为Looker并为其赋予明亮的红色。 之后,将此材质指定为Looker对象的材质以更改其颜色。 之后,场景应如下所示:


图21:具有新材质的Looker对象。

我们剩下的唯一事情就是为Guard创建一个导航网格,它可以在上面移动。 Unity编辑器顶部有一个Window菜单。 选择“ 窗口”→“导航”以打开“ 导航”窗口,如图22所示。


图22: 导航窗口。

在“ 层次结构”中选择Floor对象,然后在“ 导航”窗口中,选中“ 导航静态”复选框。


图23: 静态导航

接下来,选择窗口顶部的“ 烘烤”选项。


图24:切换到“ 烘烤”菜单。

将打开“ 烘焙”菜单,您可以在其中更改将要创建的导航网格的属性。 在我们的示例中,无需进行任何更改。 只需单击右下角的“ 烘焙”按钮。


图25:创建一个新的导航网格。

此时,Unity将要求您保存场景。 保存它,之后将创建导航网格。 现在,场景将如下所示:


图26:当前场景以及添加的导航网格。

现在,Unity中的所有内容都已配置完毕,现在该创建创建项目所需的脚本了。 在Assets窗口中,右键单击并选择Create→C#Script 。 将此脚本命名为Player 。 再重复两次此操作,创建名称为GuardLooker的脚本。


图27:创建一个新脚本。

之后,“ 资产”窗口中的“ 脚本”文件夹将如下所示:


图28: 脚本文件夹。

首先,我们将开始编写Player脚本代码。 在Assets窗口中双击Player脚本以打开Visual Studio并开始创建代码。

代号


Player脚本非常简单,它所要做的就是让用户移动球对象。 在类声明下,我们需要获取到先前在项目中创建的Rigidbody组件的链接。

private Rigidbody rb; 

之后,在Start函数中,我们命令Unity将Player对象的当前Rigidbody组件设置为rb

 rb = GetComponent<Rigidbody>(); 

之后, 播放器脚本将如下所示:


图29:目前的播放器脚本。

现在已分配了rb值,我们需要在按下箭头键时让Player对象移动。 为了移动对象,我们将使用物理方法,当用户按下箭头键时向对象施加力。 为此,只需将以下代码添加到Update函数:

 if (Input.GetKey(KeyCode.UpArrow)) rb.AddForce(Vector3.forward * 20); if (Input.GetKey(KeyCode.DownArrow)) rb.AddForce(Vector3.back * 20); if (Input.GetKey(KeyCode.LeftArrow)) rb.AddForce(Vector3.left * 20); if (Input.GetKey(KeyCode.RightArrow)) rb.AddForce(Vector3.right * 20); 

至此,我们完成了Player脚本。 完成的脚本如下所示:


图30:完成的Player脚本。

保存您的工作并返回Unity。 这次,在Assets窗口中选择Guard脚本。 为了使Guard代码正常工作,您需要在脚本顶部添加using结构。

 using UnityEngine.AI; 

接下来,在类声明之后立即声明以下变量。

 public GameObject player; private NavMeshAgent navmesh; 

Player对象用作Guard对象的player变量的值。 稍后,当我们命令Guard对象追逐玩家时,它将派上用场。 然后, 声明navmesh变量以接收对象的NavMeshAgent组件。 当守卫得知玩家正在触摸Looker对象后,我们开始追逐玩家时,我们将使用它。 在启动功能中,我们需要将navmesh变量的值设置对象组件NavMesh Agent

 navmesh = GetComponent<NavMeshAgent>(); 

然后,在Update函数中,添加一行代码:

 navmesh.destination = player.transform.position; 

此行设置Guard对象的目标。 在我们的例子中,它将采用Player对象的当前位置并移至这一点。 操作后,物体将不断追随玩家。 问题是,响应过程如何执行? 它不会在Guard脚本中编码,但会在Looker脚本中编码。 在转到Looker脚本之前,请查看图31验证Guard脚本代码。


图31:完成的Guard脚本。

Looker内部我们再次需要声明以下变量:

 public GameObject guard; private float reset = 5; private bool movingDown; 

之后,我们对Start函数进行注释,此脚本中不需要此函数。 让我们继续执行Update函数并添加以下代码:

 if (movingDown == false) transform.position -= new Vector3(0, 0, 0.1f); else transform.position += new Vector3(0, 0, 0.1f); if (transform.position.z > 10) movingDown = false; else if (transform.position.z < -10) movingDown = true; reset -= Time.deltaTime; if (reset < 0) { guard.GetComponent<Guard>().enabled = false; GetComponent<SphereCollider>().enabled = true; } 

这是项目主要动作发生的地方,因此让我们分析代码。 首先,根据布尔变量movingDown的值,此脚本所附加的对象将向上或向下移动。 一旦到达某个点,他就会改变方向。 接下来, Looker将基于实时降低重置值。 一旦计时器小于零,他将从Guard对象中获取Guard脚本并将其禁用,此后, Guard对象将开始移动到玩家最后一次知道的位置,然后再停止。 Looker还会再次打开其对撞机,以便整个过程可以重新开始。 现在,我们的脚本如下所示:


图32: Looker脚本。

说到对撞机:是时候创建一个冲突代码来重置Looker计时器并包括Guard脚本了。 在更新功能中,创建以下代码:

 private void OnCollisionEnter(Collision collision) { if (collision.gameObject.tag == "Player") { guard.GetComponent<Guard>().enabled = true; reset = 5; GetComponent<SphereCollider>().enabled = false; } } 

OnCollisionEnter Unity自动将其识别为冲突代码,因此在与另一个对象发生冲突时执行该代码。 在我们的例子中,它首先检查碰撞对象是否具有Player标签。 如果不是,它将忽略其余代码。 否则,它将打开Guard脚本,将重置计时器设置为5(即五秒),并禁用其对撞机,以便玩家仍然可以在对象中移动并且不会意外卡在Looker对象中。 该功能如图33所示。


图33: Looker的冲突代码

这就是项目代码的全部准备好了! 在完成项目之前,您可以做几件事。 保存所有工作并返回Unity。

项目完成


要完成项目,我们只需要将脚本附加到相应的对象并设置几个变量即可。 首先,从“ 导航”窗口转到“ 检查器”窗口:


图34:过渡到“检查器” 窗口

之后,让我们从Player对象开始。 在“ 层次结构”窗口中选择它,然后在“ 检查器”窗口的底部单击“ 添加组件”按钮并添加“ 播放器”脚本。 这样就完成了Player对象。


图35:播放器脚本组件

接下来,选择Guard对象。 和以前一样,将Guard脚本附加到对象。 这次我们需要告诉守卫球员是谁。 为此,将Player对象从Hierarchy中拖动到Guard脚本组件的Player字段中,如图36所示。


图36:使Player对象成为Player字段的值。

我们还需要禁用Guard脚本。 在我们的项目中, Guard将在打开脚本后追逐玩家。 仅当播放器触摸Looker对象后,才应包含此Guard脚本。 您需要做的就是取消选中组件中Guard(脚本)文本旁边的框:


图37:禁用Guard脚本。

最后,让我们转到Looker对象,并将Looker脚本附加到该对象。 这次, Looker对象将需要一个Guard对象作为其Guard变量的值。 正如我们将Player对象分配给Guard脚本的Player变量一样,我们将对Guard对象和Looker脚本执行相同的操作。 将Guard层次结构拖到Looker脚本的Guard字段中。 并且项目完成了! 单击Unity编辑器顶部的“播放”按钮以测试您的项目。


图38:测试项目。

尝试将Player对象移动到Looker对象(请记住,移动是通过箭头完成的!)。 请注意,此后, Guard对象将开始追逐玩家。 他将继续追求约5秒钟,然后他将放弃。


图39:一个完全完成的项目正在实施。

结论


该AI非常简单,但可以轻松扩展。 假设,如果我们假设Looker对象是一台相机,而后卫则通过它寻找来找到您,那么让Guard对象拥有自己的双眼是合乎逻辑的。 玩家可以靠近摄像机,但必须考虑到警卫的眼睛。 您也可以将该项目与寻找路径概念结合起来:为后卫提供他将要遵循的路径,从而为玩家创造一个更加有趣的环境。

可以通过许多不同的方式来开发这种简单的AI。 您可能不想做上述任何事情,并决定自己做某事。 我建议您尝试一下,也许您会想到一个有趣的项目的想法,这值得结束。

Source: https://habr.com/ru/post/zh-CN419641/


All Articles