Unity: Trigger, Collider, UI & NPC Interaction

Unity: Trigger, Collider, UI & NPC Interaction

触发器与碰撞器 Trigger & Collider:

Unity中的物体可以根据双方的触发器与碰撞器产生交互。
触发器与碰撞器有三种不同的状态,Enter,Stay与Exit。

1
2
3
4
5
6
7
8
//触发器事件, Tigger
private void OnTriggerEnter(Collider other)
private void OnTriggerStay(Collider other)
private void OnTriggerExit(Collider other)
//碰撞器事件, Collision
private void OnCollisionEnter(Collision collision)
private void OnCollisionStay(Collision collision)
private void OnCollisionExit(Collision collision)

不同的移动方法与触发器和碰撞器产生的交互也不相同:

Math Function:

  • 遇到目标即使是碰撞器,依然穿透
  • 不管自身是否是碰撞器或者触发器,当它接触到目标的时候(碰撞器/触发器),此时交互没有反应

Character Controller:

  • 碰撞器:有阻挡效果,没有办法接收碰撞器Collision事件
  • 触发器:可穿透,可以接收Tigger事件

Rigidbody:

  • 刚体自身的Collider如果是碰撞器,则只响应碰撞器事件。
  • 刚体自身的Collider如果是触发器,则同时响应碰撞器和触发器事件。

角色交互 NPC Interaction:

实现角色之间的交互,当玩家接近NPC时,NPC会面向玩家。当玩家远离NPC时,NPC会回归自己的朝向。

当NPC的触发器与玩家接触时(Enter),调用OnTriggerEnter(),修改枚举状态。
计算NPC到玩家的方向向量,将NPC旋转指向玩家。

当NPC的触发器与玩家分离时(Exit), 调用OnTriggerExit(),修改枚举状态。
将NPC的方向恢复到原有的方向向量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NPC : MonoBehaviour
{
public enum emTriggerType
{
None,
Enter,
Exit,
}
private Transform m_tfPlayer;
private emTriggerType m_emTriggerType;
private float m_fRotateSpeed;
private float m_fTimer;

private Quaternion m_srcRotation;

// Start is called before the first frame update
void Start()
{
m_tfPlayer = GameObject.FindObjectOfType<Player>().transform;
m_emTriggerType = emTriggerType.None;
m_fRotateSpeed = 3f;
m_fTimer = 0f;
m_srcRotation = transform.rotation;

SphereCollider trigger = transform.gameObject.AddComponent<SphereCollider>();
trigger.isTrigger = true;
trigger.radius = 10f;
}

// Update is called once per frame
void Update()
{
if(m_emTriggerType == emTriggerType.Enter)
{
//玩家位置减去NPC位置,得到非单位化的向量dir,几何意义是NPC指向玩家位置的方向向量
Vector3 dir = m_tfPlayer.transform.position - transform.position;
Quaternion targetQ = Quaternion.LookRotation(dir, Vector3.up);
transform.rotation = Quaternion.Lerp(transform.rotation, targetQ, Time.deltaTime * m_fRotateSpeed);

m_fTimer += Time.deltaTime; //计时器

if (m_fTimer >= 1f) {
m_emTriggerType = emTriggerType.None; //置空占位
m_fTimer = 0;
}
}
else if(m_emTriggerType == emTriggerType.Exit)
{
transform.rotation = Quaternion.Lerp(transform.rotation, m_srcRotation, Time.deltaTime * m_fRotateSpeed);
m_fTimer += Time.deltaTime;

if (m_fTimer >= 1f)
{
m_emTriggerType = emTriggerType.None;
m_fTimer = 0;
}
}
}

private void OnTriggerEnter(Collider other)
{
if (other.transform != m_tfPlayer) return;
m_emTriggerType = emTriggerType.Enter;
//Debug.Log("Trigger enter");

//弹出对话
}

private void OnTriggerExit(Collider other)
{
if (other.transform != m_tfPlayer) return;
m_emTriggerType = emTriggerType.Exit;
//Debug.Log("Trigger exit");

//关闭对话
}
}

