すくまりのメモ帳すくまりのメモ帳
← 記事一覧に戻る

【Unity, Teddy, 3Dモデリング】描いた絵をキャラクターに取り付ける方法

2018-11-06
【Unity, Teddy, 3Dモデリング】描いた絵をキャラクターに取り付ける方法

はじめに

この記事ではTeddyで描いた絵をモデルに取り付ける方法を紹介します。

TeddyRuntimeDemo.cs
using UnityEngine;
using UnityEngine.EventSystems;
 
using System;
using System.Linq;
using System.Collections;
//List用
using System.Collections.Generic;
 
using Teddy;
 
namespace TeddyDemo
{
 
    public class TeddyRuntimeDemo : MonoBehaviour {
 
        public enum OperationMode
        {
            Default,
            Draw,
            Move,
            Cut
        };
 
        [SerializeField] new Camera camera;
        [SerializeField] Shader lineShader;
        [SerializeField] GameObject prefab;
        [SerializeField] bool simulate;
 
        Material lineMat;
 
        OperationMode mode;
 
        List<Vector2> screenpoints;
        List<Vector3> points;
        List<Puppet> puppets;
 
        Puppet selected;
        Vector3 origin;
        Vector3 startPoint;
 
        //手書きの円を表示する場所をずらすオフセット
        const float zOffset = 10f;
 
        //シーン開始時のカメラのベクトル
        Vector3 initialCameraPosition;
 
        //生成したオブジェクトを取り付ける親オブジェクト
        GameObject parent;
 
 
 
        void Start () {
            //カメラが無いなら追加
            if(camera == null) {
                camera = Camera.main;
            }
 
            screenpoints = new List<Vector2>();
            points = new List<Vector3>();
            puppets = new List<Puppet>();
            lineMat = new Material(lineShader);
 
 
            simulate = false;
 
            initialCameraPosition = camera.transform.position;
 
        }
 
        void Update () {
            //ボタンなどのuGUIをクリックしたときは画面クリックを無視する
            if (EventSystem.current.IsPointerOverGameObject()) return;
 
            //マウスの座標を取得
            var screen = Input.mousePosition;
 
            switch (mode)
            {
 
                case OperationMode.Default:
 
                    if (Input.GetMouseButtonDown(0))
                    {
                        //Listを全て消去
                        points.Clear();
						screenpoints.Clear();
 
                        // メインカメラからクリックしたポジションに向かってRayを撃つ。
                        var ray = camera.ScreenPointToRay(screen);
 
                        RaycastHit hit;
 
                        //Rayとオブジェクトが衝突していないかを調べる
                        if (Physics.Raycast(ray.origin, ray.direction, out hit, float.MaxValue))
                        {
                            //RayがあたったオブジェクトのPuppetクラスを取得
                            var puppet = hit.collider.GetComponent<Puppet>();
 
                            //RayがヒットしたオブジェクトにPuppetクラスがあったら
                            if(puppet != null)
                            {
                                startPoint = camera.ScreenToWorldPoint(screen);
 
                                //オブジェクトに物理演算を付与
                                selected = puppet;
                                selected.Select();//body.isKinematic = true;
 
                                //クリックしたスクリーン座標をRayがヒットしたオブジェクトのワールド座標にする
                                startPoint = hit.point;
 
                                //Rayの始点を、ヒットしたオブジェクトの座標へ移動
                                origin = selected.transform.position;
 
                                mode = OperationMode.Move;
                            }
                            else
                            {
                                mode = OperationMode.Draw;
                            }
                        }
                        //Rayの先にオブジェクトが無い場合
                        else
                        {
                            mode = OperationMode.Draw;
                        }
                    }
                    //右ボタンをクリックでカット
                    else if(Input.GetMouseButtonDown(1))
                    {
                        points.Clear();
						screenpoints.Clear();
                        mode = OperationMode.Cut;
                    }
 
                    break;
 
                //Game画面上で黒い手書きの線の表示
                case OperationMode.Draw:
                    if (Input.GetMouseButtonUp(0))
                    {
                        Build();
                        mode = OperationMode.Default;
                    }
                    else
                    {
                        var point = camera.ScreenToWorldPoint(new Vector3(screen.x, screen.y, camera.nearClipPlane + zOffset));
                        points.Add(point);
						screenpoints.Add(new Vector2(screen.x, screen.y));
                    }
                    break;
 
                case OperationMode.Move:
 
                    if (Input.GetMouseButtonUp(0))
                    {
                        selected.Unselect();
                        Simulate(simulate);
 
                        selected = null;
 
                        mode = OperationMode.Default;
                    }
                    else
                    {
                        var currentPoint = camera.ScreenToWorldPoint(new Vector3(screen.x, screen.y, camera.nearClipPlane + zOffset));
                        var offset = currentPoint - startPoint;
                        selected.transform.position = origin + offset;
                    }
 
                    break;
 
                case OperationMode.Cut:
                    if(Input.GetMouseButtonUp(1))
                    {
                        Cut();
                        mode = OperationMode.Default;
                    } else
                    {
                        var point = camera.ScreenToWorldPoint(new Vector3(screen.x, screen.y, camera.nearClipPlane + zOffset * 0.5f));
                        points.Add(point);
						screenpoints.Add(new Vector2(screen.x, screen.y));
                    }
 
                    break;
 
            }
 
        }
 
