[Unity 3D] 物理引擎学习笔记(一)

刚体:

  同是物理引擎提供的功能,碰撞检测只需要有 Collider 便可以运作,但所有与作用力相关的属性和函数却都依赖 Rigidbody。

重力:

  一旦使用了 Rigidbody 组件,这个 GameObject 就会受全局重力影响,同时在发生物理碰撞时会自动调用物理引擎进行碰撞处理。

  全局重力可以在 Physics Manager 中进行调整,具体位置为:“Edit -- Project Settings -- Physics”。

  其中,

  Gravity 项就是全局重力,是一个 Vector3 型变量,包含 X,Y,Z 三个分量,默认情况下其大小为 (0, -9.81, 0) 也就是指向全局坐标 -Y 方向大小为 9.81 的作用力。

  Default Material 是所有 Collider 在默认情况下使用的物理材质,物理材质定义了跟表面摩擦系数和弹性系数相关的参数,默认情况下这里使用的是 None(Physic Material) ,在实际游戏中静摩擦为 0.4,动态摩擦为 0.4。

  Sleep Velocity 定义了 GameObject 退出物理运算的速度阈值,当 GameObject 的运动速度低于这个阈值时将进入休眠状态,物理引擎不再对其进行运算,一旦休眠的 GameObject 受到其他 GameObject 碰撞,或者被施加了作用力,则会立刻解除休眠。

  Sleep Angular Velocity 定义了 GameObject 退出物理运算的角速度阈值,当 GameObject 的旋转速度低于这个阈值时将进入休眠状态。

  Max Angular Velocity 是最大旋转速度,任何 GameObject 在默认状态下都不会超过这个旋转速度。这里有意思的一点是,当一个球在粗糙面(有摩擦)上受到外力滚动时,在旋转速度没有超过最大旋转速度阈值前,其位移速度和旋转速度是匹配的(也就是可以通过滚动一周位移距离为2πr,转过角度为2π,从一个速度换算出另一个);一旦超过阈值,球的旋转速度不再增加,而位移速度不受限制,此时两种速度不再匹配;外力撤除后,球在摩擦作用下减速,这时可以发现旋转速度不变而位移速度飞快下降,直到位移速度恢复到能够与阈值范围内的旋转速度相匹配时,两种速度才开始共同下降,并且位移速度的下降幅度也瞬间回到正常摩擦作用下的递减水平(大大减缓)。

  重力其实可以看作是一个施加在所有 rigidbody 上的 constant force,所以即便是将全局重力设为 0,也可以通过给一个 rigidbody 添加 (0, -9.81, 0) 的 constantForce 来达到模拟重力的目的。

阻力:

  阻力有 Friction 和 Drag 两种,前者是表面摩擦,后者是风阻。Friction 在 Collider 的 Physic Material 中定义,Drag 则是在 Rigidbody 里定义。

  Friction 又有 staticFriction 和 dynamicFriction 之分,staticFriction 是当 GameObject 静止时的静摩擦,它决定要用多大的作用力才能让 GameObject 开始运动,dynamicFriction 是 GameObject 开始运动时会受到来自接触面的摩擦,这是只要 GameObject 保持接触并存在相对运动就会一直存在的作用力。

  Drag 区分为 Drag 和 Angular Drag,前者影响位移,后者影响旋转,而且 Drag 与 Friction 不同的地方在于,Drag 不依赖碰撞或者接触,只要物体在运动就会一直受到的与运动反向的作用力。

  此外,在 Physics Material 里还有另外一种各向异性摩擦的定义,包括三个变量:frictionDirection2,staticFriction2,dynamicFriction2。

  其中,frictionDirection2 决定各向异性摩擦的方向,如果方向向量为 0,那么就没有各向异性摩擦。

  staticFriction2 定义静各向异性摩擦大小,其方向为 frictionDirection2 定义的方向。

  dynamicFriction2 定义动态各向异性摩擦大小,其方向为 frictionDirection2 定义的方向。

  各向异性摩擦与普通表面摩擦的区别就在于它的方向是固定的,而且跟相对运动方向无关。

  此外,在 Physic Material 中有一个变量 frictionCombine 用来决定相互接触的两个 Rigidbody 它们之间的摩擦要如何计算,因为两个 Rigidbody 都分别有各自的摩擦定义。frictionCombine 有四种模式:Average,Multiply,Minimum,Maximum。

  其中,Average 就是取两个 Rigidbody 的摩擦的平均值作为两者间的摩擦。

  Multiply 是取两个 Rigidbody 摩擦的乘积作为两者间的摩擦。

  Minimum 是取两个 Rigidbody 摩擦中最小的那个作为两者间的摩擦。

  Maximum 是去两个 Rigidbody 摩擦中最大的那个作为两者间的摩擦。