UI元素添加 UI Component:

添加UI组件:

调用Resource下的相对路径文件。
导入UnityEngine.UI包。

将UI对象实例化,并挂载到附节点上。
从Resource中获得Text,Image等UI对象。

根据场景决定显示NPC名字还是HP血条。

在进行显示时,需要将NPC和玩家的世界坐标系位置转化到屏幕坐标系,将名字显示在屏幕。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; //UI的命名空间

public class Unity_Entity : MonoBehaviour
{
private GameObject m_objModel;

private Text m_txtCharacterName;
private GameObject m_objHPBar;
private Image m_imgHP;

public Vector3 mv3Offset;
// Start is called before the first frame update
void Start()
{
GameObject uiUnit = Resources.Load<GameObject>("Prefabs/UI/Unit_Entity"); //获取UI单元
uiUnit.SetActive(false);

mv3Offset = new Vector3(0f, 2f, 0f);

m_objModel = Instantiate<GameObject>(uiUnit); //实例化对象
m_objModel.transform.parent = GameObject.Find("UnitEntityRoot").transform; //将对象添加到附节点

m_txtCharacterName = m_objModel.transform.Find("Context/txtCharacterName").GetComponent<Text>(); //获取名称
m_objHPBar = m_objModel.transform.Find("Context/HPBar").gameObject;
m_imgHP = m_objModel.transform.Find("Context/HPBar/hp").GetComponent<Image>(); //获取血条图片资源

m_txtCharacterName.text = this.gameObject.name;
m_objHPBar.SetActive(!SceneManager.Instance.isMainScene); //不在主场景时才激活血条
}

// Update is called once per frame
void Update()
{
m_objModel.transform.position = Camera.main.WorldToScreenPoint(this.transform.position + mv3Offset);
//从世界坐标系转换为屏幕坐标系,将血条显示在屏幕上
}
}

为了判断我们的场景,我们需要创建一个SceneManager并挂载到Manager上。
当当前场景是主场景时返回true。

场景判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SceneManager : MonoBehaviour
{
public static SceneManager Instance;
public bool isMainScene
{
get //get和set可以封装public的field(Member Variable)
{
UnityEngine.SceneManagement.Scene curScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
//由于Unity已经有ScenceManager类,因此需要额外注明命名空间
return curScene.name == "MainScene";
}
}
// Start is called before the first frame update
void Awake() //Awake的执行顺序优先于Start()
{
Instance = this;
}

// Update is called once per frame
void Update()
{

}
}
Unity: Move, Camera Tracking, Animator

Unity: Move, Camera Tracking, Animator

最近跟着网络上的课程班学习了一些Unity的使用,记录一下自己的学习心得。

移动:Move

实现角色的移动需要在Update方法里对物体的位置进行更新。

Unity支持数学公式,角色控制器以及刚体等移动方式。
而每种方式又有不同的子方法。

旋转:Rotation

我们先要根据input获取键盘上对应的操作命令。
如果水平和垂直数值均为0,则直接返回,不进行下面的更新命令。

将输入的方向新建为一个向量。
然后将其转换为一个四元数,通过rotation方法对角色进行旋转。

1
2
3
4
5
6
7
8
9
10
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
m_anim.SetBool("bMove", h != 0 || v != 0);
if (h == 0 && v == 0) return;

Vector3 dir = new Vector3(-h, 0, -v);
Quaternion targetQ = Quaternion.LookRotation(dir, Vector3.up);
//transform.rotation = targetQ;
transform.rotation = Quaternion.Lerp(transform.rotation, targetQ, Time.deltaTime * mfRoateSpeed);
//对四元数进行插值,控制旋转速度

数学公式:Math Function

我们可以直接以数学公式的方法更新当前位置,将其加入向量乘以帧时间乘以速度的方式更新当前位置:
transform.position += transform.forward * Time.deltaTime * mfMoveSpeed;

