【Unity】Scaleを無視してColliderを「厚み」で指定

【Unity】Scaleを無視してColliderを「厚み」で指定

同じプレハブを複数サイズで使う&一回り大きめのTriggerなColliderを使う時に地味に役立つ。
自己/親オブジェクトのScaleの影響を、身体の表面までは受け、厚みの部分は固定値で指定するコード。

(「1.0f, 1.0f, 1.0f」や、「2.5f, 2.5f, 2.5f」等、スケールの全要素が同じ場合を想定)

サンプル動画

(サンプルでもResetを有効にしているので、アタッチすると同時にCapsule Colliderが生成されている)

仕組み

  1. Capsule Colliderのサイズを直で指定するのではなく、身体のサイズ + 厚みで保持
  2. Scaleの逆数を厚みの方にだけ適用している。

(Resetメソッドの方は、アタッチ時にCapsule Colliderを生成するオマケ(長い))

使い方

Capsule Colliderの生成&設定

自動調整Ver

生成&設定処理は全部Resetメソッド*にぶち込んであるので、基本アタッチするだけでOkです。
*(エディタ非実行時に、アタッチ or Resetコマンド時に呼ばれる便利メソッド)

  1. 新規スクリプトでColliderFitterを作成して、コードを全文コピペ。
  2. コード内でThicknessを好きな厚みに調整。
  3. 対象のオブジェクト(3Dモデルを含む物)にアタッチ。
  4. 自動設定。

手動調整Ver

  1. (自動生成Verと同じく)コードをコピペしColliderFitterスクリプトを作成。
  2. コード内でThicknessを調整。
  3. 対象のオブジェクトにアタッチ。
  4. 自動設定された、baseRadiusbaseHeightの値をインスペクターからチェック。
  5. ↑の値を参考にしつつ、コード内でmeshSizeの初期値を調整
    (サイズに変換する為、Radius相当の要素は参考値の2倍を基準にする)
  6. 【meshSize手動調整時に前回のColliderを消す時用】の箇所を有効化しておく。
    (ココの処理は、確認なく対象オブジェクトのColliderを消すので注意。子の奴は大丈夫)
  7. インスペクター -> ColliderFitter -> 右クリック -> Reset
  8. 4、6を納得行くまで繰り返す。

Trigger Colliderのサイズの更新

  1. (前項の通り、ColliderFitterを導入)
  2. 他のクラスでColliderFitterを保持しておく。
  3. 対象オブジェクトのScale変更後に、AdjustColliderByThicknessを呼ぶ。

【コード】ColliderFitter


using UnityEngine;

public class ColliderFitter : MonoBehaviour
{
//キャッシュしておく。
	[HideInInspector]
	[SerializeField]
	Transform tf;


//キャラクターのColliderなので、CapsuleColliderを想定。
//ボディの物理的な当たり判定。要らない場合はReset内のcol関係の操作と共に消してください。
	[SerializeField]
	CapsuleCollider col;

//調整対象のCollider。
	[SerializeField]
	CapsuleCollider triggerCol;


	SkinnedMeshRenderer skinnedMeshRenderer;

//自分で調整する場合は右辺の値を変更してください(Radius相当の要素はサイズに変換する為2倍する)。
//Vector3.zeroを設定するとオート設定。
	[System.NonSerialized]
	Vector3 meshSize = new Vector3(0, 0, 0);


//3Dモデルの大体の大きさ(表面に合わせてColliderを調整した場合の値)。
	[SerializeField]
	float baseRadius;
	[SerializeField]
	float baseHeight;


//【任意の値】【スクリプトアタッチ前に設定してください】
//厚み = 対象TriggerなColliderと基本の大きさとの差(Heightの場合はそのままで、Radiusの場合は差の2倍)。
	static readonly float Thickness = 0.25f;
//【任意の値】基準となるScale(インスペクターでColliderのサイズを合わせた時のScale)
	static readonly float DefaultScale = 1.0f;

//Scaleの逆数。
	float inverseScale;
//CapsuleColliderの向き。
	int capsuleColliderDirection;



//Transformのスケールを変更した後にコレを呼ぶと、対象のColliderの大きさを指定した厚みにより調整する。
	public void AdjustColliderByThickness()
	{
		if (tf == null)
			tf = transform;

		inverseScale = DefaultScale / tf.localScale.x;

//厚さのみに逆数を適用。
//Radiusなので1/2。
		triggerCol.radius = baseRadius + Thickness * inverseScale * 0.5f;
		triggerCol.height = baseHeight + Thickness * inverseScale;
	}



//アタッチ時にCapsuleColliderを生成したい場合にはコレを有効化しておく。
	void Reset()
	{
		tf = transform;
		skinnedMeshRenderer = GetComponentInChildren<SkinnedMeshRenderer>();

//そもそも対象プレハブに3Dモデルが含まれていないのでキャンセル。
		if (skinnedMeshRenderer == null)
			return;


//【meshSize手動調整時に前回のColliderを消す時用】
//【注意】仕様上、アタッチ時とResetコマンド時を判断出来ない為、同オブジェクトのColliderを全て(子は大丈夫)消してしまうので気を付けてください。
//		foreach (CapsuleCollider cc in GetComponents<CapsuleCollider>()) {
//			DestroyImmediate(cc);
//		}


		col = GetComponent<CapsuleCollider>();
//CapsuleColliderが存在しない場合は生成。
		if (col == null) {
			col = gameObject.AddComponent<CapsuleCollider>();
		}

//3Dモデルのおおよその大きさを取得。
//手動で調整したい場合は、インスペクターからbaseRadius、baseHeightの値をメモって参考にしつつ、コード内のmeshSizeに設定し、再度アタッチし直す or インスペクター -> ColliderFitterを右クリック -> Reset。
		if (meshSize == Vector3.zero)
			meshSize = skinnedMeshRenderer.sharedMesh.bounds.size;


//3Dモデルのサイズの一番大きい(長い)要素をbaseHeightと判定。
		baseHeight = Mathf.Max(meshSize.x, meshSize.y, meshSize.z);


		baseRadius = Mathf.Infinity;
//3Dモデルのサイズの1番小さい要素にRadiusを合わせるVer。
		for (int i = 0; i < 3; i++) {
//Vecto3は配列っぽくindexで指定も出来る。
			if (baseHeight == meshSize[i]) {
//directionは、0 = x, 1 = y, 2 = zと、Vector3型のindexと対応している。
				capsuleColliderDirection = i;
			} else if (meshSize[i] < baseRadius) {
				baseRadius = meshSize[i];
			}
		}


//3Dモデルのサイズの2番目に大きい要素にRadiusを合わせるVer。
//                baseRadius = 0;
//                for (int i = 0; i < 3; i++) {
//                    if (baseHeight == meshSize[i]) {
//                        capsuleColliderDirection = i;
//                    } else if (baseRadius < meshSize[i]) {
//                        baseRadius = meshSize[i];
//                    }
//                }


//半径なので1/2にする。
		baseRadius *= 0.5f;

		col.radius = baseRadius;
		col.height = baseHeight;
		col.direction = capsuleColliderDirection;



		triggerCol = gameObject.AddComponent<CapsuleCollider>();   
		triggerCol.direction = capsuleColliderDirection;
		triggerCol.isTrigger = true;
		AdjustColliderByThickness();
	}

}

タイトルとURLをコピーしました