Vector3.Dot()
在游戏中,我们可能会设置敌人的视野范围,这个时候我们就可以使用Unity自带的函数方法Vector3.Dot()来计算两个向量之间的夹角的余弦值,当然我们可以通过夹角的余弦值来判角的度数。下面给出Vector3.Dot()官方文档解释。
描述
public static float Dot (Vector3 lhs, Vector3 rhs);
两个向量的点积。
点积是一个浮点值,它等于 将两个向量的大小相乘,然后乘以向量之间角度的余弦值。对于 normalized 向量,如果它们指向完全相同的方向,Dot 返回 1; 如果它们指向完全相反的方向,返回 -1;如果向量彼此垂直,则 Dot 返回 0。
可以看出想要知道夹角的度数,我们就需要知道夹角边的向量Vector3,然后通过点积来实现计算两个夹角的余弦值。
官方文档例子(相当完美)
// detects if other transform is behind this object
using UnityEngine;
using System.Collections;
public class ExampleClass : MonoBehaviour
{
// 获取另一个物体Transform
public Transform other;
void Update()
{
if (other)
{
// 获取当前物体的面朝方向的Vector3.forward值,而Transform.TransformDirection(Vector3 direction)方法是将direction从本地空间变换到世界空间。该操作不受变换的缩放或位置的影响。 返回的矢量与 direction 的长度相同。
Vector3 forward = transform.TransformDirection(Vector3.forward);
Vector3 toOther = other.position - transform.position;
// 余弦值是负数说明就是夹角大于90度,在身后
if (Vector3.Dot(forward, toOther) < 0)
{
print("The other transform is behind me!");
}
}
}
}
MonoBehaviour.OnTriggerStay(Collider)
这个函数方法是在3D RPG项目中制作传送门的时候用到的
首先我们需要把物体的Collider设置为Is Trigger即true,碰撞器被物理引擎所忽略,没有碰撞效果,可以去调用OnTriggerEnter/Stay/Exit函数。(当IsTrigger = false时,碰撞器根据物理引擎引发碰撞,产生碰撞的效果,可以调用OnCollisionEnter/Stay/Exit函数)这个函数的作用就是对于接触触发器的每一个Collider的每一次物理更新调用一次OnTriggerStay。
但是在制作传送门的时候,我发现没有当两个碰撞体接触的时候没有去调用OnTriggerStay的函数方法,查阅官方的手册,里面有这么一段话
**注意:**如果其中一个碰撞体还附加了刚体,则仅发送触发器事件。触发器事件将发送到已禁用的 MonoBehaviour,以便允许启用 Behaviour,以响应碰撞。
此消息被发送到该触发器以及接触该触发器的碰撞体。
然后我为传送门添加了Rigidbody,然后成功实现了传送门的功能,下面是传送门的代码(用到OnTriggerStay)
TransitionPoint
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TransitionPoint : MonoBehaviour
{
// 标记是否是场景传送
public enum TransitionType {SameScene, DifferentScene};
[Header("Transition Info")]
public string sceneName;
public TransitionType transitionType;
public TransitionDestination.DestinationTag destinationTag;
private bool canTrans;
void Update()
{
if (Input.GetKeyDown(KeyCode.E) && canTrans)
{
// 传送
SceneController.Instance.TransitionToDestination(this);
}
}
void OnTriggerStay(Collider other)
{
if (other.CompareTag("Player"))
canTrans = true;
}
void OnTriggerExit(Collider other)
{
if (other.CompareTag("Player"))
canTrans = false;
}
}
TransitionDestination
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TransitionDestination : MonoBehaviour
{
public enum DestinationTag { ENTER, A, B, C }
public DestinationTag destinationTag;
}
SceneController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.AI;
public class SceneController : Singleton<SceneController>
{
GameObject player;
NavMeshAgent agent;
public void TransitionToDestination(TransitionPoint transitionPoint)
{
switch (transitionPoint.transitionType)
{
case TransitionPoint.TransitionType.SameScene:
StartCoroutine(Transition(SceneManager.GetActiveScene().name, transitionPoint.destinationTag));
break;
case TransitionPoint.TransitionType.DifferentScene:
break;
}
}
IEnumerator Transition(string sceneName, TransitionDestination.DestinationTag destinationTag)
{
player = GameManager.Instance.playerStates.gameObject;
agent = player.GetComponent<NavMeshAgent>();
// 停止启用agent
agent.enabled = false;
// 这里遇到了一个困难就是如何从这个进来的枚举变量来找到我想要的目的地,采用GetDestination()来解决
player.transform.SetPositionAndRotation(GetDestination(destinationTag).transform.position, GetDestination(destinationTag).transform.rotation);
agent.enabled = true;
yield return null;
}
private TransitionDestination GetDestination(TransitionDestination.DestinationTag destinationTag)
{
var entrances = FindObjectsOfType<TransitionDestination>();
for (int i = 0; i < entrances.Length; i++)
{
if (entrances[i].destinationTag == destinationTag)
return entrances[i];
}
return null;
}
}
在我寻找Collider中Is Trigger是什么意思的时候,我找到了一篇[博文](Collider(碰撞器)与IsTrigger(触碰器)详解 - 简书 (jianshu.com))里面比较详细地些了碰撞器和触发器的东西,最后的结论如下:
- 想要打印触发器方法,必须有一方是触发器,必须有一方带有刚体。二者缺一不可。
- 双方都是触发器,或者其中一方是触发器,另一方是碰撞器,都不会打印。
- 只要一方是触发器,并且有刚体组件,不管另一方是碰撞体还是触发器都会打印各自的触发器方法。
- 一般我们将触发器方法写在角色碰到的物体上,角色一般不参与触发方法。只写碰撞方法。
其实第一条和第二条我们都已经遇到过了,这就是为什么两者都有碰撞器的时候,但是传送门的效果没有实现。而第三条结论就是官方文档里的那句话此消息被发送到该触发器以及接触该触发器的碰撞体,而第四点是我们需要注意的!其实这样做也不无道理,本来某些物体的功能就不是归我们玩家管的,而是自己携带的某些特性,所以若某些触发方法写在我们的玩家上面就显得很奇怪,这还不利于代码的管理和维护,显得代码很乱没有逻辑可言(上述只是我自己想法)。
简单提一些其他的函数方法吧…
Transform.SetPositionAndRotation()
在传送的时候我们需要设置人物最后传送到的位置,这里就需要使用这个函数Transform.SetPositionAndRotation(),设置变换组件的世界空间位置和旋转。
Mathf.Clamp(float value, float min, float max)
在给定的最小浮点值和最大浮点值之间钳制给定值。如果在最小和最大范围内,则返回给定值。
Transform.GetChild()
按索引返回变换子项。如果该变换没有子项,或者 index 参数的值大于子项数,则会生成错误。我们假设一个GameObject有一个子物体ChildObject那么GameObject.transform.getChild(0)拿到的就是这个ChildObject.transform。
MonoBehaviour.OnEnable()
该函数在对象变为启用和激活状态时调用。
Transform.Translate()
根据 translation 的方向和距离移动变换。
如果 relativeTo 被省略或设置为 Space.Self,则会相对于变换的本地轴来应用该移动。(在场景视图中选择对象时显示的 X、Y 和 Z 轴。) 如果 relativeTo 为 Space.World,则相对于世界坐标系应用该移动。
MonoBehaviour.LateUpdate()
如果启用了 Behaviour,则每帧调用 LateUpdate。
LateUpdate 在调用所有 Update 函数后调用。 这对于安排脚本的执行顺序很有用。例如,跟随摄像机应始终在 LateUpdate 中实现, 因为它跟踪的对象可能已在 Update 中发生移动。
Object.FindObjectsOfType()
在3D RPG项目中,使用这个函数来寻找转送的目的地点
返回所有类型为 type 的已加载的激活对象的列表。
这不返回任何资源(如网格、纹理、预制件)或设置了 HideFlags.DontSave 的对象。 仅当 inactiveObjects 设置为 true 时,才会包括附加到非活动 GameObjects 的对象。 使用 Resources.FindObjectsOfTypeAll 可避免这些限制。
在编辑器中,这在默认情况下会搜索 Scene 视图。如果要在预制件阶段查找对象,请参阅 StageUtility API。
注意:此函数的运行速度非常缓慢。建议不要每帧都使用此函数。 在大多数情况下,可以改为使用单例模式。
Rigidbody.AddForce(Vector3 force, ForceMode mode= ForceMode.Force)
这里主要介绍一下ForceMode的几种变量
| 变量 | 描述 |
|---|---|
| Force | 向此刚体添加连续力,使用其质量。 |
| Acceleration | 向此刚体添加连续加速度,忽略其质量。 |
| Impulse | 向此刚体添加瞬时力冲击,考虑其质量。 |
| VelocityChange | 向此刚体添加瞬时速度变化,忽略其质量。 |
Vector3.Normalize()
使该向量的 magnitude 为 1。进行标准化时,向量方向保持不变,但其长度为 1.0。请注意,此函数将更改当前向量。如果 要保持当前向量不变,请使用 normalsized 变量。如果该向量太小而无法标准化,则将其设置为零。
Vector3.normalized
简单地来说这是Vector3的一个只读变量,而前者Vector3.Normalize()是一种方法,标准化一个Vector3的变量;后者是一个变量。(个人理解勿喷)
返回 magnitude 为 1 时的该向量。(只读)进行标准化时,向量方向保持不变,但其长度为 1.0。请注意,当前向量保持不变,返回一个新的归一化向量。如果要归一化当前向量,请使用 Normalize 函数。如果向量太小而无法标准化,则返回零向量。