読者です 読者をやめる 読者になる 読者になる

Onoty3D

Unityに関するメモとか

メッシュの頂点を動かして、プロ生ちゃんの舌を操作する

プロ生ちゃんの舌をスクリプトで動かせるようにしてみました。


舌ネタは以前にもやりました。
onoty3d.hatenablog.com
この投稿では、ニコニ立体ちゃんの舌を拝借してプロ生ちゃんに舌を足しました。
また、以下の投稿で公開したスクリプトを使えば、ニコニ立体ちゃんの舌でなくても、プロ生ちゃん自身の舌を切り出して利用することができます。
onoty3d.hatenablog.com

しかし今回はメッシュの舌部分の頂点を移動させることで制御してみました。
(はじめはシェーダでの擬似的な頂点移動も考えましたが、その場合自分が使いたいシェーダ毎に実装する必要があるため面倒になった。)

Unity Editor上で表情なんかを楽しむ分には問題ないはずです。
UnityPackageはこちら。(ソースは末尾。)

TongueMorph

使い方

UnityPackageを任意のプロジェクトにインポートしたら、追加されるTongueMorphというスクリプトをプロ生ちゃんにセットしてください。
セットすると、インスペクタ上の表示はこのようになります。
f:id:onoty3d:20161021135110p:plain

TongueIndex

セットするモデルによって変更してください。
ノーマルモデル:21
Tシャツモデル:17
SDモデル:19
TongueIndexの値を間違うと、エラーになったり他の部位が動いたりするので気をつけてください。
なお実行中に値を変更しても効果はありません。

SkinnedMeshRenderer

プロ生ちゃんのSkinnedMeshRendererです。
基本的には実行時に勝手に見つけてきますが、プロ生ちゃん内部に他にSkinnedMeshRendererを持つオブジェクトを持たせたりしている場合は、手動で正しいSkinnedMeshRendererをセットしておいてください。

Width,MoveForward,MoveUp

Width:舌の幅を調整します。
MoveForward:舌を前方に移動させます。
MoveUp:舌を上方に移動させます。
f:id:onoty3d:20161021135209g:plain

VertucalRotationCenter,VertucalRotation

VertucalRotation:舌を縦方向に回転させます。
VertucalRotationCenter:縦方向回転の中心を移動させます。中心より先の部分だけが回転します。
0が舌の根元、1が舌の先端になります。
f:id:onoty3d:20161021135419g:plain

HorizontalRotation,Inclination

HorizontalRotation:舌を水平方向に回転させます。
Inclination:舌を傾斜させます。
f:id:onoty3d:20161021135457g:plain

以上を組み合わせて、いろいろな表情を作ってみてください。
f:id:onoty3d:20161021135553p:plain

ソース

using System.Linq;
using UnityEngine;

namespace PronamaChan
{
    public class TongueMorph : MonoBehaviour
    {
        private class DefaultVertex
        {
            public int Index { get; set; }

            public Vector3 Vertex { get; set; }
        }

        public SkinnedMeshRenderer SkinnedMeshRenderer;

        public float Width = 1;

        //前後移動
        [Range(0, 4)]
        public float MoveForward = 0;

        //上下移動
        [Range(0, 2)]
        public float MoveUp = 0;

        //縦回転中心
        [Range(0, 1)]
        public float VerticalRotationCenter = 0.5f;

        //縦回転
        [Range(-90, 90)]
        public float VerticalRotation = 0;

        //横回転
        [Range(-90, 90)]
        public float HorizontalRotation = 0;

        //傾き
        [Range(-90, 90)]
        public float Inclination = 0;

        public int TongueIndex;

        private Vector3 _rotationCenter;

        private float _verticalRotationCenterZero;
        private float _verticalRotationCenterVolume;

        private Mesh _mesh;

        private DefaultVertex[] _defaultTongueVertices;

        // Use this for initialization
        private void Start()
        {
            //SkinnedMeshRendererの取得
            if (this.SkinnedMeshRenderer == null)
            {
                this.SkinnedMeshRenderer = this.GetComponentInChildren<SkinnedMeshRenderer>();
            }

            //メッシュのクローンをSkinnedMeshRendererにセット
            this._mesh = this.SkinnedMeshRenderer.sharedMesh;
            this._mesh = GameObject.Instantiate(this._mesh);
            this._mesh.MarkDynamic();
            this.SkinnedMeshRenderer.sharedMesh = this._mesh;

            //舌のサブメッシュのインデックスリスト取得
            var triangles = this._mesh.GetTriangles(this.TongueIndex);
            var trianglesUnique = triangles.Distinct().ToArray();

            //舌の頂点位置保存
            this._defaultTongueVertices = trianglesUnique.Select(x => new DefaultVertex { Index = x, Vertex = this._mesh.vertices[x] }).ToArray();

            //回転中心の決定
            var minMax = _defaultTongueVertices.Select(x => new { MinY = x.Vertex.y, MaxY = x.Vertex.y, MinZ = x.Vertex.z, MaxZ = x.Vertex.z })
                .Aggregate((x, y) => new { MinY = Mathf.Min(x.MinY, y.MinY), MaxY = Mathf.Max(x.MaxY, y.MaxY), MinZ = Mathf.Min(x.MinZ, y.MinZ), MaxZ = Mathf.Max(x.MaxZ, y.MaxZ) });
            this._rotationCenter = new Vector3(0, 0, (minMax.MinZ + minMax.MaxZ) / 2f);
            this._verticalRotationCenterZero = minMax.MaxY;
            this._verticalRotationCenterVolume = minMax.MaxY - minMax.MinY;
        }

        // Update is called once per frame
        private void Update()
        {
        }

        public void OnValidate()
        {
            if (this._defaultTongueVertices == null)
            {
                return;
            }

            //現在の頂点取得
            var vertices = this._mesh.vertices;

            foreach (var defaultVertex in this._defaultTongueVertices)
            {
                var newVertex = defaultVertex.Vertex;

                //舌の幅の変更
                newVertex.x *= this.Width;

                //縦方向回転
                {
                    //回転の中心を舌の根元から任意の位置に移動
                    var rotationCenterVertical = this._rotationCenter;
                    rotationCenterVertical.y = this._verticalRotationCenterZero - this._verticalRotationCenterVolume * this.VerticalRotationCenter;
                    //任意の位置から先だけを回転
                    if (defaultVertex.Vertex.y < rotationCenterVertical.y)
                    {
                        newVertex = Quaternion.Euler(this.VerticalRotation, 0, 0) * (newVertex - rotationCenterVertical) + rotationCenterVertical;
                    }
                }

                //横方向回転・傾き
                newVertex = Quaternion.Euler(0, this.Inclination, this.HorizontalRotation) * (newVertex - this._rotationCenter) + this._rotationCenter;

                //前後上下移動
                newVertex.y += -this.MoveForward / 100.0f;
                newVertex.z += this.MoveUp / 100.0f;

                vertices[defaultVertex.Index] = newVertex;
            }

            this._mesh.vertices = vertices;
            this._mesh.RecalculateNormals();
            this._mesh.RecalculateBounds();
        }
    }
}