同じプレハブを複数サイズで使う&一回り大きめのTriggerなColliderを使う時に地味に役立つ。
自己/親オブジェクトのScaleの影響を、身体の表面までは受け、厚みの部分は固定値で指定するコード。
(「1.0f, 1.0f, 1.0f」や、「2.5f, 2.5f, 2.5f」等、スケールの全要素が同じ場合を想定)
サンプル動画
(サンプルでもResetを有効にしているので、アタッチすると同時にCapsule Colliderが生成されている)
仕組み
- Capsule Colliderのサイズを直で指定するのではなく、身体のサイズ + 厚みで保持。
- Scaleの逆数を厚みの方にだけ適用している。
(Resetメソッドの方は、アタッチ時にCapsule Colliderを生成するオマケ(長い))
使い方
Capsule Colliderの生成&設定
自動調整Ver
生成&設定処理は全部Resetメソッド*にぶち込んであるので、基本アタッチするだけでOkです。
*(エディタ非実行時に、アタッチ or Resetコマンド時に呼ばれる便利メソッド)
- 新規スクリプトでColliderFitterを作成して、コードを全文コピペ。
- コード内でThicknessを好きな厚みに調整。
- 対象のオブジェクト(3Dモデルを含む物)にアタッチ。
- 自動設定。
手動調整Ver
- (自動生成Verと同じく)コードをコピペしColliderFitterスクリプトを作成。
- コード内でThicknessを調整。
- 対象のオブジェクトにアタッチ。
- 自動設定された、baseRadius、baseHeightの値をインスペクターからチェック。
- ↑の値を参考にしつつ、コード内でmeshSizeの初期値を調整。
(サイズに変換する為、Radius相当の要素は参考値の2倍を基準にする) - 【meshSize手動調整時に前回のColliderを消す時用】の箇所を有効化しておく。
(ココの処理は、確認なく対象オブジェクトのColliderを消すので注意。子の奴は大丈夫) - インスペクター -> ColliderFitter -> 右クリック -> Reset
- 4、6を納得行くまで繰り返す。
Trigger Colliderのサイズの更新
- (前項の通り、ColliderFitterを導入)
- 他のクラスでColliderFitterを保持しておく。
- 対象オブジェクトの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();
}
}