让物体动起来:

  要让附加了 Rigidbody 的物体运动起来有两种方式,利用重力和施加额外作用力。

  利用重力又可以有两种方式,一种是让 GameObject 落在倾斜的平面上,它会自然向平面倾斜方向运动,另一种是改变重力方向,通过 Physics.gravity 来重新指定全局重力的大小和方向。

  施加外力是物理引擎下让 Rigidbody 运动的常规方法,利用 Physics.AddForce 或者 Physics.AddRelativeForce 来给 GameObject 施加作用力。这两个函数的区别在于,前者参数中定义作用里的 Vector3 变量使用的是全局坐标系(世界坐标系),后者则是用的是 GameObject 的本地坐标系。这两个函数还有第二个参数 ForceMode,用来区分不同的作用力施加方式,ForceMode 有四种:Force,Acceleration,Impulse,VelocityChange。

  Force 模拟在一个 FixedUpdate 的时间片段内持续作用在 Rigidbody 上的力(一个 FixedUpdate 的时间片段长度可以在 Project Settings 中设置,默认为 0.02 秒),假设使用 AddForce 函数对静止的 Rigidbody 施加一个大小为 100 单位的作用力,模式选用 Force,且 Rigidbody 的质量为 1 单位,那么在这个作用力作用结束后,Rigidbody 的位移速度将从 0 变为大小 2 单位,方向为作用里方向(v=f*t/m)。由于模拟里作用的结果就是 Rigidbody 的速度发生变化,所以虽然这个模式是模拟一个持续作用的力,但实际上在 AddForce 函数调用后的下一帧里,Rigidbody 的速度已经变化了(默认情况下帧的间隔时间是 0.16 秒左右,也就是说,本来应该是力持续作用 0.02 秒后才会让 Rigidbody 达到的速度,其实在 0.16 秒后 Rigidbody 就已经达到这个速度了,如果帧的间隔时间更短,那么 Rigidbody 就会更早获得这个速度),这也是涉及到 Rigidbody 的操作最好都放到 FixedUpdate 而不是 Update 里的原因——物理引擎对 Rigidbody 的各种模拟都是以 FixedUpdate 的时间间隔来计算的,如果将这些操作放在 Update 里进行,那么就会因为 FixedUpdate 和 Update 的时间差而导致每一帧都会出现误差,从而最后模拟出来的物理现象与理论不符。

  Impulse 模拟一个瞬间作用在 Rigidbody 上的冲量,AddForce 的第一个参数指定的 Vector3 类型变量就是这个冲量,假设使用 AddForce 函数对精致的 Rigidbody 施加一个大小是 100 单位的冲量,且 Rigidbody 的质量为 1 单位,那么这个冲量的作用结果就是 Rigidbody 的位移速度从 0 变化为大小 100 单位,方向为冲量方向(I=mv1-mv0,这里v0=0)。

  Acceleration 模拟在一个 FixedUpdate 的时间片段内持续作用在 Rigidbody 上的加速度,假设使用 AddForce 对一个静止的 Rigidbody 施加 100 单位的加速度,且 Rigidbody 的质量为 1 单位,那么在这个加速度作用结束后,Rigidbody 的位移速度将从 0 变为大小 2 单位,方向为加速度方向(v=v0+at,这里v0=0)。这个模式与 Force 模式类似,是模拟持续作用约 0.02 秒的加速度,所以 FixedUpdate 和 Update 对其的影响也跟 Force 模式类似。

  VelocityChange 模拟一个 Rigidbody 在瞬间获得一个位移速度。

几段测试用的代码:

GiveForce.cs

using UnityEngine;
using System.Collections;

public class GiveForce : MonoBehaviour
{

	public float ForceFactor = 100;

	private float time = 0.0f;

	// Use this for initialization
	void Start()
	{
	}

	// Update is called once per frame
	void Update()
	{
		//Debug.Log("Time of frame: " + (Time.time - time).ToString());
		//time = Time.time;
	}

