文章目录
简介:我是一名Unity
游戏开发工程师,皮皮是我养的猫,会讲人话,它接到了喵星的特殊任务:学习编程,学习Unity
游戏开发。
于是,发生了一系列有趣的故事。
10.1 流浪喵星
2020年,喵星所在的拉姆达星系中,巨大的鲁特恒星即将毁灭,已经不适合喵星人生存,面对绝境,喵星人开启了 “流浪喵星” 计划,试图带着喵星一起逃离拉姆达星系,寻找喵星人新的家园。
然而,喵星在靠近纳美星的时候被强大的引力吸住,马上就要撞上去了,情况十分危急。
皮皮:“呼叫铲屎官,呼叫铲屎官,只剩最后1分钟了。”
我:“请点燃纳美星!”
皮皮:“来不及了。”
我:“以下是命令 :活下去!”
皮皮:“奇迹出现了,喵星直接穿透了纳美星。”
10.2 碰撞的必要条件
皮皮:“铲屎官,你刚刚是怎么做到的?”
我:“最后关头,我将纳美星的碰撞器禁用了,避免了这次物理碰撞。”
Unity3D
内置物理引擎,可以模拟物理效果。典型的一个物理效果就是碰撞。
两个物体发生物理碰撞的必要条件是:两个物体都带有Collider
(碰撞器)组件,其中一个物体带有Rigidbody
(刚体)组件,并且是运动的物体带有Rigidbody
组件。
10.2.1 Collider,碰撞器组件
Collider
,即碰撞器。Unity
中提供了各种形状的碰撞器组件。点击AddComponent
,可以看到如下的各种碰撞器组件,这些碰撞器组件名字带2D
的都是继承自Collider2D
类,名字没带2D
的都继承自Collider
类,一般3D
物体的使用Collider
碰撞器,2D
物体使用Collider2D
碰撞器。
我们通过3D Object
菜单创建的几何体都默认带了相应的碰撞器组件。
比如创建球体Sphere
,它会自带球形碰撞器组件Sphere Collider
。
10.2.2 Rigidbody,刚体组件
Rigidbody
,刚体组件,刚体能让你的游戏对象被物理引擎所控制,它能通过受到推力和扭力来实现真实的物理表现效果。
我们可以通过给刚体施加一个力来改变一个它的运动状态:
// 获取刚体组件
Rigidbody rigidBody = gameObject.GetComponent<Rigidbody>();
// 给刚体一个向量为(1, 0, 0)的力
rigidBody.AddForce(new Vector3(1, 0, 0));
刚体受到力的作用,会改变运动状态。假设初始状态是禁止的,那么施加一个力之后,刚体会动起来。动起来的速度与刚体受到的力成正比,与刚体的质量成反比。
Rigidbody
的Mass
属性就是刚体的质量。
使用代码设置刚体的质量:
// 设置刚体的质量为1
rigidBody.mass = 1;
我们要可以直接给刚体设置速度:
// 给刚体设置速度
rigidBody.velocity = new Vector3(1, 0, 0);
另外,还可以设置角速度:
// 给刚体设置角速度
rigidBody.angularVelocity = new Vector3(0, 1, 0);
有了角速度,物体就会开始旋转。
刚体的运动状态还会受到空气阻力的影响,Rigidbody
的Drag
属性就是刚体的空气阻力,默认为0,即空气阻力为0。
通过代码设置刚体的空气阻力:
// 设置刚体的空气阻力为0
rigidBody.drag = 0;
物体的旋转受到角阻力Angular Drag
的影响。
通过代码设置刚体的角阻力:
// 设置刚体的角阻力为0.05f
rigidBody.angularDrag = 0.05f;
皮皮拿过鼠标,点击AddComponent
,添加了刚体组件。
刚体的属性如下:
点击运行Unity
。
皮皮:“怎么回事呀?球体直接垂直往下掉了。”
我:“因为Rigidbody
有个Use Gravity
属性,意思就是是否使用重力,默认是勾选着的,所以球体就受到重力作用直接垂直往下掉了。”
去掉Use Gravity
的勾选,移动球体,可以产生碰撞了,不过如果移动速度快一点,还是可以穿过去。
皮皮:“这又是怎么回事呀?”
我:“因为我们是强行拖拉来移动刚体物体的,并不是由物理引擎来驱动移动,刚体的碰撞检测不是连续的。另外,被撞的物体是一个静态碰撞器,我们这样强行拉着刚体移动,只要速度稍快一些就会出现穿透现象。”
皮皮:“你解释一个问题,中间又引入了新的概念,什么是静态碰撞器?”
10.3 碰撞器类型
10.3.1 Static Collider,静态碰撞器
静态碰撞器是指只挂了Collider
而没挂Rigidbody
的游戏对象。这类对象会保持静止或者很轻微的移动。这类碰撞器被刚体碰撞后不会移动。
皮皮:“难怪刚刚被撞的另一个球不会动,因为它是一个静态碰撞器。”
10.3.2 Rigidbody Collider,刚体碰撞器
刚体碰撞器是指既挂了Collider
又挂了Rigidbody
的游戏对象。运动的刚体碰撞器可以与静态碰撞器发生碰撞,两个刚体碰撞器之间也可以发生碰撞。如果刚体碰撞器禁止不动,强行移动静态碰撞器去碰刚体碰撞器,此时并不会发生碰撞,因为禁止的刚体碰撞器会进入休眠状态。
10.3.3 Kinematic Rigidbody Collider,运动学刚体碰撞器
运动学刚体碰撞器是指在刚体碰撞器的基础上,激活了Kinematic
的游戏对象。
要移动这类游戏对象,要修改它的Transform
组件(坐标或角度),而不能通过力。
我:“运动学刚体碰撞器是个非常懒惰和霸道的家伙,它不受力、重力或扭矩的影响,它去碰别人,它自己不会受到反作用力。所以你可以把运动学刚体碰撞器看成一个所向披靡的碰撞器,谁遇到它都不是对手。”
皮皮:“那如果两个运动学刚体碰撞器相互碰撞会怎样?”
我:“谁也不让谁,直接穿透过去。”
10.4 碰撞事件
在MonoBehavior
脚本中,我们可以通过OnCollisionEnter
、OnCollisionStay
、OnCollisionExit
来处理收碰撞事件。其中collision
参数是对方的碰撞器对象,脚本挂在两个碰撞体上都可以触发OnCollisionXXX
。
private void OnCollisionEnter(Collision collision)
{
Debug.Log("碰撞进入");
}
private void OnCollisionStay(Collision collision)
{
Debug.Log("碰撞中");
}
private void OnCollisionExit(Collision collision)
{
Debug.Log("碰撞结束");
}
10.5 触发器事件
有时候我们并不想产生物理碰撞的效果,但是又想检测碰撞器之间的碰撞事件,这个时候就可以使用触发器。
触发器其实是碰撞器,只是勾选了Is Trigger
。
在MonoBehavior
脚本中,我们可以通过OnTriggerEnter
、OnTriggerStay
、OnTriggerExit
来处理收碰撞事件。
private void OnTriggerEnter(Collider other)
{
Debug.Log("触发进入");
}
private void OnTriggerStay(Collider other)
{
Debug.Log("触发中");
}
private void OnTriggerExit(Collider other)
{
Debug.Log("触发结束");
}
想要产生触发器事件,两个碰撞器中至少要有一个勾选了Is Trigger
,并且其中一个要带Rigidbody
组件。
皮皮:“触发器有什么应用吗?看起来不知道用在哪里。”
我:“举个简单的例子,你走近一个门的时候,要触发播放开门的动作,那么门就可以弄成触发器。门的碰撞体可以稍微调大一点,这样人就可以提前碰到触发器,触发开门动作。”
10.6 射线碰撞检测
皮皮:“可不可以实现一个鼠标拖动物体的功能,这样就可以直接用鼠标拖动喵星逃离兰姆达星系啦。”
我:“可以呀,原理很简单,点击鼠标左键,从摄像机的位置往里发射射线,检测到碰撞,则把碰撞体缓存到一个对象中,移动鼠标的时候,把鼠标坐标转成世界坐标赋值给刚刚的物体,实现鼠标抓取物体移动的效果,鼠标松开时,释放缓存对象。”
Demo
工程如下,把下文的TouchAndMoveObj .cs
脚本挂在某个GameObject
即可,比如挂在Main Camera
上。
运行效果:
TouchAndMoveObj .cs
脚本代码如下:
using UnityEngine;
public class TouchAndMoveObj : MonoBehaviour
{
private Transform m_targetTrans;
private Camera cam;
private float m_posZ;
private void Start()
{
cam = Camera.main;
}
private void Update()
{
// 鼠标左键按下
if (Input.GetMouseButtonDown(0))
{
Ray ray = cam.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 100))
{
// 缓存射线碰撞到的物体
m_targetTrans = hit.transform;
// 缓存物体与摄像机的距离
m_posZ = m_targetTrans.position.z - cam.transform.position.z;
}
}
// 鼠标左键抬起
if (Input.GetMouseButtonUp(0))
{
// 释放碰撞体缓存
m_targetTrans = null;
}
// 鼠标按住中
if (null != m_targetTrans && Input.GetMouseButton(0))
{
// 鼠标的屏幕坐标转成世界坐标
// 由于鼠标的屏幕坐标的z轴是0,所以需要使用物体距离摄像机的距离为z周的值
var targetPos = cam.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, m_posZ)); ;
// 让物体的坐标跟着鼠标走
m_targetTrans.position = targetPos;
}
}
}