刚体运动
由于为Player对象添加了 Rigidbody 组件,因此我们应该使用物理引擎来控制移动而不是直接进行移动和旋转。力的施加方式有两种:
直接使用 Rigidbody 类的AddForce 和AddTorque 方法分别移动或旋转对象这种方式存在一些不足,通常需要编写额外的代码以修正非预期的物理行为.
使用其他的 Rigidbody 类方法,例如 MovePosition 和MoveRotation 方法。这种方式依然会施加力,但是系统会在幕后处理好边界情形。
实践:访问Rigidbody组件
我们首先需要从 Player 对象中获取并存储想要修改的 Rigidbody 组件。
(1)按如下代码修改 PlayerBehavior 脚本:
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 = InputGetAxis("Vertical") * moveSpeed; hInput = Input.GetAxis("Horizontal") * rotateSpeed; this.transform,Translate(Vector3.forward * vInput*Time.delatTime); this,transform.Rotate(Vector3.up * hInput * Time.deltaTime); } }
下面对上述代码进行解释。
添加一个私有的 Rigidbody 变量,用来存储胶囊的 Rigidbody 组件信息
Start 方法会在初始化脚本时触发,也就是单击 Play 按钮时。在初始化过程中设置变量时都应该使用Start 方法。
使用GetComponent方法检查脚本上附加的对象是否包含指定的组件类型,在本例中也就是 Rigidbody 组件。如果找到了,就返回。如果没有找到,那么返回 null.但在这里,我们已经知道Player对象上附有 Rigidbody 组件。
注释掉 Update 方法中对 Transform 和 Rotate 方法的调用,从而避免同时使用两种不同的控制方式。这里依然保留获取玩家输入的方式,以便后续继续使用。
实践:移动刚体
打开PiayerBehavior脚本,在Update 方法中添加如下代码并保存文件.
void FixedUpdate() { Vector3 rotation = Vector3.up * hInput; Quaternion angleRot = Quaternion.Euler(rotation *Time.fixedDeltaTime); _rb.MovePosition(this.transform.position +this.transform.forward * vInput * Time.fixedDeltaTime); _rb.MoveRotation( rb.rotation * angleRot); }
下面对上述代码进行解释
任何物理的或 Rigidbody 相关的代码都要放在 FixedUpdate 方法中,而不是放在Update 或其他 MonoBehaviour 方法中
创建一个新的 Vector3 变量以存储左右旋转值。Vector3.up* hInput 与我们之前在Rotate 方法中使用的旋转向量是相同的。
Outernion.Eulcr 接收一个 Vector3 变量作为参数并使用欧拉角的格式返回旋转值。
在MoveRotation 方法中,我们需要使用Qutemion 值而不是Vector3 变量,这是 Unity 首选的旋转类型的转换。
这里乘以Timc.fixcdDeltaTime 的原因与在 Update 方法中乘以Time.deltaTime相同。
调用b组件的MovePosition 方法,该方法将接收一个 Vector3 变量作为参数并施加相应的力。
使用的向量可以如下分解:胶囊的位置向量加上前向的方向向量与垂直输入和Time.fixedDeltaTime的乘积。
Rigidbody 组件负责调整施加的力以满足输入的向量参数
调用_b组件的MoveRotate 方法,该方法也将接收一个 Vector3 变量作为参数并施加相应的力。angleRot 已经包含来自键盘的水平输入,所以只需要将当前Rigidbody组件的旋转值乘以 angleRot 就能得到同样的左右旋转值。
刚刚发生了什么
如果现在运行游戏,就会发现玩家已经可以向着你看的方向前后移动了,同时还能沿着Y轴进行旋转。施加的力提供了相比直接平移/旋转更强大的效果,所以你需要调整好 Inspector 面板中的 moveSpeed 和 rotateSpeed 变量。至此,我们重建了之前已有的移动模式,并且拥有了更真实的物理效果。
碰撞体和碰撞
Collider 组件不仅仅使游戏对象能被 Unity 的物理系统认知到,也使交互和碰撞成为可能。可将碰撞体想象为围绕在游戏对象周围的不可见力场:取决于设置,它们可能被通过,也可能被撞上,并且有一系列方法会在发生不同的交互行为时触发。
Pickup_Prefab 对象层次中的Capsule 对象
Capsule 对象周围的绿色形状是 Capsule Collider,可以通过 Center、Radius、Heig等属性进行移动和缩放。当创建基础图元时,碰撞体默认与图元形状匹配;因为现在创建了Capsule 图元,因此也会同时创建 Capule Collider。
碰撞体还支持Box、Sphere 和 Mesh 形状,可以手动从Compoent| Physics 菜单或单击Inpector 面板中的Add Component 按钮进行添加。
举个例子,当两个带有碰撞体的游戏对象碰在一起时,它们都会发送OnCollisionEnter 消息,其中包含将要碰到的对象的引用。这种消息可用于各种交互式事件,比如拾取物品。
实践:拾取物品
为了给 Pickup Item 更新碰撞逻辑,需要执行以下步骤。
(1)在Scripts 文件夹中创建一个新的C#脚本,命名为ItemBehavior,然后拖放至场景中Pickup Item 预制体之下的 Capsule 对象上,如图7-9 所示。注意,任何使用了碰撞检测的脚本都必须被附加到包含了 Collider 组件的游戏对象上,即使是预制体的子对象。
(2)使用Pickup Item 更新根预制体
(3)在ItemBehavior脚本中添加如下代码并保存:
public class ItemBehavior : MonoBehaviour { void OnCollisionEnter(Collision collision) { if(collision.gameObject.name == “Player") { Destroy(this.transform.parent.gameObject); Debug.Log("Item collected!"); } } }
(4)单击 Play 按钮,移动玩家至胶囊处并捡起胶囊!
下面对步骤(3)中的代码进行解释。
1.当把另一个对象移至 Pikcup_Item 且 isTrigger 处于关态时,Umy 会自动调用OnCollisionEnter 方法。
- OnCollisionEnter 方法有一个参数用于存储 Collider 引用。
- 注意 collision 变量的类型是 Collision 而不是 Collider。
2.Collision 类的 gamebject 属性用于保存对 GameObject 碰撞体的引用。
以使用gameObject属性获取游戏对象的名称并使用if语检查碰撞体是否是Play对象。
3.如果碰撞体是 Player 对象,就调用 Destroy 方法,该方法接收一个游戏对象作为参数。
我们必须使整个Pickup Item对象被销毁,而不仅仅是销毁 Capsule 对象
因为 ItemBehaivor 脚本被附加到了 Capsule 对象上,而 Capsule 对象又是Pickup_Item 对象的子对象,所以可以使用 this.transform.parentgameObject将Pickup Item 对象销毁
向控制台打印一条日志,指明已经收集了道具。
刚刚发生了什么
我们在本质上相当于将 lemBehavior 脚本设置为监听与 Pikup_Item 预制体Capsule 子对象发生的任何碰撞。每当发生碰撞时,ItemBehavior 脚本就会使OnCollisionEnler 方法检查碰撞对象是否为 Player 对象。如果是,就销毁(或收集)。果感到困惑,请将编写的碰撞相关代码当作来自 Pickup Item 预制体的通知的接收者每当胶囊被碰撞时,就会触发这些代码。
提示:
也可以创建类似的包含OnCollisionEnter方法的脚本并附加到Player对象上然后检测是否与 Pickup Item 预制体发生了碰撞。碰撞逻辑取决于碰撞对象的角度
使用碰撞体触发器
默认情况下,碰撞体的 isTrigger 属性并未启用,物理系统会把这些碰撞体视为实体。然而,某些情况下我们需要使游戏对象可以穿过碰撞体,触发器就是为了处理这种情况而存在的。当isTrigger 属性被启用后,游戏对象就可以穿过碰撞体,但发送的通知会变为OnTriggerEnter、OnTriggerExit 和OnTriggerStay。
触发器多用于检测游戏对象是否进入某个特定区域或通过某个点。可使用触发器在敌人周围设置警戒区域,如果玩家进入触发区域,敌人就会受到惊扰,然后开始攻击玩家。
实践:创建敌人
为了创建敌人,需要执行以下步骤:
(1)在Hierarchy 面板中使用 Create 3D Object|Capsule 创建一个新的 Capsule图元,命名为 Enemy。
(2)在Materials 文件夹中使用Create|Material创建一个材质,命名为 Enemy_Mat,设置AIbedo属性为亮红色。然后拖动Enemy Mat 材质至 Enemy游戏对象上。
(3)选中Enemy游戏对象,单击Add Component按并搜索 Sphere Collider,然后按Enter键进行添加。选中is Trigger 复选框并将Radius设置为8.
刚刚发生了什么
新建的Enemy游戏对象现在围绕着一个半径为8的球形触发器。任何时候,当另一个对象进入、停留或离开时,Unity 都会发送能够被捕获到的通知,就像处理碰撞时一样。
实践:捕获触发器事件
为了捕获触发器事件,需要执行如下步骤
(1)在Scripts文件夹中创建一个新的C#脚本,命名为 EnemyBehavior,然后拖至Enemy游戏对象上。
(2)添加如下代码并保存文件。
public class EnemyBehavior : MonoBehaviour { void OnTriggerEnter(Collider other) { if(other.name=="Player") { Debug.Log('Player detected - attack!"); } } void OnTriggerExit(Collider other) { if(other.name=="Player") { Debug.Log("Player out of range,resume patrol"); } } }
(3)单击 Play 按钮,走向Enemy 游戏对象以触发第一个通知,然后远离 Enemy游戏对象以触发第二个通知。
下面对步骤(2)中的代码进行解释。
任何时候,当一个对象进入 Enemy 游戏对象的球形触发器时,OnTriggerEnter方法就会被触发。
类似于 OnCollisionEnter 方法,OnTriggerEnter 方法的参数用于存储对象的Collider组件的引用。
注意参数对象的类型是 Collider 而不是 Collision。
使用other 获取碰撞体对象的名称并使用if语句检查是不是 Player 对象。如果是,就输出 Player 对象位于危险区域的提示信息
当对象离开Enemy游戏对象的球形触发器时,触发OnTriggerExit 方法。
使用f 语句按名称检查离开球形触发器的对象。如果是 Player 对象,就将另一条信息打印到控制台,指示玩家现在是安全的
刚刚发生了什么
Enemy游戏对象的球形触发器会在自身被侵入时发送通知,EnemyBehavior脚本将捕获这些事件。任何时候,当玩家进入或离开球形触发器时,都会在控制台中打印调试日志以确保代码正常工作。
总结
- Rigidbody组件能为附加到的对象添加真实的物理模拟。
- Collider 组件之间可以相互交互,并且 Collider 组可以作为对象与 Rigidbo组件进行交互。
- 如果一个对象使用了 Rigidbody 组件但没有启用 isKinematic 属性,那么得的就是运动学效果,因为物理系统会忽略这个对象。
- 如果一个对象使用了 Rigidbody 组件并且施加了力和扭矩,那么得到的将是非运动学效果。
- 碰撞体基于交瓦行为发送通知。
在本章,你创建了自己的第一款游戏,并积累了一定的经验。现在,你已经能使用向量和基本的向量运算来确定 3D 空间中的位置和角度,并且熟悉了玩家输入及移动和旋转 GameObject 的两种主要方法。你甚至深入接触了 Unity 的物理系统,熟悉了刚体、碰撞、触发器以及事件通知等知识。总而言之,你为 Hero Bor 游戏开了个好头。
下面将开始处理更多的游戏机制,包括跳跃、冲刺、射击以及与环境中的各个部分进行交互。