VRM の揺れものを一括設定する Unity Editor 拡張を作成しました。
何ができるか
導入方法
適当な C# Script を作って以下のソースコードをコピペしてください。後の使い方は動画を参考にしてください。なお、UniVRM
をインポートした後でないと正常に動かない のでご注意ください。
using UnityEditor;
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class EditorExtentionSample : EditorWindow
{
Transform armature;
GameObject secondary;
[MenuItem("VRM/SpringBoneAutoSet", false, 1)]
private static void ShowWindow()
{
EditorExtentionSample window = GetWindow<EditorExtentionSample>();
window.titleContent = new GUIContent("Spring Bone Auto Set");
}
private void OnGUI()
{
GUILayout.Label("Spring Bone を設定する Armature を指定してください。");
GUILayout.Label("※「Hips」等ではなく「Armature」等です。");
armature = EditorGUILayout.ObjectField(armature, typeof(Transform), true) as Transform;
GUILayout.Label("コンポーネントを追加する Game Object を指定してください。");
GUILayout.Label("※ 通常は「secondary」");
secondary = EditorGUILayout.ObjectField(secondary, typeof(GameObject), true) as GameObject;
if (GUILayout.Button("Auto set"))
{
var bone_tuple = new List<List<string>>();
for (int i = 0; i < 20; i++)
{
bone_tuple.Add(new List<string>());
}
bone_tuple[1].Add("armature");
bone_tuple[2].Add("hips");
bone_tuple[3].Add("spine");
bone_tuple[4].Add("chest");
bone_tuple[5].Add("neck");
bone_tuple[6].Add("head");
bone_tuple[7].Add("eye");
bone_tuple[5].Add("shoulder");
bone_tuple[6].Add("arm");
bone_tuple[7].Add("arm");
bone_tuple[8].Add("hand");
bone_tuple[3].Add("leg");
bone_tuple[4].Add("leg");
bone_tuple[5].Add("foot");
bone_tuple[5].Add("toe");
for (int i = 9; i < 13; i++)
{
bone_tuple[i].Add("thumb");
bone_tuple[i].Add("index");
bone_tuple[i].Add("middle");
bone_tuple[i].Add("ring");
bone_tuple[i].Add("little");
}
int depth = 0;
var bone = secondary.AddComponent<VRM.VRMSpringBone>();
void dfs(Transform now_obj)
{
depth++;
bool is_humanoid_bone = false;
foreach (string s in bone_tuple[depth])
{
if (now_obj.name.ToLower().Contains(s))
{
is_humanoid_bone = true;
}
}
if (is_humanoid_bone == false)
{
bone.RootBones.Add(now_obj);
depth--;
return;
}
Debug.Log(depth);
Debug.Log(now_obj.name);
int child_num = now_obj.childCount;
for (int i = 0; i < child_num; i++)
{
var child = now_obj.transform.GetChild(i);
if (child.transform.GetComponent<SkinnedMeshRenderer>())
{
continue;
}
dfs(child);
}
depth--;
return;
}
dfs(armature);
}
}
}
設計意図
VRM を作る際に、揺れものを個別に指定するのは大変なので一括で設定できるようにしました。
どうやって揺れものと判断するか?
多くのアバターにおいて、体の関節に相当するボーン(Spine
や Chest
等。以降、これらを ヒューマノイドボーン と名付けます)は揺れませんが、それ以外のボーン、つまりアバター製作者によって意図的で付加されたボーンは、ほとんどの場合揺れものに対応したボーンになります。
なので、ヒューマノイドボーン以外のボーン(これらを 揺れものボーン と名付けます)に揺れを適用させられれば、おおよそ想定通りの設定になると言えます。あるゲームオブジェクトに Spring Bone を適用すると、下流のボーンにもすべて揺れが適用されるので、要は ヒューマノイドボーンの直下にある揺れものボーン にピンポイントで Spring Bone を適用したいということになります。
どうやってヒューマノイドボーンと判断するか?
多くのヒューマノイドボーンは決まった命名パターン(Hips
,Spine
,Chest
...)があるので、基本的にはこの名前を頼りに決定していくことができます。しかし、名前だけを頼りに決定していくと、以下の問題点が出てきます。
- ごくわずかな表記揺れがある(
left
かLeft
かL
か?) - 揺れものボーンも同じ文字を含む可能性がある
特に、2 点目は大きな問題で、たとえば arm
や eye
等の文字列は、揺れものボーンにも同じ文字列が含まれる可能性があります。
これらの問題点を解決するために、名前と 深さ を基準にヒューマノイドボーンかそうでないかを推定するアルゴリズムを考案しました。
深さ
これは、「今自分が、Armature
から見て何層目にいるか?」という値です。例えば、Hips
は深さ $2$ で、Spine
は深さ $3$、Chest
は深さ $4$、Upper_Leg
は左右ともに深さ $3$ になります。これらの深さは、いくら余計な揺れものボーンがあろうが、ヒューマノイドボーンでは不変です。
よって、深さが一致する時に、その深さのヒューマノイドボーンに含まれるであろう文字列を含むか? を調べることにより、ヒューマノイドボーンかそうでないかを確実に高速に調べることができます。これは、ソースコードの前半で定義している部分になります。
bone_tuple[1].Add("armature");
bone_tuple[2].Add("hips");
bone_tuple[3].Add("spine");
bone_tuple[4].Add("chest");
bone_tuple[5].Add("neck");
bone_tuple[6].Add("head");
bone_tuple[7].Add("eye");
bone_tuple[5].Add("shoulder");
bone_tuple[6].Add("arm");
bone_tuple[7].Add("arm");
bone_tuple[8].Add("hand");
bone_tuple[3].Add("leg");
bone_tuple[4].Add("leg");
bone_tuple[5].Add("foot");
bone_tuple[6].Add("toe");
for (int i = 9; i < 13; i++)
{
bone_tuple[i].Add("thumb");
bone_tuple[i].Add("index");
bone_tuple[i].Add("middle");
bone_tuple[i].Add("ring");
bone_tuple[i].Add("little");
}
後は、この情報をもとに、Armature
から深さ優先探索を行っていきます。
- ヒューマノイドボーンならば、子の探索を続ける
- 揺れものボーンならば、Spring Bone の Root Bones にそのボーンを追加し、探索を打ち切る
再帰的にこのような処理を続けていくことにより、ヒューマノイドボーンの直下にある揺れものボーンにだけピンポイントに Spring Bone を適用するという当初の目的を達成することができます。
注意点
- 揺れもののパラメータはすべて初期値なので、細かい調整はもちろん手作業でする必要があります。
- 深さが一致しかつヒューマノイドボーン的な文字列を含む揺れものボーンは、揺れものとして設定されません。レアケースだと思いますが、そこはご了承ください。