文章目录内容摘取总结自《Unity3D脚本编程与游戏开发》(马遥,沈琰)第三章——物理系统脚本编程
搬运自我的博客:浮生如梦 岁月如歌
- 基础
- 获取刚体组件
- 施加作用力
- 修改速度
- 二段跳
- 射线
- 层和层遮罩
- 射线编程详解
- 修改物理材质
- FixedUpdate
- 修改角速度
- 质心
- 更多施加力的方式
- 刚体约束
using UnityEngine;
public class Test : MonoBehaviour
{
Rigidbody rigid;
void Start()
{
rigid = GetComponent<Rigidbody>();
}
}
施加作用力
private void Update()
{
if (Input.GetButtonDown("Jump"))
{
rigid.AddForce(new Vector3(0, 100, 0));
}
}
以上代码的作用是在玩家按下空格键时,对刚体施加一个向上的力,大小为100牛,持续时间是一个物理帧间隔(默认0.02秒)。如果物体只有1千克,重力不到10牛,那么这个力会让它跳起一定高度。
修改速度// 获取当前物体速度
Vector3 vel = rigid.velocity;
// 将当前速度沿z轴增加1m/s
rigid.velocity = vel + new Vector3(0, 0, 1);
二段跳
using UnityEngine;
public class SimpleJump : MonoBehaviour
{
Rigidbody rigid;
void Start()
{
rigid = GetComponent<Rigidbody>();
}
private void Update()
{
if (Input.GetButtonDown("Jump"))
{
rigid.velocity = new Vector3(rigid.velocity.x, 0, rigid.velocity.z);
rigid.AddForce(new Vector3(0, 100, 0));
}
}
}
射线
bool Raycast(Vector3 origin, Vector3 direction);
bool Raycast(Vector3 origin, Vector3 direction, float maxDistance);
bool Raycast(Vector3 origin, Vector3 direction, float maxDistance, int layerMask);
bool Raycast(Ray ray, out RaycastHit hitInfo);
bool Raycast(Ray ray, out RaycastHit hitInfo, float maxDistance);
bool Raycast(Ray ray, out RaycastHit hitInfo, float maxDistance, int layerMask);
// 创建从原点向上的射线
Ray ray = new Ray(Vector3.zero, Vector3.up);
// 获得当前鼠标指针在屏幕上的位置(单位是像素)
Vector2 mousePos = Input.mousePosition;
// 创建一条射线,起点是摄像机位置,方向指向鼠标指针所在的点(隐含了从屏幕到世界的坐标转换)
Ray ray2 = Camera.main.ScreenPointToRay(mousePos);
// 之后可以将ray或ray2发射出去,例如:
Physics.Raycast(ray, 10000, LayerMask.GetMask("Default"));
层和层遮罩
int mask = LayerMask.GetMask("Ground", "Player", "Obstacle");
if (Physics.Raycast(transform.position, Vector3.forward, mask))
{
// 碰到了物体
}
mask = ~mask; // 英文波浪线,代表二进制取反
gameObject.layer = LayerMask.NameToLayer("Default");
射线编程详解
- 射线碰撞信息
以下几个Raycast()函数的重载可以获取到碰撞信息
bool Raycast(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, float
maxDistance);
bool Raycast(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, float
maxDistance, int layerMask);
bool Raycast(Ray ray, out RaycastHit hitInfo, float maxDistance, int layerMask);
//演示
private void TestRay()
{
// 声明变量,用于保存碰撞信息
RaycastHit hitInfo;
// 发射射线,起点是当前物体的位置,方向是世界前方
if (Physics.Raycast(transform.position, Vector3.forward, out hitInfo))
{
// 如果确实碰到物体,会运行到这里。没碰到物体就不会
// 获取碰撞点的坐标(世界坐标)
Vector3 point = hitInfo.point;
// 获取对方的碰撞体组件
Collider coll = hitInfo.collider;
// 获取对方的Transform组件
Transform trans = hitInfo.transform;
// 获取对方的物体名称
string name = coll.gameObject.name;
// 获取碰撞点的法线向量
Vector3 normal = hitInfo.normal;
}
- 其他形状的射线
// 球形射线:
bool SphereCast(Ray ray, float radius);
bool SphereCast(Ray ray, float radius, out RaycastHit hitInfo);
// 盒子射线:
bool BoxCast(Vector3 center, Vector3 halfExtents, Vector3 direction);
bool BoxCast(Vector3 center, Vector3 halfExtents, Vector3 direction, out
RaycastHit hitInfo, Quaternion orientation);
// 胶囊体射线:
bool CapsuleCast(Vector3 point1, Vector3 point2, float radius, Vector3 direction);
bool CapsuleCast(Vector3 point1, Vector3 point2, float radius, Vector3 direction,
out RaycastHit hitInfo, float maxDistance);
- 穿过多个物体的射线
RaycastHit[] RaycastAll(Ray ray, float maxDistance);
RaycastHit[] RaycastAll(Vector3 origin, Vector3 direction, float maxDistance);
RaycastHit[] RaycastAll(Ray ray, float maxDistance, int layerMask);
RaycastHit[] RaycastAll(Ray ray);
- 区域覆盖型射线(Overlap)
Collider[] OverlapBox(Vector3 center, Vector3 halfExtents, Quaternion
orientation, int layerMask);
Collider[] OverlapCapsule(Vector3 point0, Vector3 point1, float radius, int
layerMask);
Collider[] OverlapSphere(Vector3 position, float radius, int layerMask);
- 射线调试技巧
void DrawLine(Vector3 start, Vector3 end, Color color);
void DrawLine(Vector3 start, Vector3 end, Color color, float duration);
void DrawRay(Vector3 start, Vector3 dir, Color color);
void DrawRay(Vector3 start, Vector3 dir, Color color, float duration);
// 以一个简单的射线为例
Raycast(起点, 方向向量, 长度);
// 对应的可视化线条
DrawLine(起点, 起点+方向向量.normalized * 长度, Color.red);
// 其中nomalized是将向量标准化,即方向不变长度变为1
修改物理材质
在Project窗口中单击鼠标右键,选择Create→Physics Material,就可以创建一个物理材质。物理材质的参数被简单定义为Dynamic Friction(动态摩擦系数)、Static Friction(静态摩擦系数)、Bounciness(d性系数)、与其他物体接触时的Friction Combine(摩擦力系数算法)和Bounce Combine(d性系数算法),如图所示。
动态摩擦系数就是物体之间正在相对滑动时的摩擦系数。例如0.1代表很光滑的表面,0.9代表很粗糙的表面。
静态摩擦系数就是物体之间没有相对滑动时的摩擦系数。现实生活中,物体的静态摩擦力一般略大于动态摩擦力,当然在游戏世界中可以随意调节它们的大小。
d性系数可以调节物体反d力的大小。例如0.8可以代表充气很足的篮球,0则代表没有任何反d力。d性系数一般不能高于0.9,否则会导致物体反d的速度比撞击前的速度还快,这样它会变得越来越快,没有止境。
最后两个参数决定了两个物体表面都具有摩擦系数和d性系数时,如何计算综合的摩擦系数和d性系数。可选择取平均值、取最大值、取最小值或相乘4种方式。
最后,有两点值得说明。
一是物理材质是配合碰撞体使用的。碰撞体有一个“材质”(Material)的属性,这里自然不是指渲染材质,而是指物理材质。将创建好的物理材质拖曳到该属性上即可指定该属性。
二是不指定任何物理材质时,碰撞体具有默认的物理材质。
FixedUpdate当设备运行不流畅、帧率下降时,会发现Time.deltaTime变大了(即帧与帧之间的时间间隔变长),但是Time.fixedDeltaTime却不会。一般Time.fixedDeltaTime会是一个固定的值(默认为0.02秒,可以通过选择主菜单的Edit→Project Settings→Time来修改)。
物理更新不仅要保证频率高,还要保证频率稳。不稳定的频率一样会带来糟糕的效果,因此所有的物理系统处理都在引擎循环中的一个专门环节上完成。
游戏世界的时间是一个虚拟的概念,一定程度上可以人为控制。如果在某个时刻T,硬件卡顿了0.06秒,正好错过了3次FixedUpdate()的调用时机,那么在下一次有机会运行的时候,FixedUpdate()函数会补上之前错过的3次,连续执行4次,而且还会“假装”这4次的调用时间点分别是T+0.02s、T+0.04s、T+0.06s、T+0.08s。通过这样的机制,就能确保无论硬件运行是否稳定,游戏都能保证“稳定”的物理更新,避免出现奇怪的结果。作为对比,Update()函数则没有这个特性。
对于摄像机抖动问题:
由于刚体因速度或受力而产生的运动,属于物理更新。而Update()函数和LateUpdate()函数不属于物理更新,这其中有着微妙的时间差。要解决这个问题并不难,针对物理移动的刚体,只要将跟随摄像机的移动也编写到FixedUpdate()里,抖动的问题就会消失了。
修改角速度void Update()
{
if (Input.GetKeyDown(KeyCode.R))
{
rigid.angularVelocity = new Vector3(0, 60, 0);
}
}
质心
public class Tumbler : MonoBehaviour
{
Rigidbody rigid;
void Start () {
rigid = GetComponent<Rigidbody>();
// 设置centerOfMass就可以指定重心了(本地坐标系)
rigid.centerOfMass = new Vector3(0, -1, 0);
}
}
更多施加力的方式
void AddForceAtPosition(Vector3 force, Vector3 position);
void AddForceAtPosition(Vector3 force, Vector3 position, ForceMode mode);
//之前讲解函数AddForce()时,忽略了它的最后一个参数——ForceMode(力的模式),AddForceAtPostion()函数同样也有该参数。
//“力的模式”参数是一个枚举类型,定义如下
public enum ForceMode
{
// 默认方式为持续施加力,符合牛顿力学
Force = 0,
// 设置为瞬间爆发力,适合表现快速猛烈的力,例如爆炸
// 力的持续时间有区别,但仍然符合牛顿力学
Impulse = 1,
// 瞬时改变刚体速度,不考虑物体质量
VelocityChange = 2,
// 直接改变加速度,不考虑物体质量
Acceleration = 5
}
刚体约束
- 编辑器界面修改
- 脚本修改
// 冻结所有的缩放和旋转
rigid.constraints = RigidbodyConstraints.FreezeAll;
// 仅冻结沿x轴的位移,取消所有其他约束
rigid.constraints = RigidbodyConstraints.FreezePositionX;
// 仅冻结所有旋转,取消位移约束
rigid.constraints = RigidbodyConstraints.FreezeRotation;
// 冻结沿x轴和z轴的旋转,冻结沿y轴的位移
rigid.constraints = RigidbodyConstraints.FreezeRotationX
| RigidbodyConstraints.FreezeRotationZ
| RigidbodyConstraints.FreezePositionY;
Rayhirox于2022-04-08使用Notion整理完毕
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)