        //----------------------------------
        //線を引く
        //----------------------------------
        void Build ()
        {
            if (screenpoints.Count > 5)
            {
                var go = Instantiate(prefab);
 
                try
                {
                    Create(go, screenpoints);
                    var puppet = go.GetComponent<Puppet>();
                    puppets.Add(puppet);
                    Simulate(simulate);
                } catch(Exception)
                {
                    Destroy(go);
                }
            }
        }
 
        void Cut()
        {
            for(int i = 0, n = screenpoints.Count; i < n; i++)
            {
                var screen = screenpoints[i];
                var ray = camera.ScreenPointToRay(screen);
                RaycastHit hit;
                if (Physics.Raycast(ray.origin, ray.direction, out hit, float.MaxValue))
                {
                    var teddy = hit.collider.GetComponent<Teddy.Teddy>();
                    if(teddy != null)
                    {
                        try
                        {
                            TeddyUtility.CutMesh(teddy, camera, screenpoints);
 
                            // Copy material for sub meshes
                            var filter = teddy.GetComponent<MeshFilter>();
                            var renderer = teddy.GetComponent<MeshRenderer>();
                            var mat = renderer.sharedMaterial;
                            int count = filter.sharedMesh.subMeshCount;
                            var materials = new Material[count];
                            for(int j = 0; j < count; j++)
                            {
                                materials[j] = mat;
                            }
                            renderer.sharedMaterials = materials;
                        } catch
                        {
                        }
                        break;
                    }
                }
            }
        }
 
        public void Reset()
        {
            Simulate(true);
            puppets.ForEach(puppet => {
                puppet.Ignore();
                Destroy(puppet.gameObject, 10f);
            });
            puppets.Clear();
        }
 
 
        //-----------------------------------------
        //手書きのオブジェクトを生成する
        //------------------------------------------
        void Create (GameObject go, List<Vector2> screens)
        {
            //カメラのRotateの角度を取得
            var cameraQuaternion = camera.transform.rotation;
 
 
            //(Closureクラスが見つからない。たぶんTeddyRuntime.dllの中に入っていると思われます)
            var closure = Closure.GetClosure(screens);
            if (closure.Count > 2)
            {
                closure.RemoveAt(0);
                closure.RemoveAt(closure.Count - 1);
            }
            screens.AddRange(closure);
 
            //screenの座標を引数にして、camera.ScreenToWorldPointの座標を返す
            var world = screens.Select(screen => {
                
                var makePos = camera.ScreenToWorldPoint(new Vector3(screen.x, screen.y, camera.nearClipPlane + zOffset));
 
 
                //cameraが90°、または-90°のとき、手書きの円で作ったポイントをxy座標へ転写
                if (cameraQuaternion.eulerAngles.y == 90f)
                {
                    
                    makePos = Quaternion.Euler(0f, 90f, 0f) * makePos;
 
                }
                else if(cameraQuaternion.eulerAngles.y == -90f || cameraQuaternion.eulerAngles.y == 270f)
                {
                    makePos = Quaternion.Euler(0f, -90f, 0f) * makePos;
 
                }
 
                return makePos; 
 
            }).ToList();//ToList()で、screens全体にbody.isKinematic = true;を付与
 
            //go オブジェクトをカメラの向きに合わせる
            //go.transform.rotation = camera.transform.rotation;
 
            var points = world.Select(p => new Vector2(p.x, p.y)).ToList();
 
            // create lowpoly mesh for MeshCollider vertex count limit.(APIの部分)
            var lowpoly = TeddyUtility.CreateMesh(points, 200, 0.85f);
            var collider = go.GetComponent<MeshCollider>();
            collider.sharedMesh = lowpoly;
 
            TeddyUtility.CreateTeddy(go, points, 600, 0.85f);
 
            //生成したオブジェクトの色を変更する
            go.GetComponent<Renderer>().material.color = Color.red;
 
            //カメラ移動した分のベクトルを取得
            var offsetPos = camera.transform.position - initialCameraPosition;
 
            //goをの位置ベクトルを取得
            var goPos = go.transform.position;
 
 
            //カメラがのy軸が0, 90, 180°, 270のとき
            if(cameraQuaternion.eulerAngles.y == 0f)
            {
                goPos = new Vector3(0f, goPos.y, offsetPos.z);
 
            }
            else if(cameraQuaternion.eulerAngles.y == 180f)
            {
 
                //カメラを基準にx軸対象にPuppetを移動させる
                offsetPos.z -= (offsetPos.z - camera.transform.position.z) * 2f;
                goPos = new Vector3(0f, goPos.y, offsetPos.z);
 
 
            }
            else if(cameraQuaternion.eulerAngles.y == 90f)
            {
                go.transform.rotation = Quaternion.Euler(0f, -90f, 0f);
                goPos = new Vector3(offsetPos.x + zOffset, goPos.y, 0f);
 
 
            }
            else if (cameraQuaternion.eulerAngles.y == -90f || cameraQuaternion.eulerAngles.y == 270f)
            {
                go.transform.rotation = Quaternion.Euler(0f, 90f, 0f);
                goPos = new Vector3(offsetPos.x - zOffset, goPos.y, 0f);
 
            }
 
            //goを手書きの円の位置に移動
            go.transform.position = goPos;
 
            //goを指定したオブジェクトの子に配置
            if(parent != null)
            {
                go.transform.SetParent(parent.transform, true);
 
            }
            else
            {
                Debug.Log("部位を選択してください。");
            }
 
 
        }
 
