SAFullBodyIKのヘルパーを作ってみたり
Unityでポーズを作成するとき、よく利用させてもらっているのが、Noraさん作の
SAFullBodyIKです。
Stereoarts Homepage
無料で公開されている、素晴らしいスクリプトです。
利用方法は、モデルにSAFullBodyIKをセットして、実行するだけ。
モデルの子に"FullBodyIK"というオブジェクトが追加されるので、Hierarchyビューで更にその子であるIK用の各種パーツを選んで、Sceneビューで動かす…といった仕組みです。
長らく利用させていただいているので、この操作になれてしまったのですが、ポーズの完成までHierarchyビューとSceneビューを往復することになります。
これが結構タイムロスになることもあります。
なので、Sceneビューだけでポーズが作れるよう、ヘルパーとなるスクリプトを作成しました。
仕組みとしては、Sceneビューで操作できるように、IK用の各種パーツにMeshを追加しているだけです。
利用方法はスクリプトをSAFullBodyIKが適用されたモデルにセットし、
IKScriptInstanceに、モデルに適用されているSAFullBodyIK
BaseMeshにセットしたいメッシュ、
Materialにそのメッシュに適用するマテリアルを指定するだけです。
※マテリアルは半透明なものを利用したほうが、元のモデルが隠れないので分かりやすいです。
実行すると、IK用の各種パーツに指定したメッシュが追加され、Sceneビューのみで操作が可能になります。
※なお、この時表示されるのはSAFullBodyIKでPositionEnabledまたはRotationEnabledのいずれかがONになっているパーツのみです。
また実行中にそれらを切り替えると、メッシュの表示/非表示も同期します。
ScaleFingerは指のメッシュ、Scaleはその他のメッシュの大きさを動的に変更できます。
またMeshVisibleで、全体のメッシュの表示/非表示が切り替えられます。
メッシュやマテリアル自体の動的な変更には対応していません(面倒だった)。
マテリアルのパラメータの動的変更(色を変えたりシェーダー変えたり)には対応しています。
using System.Linq; using UnityEngine; public class HelperForSAFullBodyIK : MonoBehaviour { private class EffectorDetail { public SAFullBodyIK.Effector Effector { get; set; } public MeshFilter MeshFilter { get; set; } public MeshRenderer MeshRenderer { get; set; } } //SAFullBodyIKのInstance public SAFullBodyIK IKScriptInstance; //追加するメッシュ public Mesh BaseMesh; //メッシュに適用するマテリアル public Material Material; //メッシュのスケール public float Scale = 0.2f; //通常 public float ScaleFinger = 0.1f; //指 public bool MeshVisible = true; private Mesh _mesh; private Mesh _meshFinger; private Vector3[] _baseVertices; private EffectorDetail[] _effectorDetails; private float _latestScale; private float _latestScaleFinger; // Use this for initialization private void Start() { this._latestScale = this.Scale; this._latestScaleFinger = this.ScaleFinger; if (this.BaseMesh != null) this._baseVertices = this.BaseMesh.vertices; } // Update is called once per frame private void Update() { //SAFullBodyIKがセットされていなければ終わり if (this.IKScriptInstance == null) return; //SAFullBodyIKの諸々の準備が終わっていたら、初期処理 if (this.IKScriptInstance._model != null && this.IKScriptInstance._model._effectors != null && this._effectorDetails == null) { //エフェクターを取得 this._effectorDetails = this.IKScriptInstance._model._effectors.Select(x => new EffectorDetail { Effector = x }).ToArray(); //メッシュのスケール初期値を取得 this._latestScale = this.Scale; this._latestScaleFinger = this.ScaleFinger; //元となるメッシュのクローンを作成 this.CloneMesh(ref this._mesh, this.BaseMesh); this.CloneMesh(ref this._meshFinger, this.BaseMesh); //メッシュのスケール変更 this.RescaleMesh(this._mesh, this._latestScale); this.RescaleMesh(this._meshFinger, this._latestScaleFinger); for (int i = 0; i < this._effectorDetails.Length; i++) { var detail = this._effectorDetails[i]; //MeshFilterの追加 detail.MeshFilter = detail.Effector.transform.gameObject.AddComponent<MeshFilter>(); if (detail.Effector._bone == null) { detail.MeshFilter.mesh = this._mesh; } else { if (detail.Effector._bone._boneType == SAFullBodyIK.BoneType.Finger) { detail.MeshFilter.mesh = this._meshFinger; } else { detail.MeshFilter.mesh = this._mesh; } } //MeshRendererの追加 detail.MeshRenderer = detail.Effector.transform.gameObject.AddComponent<MeshRenderer>(); detail.MeshRenderer.material = this.Material; detail.MeshRenderer.enabled = this.MeshVisible && (detail.Effector.positionEnabled || detail.Effector.rotationEnabled); } } //まだエフェクターが取得できていなかったら終わり if (this._effectorDetails == null) return; //インスペクタ上などでScaleの値が変更されていたら、メッシュのScaleも変更 if (this.Scale != this._latestScale) { this._latestScale = this.Scale; //元となるメッシュのクローンを作成 this.CloneMesh(ref this._mesh, this.BaseMesh); //メッシュのスケール変更 this.RescaleMesh(this._mesh, this._latestScale); } if (this.ScaleFinger != this._latestScaleFinger) { this._latestScaleFinger = this.ScaleFinger; //元となるメッシュのクローンを作成 this.CloneMesh(ref this._meshFinger, this.BaseMesh); //メッシュのスケール変更 this.RescaleMesh(this._meshFinger, this._latestScaleFinger); } //メッシュのスケールや表示の変更 for (int i = 0; i < this._effectorDetails.Length; i++) { var detail = this._effectorDetails[i]; if (detail.Effector._bone == null) { detail.MeshFilter.mesh = this._mesh; } else { if (detail.Effector._bone._boneType == SAFullBodyIK.BoneType.Finger) { detail.MeshFilter.mesh = this._meshFinger; } else { detail.MeshFilter.mesh = this._mesh; } } detail.MeshRenderer.enabled = this.MeshVisible && (detail.Effector.positionEnabled || detail.Effector.rotationEnabled); } } private void RescaleMesh(Mesh mesh, float scale) { //スケールの変更 var vertices = new Vector3[this._baseVertices.Length]; for (var i = 0; i < vertices.Length; i++) { vertices[i] = this._baseVertices[i] * scale; } //スケールの反映 mesh.vertices = vertices; mesh.RecalculateBounds(); } private void CloneMesh(ref Mesh to, Mesh from) { if (to != null) { Object.Destroy(to); } to = (Mesh)Object.Instantiate(this.BaseMesh); } }
2015/09/25 ソース修正:Scale/ScaleFinger変更時にMeshを作り変える際、直前に利用していたMeshを明示的にDestroyするように。