	void FixedUpdate()
	{
		//Debug.Log("Time of fixedframe: " + (Time.time - time).ToString());
		//time = Time.time;
		if (Input.GetMouseButtonDown(0))
		{
			gameObject.rigidbody.AddForce(new Vector3(-1, 0, 0) * ForceFactor);

			//gameObject.rigidbody.AddForce(gameObject.transform.TransformDirection(new Vector3(-1, 0, -1)) * ForceFactor);

			//gameObject.rigidbody.AddRelativeForce(new Vector3(-1, 0, 0), ForceMode.VelocityChange);

			Debug.Log(gameObject.rigidbody.velocity.ToString() + gameObject.rigidbody.angularVelocity.ToString() + gameObject.rigidbody.drag.ToString());
			Debug.Log(gameObject.rigidbody.constantForce.ToString());
		}
		else if (Input.GetMouseButtonDown(1))
		{
			gameObject.rigidbody.AddForce(new Vector3(1, 0, 0) * ForceFactor, ForceMode.Impulse);

			//gameObject.rigidbody.AddForce(gameObject.transform.TransformDirection(new Vector3(1, 0, 1)) * ForceFactor);

			Debug.Log(gameObject.rigidbody.constantForce.ToString());
		}
		else if (Input.GetMouseButtonDown(2))
		{
			gameObject.rigidbody.AddForce(new Vector3(1, 0, 0) * ForceFactor, ForceMode.VelocityChange);

			//gameObject.rigidbody.AddForce(gameObject.transform.TransformDirection(new Vector3(1, 0, 1)) * ForceFactor);

			Debug.Log(gameObject.rigidbody.constantForce.ToString());
		}
	}
}

GiveConstantForce.cs

using UnityEngine;
using System.Collections;

public class GiveConstantForce : MonoBehaviour
{

	public bool Enabled = false;

	// Use this for initialization
	void Start()
	{

	}

	// Update is called once per frame
	void Update()
	{
		if (Enabled && constantForce != null)
		{
			constantForce.force = new Vector3(0.0f, -9.81f, 0.0f);
		}
		else if (!Enabled && constantForce != null)
		{
			constantForce.force = Vector3.zero;
		}
	}
}

SetFriction.cs

using UnityEngine;
using System.Collections;

public class SetFriction : MonoBehaviour
{
	public float StaticFrictionFactor = 0.0f;
	public float DynamicFrictionFactor = 0.0f;
	public bool ShowInstanceID = false;
	// Use this for initialization
	void Start()
	{
		//Debug.Log(gameObject.collider.material.dynamicFriction.ToString());
		//Debug.Log(gameObject.collider.material.dynamicFriction2.ToString());
		//Debug.Log(gameObject.collider.material.staticFriction.ToString());
		//Debug.Log(gameObject.collider.material.staticFriction2.ToString());
	}

	// Update is called once per frame
	void Update()
	{
		PhysicMaterial material = new PhysicMaterial();

		material.staticFriction = StaticFrictionFactor;
		material.dynamicFriction = DynamicFrictionFactor;
		material.frictionCombine = PhysicMaterialCombine.Maximum;

		gameObject.collider.material = material;
		//Debug.Log(gameObject.collider.material.dynamicFriction.ToString() + " " + 
		//    gameObject.collider.material.dynamicFriction2.ToString() + " " + 
		//    gameObject.collider.material.staticFriction.ToString() + " " + 
		//    gameObject.collider.material.staticFriction2.ToString());
		if (ShowInstanceID)
		{
			Debug.Log(gameObject.collider.material.GetInstanceID().ToString());
		}
	}
}

ShowVelocity.cs

using UnityEngine;
using System.Collections;

public class ShowVelocity : MonoBehaviour {

	// Use this for initialization
	void Start () {
	
	}
	
	// Update is called once per frame
	void Update () {
		Debug.Log(gameObject.rigidbody.velocity.ToString() + gameObject.rigidbody.angularVelocity.ToString() + gameObject.rigidbody.drag.ToString());
	}
}

HoldStill.cs

using UnityEngine;
using System.Collections;

public class HoldStill : MonoBehaviour
{

	public bool Hold = false;
	// Use this for initialization
	void Start()
	{

	}

	// Update is called once per frame
	void Update()
	{
		if (Hold)
		{
			gameObject.rigidbody.Sleep();
			Hold = false;
		}
	}
}

MoveCamera.cs

using UnityEngine;
using System.Collections;

public class MoveCamera : MonoBehaviour {

	public Transform targetTransform = null;

	// Use this for initialization
	void Start () {
	
	}
	
	// Update is called once per frame
	void Update () {
		if (targetTransform)
		{
			transform.position = targetTransform.position + new Vector3(0, 3, -10);
		}
	}
}


展开阅读全文

没有更多推荐了,返回首页