        //-------------------------------
        //生成したオブジェクトのisKinematicの切り替え
        //-------------------------------
        public void OnToggleSimulate(bool flag)
        {
            simulate = flag;
            Simulate(simulate);
        }
 
        void Simulate (bool flag)
        {
            puppets.ForEach(puppet =>
            {
                puppet.body.isKinematic = !flag;
            });
        }
 
        //----------------------------------
        //ゲーム画面でドラッグした時に線を引く
        //-------------------------------------
        void OnRenderObject()
        {
            GL.PushMatrix();
 
            lineMat.SetPass(0);
 
            GL.Begin(GL.LINES);
            GL.Color(Color.black);
 
            for(int i = 0, n = points.Count; i < n - 1; i++)
            {
                GL.Vertex(points[i]);
                GL.Vertex(points[i + 1]);
            }
 
            GL.End();
            GL.PopMatrix();
        }
 
 
        //---------------------------------------------------
        //生成したオブジェクトを取り付ける親オブジェクトの選択
        //--------------------------------------------------
        public void OnRightHand()
        {
            parent = GameObject.Find("unitychan/Character1_Reference/Character1_Hips/Character1_Spine/Character1_Spine1/Character1_Spine2/Character1_RightShoulder/Character1_RightArm/Character1_RightForeArm/Character1_RightHand").gameObject;
            Debug.Log("a" + parent.name);
        }
 
 
 
 
    }
 
 
 
 
 
}
 

取り付けるモデルの配置

モデルは何でもいいので、今回はユニティーちゃんを使用しました。また、体の部位を選択するボタン「SelectBodyButton」を作成しました。これを押すと、選択した体の部位に、手書きのオブジェクトが子オブジェクトとしてセットされます。

では、TeddyRuntimeDemo.cs内に以下のコードを足します。

グローバル変数

//生成したオブジェクトを取り付ける親オブジェクト
GameObject parent;

Create () メソッドの最下部

SetParent(parent.transform, true); でpearent オブジェクトの子にgoを配置できます。

//goを指定したオブジェクトの子に配置
if(parent != null)
{
    go.transform.SetParent(parent.transform, true);
 
}
else
{
    Debug.Log("部位を選択してください。");
}

新規メソッド OnRightHand()

//---------------------------------------------------
//生成したオブジェクトを取り付ける親オブジェクトの選択
//--------------------------------------------------
public void OnRightHand()
{
    parent = GameObject.Find("unitychan/Character1_Reference/Character1_Hips/Character1_Spine/Character1_Spine1/Character1_Spine2/Character1_RightShoulder/Character1_RightArm/Character1_RightForeArm/Character1_RightHand").gameObject;
    Debug.Log(parent.name);
}

ここではユニティちゃんの右手を取得しています。

(Optimize Transform Hierarchy を使えば見た目の階層を減らせます)

最後に、TeddyRutimeDemo.csのOnRightHand () をSelectBodyButtonに取り付けてください(※正確にはSelectBodyButton/RightHand のButtonコンポーネントです。SelectBodyButtonは空オブジェクトなので)。

これで準備完了です。実行すると、描いた絵がユニティーちゃんの右手の子オブジェクトとして張り付きます。

ホームへ戻る