也可以使用Translate()方法,注意这里需要传入world空间模式
transform.Translate(transform.forward * Time.deltaTime * mfMoveSpeed, Space.World);

MoveTowards方法,不太常用。
transform.position = Vector3.MoveTowards(transform.position, transform.position + transform.forward * Time.deltaTime * mfMoveSpeed, Time.deltaTime * mfMoveSpeed);

角色控制器:Character Controller

这种模式需要在全局变量中创建并获取角色控制器对象。
使用角色控制器需要设定其盒子范围。

1
2
3
4
5
6
7
m_characterController = transform.gameObject.GetComponent<CharacterController>();
if(m_characterController == null)
{
m_characterController = transform.gameObject.AddComponent<CharacterController>();
}
m_characterController.center = new Vector3(0f, 0.6f, 0f);
m_characterController.height = 1.3f;

我们也可以使用角色控制器移动角色。

Simple Move模式可以直接模拟重力:
m_characterController.SimpleMove(transform.forward * mfMoveSpeed);

注意Simple Move模式下按秒进行移动,不需要乘以帧时间。

Move模式则需要手动添加向下的重力,否则物体Y轴移动后不会下降。
m_characterController.Move(-transform.up * Time.deltaTime * mfMoveSpeed);m_characterController.Move(transform.forward * Time.deltaTime * mfMoveSpeed);

刚体:Rigidbody

首先需要在全局变量中创建并获取刚体对象。

刚体模式需要创建碰撞盒,并且将Y轴freeze,否则物体会直接向下落下。

1
2
3
4
5
6
7
8
9
10
11
m_rigidbody = transform.gameObject.GetComponent<Rigidbody>();
if(m_rigidbody == null)
{
m_rigidbody = transform.gameObject.AddComponent<Rigidbody>();
}
m_rigidbody.constraints = RigidbodyConstraints.FreezePositionY | RigidbodyConstraints.FreezeRotationX |
RigidbodyConstraints.FreezeRotationY | RigidbodyConstraints.FreezeRotationZ;

CapsuleCollider collider = transform.gameObject.AddComponent<CapsuleCollider>();
collider.center = new Vector3(0f, 0.6f, 0f);
collider.height = 1.3f;

可以采用恒速的velocity方法
m_rigidbody.velocity = transform.forward * mfMoveSpeed;

或者采用AddForce方法,这样会不断加速。
m_rigidbody.AddForce(transform.forward * mfMoveSpeed * mAddForceFactor);

MovePosition方法,同样是直接计算移动后的位置。
m_rigidbody.MovePosition(transform.position + transform.forward * Time.deltaTime * mfMoveSpeed);

镜头跟随:Camera Tracking

脚本需要挂在到相机下。

两个向量相减的几何意义是减数方向指向被减数方向

用相机位置减去玩家的位置,则可以得到玩家指向相机的向量。
然后在Update中实时更新相机的位置,就可以实现镜头跟随的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MainCamera : MonoBehaviour
{
private Transform m_player; //需要聚焦的角色的位置
private Vector3 m_v3RelativePos; //相机和角色的相对向量

// Start is called before the first frame update
void Start()
{
m_player = GameObject.FindGameObjectWithTag("Player").transform; //获取玩家角色
m_v3RelativePos = transform.position - m_player.position; //计算相对位置
}

// Update is called once per frame
void Update()
{
transform.position = m_player.position + m_v3RelativePos; //实时更新相机的位置
}
}

动画状态机:Animator

模型下需要有Anim模块才能实现动画。
原有的资源里已经制作了动画,在这里额外实现的是根据动作来切换动画。

在Start()里初始化成员变量Animator m_anim。
m_anim = transform.Find("Anim").GetComponent<Animator>();
然后在Update()里使用SetBool方法对Animator的开关真值进行更新。
m_anim.SetBool("bMove", h != 0 || v != 0);

Animator支持几种类型的trigger如Bool,Int,