【Unity3D】物理系统脚本编程

内容摘取总结自《Unity3D脚本编程与游戏开发》(马遥,沈琰)第三章——物理系统脚本编程
搬运自我的博客:浮生如梦 岁月如歌

基础

获取刚体组件

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(弹性系数)、与其他物体接触时的Friction Combine(摩擦力系数算法)和Bounce Combine(弹性系数算法),如图所示。

简单图床 - EasyImage

动态摩擦系数就是物体之间正在相对滑动时的摩擦系数。例如0.1代表很光滑的表面,0.9代表很粗糙的表面。

静态摩擦系数就是物体之间没有相对滑动时的摩擦系数。现实生活中,物体的静态摩擦力一般略大于动态摩擦力,当然在游戏世界中可以随意调节它们的大小。

弹性系数可以调节物体反弹力的大小。例如0.8可以代表充气很足的篮球,0则代表没有任何反弹力。弹性系数一般不能高于0.9,否则会导致物体反弹的速度比撞击前的速度还快,这样它会变得越来越快,没有止境。

最后两个参数决定了两个物体表面都具有摩擦系数和弹性系数时,如何计算综合的摩擦系数和弹性系数。可选择取平均值、取最大值、取最小值或相乘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
}

刚体约束

  • 编辑器界面修改

简单图床 - EasyImage

  • 脚本修改
//  冻结所有的缩放和旋转
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整理完毕

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值