髪の毛やスカートなど、3Dモデルにとって揺れものは避けて取れない。
ということで、次はこれに取り組んでみます。
ricopinさんが初めに作られたSpringBone.csは、物理エンジンを使わずにコードで物体同士の衝突を計算して揺れものを動かしている。
これは改良されてUnityChanやプロ生ちゃんにも搭載されている。
SpringBoneという名称からUnityのSpringJointを使ってるかと思っていたら、そんなものは使ってなくて、positonとrotationを揺れものとコライダーの衝突で計算している仕組みでした。
髪の毛だと、例えば、Hair1→Hair2→Hair3→Hair4みたいにボーンが階層化されているとします。
この時に、それぞれにSpringBone.csをAddして、親から子への関係をリストとしてそれぞれ登録します。
次に、髪の毛と衝突する可能性のある例えば頭にコライダー(SpringCollider)を設定します。
最終的に、SpringManagerを登録しますが、これがすべてのSpringBoneに指令をだして、個々のSpringBoneがすべてのコライダーとの衝突を考慮しながら、子供のSpringBoneにばねとしての動きを伝えます。
この遅延した連動によって、髪の毛やスカートのうねうねした動きが実現されている。
ただし、SpringManagerにすべてのSpringBoneとSpringColliderをちまちま登録しないといけない。
プロ生ちゃんの頭部のSpringManagerには50個のSpringBoneが登録されています。これはとても人間がやる作業ではないです(´・ω・`)
しかも、SpringBoneには子供を登録しつつ、対象のSpringColliderも登録しないといけません。髪の毛だけで50個のSpringBoneがあるのです。
スカートは別ですw
とてもやってられないので、プログラムでアタッチする自動化を試みます。
まず、親BONEから自動的に子供にSpringBoneをアタッチして、SpringManagerに登録してくれるコードを書いて自動化してみました。
まだ設定がおかしいらしく、ポニーテールが直立しています(^◇^)
プロ生ちゃんを揺すってみると、髪の毛やスカートの動きは完璧ですね!
あとは登録作業の煩雑さを解消すればいい感じ!
ポニーテールの親BONEであるBackHair1を引数に指定すれば、その子供と子供と子供といった感じで末端LEAFまで自動でアタッチしてくれる。コライダーもsc0を設定してくれる。
setupSpringBone("88.joint_BackHair1",sc0);
このBoneのGameObjectは配列に保持しているので、SpringManagerに自動で登録される。
setupSpringManager("3.joint_Head");
考え方として、スカートにしても髪の毛にしても、SpringBoneのROOTの下の子供は一人しかいません(というのが多いと思う)
ならば、ROOTだけ指定すれば、あとは自動的に子供を探索して自動アタッチすればいいんです。そして同時にManagerにも自動登録すればいいんです。
そもそも、boneごとにアタッチしないでも、Managerがboneのtransformをすべて動かせばいい。これならアタッチするのは一つで済むんです。
UnityStoreで売られているDynamicBoneは解説動画を見る限り、同じような簡単な登録を実現しているようです。
public class SpringSetup : MonoBehaviour { GameObject[] gm = new GameObject[10]; PronamaChan.SpringBone[] sb = new PronamaChan.SpringBone[10]; int sbIndex; void Start () { sbIndex = 0; PronamaChan.SpringCollider sc0 = setupSpringCollider("3.joint_Head", "HeadCollider", -Vector3.up * 0.1f); setupSpringBone("88.joint_BackHair1",sc0); setupSpringManager("3.joint_Head"); sbIndex = 0; PronamaChan.SpringCollider sc1 = setupSpringCollider("30.joint_LeftHip", "LegColliderL1" ,- Vector3.up*0.1f); PronamaChan.SpringCollider sc2 = setupSpringCollider("55.joint_RightHip", "LegColliderR1", - Vector3.up * 0.1f); setupSpringBone("28.joint_LeftSkirtFront",sc1); setupSpringBone("29.joint_LeftSkirtBack", sc1); setupSpringBone("53.joint_RightSkirtFront", sc2); setupSpringBone("54.joint_RightSkirtBack", sc2); setupSpringManager("7.joint_HipMaster"); } PronamaChan.SpringCollider setupSpringCollider(string bone,string colName,Vector3 pos) { GameObject manager = GameObject.Find(bone) as GameObject; GameObject col =Instantiate((GameObject)Resources.Load("SpringCollider"), pos, Quaternion.identity) as GameObject; col.name = colName; col.transform.SetParent(manager.transform); PronamaChan.SpringCollider sc = col.AddComponent<PronamaChan.SpringCollider>() as PronamaChan.SpringCollider; sc.radius = 0.07f; return sc; } void setupSpringManager(string bone) { GameObject manager = GameObject.Find(bone) as GameObject; PronamaChan.SpringManager sm = manager.AddComponent<PronamaChan.SpringManager>() as PronamaChan.SpringManager; /* sm.dynamicRatio = 1f; sm.stiffnessForce = 0.0001f; // 柔らかさ(小さい程揺れる) sm.stiffnessCurve = new AnimationCurve(new Keyframe(0, 1), new Keyframe(1, 0)); sm.dragForce = 0f; // 揺れの減衰力 sm.dragCurve = new AnimationCurve(new Keyframe(0, 0.5f), new Keyframe(1, 0.5f)); */ sm.springBones = new PronamaChan.SpringBone[sbIndex]; for (int i = 0; i < sbIndex; i++) sm.springBones[i] = sb[i]; } void setupSpringBone(string root,PronamaChan.SpringCollider col) { gm[0] = GameObject.Find(root) as GameObject; sb[0] = gm[0].AddComponent<PronamaChan.SpringBone>() as PronamaChan.SpringBone; sb[0].radius = 0.05f; //sb[0].isUseEachBoneForceSettings = true; // 柔らかさ・揺れ減衰を使用するかどうか sb[0].stiffnessForce = 0.01f; //ばねが戻る力(小さい程揺れる) sb[0].dragForce = 0.4f;//揺れ減衰力 for (;;) { bool flag = false; foreach (Transform child in gm[sbIndex].transform) { gm[sbIndex + 1] = child.gameObject; if (gm[sbIndex + 1].transform.gameObject == null) break; sb[sbIndex + 1] = gm[sbIndex + 1].AddComponent<PronamaChan.SpringBone>() as PronamaChan.SpringBone; sb[sbIndex + 1].radius = 0.05f; //sb[i + 1].isUseEachBoneForceSettings = true; // 柔らかさ・揺れ減衰を使用するかどうか sb[sbIndex + 1].stiffnessForce = 0.01f; //柔らかさ sb[sbIndex+ 1].dragForce = 0.4f;//揺れ減衰力 sb[sbIndex].child = gm[sbIndex + 1].transform; sb[sbIndex].colliders = new PronamaChan.SpringCollider[1]; sb[sbIndex].colliders[0] = col; flag = true; break; } if (!flag) break; sbIndex++; } } void Update() { } }