クマテクブログ

日々のメモ

Meshを更新した際にPrefabが壊れてしまう問題を解決する

Unityを使用しているときによく使う機能でPrefabというものがあります。
私も普段よくPrefabを利用していますが先日このような事が起こりました。

とあるMesh(TestModel.fbxとします)を参照しているPrefab(TestModel_Prefabとします)があり。
Meshにいくつかの修正を加えMeshを入れ替えるとPrefabの表示がとんでもない事になってしまいました。


このPrefabが参照しているMeshを更新します。
f:id:Cyario:20160831164521p:plain


すると、とんでもない事に....
f:id:Cyario:20160831164526p:plain



この時Meshの構造、ボーンの構造に特に変更はない簡単な変更のはずなのにこのような事態になりました。
Prefabの参照元のMeshを更新できないとなると毎回Prefabを作成しないといけなくなり、参照しているPrefabが多いと大変面倒でヒューマンエラーの原因にもなりかねないです。
なんとか解決しようと詳しく調べてみるとどうもMeshの頂点番号に変更があり、UnityではMeshのもつ情報からボーンとの接続を行うらしくそれが原因らしいと分かりました。

そして色々調べて試しにこのようなScriptを作成してみると

public class ResetBoneMap : MonoBehaviour
{
	public GameObject original;

	public void Convert()
	{
		SkinnedMeshRenderer[] skinnedMeshs = this.GetComponentsInChildren<SkinnedMeshRenderer>();

		foreach ( var s in skinnedMeshs )
		{
			Reset( s.GetComponent<SkinnedMeshRenderer>(), s.name );
		}
	}

	void Reset( SkinnedMeshRenderer targetRenderer, string name )
	{
		SkinnedMeshRenderer originalRenderer = getChildGameObject( original, name ).GetComponent<SkinnedMeshRenderer>();

		if (targetRenderer == null || originalRenderer == null ) return;

		Transform[] newBones = new Transform[targetRenderer.bones.Length];

		for ( int i = 0; i < originalRenderer.bones.Length; i++ )
		{
			for ( int j = 0; j < targetRenderer.bones.Length; j++ )
			{
				if ( targetRenderer.bones[j].name == originalRenderer.bones[i].name )
				{
					newBones[i] = targetRenderer.bones[j];
					continue;
				}
			}
		}

		targetRenderer.sharedMesh = originalRenderer.sharedMesh;
		targetRenderer.bones = newBones;		
	}

	GameObject getChildGameObject(GameObject fromGameObject, string withName)
	{
		Transform[] ts = fromGameObject.transform.GetComponentsInChildren<Transform>();
		foreach (Transform t in ts) if (t.gameObject.name == withName) return t.gameObject;
		return null;
	}
}

なんと・・うまく出力されました!!
f:id:Cyario:20160831170159p:plain


これでMeshの更新がなんとか楽になりそうです。


こちらのScriptはGithubにて公開しました。
github.com


使い方は簡単で壊れたPrefabにアタッチして元のMeshをOriginalに入れて・・・
f:id:Cyario:20160831164530p:plain


あとはボタンを押すだけ。
f:id:Cyario:20160831164534p:plain

処理が終われば綺麗な状態に直ります。