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

【Unity, Teddy, 3Dモデリング】カメラを移動したときのオブジェクト生成位置のずれを解消

2018-11-03
【Unity, Teddy, 3Dモデリング】カメラを移動したときのオブジェクト生成位置のずれを解消

はじめに

この記事は前回の記事の続きです。カメラを移動させたときに、手書きの円とオブジェクトの生成位置がずれる問題に取り組みます。

前回の記事

【Unity, Teddy】カメラを回転したときのオブジェクト生成位置のずれを解消

z軸方向のオブジェクト生成位置の補正

TeddyのDemoシーンでは、カメラを移動させるとオブジェクトの生成位置がずれるという問題がありました。

これを解消していきます。

TeddyRuntimeDemo.cs

        //シーン開始時のカメラのベクトル
        Vector3 initialCameraPosition;
 
        //-----------------------------------------
        //手書きのオブジェクトを生成する
        //------------------------------------------
        void Create (GameObject go, List<Vector2> screens)
        {
            //(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 => {
 
                //(zOffsetが0だと生成したオブジェクトの中身が見えるので、camera.nearClipPlaneでz軸方向の撮影平面位置を少しずらしている)
                return camera.ScreenToWorldPoint(new Vector3(screen.x, screen.y, camera.nearClipPlane + zOffset)); 
 
            }).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.transform.position = offsetPos;
 
        }

グローバル変数にVector3 initialCameraPosition; を追加して下さい。これはシーン開始時のカメラの位置ベクトルです。

次に、void Create() メソッド内に var offsetPos = camera.transform.position - initialCameraPosition; を追加し、ゲーム実行中にカメラが移動した分のベクトルを取得。

最後に、生成した go オブジェクトにoffsetPos を代入しています。

これにより、手書きの円とオブジェクトの生成位置のずれを解消できます。

これでz軸方向の補正はできました。

しかし、横方向へ移動させると上手くいきません。

x軸方向のオブジェクト生成位置の補正

カメラを横に移動すると、オブジェクトの生成位置が円からずれてしまいました。どうやら、go (Puppet)のx 座標を0にすれば、下の動画のように手書きの円の位置に来そうです。

では、先ほど追加した、「//カメラ移動した分のベクトルを取得」以下2行を削除し、以下のコードに変えてください。

            //カメラ移動した分のベクトルを取得
            var offsetPos = camera.transform.position - initialCameraPosition;
 
            //goをoffsetPositionの分だけ移動
            var goPos = go.transform.position;
            goPos = new Vector3(0f, goPos.y, offsetPos.z);
            go.transform.position = goPos;

これを追加すると、以下のように横方向のズレにも対応できます。


上記のコードは、goPosでgoの位置ベクトルを取得し、x座標を0、y座標はそのまま、z座標はカメラが移動したオフセットを代入しています。

ちなみに、直接 go.transform.position.x = 0f ; のように数値を代入しても、Unityの仕様上オブジェクトの位置は変化しないので、一旦 goPos というベクトルを生成し、goへVectorとして代入する必要があります。

詳しくはこちらの外部URLを参照してください。

【Unity】C#でtransform.positionの値を変更する

これで上下左右方向の補正はできました。

しかし、カメラを180°回転させたときにも補正が必要です。

前回、カメラを180°回転させても手書きの円内でオブジェクトが発生しましたが、今度はoffsetPos のz成分だけPuppet がずれてしまうのでこれを補正してあげる必要があります。

カメラが180°回転したときの補正

Create()内の「//カメラが移動した分のベクトルを取得」より下のコードを以下のコードに置き換えて下さい。

            //カメラ移動した分のベクトルを取得
            var offsetPos = camera.transform.position - initialCameraPosition;
 
            //goをoffsetPositionの分だけ移動
            var goPos = go.transform.position;
 
            //カメラのRotateの角度を取得
            var cameraQuaternion = camera.transform.rotation;
 
            if(cameraQuaternion.eulerAngles.y == 0f)
            {
                Debug.Log("0°");
            }
            //カメラがのy軸が180°のとき
            else if(cameraQuaternion.eulerAngles.y == 180f)
            {
                Debug.Log("180°");
 
                //カメラを基準にx軸対象にPuppetを移動させる
                offsetPos.z -= (offsetPos.z - camera.transform.position.z) * 2f;
 
            }
 
            goPos = new Vector3(0f, goPos.y, offsetPos.z);
            go.transform.position = goPos;
 

これでカメラのy軸のRotateが180°のときにPuppetが以下のように移動します。

実際に動いている映像はこちらです。

今回いろいろ書きましたが、TeddyRuntimeDemo.csの内容を全て削除し、以下をコピペすればオッケーです。

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;
        
        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)
        {
            //(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 => {
 
                //(zOffsetが0だと生成したオブジェクトの中身が見えるので、camera.nearClipPlaneでz軸方向の撮影平面位置を少しずらしている)
                return camera.ScreenToWorldPoint(new Vector3(screen.x, screen.y, camera.nearClipPlane + zOffset)); 
 
            }).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をoffsetPositionの分だけ移動
            var goPos = go.transform.position;
 
            //カメラのRotateの角度を取得
            var cameraQuaternion = camera.transform.rotation;
 
            if(cameraQuaternion.eulerAngles.y == 0f)
            {
                Debug.Log("0°");
            }
            //カメラがのy軸が180°のとき
            else if(cameraQuaternion.eulerAngles.y == 180f)
            {
                Debug.Log("180°");
 
                //カメラを基準にx軸対象にPuppetを移動させる
                offsetPos.z -= (offsetPos.z - camera.transform.position.z) * 2f;
 
            }
 
            goPos = new Vector3(0f, goPos.y, offsetPos.z);
            go.transform.position = goPos;
 
 
        }
 
        //-------------------------------
        //生成したオブジェクトの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();
        }
 
    }
 
 
 
}
 

まとめ

カメラを移動させたとき、オブジェクトの発生場所がずれる問題を解決した

ただし、カメラを90°回転させたときにオブジェクトが生成されない問題があります。

これは次回解決します。

忙しい人のためのUnity入門講座へ