[Unity] ポーズ動作をTime.timeScale=0を使わずに実現する(その2)

前回の記事でTime.timeScale=0を使わずにポーズ動作を実現する方法について書きました。
しかし、Rigidbodyコンポーネントを持ったゲームオブジェクトでは、ポーズしてもそのまま物体をすり抜けて慣性で動き続けてしまいます。
逆にRigidbodyを使わないゲームオブジェクトであれば前回の記事で公開したスクリプトだけでも十分でしょう。

今回はRigidbodyのポーズ動作も考慮したポーズ動作の実現方法について書きます。

結論から言うと、rigidbodyコンポーネントのSleep()、WakeUp()メソッド呼び出しで実現できます。
しかし、上記だけではWakeUp()メソッドの実行時に物体の速度と角速度が0になってしまいます。
この問題を解決するために、WakeUp()呼出し後にSleep()呼び出し前の物体の速度と角速度に戻す必要があります。

これらを考慮することで、綺麗なポーズ/ポーズ解除が実現できます。
前回の記事で公開したスクリプトにRigidbodyのポーズ機能を追加したスクリプトを公開いたします。

Pauser.cs

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;

public class Pauser : MonoBehaviour {
	static List<Pauser> targets = new List<Pauser>();	// ポーズ対象のスクリプト

	// ポーズ対象のコンポーネント
	Behaviour[] pauseBehavs = null;

	Rigidbody[] rgBodies = null;
	Vector3[] rgBodyVels = null;
	Vector3[] rgBodyAVels = null;

	Rigidbody2D[] rg2dBodies = null;
	Vector2[] rg2dBodyVels = null;
	float[] rg2dBodyAVels = null;

	// 初期化
	void Start() {
		// ポーズ対象に追加する
		targets.Add(this);
	}

	// 破棄されるとき
	void OnDestory() {
		// ポーズ対象から除外する
		targets.Remove(this);
	}

	// ポーズされたとき
	void OnPause() {
		if ( pauseBehavs != null ) {
			return;
		}

		// 有効なコンポーネントを取得
		pauseBehavs = Array.FindAll(GetComponentsInChildren<Behaviour>(), (obj) => { return obj.enabled; });
		foreach ( var com in pauseBehavs ) {
			com.enabled = false;
		}

		rgBodies = Array.FindAll(GetComponentsInChildren<Rigidbody>(), (obj) => { return !obj.IsSleeping(); });
		rgBodyVels = new Vector3[rgBodies.Length];
		rgBodyAVels = new Vector3[rgBodies.Length];
		for ( var i = 0 ; i < rgBodies.Length ; ++i ) {
			rgBodyVels[i] = rgBodies[i].velocity;
			rgBodyAVels[i] = rgBodies[i].angularVelocity;
			rgBodies[i].Sleep();
		}

		rg2dBodies = Array.FindAll(GetComponentsInChildren<Rigidbody2D>(), (obj) => { return !obj.IsSleeping(); });
		rg2dBodyVels = new Vector2[rg2dBodies.Length];
		rg2dBodyAVels = new float[rg2dBodies.Length];
		for ( var i = 0 ; i < rg2dBodies.Length ; ++i ) {
			rg2dBodyVels[i] = rg2dBodies[i].velocity;
			rg2dBodyAVels[i] = rg2dBodies[i].angularVelocity;
			rg2dBodies[i].Sleep();
		}
	}

	// ポーズ解除されたとき
	void OnResume() {
		if ( pauseBehavs == null ) {
			return;
		}

		// ポーズ前の状態にコンポーネントの有効状態を復元
		foreach ( var com in pauseBehavs ) {
			com.enabled = true;
		}

		for ( var i = 0 ; i < rgBodies.Length ; ++i ) {
			rgBodies[i].WakeUp();
			rgBodies[i].velocity = rgBodyVels[i];
			rgBodies[i].angularVelocity = rgBodyAVels[i];
		}

		for ( var i = 0 ; i < rg2dBodies.Length ; ++i ) {
			rg2dBodies[i].WakeUp();
			rg2dBodies[i].velocity = rg2dBodyVels[i];
			rg2dBodies[i].angularVelocity = rg2dBodyAVels[i];
		}

		pauseBehavs = null;

		rgBodies = null;
		rgBodyVels = null;
		rgBodyAVels = null;

		rg2dBodies = null;
		rg2dBodyVels = null;
		rg2dBodyAVels = null;
	}

	// ポーズ
	public static void Pause() {
		foreach ( var obj in targets ) {
			obj.OnPause();
		}
	}

	// ポーズ解除
	public static void Resume() {
		foreach ( var obj in targets ) {
			obj.OnResume();
		}
	}
}

RigidBodyのポーズ操作は2D用に対しても行い、子オブジェクト全てに適用できるようにしておきました。
これで、物理演算を用いたポーズ処理もスクリプトの追加だけで実現できるようになりました。

めでたしめでたし。