天天看點

[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);
		}
	}
}
           

繼續閱讀