移动,相机控制与碰撞
移动玩家
当决定以何种最佳方式在虚拟世界中移动玩家时,需要考虑如何才能使移动看起来最真实,并避免昂贵的计算开销.在大部分情况下,我们都需要采用折中方案,Unity也不例外.
移动游戏对象的三种常见方法及结果如下:
- 使用游戏对象的Transform组件来进行移动和旋转.这时最简单的方式,也是我们的首选的方式.
- 为游戏对象附加RigidBody组件并在代码中施加力.这种方法需要依赖Unity的物理系统来处理繁重的工作,从而提供更真实的效果.本章后续部分会修改代码以使用这种方式.
附加现有的Unity组建或预制体,例如CharacterController 或 FirstPersonController. 这样可以减少样板代码,并且仍然能够提供真实的效果,另外还缩短了原型制作时间.
可以在文档中找到关于CharacterController组件的更多信息. FirstPersonController 预制体可以从 Standard Asset Package中获取
玩家对象的创建
我们想把Hero Born 设计为第三人称冒险游戏,这个游戏的起点就是一个可以通过键盘输入进行控制的胶囊以及跟随这个胶囊进行移动的相机.即使这两个对象在游戏中可以一起工作,但为了方便控制,我们还是将他们分开为好.
实践:创建胶囊
(1)在Hierarchy面板中使用Create | 3D Object | Capsule 创建一个新的 Capsule 对象,命名为Player.
(2)选择Player对象,单击Inspector面板底部的 Add Component 按钮.搜索Rigidbody,并按Enter键,将Rigidbody组件添加到Player对象上.
(3)展开Rigidbody组件底部的Constraint属性并选中Freeze Rotation中的x轴和y轴.
(4)选择Materials文件夹并使用 Create | Material 创建材质, 命名为Player_Mat
(6)改变Albedo属性至亮绿色并拖动Player_Mat材质到Hierarchy面板中的Player对象上.
![image.png](https://img-blog.csdnimg.cn/img_convert/51e00215e5cfa69dc4524e4c4ee6723d.png#clientId=u1ca2e895-04f8-4&from=paste&height=321&id=u096c59d0&margin=[object Object]&name=image.png&originHeight=642&originWidth=1920&originalType=binary&ratio=1&size=355379&status=done&style=none&taskId=ubf4ef3f9-8499-41a1-92f4-8cf31a4da0c&width=960)
我们使用Capsule对象,Rigidbody组件和亮绿色材质创建了Player对象.不必疑惑Rigidbody组件到底是什么,现在只需要知道Rigidbody组件与物理系统进行交互即可.
理解向量
Translate 和 Rotate 方法属于Unity提供的 Transform类,它们都需要使用一个向量作为参数.
在Unity中,向量用来保存2D或3D空间中的位置和方向,因而存在两种变量:Vector2
和Vector3
.这两种变量能够像任何其他变量那样使用,只不过它们代表不同的信息.因为我们的游戏是三维的,所以需要使用Vector3
.这意味着你需要知道x,y,z的值.对于2D向量,则只需要知道x和y的值.记住,3D场景的当前朝向显示场景中右上角的几何图形上.
![image.png](https://img-blog.csdnimg.cn/img_convert/e6982a81232c9ba2e859ca82db90ccc1.png#clientId=u1ca2e895-04f8-4&from=paste&height=287&id=u8460253e&margin=[object Object]&name=image.png&originHeight=573&originWidth=1046&originalType=binary&ratio=1&size=285551&status=done&style=none&taskId=u59cdb449-c6a3-4051-ae98-f2f7de4a45b&width=523)
了解有关向量的更多信息,可以查看文档.
例如,如果想要创建向量来保存场景中原点的位置,可以使用如下代码:
Vector3 origin = new Vector(0f,0f,0f);
上述代码将创建一个Vector3变量并使用0按顺序初始化位置值.float类型的值带不带小数点都可以,但是必须以小写的f结尾.
我们还可以使用 Vector2
和Vector3
类的属性来创建方向向量:
Vector3 forwardDirection = Vector3.forward;
forwardDirection
变量指的是3D空间中沿着z轴的场景方向,也就是前方,其中存储的并不是位置.本章后续部分会使用向量,现在只需要考虑3D运动的位置和朝向即可.
可以参考Unity提供的向量手册
获取玩家输入
位置和朝向是很有用的概念,但只靠它们无法形成运动,还需要配合玩家输入才行.这就是引入Input
类的原因所在,Input
类能将所有的按键输入和鼠标位置处理为相应的加速及陀螺仪数据.
对于Hero Born游戏来说,可使用键盘上的 W,A,S,D键以及方向键来控制移动,并配合使用脚本,让相机能够跟随玩家光标所指的位置.为此,需要理解输入轴是如何工作的.
使用Edit | Project Setting | Input 打开 InputManager 面板
![image.png](https://img-blog.csdnimg.cn/img_convert/a57321cefdeeb6951e991f98ebc2b18f.png#clientId=u12f559c8-44ff-4&from=paste&height=252&id=u48482cd0&margin=[object Object]&name=image.png&originHeight=503&originWidth=641&originalType=binary&ratio=1&size=57108&status=done&style=none&taskId=u41406ac3-90b2-4d68-9f78-f2a8bcac274&width=320.5)
从中可以看到已配置好的Unity默认的输入列表,一Horizontal输入轴为例:Horizontal输入轴将Negative Button 和 Positive Button 按钮分别设置为键位 left 和 right, 且将Alt Negative Button 和 Alt Negative Button 设置为键位a和d
任何时候,当从代码中获取输入轴时,值的范围始终是-1~1. 例如当左方向键或a键按下时,水平轴的值变为-1;释放按键时,值变回0.同样,当右方向键或D键按下时,水平轴的值会变为1.这种行为是你可以从代码中获取某个不同的输入,并且只需要一行代码,你不需要为了获取不同的值而输入一长串的if/else语句.
获取输入轴很简单,只需要调用Input.GetAxis
方法并指定输入类型的名称即可,稍后我们将采用这种方式来获取水平和垂直输入.
既可以按自己的需要修改默认的输入配置,也可以通过递增Size的值来创建自定义输入轴,然后重命名创建出来的脚本.
实践:移动玩家
为了使玩家可以移动,需要为Player对象添加脚本
(1)在Scripts文件夹中创建一个新的C#脚本,命名为PlayerBehavior,然后将这个脚本拖动至Player对象上.
(2)添加如下代码并保存:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerBehavior : MonoBehaviour
{
// ①
public float moveSpeed = 10f;
public float rotateSpeed = 75f;
// ②
private float vInput;
private float hInput;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
//③
vInput = Input.GetAxis("Vertical") * moveSpeed;
//④
hInput = Input.GetAxis("Horizontal") * rotateSpeed;
//⑤
this.transform.Translate(Vector3.forward * vInput * Time.deltaTime);
//⑥
this.transform.Rotate(Vector3.up * hInput * Time.deltaTime);
}
}
如果存在空的方法,例如本例中的Start方法,可以将其删除以保持代码清晰.当然,如果倾向于在脚本中保留它们,也可以.
下面按照标志对上述代码进行解释
①声明两个公共变量作为乘数
-
moveSpeed
表示玩家可以向前或向后移动的速度 -
rotateSpeed
表示玩家可以向左或向右旋转的速度
②声明两个私有变量来保存自玩家的输入,刚开始不设置任何值:
-
vInput
用来保存来自垂直值的输入 -
hInput
用来保存来自水平轴的输入
③Input.GetAxis("Vertical")
用于检查上下方向键以及W和S键何时被按下,然后将其值乘以moveSpeed
- 上方向键和W键会返回1,使得玩家向前方(正方向)移动
- 下方向键和S键会返回-1,使得玩家向后方(反方向)移动
④Input.GetAxis("Horizontal")
用于检查左右方向键以及A和D键何时被按下,然后将其值乘以rotateSpeed
- 右方向键和D键会返回1,使得玩家向右旋转
- 左方向键和A键会返回-1,使得玩家向左旋转
是否可以将所有移动计算集中至一行代码?当然可以,但是,将代码分解成多好更好些,这样可便于他人理解.
⑤使用Translate
方法移动Player对象的Transform组件
- 记住
this
关键字指代当前脚本被附加到的游戏对象,在本例中也就是Player对象. - 将
Vector3.forward
与vInput
和Time.deltaTime
相乘,后面两者提供了玩家沿着z轴向前或向后移动的速度和方向. - 当游戏运行时,
Time.deataTime
会返回从上一帧到现在经历的时间,单位为秒.Time.dealtTime
通常用于平滑Update
方法获取的值,使其不受设备帧率的影响.
⑥使用Rotate
方法相对传入向量旋转玩家
- 将
Vector3.up
乘以hInput
和Time.deltaTime
可得到向左或向右的旋转轴 - 这里使用
this
关键字和Time.deltaTime
的原因与上面的相同.
如前所述,在Translate和Rotate方法中使用方向向量只是达成目的的方式之一.也可根据轴输入创建新的Vector3变量并用作参数.
相机跟随
是一个对象跟随另一个对象的最简单方式就是将其中一个对象设置为另一个对象的子对象.但是,这意味着Player对象发生的任何移动或旋转都会影响到相机,这并不是我们想要的效果. 幸运的是,Transform类提供的方法使得相对于Player对象移动相机的位置和旋转变得十分简单.
实践:编写相机行为
(1)在Scipts文件夹中创建一个新的C#脚本,命名为CameraBehavior,然后拖放至Main Camera对象上
(2)添加如下代码并保存
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraBehavior : MonoBehaviour
{
// 1
public Vector3 camOffset = new Vector3(0f,1.2f,-2.6f);
// 2
private Transform target;
// Start is called before the first frame update
void Start()
{
// 3
target = GameObject.Find("Player").transform;
}
// 4
void LateUpdate()
{
// 5
this.transform.position = target.TransformPoint(camOffset);
// 6
this.transform.LookAt(target);
}
}
下面对上述代码进行解释
- 声明一个Vector3变量来存储想要的Main Camera对象与Player对象之间的偏移距离
- 可以在Inspector面板中手动设置相机的偏移位置,因为camOffset变量是公共的.
- 现有的默认值比较合适,当然你也可以尝试修改
- 创建一个Transform变量来保存Player的位置变换信息
- 这使得我们可以获取位置,旋转和缩放信息
- 这些信息不应该能够从CameraBehavior脚本之外进行访问,所以将target变量设置为私有的
- 使用GameObject.Find方法在场景中按名称查找Player对象并获取Player对象的transform属性,这意味着存储在target变量中的Player对象的位置会在每一帧进行更新.
- LateUpdate是MonoBehavior脚本提供的方法,就像Start和Update方法一样,LateUpdate方法也在Update方法之后执行,由于PlayerBehavior脚本在Update方法中移动了Player对象,因此我们希望CameraBehavior脚本在移动操作完成之后执行;这样可以确保target变量引用的是最新位置.
- 每帧都把相机的位置设置为
target.TransformPoint(camoffset)
以产生跟随效果
- TransformPoint方法用于计算并返回世界空间的相对位置
- 在这里,TransformPoint方法会返回target对象的偏移位置,x轴为0,y轴为1.2(将相机置于胶囊上方),z轴为-2.6(将相机置于胶囊后方)
6.LookAt
方法会在每一帧更新胶囊的旋转值,使其朝向传入的Transform对象(本例中的targer变量)所在的位置.
![GIF 2021-8-16 17-00-58.gif](https://img-blog.csdnimg.cn/img_convert/5017acf7b3e2069cd33bf504454b7977.gif#clientId=u12f559c8-44ff-4&from=ui&height=274&id=u4c7fbb5e&margin=[object Object]&name=GIF 2021-8-16 17-00-58.gif&originHeight=547&originWidth=1048&originalType=binary&ratio=1&size=2520055&status=done&style=none&taskId=u579cacb7-d267-4b00-8214-6b38f984af5&width=524)
按时间顺序进行分解
(1)首先为相机创建了偏移位置
(2)然后查找并存储Player对象(也就是场景中的胶囊)的位置
(3)最后,在每一帧都手动更新位置和朝向,使相机一直按设置好的距离跟随并朝向外加
提示:当使用那些提供了平台特有功能的类方法时,始终记住将任务分解为最基础的步骤,这将使你在新的编程环境中如鱼得水
使用Unity的物理系统
Unity的物理系统使用的是PhysX印象,PhysX引擎中最重要的两个组件如下:
- RigidBody组件,这种组件允许你使用重力以及其他因素对游戏对象施加影响.
- Rigidbody组件还会受施加的力的影响,从而产生更真实的运动效果.
![image.png](https://img-blog.csdnimg.cn/img_convert/888e52d973064fb00fce44dea93f8b63.png#clientId=u5de4cde2-5c33-4&from=paste&id=u25132333&margin=[object Object]&name=image.png&originHeight=257&originWidth=462&originalType=binary&ratio=1&size=18395&status=done&style=none&taskId=ueaf3990f-fe24-406f-8026-165953f7df1)
- Collider组件,这种组件决定了游戏对象何时以及怎样进入,离开其他对象的物理空间,抑或简单地碰撞并摊开.对于给定的游戏对象来说,只能添加一个RigBody组件,但可以添加Collider组件.
![image.png](https://img-blog.csdnimg.cn/img_convert/abd373ee0cc6b849e130fc89cd7c56ea.png#clientId=u5de4cde2-5c33-4&from=paste&id=uea4533dc&margin=[object Object]&name=image.png&originHeight=137&originWidth=461&originalType=binary&ratio=1&size=11879&status=done&style=none&taskId=uc8d889a9-9e1d-45d6-bc1d-779ef6b37a9)
当两个游戏对象彼此碰撞时,Rigidbody组件的属性决定了碰撞结果.举例来说,如果一个游戏对象的质量比另一个大,那么轻一点的那个会被弹开,就像现实生活中一样. Rigidbody 和 Collider 组件负责Unity中所有的物理交互.
在使用Rigidbody和Collider组件时有一些注意事项,下面使用Unity允许的移动类型术语来进行解释.
- Kinematic移动发生在添加了Rigidbody组件的游戏对象上,但没有向场景中的物体系统进行注册.这种行为仅仅在某些特定情形下使用,可以通过选中Rigidbody组件的isKinematic属性来启用.因为我们希望胶囊能够与物理系统进行交互,所以这里不会使用这种运动方式.
- Non-Kinematic移动指的是通过施加力来对Rigidbody组件进行移动和旋转,而不是直接操作游戏对象的transform属性.我们的目标就是修改PlayerBehaviour脚本以实现这种类型的移动.
我们现有的方式是操纵胶囊的Transform组件,同时使用RigidBody组件与物理系统进行交互,目的是想要在3D空间中考虑移动与选装.然而,这并不意味着可以在实际产品中使用,并且Unity也建议避免在代码中混合使用Kinematic和Non-Kinematic移动方式
刚体运动
由于为Player对象添加了RigidBody组件,因此我们应该使用物理引擎来控制移动而不是直接进行移动和旋转.力的施加方式有两种:
- 直接使用RigidBody类的AddForce和AddTorque方法分别移动或旋转对象.这种方式存在一些不足,通常需要编写额外的代码以修正非预期的物理行为.
- 使用其他的RigidBody类方法,利用MovePosition 和 MoveRotation方法.这种方法依然会施加力,但是系统会在幕后处理好边界情形.
我们将选用第二种方式,如果对手动向游戏对象施加力与扭矩感兴趣,可以访问文档获取相关知识.
实践:RigidBody组件
我们首先需要从Player对象中获取并存储想要修改的RigidBody组件.
(1)按如下代码修改PlayerBehavior脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerBehavior : MonoBehaviour
{
public float moveSpeed = 10f;
public float rotateSpeed = 75f;
private float vInput;
private float hInput;
// 1
private Rigidbody _rb;
// Start is called before the first frame update
// 2
void Start()
{
// 3
_rb = GetComponent<Rigidbody>();
}
// Update is called once per frame
void Update()
{
vInput = Input.GetAxis("Vertical") * moveSpeed;
hInput = Input.GetAxis("Horizontal") * rotateSpeed;
/* 4
this.transform.Translate(Vector3.forward * vInput * Time.deltaTime);
this.transform.Rotate(Vector3.up * hInput * Time.deltaTime);
*/
}
}
下面对上述代码进行解释
- 添加一个私有的RigidBody变量,用来存储胶囊的RigidBody组件信息.
- Start方法会初始化脚本时触发,也就是单击Play按钮时.在初始化过程中,设置变量时都应该使用Start方法.
- 使用GetComponent方法检查脚本上附加的对象是否包含指定的组件类型,在本例中也就是RigidBody组件.如果找到了,就返回.如果没有返回,那么返回null.但在这里,我们已经知道Player对象上附有RigidBody组件.
- 注释掉Update方法中对Transform 和 Rotate 方法的调用,从而避免同时使用两种不同的控制方式.这里依然保留获取玩家输入的方式,以遍后续继续使用.
实践:移动刚体
打开PlayBehavior脚本,在Update方法中添加如下代码并保存文件.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerBehavior : MonoBehaviour
{
public float moveSpeed = 10f;
public float rotateSpeed = 75f;
private float vInput;
private float hInput;
private Rigidbody _rb;
void Start()
{
_rb = GetComponent<Rigidbody>();
}
void Update()
{
vInput = Input.GetAxis("Vertical") * moveSpeed;
hInput = Input.GetAxis("Horizontal") * rotateSpeed;
/*
this.transform.Translate(Vector3.forward * vInput * Time.deltaTime);
this.transform.Rotate(Vector3.up * hInput * Time.deltaTime);
*/
}
// 1
void FixedUpdate()
{
// 2
Vector3 rotation = Vector3.up * hInput;
// 3
Quaternion angleRot = Quaternion.Euler(rotation * Time.fixedDeltaTime);
// 4
_rb.MovePosition(this.transform.position + this.transform.forward * vInput * Time.fixedDeltaTime);
// 5
_rb.MoveRotation(_rb.rotation * angleRot);
}
}
下面对上述代码进行解释
- 任何物理的或RigidBody相关的代码都要放在FixedUpdate方法中,而不是放在Update或其他MonoBehavior方法中.
- 创建一个新的Vector3变量以存储左右旋转值. Vector3.up * hInput 与我们之前在 Rotate 方法中使用的旋转向量是相同的
-
Quternion.Euler
接收一个Vector3变量作为参数并使用欧拉角的格式返回旋转值
- 在MoveRotation方法中,我们需要使用Quternion值而不是Vector3变量,这是Unity首选的旋转类型的转换.
- 这里乘以Time.fixedDeltaTime的原因与在Update方法中乘以Time.deltaTime相同
- 调用_rb组件的MovePosition方法,该方法接收一个Vector3变量作为参数并施加相应的力.
- 使用的向量可以如下分解:胶囊的位置向量加上前向的方向向量与垂直输入和Time.fixedDeltaTime的乘积.
- RigidBody组件负责调整施加的力以满足输入的向量参数.
- 调用_rb逐渐的MoveRotate方法,该方法也将接受一个Vector3变量作为参数并施加相应的力.angleRot已经包含来自键盘的水平输入,所以你需要调整好Inspector面板中的moveSpeed和rotateSpeed变量.至此,我们重建了之前已有的移动模式,并且拥有了更真实的物理效果.
如果跑上坡道或者从*平台落下,就会看见玩家飞到空中或者缓慢落至地面.即使为RigidBody组件设置了重力,效果也很弱.
碰撞体和碰撞
Collider组件不仅仅使游戏对象能被Unity的物理系统认识到,也使交互和碰撞成为可能.可将碰撞体想象为围绕在游戏对象周围的不可见立场;取决于设置,它们可能被通过,也可能被撞上,并且有一系列方法会在发生不同的交互行为时触发.
Unity的物理系统针对2D或3D游戏的工作方式有所不同,本书只考虑3D相关主题.如果对2D游戏制作感兴趣,可以了解一下Rigidbody2D组件以及可用的2D碰撞体.
![image.png](https://img-blog.csdnimg.cn/img_convert/c61d60ef05f41a4dd87b68281e1ff5e6.png#clientId=u5de4cde2-5c33-4&from=paste&height=160&id=uf217e038&margin=[object Object]&name=image.png&originHeight=160&originWidth=332&originalType=binary&ratio=1&size=12220&status=done&style=none&taskId=u7e13556a-127f-4ca6-8924-6da422e8fa2&width=332)
Capsule对象周围的绿色形状是Capsule Collider,可以通过Center,Radius,Height等属性进行移动和缩放.当创建基础图元时,碰撞体默认与图元形状匹配;因为现在创建了Capsule图元,因此也会同时创建Capsule Collider
碰撞体还支持Box,Sphere和Mesh形状,可以手动从 Component | Physics 菜单或单击 Inspector 面板中的 Add Component按钮进行添加.
当碰撞体与其他对象触发某种联系时,就会发出所谓的消息或广播.当碰撞体发送消息时,任何添加了一或多个此类方法的脚本将收到通知.这就是时间(Event).
举个例子,当两个带有碰撞体的游戏对象碰在一起时,它们都会发送OnCollisionEnter消息,其中包含将要碰到的对象的引用.这种消息可用于各种交互式事件,比如拾取物品.
实践:拾取物品
(1)在Scripts文件夹中创建一个新的C#脚本,命名为ItemBehavior,然后拖放至场景中Pickup_Item预制体之下的Capsule对象上.注意,任何使用了碰撞检测的脚本都必须附加到包含Collider组件的游戏对象上,即使是预制体的子对象.
(2)使用Pickup_Item更新根预制体
(3)在ItemBehavior脚本中添加如下代码并保存
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ItemBehavior : MonoBehaviour
{
// 1
private void OnCollisionEnter(Collision collision)
{
// 2
if (collision.gameObject.name == "Player")
{
// 3
Destroy(this.transform.parent.gameObject);
// 4
Debug.Log("Item collected!");
}
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
(4)单击Play按钮,移动玩家至胶囊处并捡起胶囊
下面对步骤(3)中的代码进行解释
- 当把另一个对象移至 Pickup_Item 且 isTrigger 处于关闭状态时, Unity会自动调用OnCollisionEnter方法
- OnCollisionEnter方法有一个参数用于存储Collider引用.
- 注意collision变量的类型是Collision而不是Collider
- Collision 类的 gameObject 属性用于保存地狱 GameObject碰撞体的引用. 可以使用 gameObject属性获取游戏对象的名称并使用if语句检查碰撞体是否是Player对象.
- 如果碰撞体是Player对象,就调用Destroy对象,该方法接收一个游戏对象作为参数.
- 我们必须使整个Pickup_Item对象被销毁,而不仅仅是销毁Capsule对象.
- 因为ItemBehavior 脚本被附加到了Capsule对象上,而capsule对象又是Pickup_Item的子对象,所以可以使用this.transform,parent.gameObject 将Pickup_Item对象销毁
- 向控制台打印一条日志,指明已经收集了道具.
![image.png](https://img-blog.csdnimg.cn/img_convert/c161ee70ef53abefa295c2d78c3d89db.png#clientId=ua3da4709-8c6b-4&from=paste&height=1048&id=u5c4780a1&margin=[object Object]&name=image.png&originHeight=1048&originWidth=1922&originalType=binary&ratio=1&size=324945&status=done&style=none&taskId=u86ccb5dc-8a47-4d05-9be3-fce135a3835&width=1922)
使用碰撞体触发器
某些情况下,碰撞体的isTrigger属性并未启用,物理系统会把这些碰撞体视为实体.然而,某些情况下我们需要使游戏对象可以穿过碰撞体,触发器就是为了处理这种情况而存在的.当isTrigger属性被启用后,游戏对象就可以穿过碰撞体,但发送的通知会变为OnTriggerEnter,OnTriggerExit和OnTriggerStay
触发器多用于监测游戏对象是否进入某个特定区域或通过某个点.可使用触发器在敌人周围设置警戒区域,如果玩家进入触发区域,敌人就会受到惊扰,然后开始攻击玩家
实践:创建敌人
![image.png](https://img-blog.csdnimg.cn/img_convert/db8475a94069cb018fedf1a55b352961.png#clientId=u10bb72e9-58f5-4&from=paste&height=1033&id=u5438e849&margin=[object Object]&name=image.png&originHeight=1033&originWidth=1922&originalType=binary&ratio=1&size=454135&status=done&style=none&taskId=u9031ee6a-9b4a-4208-a3e1-fdae1ab781b&width=1922)
实践:捕获触发器事件
// EnemyBehavior.CS
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyBehavior : MonoBehaviour
{
// 1
private void OnTriggerEnter(Collider other)
{
// 2
if (other.name == "Player")
{
Debug.Log("Player detected - attack!");
}
}
//3
private void OnTriggerExit(Collider other)
{
// 4
if (other.name == "Player")
{
Debug.Log("Player out of range, resume patrol");
}
}
}
- 任何时候,当一个对象进入Enemy游戏对象的球形触发器时,OnTriggerEnter方法就会触发.
- 类似OnCollisionEnter方法,OnTriggerEnter方法的参数用于存储对象的Collider组件的引用
- 注意参数对象的类型是Collider而不是Collision
- 使用other获取碰撞体对象的名称并使用if语句检查是不是Player对象,如果是,就输出Player对象位于危险区域的提示信息
- 当对象离开Enemy游戏对象的球形触发器时,触发OnTriggerExit方法
- 使用if语句按名称检查离开球形触发器的对象,如果是Player对象,就将另一条信息打印到控制台.
Enemy游戏对象会在自身被侵入时发送通知,EnemyBehavior脚本将捕获这些事件.任何时候,当玩家进入或离开球形触发器时,都会在控制台中打印调试日志以确保代码正常工作.
Unity利用了一种称为组件的设计模式.这里不涉及过多细节,简单来讲就是对象(以及类)应该负责自己的行为.正因为如此,我们才能将可拾取道具和Player对象的碰撞脚本分开,而不是使用单独的类处理所有事情.
总结
- Rigidbody组件能为附加到的对象添加真实的物理模拟
- Collider组件之间可以相互交互,并且Collider组可以作为对象与Rigidbody组件进行交互.
- 如果Collider不是触发器,那么行为看起来就像实际存在的物体.
- 如果Collider是触发器,那么可以轻松被穿过.
- 如果一个对象使用了Rigidbody组件但没有启动isKinematic属性,那么得到的就是运动学效果,因为物理系统会忽略这个对象.
- 如果一个对象使用了Rigidbody组件并且施加了力和扭矩,那么得到的将是非运动学效果.
- 碰撞体基于交互行为发送通知
- 这些通知取决于碰撞体是否被设置为触发器
- 碰撞任何一方都可以接受通知,并且都包含了一个保存了对象的碰撞信息的引用变量.