質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.47%

  • C#

    7437questions

    C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

  • Unity

    4203questions

    Unityは、ユニティテクノロジーが開発したゲームエンジンです。 主にモバイルやブラウザ向けのゲーム製作に利用されていましたが、3Dの重力付きゲームが簡単に作成できることから需要が増え、現在はマルチプラットフォームに対応しています。 言語はC言語/C++で書かれていますが、C#、JavaScript、Booで書かれたコードにも対応しています。

Unityでのコルーチンを用いて別々に処理を行いたい

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 508

kaz2zak

score 23

実現したいこと

カメラのズームと移動を切り離して実行したい。

問題点

現在、GearVRのコントローラーのタッチパッドからの入力を使用して、カメラが物体の回りを球体座標内で回転・ズームすることができる、カメラの動きに関するスクリプトを書いています。

回転:タッチパッドをクリックしている間、4分割した方向に合わせて回転
ズーム:トリガー+上クリックでズームイン、トリガー+下クリックでズームアウト
※タッチパッドは上・下・右・左に相当するように4分割にしています

ズームインをしようとトリガーと上クリックしても、回転処理が優先され、回転の動きが止まってから(y軸方向はClampで制限)ズームの処理が始まってしまいます。
そのため、カメラが物体の正面方向を向いている状態でズームインをしても、物体の上方向を向いてからのズームとなり、意図したズームの動きとは異なってしまいます。

試したこと

コルーチンを用いて、回転とズームを切り離して処理を行うようにすれば解決できるのではないかとスクリプトを書いてみましたが、一向に改善されません。
該当スクリプトは以下の通りです。
プログラミング初心者になるので見にくいかもしれませんがご容赦ください。
解決方法だけでなく、現在のスクリプトでもっとこうしたら良くなるとかもありましたらご教示いただければ幸いです。
よろしくお願い致します。

public class CoroCam : MonoBehaviour
{

    public Transform target;
    public float spinSpeed = f;
    public float distance = 1.f;
    public float diffR = 0.f;

    Vector3 nowPos;
    Vector3 pos = Vector3.zero;
    public Vector2 pad = Vector2.zero;

    Transform currentTarget;
    Vector3 currentTargetCenter;
    Vector2 touchPosition;

    bool isZooming = (OVRInput.Get(OVRInput.Button.PrimaryIndexTrigger));


    void Start()
    {
        // 初期位置の取得
        nowPos = transform.position;
    }


    void Update()
    {
        StartCoroutine(CamZoom());
        StartCoroutine(CamRoatation());

        UpdateCurrentTarget();
        transform.position = pos + currentTargetCenter;
        transform.LookAt(currentTargetCenter);
    }




    IEnumerator CamRoatation()
    {
        if(isZooming)
            yield break;


        if (!isZooming)
        {
            if (OVRInput.Get(OVRInput.Button.PrimaryTouchpad))
            {
                if (OVRInput.Get(OVRInput.Touch.PrimaryTouchpad))
                {
                    touchPosition = OVRInput.Get(OVRInput.Axis2D.PrimaryTouchpad);


                    //極座標系内での移動
                    if (touchPosition.y / touchPosition.x > 1 || touchPosition.y / touchPosition.x < -1)
                    {
                        if (touchPosition.y > 0)
                        {
                           pad.y += spinSpeed / 60;
                        }
                        else
                        {
                            pad.y -= spinSpeed / 60;
                        }
                    }
                    else
                    {
                        if (touchPosition.x > 0)
                        {
                            pad.x += spinSpeed / 60;
                        }
                        else
                        {
                            pad.x -= spinSpeed / 60;
                        }
                    }

                }
            }

        }



        pad.y = Mathf.Clamp(pad.y, 0.f, 0.f);

        pos.x = distance * Mathf.Sin(pad.y * Mathf.PI) * Mathf.Cos(pad.x * Mathf.PI);
        pos.y = distance * Mathf.Cos(pad.y * Mathf.PI);
        pos.z = -distance * Mathf.Sin(pad.y * Mathf.PI) * Mathf.Sin(pad.x * Mathf.PI);

        pos *= nowPos.z;
        pos.y += nowPos.y;

        yield return null;
    }



    IEnumerator CamZoom()
    {
        if (OVRInput.Get(OVRInput.Button.PrimaryTouchpad))
        {
            if (OVRInput.Get(OVRInput.Touch.PrimaryTouchpad))
            {
                touchPosition = OVRInput.Get(OVRInput.Axis2D.PrimaryTouchpad);

                if ((OVRInput.Get(OVRInput.Button.PrimaryIndexTrigger) && ((touchPosition.y / touchPosition.x > 1 || touchPosition.y / touchPosition.x < -1))))
                {
                    if (touchPosition.y < 0)
                    {
                        distance += diffR * Time.deltaTime;
                    }
                    if (touchPosition.y > 0)
                    {
                        distance -= diffR * Time.deltaTime;
                    }
                }               
            }
        }
         distance = Mathf.Max(distance, 0.f);

        yield return null;
    }
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+1

まず、やりたいことは下記のコードでできそうでしょうか。試してみてください。
GearVRを持っていないのでキーボード操作に置き換えています。

動画1
動画2(上下移動の制限をつけたもの)

using UnityEngine;

// カメラにアタッチする
public class CameraController : MonoBehaviour
{
    // 各種パラメーターはインスペクターから設定する
    public Transform target;                // カメラはこの物体の周りを回転する
    public float zoomSpeed = 10f;           // ズームの速度
    public float minimumDistance = 1f;      // 物体との最小距離(これ以上接近しない)
    public float rotateSpeed = 50f;         // 回転の速度
    public float verticalMoveSpeed = 10f;   // 上下移動の速度
    public float yMin = 0f;     // 【追記】上下移動の下限
    public float yMax = 6f;     // 【追記】上下移動の上限

    private void Update ()
    {
        // 対象の物体とカメラとの相対座標
        var diff = target.position - transform.position;

        // Aキーで物体に接近する(最小距離よりは接近しない)
        if ( Input.GetKey ( KeyCode.A ) && diff.sqrMagnitude > Mathf.Pow ( minimumDistance, 2 ) )
        {
            transform.position = Vector3.MoveTowards ( transform.position, target.position, zoomSpeed * Time.deltaTime );
        }
        // Sキーで物体から遠ざかる
        else if ( Input.GetKey ( KeyCode.S ) )
        {
            // カメラを挟んで、物体とは反対側の座標
            var otherSide = target.position - diff * 2;

            transform.position = Vector3.MoveTowards ( transform.position, otherSide, zoomSpeed * Time.deltaTime );
        }

        // 左右キーで物体の周りを回転する
        if ( Input.GetKey ( KeyCode.LeftArrow ) )
        {
            transform.RotateAround ( target.position, Vector3.up, rotateSpeed * Time.deltaTime );
        }
        else if ( Input.GetKey ( KeyCode.RightArrow ) )
        {
            transform.RotateAround ( target.position, -Vector3.up, rotateSpeed * Time.deltaTime );
        }

        //上下キーでカメラを上下に移動する
        if ( Input.GetKey ( KeyCode.UpArrow ) )
        {
            if ( transform.position.y < yMax )  // 【追記】ifブロック追加
            {
                transform.Translate ( Vector3.up * verticalMoveSpeed * Time.deltaTime );
            }
        }
        else if ( Input.GetKey ( KeyCode.DownArrow ) )
        {
            if(transform.position.y > yMin )    // 【追記】ifブロック追加
            {
                transform.Translate ( -Vector3.up * verticalMoveSpeed * Time.deltaTime );
            }
        }

        // 物体を見る
        transform.LookAt ( target );
    }
}

もともとのスクリプトで気になった点をいくつか挙げておきます。

  • 上記のとおり、今回はおそらくコルーチン不要だと思いますが、Update内で毎フレーム無条件にコルーチンを呼び出すような書き方は意図したとおり動作しないことが多いです。
  • if 文が重なって深い階層になっているので、階層を減らす工夫をすると読みやすくなるかもしれません。
    参考:コードのネストを深くするな

以上です。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/04/19 23:26

    negitama様
    回答ありがとうございます。
    少し苦戦しましたが、ズームをしても、回転の処理が加わることなく、別個に処理することが可能になりました。
    ありがとうございます!
    Update内でのコルーチンは良くないと見たので気になってははいたのですが、そもそもコルーチンをする必要がなかったのですね!

    3点質問があります。
    ①このスクリプトはどこがミソになるのでしょうか。
     Update内でズーム→左右移動→上下移動の順番で処理をすれば、ズーム処理の際に上下移動が干渉しないかと、コルーチンを使わず書き直してみたのですが、元と同じでした。。そんな安直な理由ではないのですね。。

    ②ズームが上手く挙動しません。
     targetのpivotの座標の影響を受けているのか、ズーム自体はできるのですが、オブジェクトの上に吸い寄せられるようにズームインされてしまいます。
     以前は、対象のオブジェクトのpivotでの中心座標が物体よりも随分と上方にあるので、ポリゴンの中心を新たに求めて、ポリゴンの中心にカメラが向くように処理をしていました。
    現在のスクリプトでは、ズームの処理だけtargetの計算前のデータを用いて計算していることが原因ではないかと思うのですが、解決できません。

    ③上下方向の移動の制限を設けたいのですが、どうすれば良いでしょうか。
     現在のスクリプトでは真上・真下まで移動するので制限を設けたく、Vector3.upをClampで範囲を指定できないものかと試みましたが、エラーが出てしまいました。

    質問ばかりで申し訳ありません。回答頂ける質問だけでも大変ありがたいです。
    よろしくお願い致します。
    解決方法の方に現在のスクリプトを貼りますので、見て頂ければ幸いです。

    キャンセル

  • 2018/04/19 23:43 編集

    まず、さきほどの回答にこちらで録画した動画へのリンクを追加したので、イメージどおりか確認してみてください。

    キャンセル

  • 2018/04/20 00:05 編集

    取り急ぎ、①と③に回答します。

    回答 ①
    ズーム・左右移動・上下移動をスクリプト内に順序は関係ありません。今回の場合、それぞれ独立して動作しているためです。

    回答 ③
    まずは上限・下限の値を設定します。

    public float yMin = 0f; // 上下移動の下限
    public float yMax = 6f; // 上下移動の上限

    カメラの上下動の箇所で、if 文で制御するほうがシンプルかもしれません。

    if ( transform.position.y < yMax )
    {
    transform.Translate ( Vector3.up * verticalMoveSpeed * Time.deltaTime );
    }

    キャンセル

  • 2018/04/20 00:37

    質問② については内容をよく理解しきれていませんが、Update文の中での UpdateCurrentTargetの呼び出しをもっと前に、Updateの直後の行などにもってきても変化ありませんか?

    キャンセル

  • 2018/04/20 12:36

    negitama様
    早速の回答ありがとうございます。
    朝からドツボにはまっています。。
    動画ありがとうございます。まさに自分がやりたい動きと同じです。

    回答①に対して
    毎回transformで計算をし独立して動いているからなんですね。

    回答②に対して
    UpdateCurrentTargetをUpdate文の始めに移動してみましたが、改善されませんでした。。
    上の自己解決の方に動画をアップしますので見て頂けたらと思います。
    再生後、ズーム、元の位置に戻って上に移動となっています。

    回答③に対して
    制限した範囲に行ってしまうと、そこから上下の移動が全く効かなくなってしまいます。
    また、制限をかけないと真上・真下に来た際に激しく回転を繰り返してしまいます。

    キャンセル

  • 2018/04/20 18:32

    もとの回答のほうに上下移動の制限をつけてみた動画へのリンクを追加したので確認願います。

    スクリプト内で上下移動の制限のために追記した箇所を【追記】として示しています(4箇所、昨夜)ので、再確認願います。
    そのうえで、制限をつけてみたときのスクリプトを見せてください。

    ※ なおこちらはGearVRを持っていないため、GearVR用のスクリプトを実際に動かして確認することができません。掲題の質問内容からも離れてきていますので、この質問に関するコメントは次あたりで切り上げさせていただきます。

    キャンセル

  • 2018/04/21 17:04

    大変遅れて申し訳ありません。
    色々とやってみて、問題はキーの共有によるものだと分かりました。
    親身に答えてくださってありがとうございました。

    キャンセル

0

追記
上段への追記となり申し訳ありません。
再生した際の挙動です。
[https://twitter.com/kazu_bou21/status/987174234538455041]

階層が深く見にくくなってしまい申し訳ありません。
今後見直しますので、今回は見逃してください。。

public class TeratailCam : MonoBehaviour {

    public Transform target;                // カメラはこの物体の周りを回転する
    public float zoomSpeed = 0.01f;           // ズームの速度
    public float minimumDistance = 0.1f;      // 物体との最小距離(これ以上接近しない)
    public float maxDistance = 5f;
    public float rotateSpeed = 60f;         // 回転の速度
    public float verticalMoveSpeed = 100f;   // 上下移動の速度

    public Vector2 pad = Vector2.zero;
    Vector2 touchPosition;
    Transform currentTarget;
    Vector3 currentTargetCenter;

    private void Update()
    {
        // 対象の物体とカメラとの相対座標
        var diff = target.position - transform.position;


        //A,Sキーでの入力をトリガー+上or下パッドでの入力に変換
        if (OVRInput.Get(OVRInput.Button.PrimaryTouchpad))
        {
            if (OVRInput.Get(OVRInput.Touch.PrimaryTouchpad))
            {
                touchPosition = OVRInput.Get(OVRInput.Axis2D.PrimaryTouchpad);

                if ((OVRInput.Get(OVRInput.Button.PrimaryIndexTrigger) && ((touchPosition.y / touchPosition.x > 1 || touchPosition.y / touchPosition.x < -1))))
                {
                    //遠ざかる
                    if (touchPosition.y < 0 && diff.sqrMagnitude > Mathf.Pow(minimumDistance, 0.1f))
                    {

                        var otherSide = currentTarget.position - diff * 2;

                        transform.position = Vector3.MoveTowards(transform.position, otherSide, zoomSpeed * Time.deltaTime);
                    }
                    //近づく
                    if (touchPosition.y > 0 && diff.sqrMagnitude < Mathf.Pow(maxDistance, 5f))
                    {

                        transform.position = Vector3.MoveTowards(transform.position, currentTarget.position, zoomSpeed * Time.deltaTime);

                    }
                }
            }
        }




        // 上下左右キーでの回転をコントローラーのタッチパッド入力に変換

        if (OVRInput.Get(OVRInput.Button.PrimaryTouchpad))
        {
            if (OVRInput.Get(OVRInput.Touch.PrimaryTouchpad))
            {
                touchPosition = OVRInput.Get(OVRInput.Axis2D.PrimaryTouchpad);


                //上下パッドでの移動
                if (touchPosition.y / touchPosition.x > 1 || touchPosition.y / touchPosition.x < -1)
                {
                    if (touchPosition.y > 0)
                    {
                        transform.Translate(Vector3.up * verticalMoveSpeed * Time.deltaTime);
                    }
                    else
                    {
                        transform.Translate(-Vector3.up * verticalMoveSpeed * Time.deltaTime);
                    }
                }
            }
        }


        //左右パッドでの移動
        if (OVRInput.Get(OVRInput.Button.PrimaryTouchpad))
        {
            if (OVRInput.Get(OVRInput.Touch.PrimaryTouchpad))
            {
                touchPosition = OVRInput.Get(OVRInput.Axis2D.PrimaryTouchpad);


                //上下パッドでの移動
                if (touchPosition.x / touchPosition.y > 1 || touchPosition.x / touchPosition.y < -1)
                {
                    if (touchPosition.x > 0)
                    {
                        transform.RotateAround(currentTarget.position, -Vector3.up, rotateSpeed * Time.deltaTime);
                    }
                    else
                    {
                        transform.RotateAround(currentTarget.position, Vector3.up, rotateSpeed * Time.deltaTime);
                    }
                }
            }
        }


        UpdateCurrentTarget();
        // 物体を見る
        transform.LookAt(currentTargetCenter);


    }

    void UpdateCurrentTarget() // 
    {
        if (target != currentTarget)
        {
            currentTarget = target;

            RecalculateCurrentTargetCenter(); // 中心点再計算部分を別メソッドに分離
        }
    }

    // 中心点再計算部分を別メソッドに分離
    void RecalculateCurrentTargetCenter()
    {
        if (currentTarget == null)
        {
            currentTargetCenter = Vector3.zero; // ターゲットがnullなら(0, 0, 0)を中心とする
        }
        else
        {
            Renderer[] renderers = currentTarget.GetComponentsInChildren<Renderer>();
            int rendererCount = renderers.Length;
            if (rendererCount > 0)
            {
                Bounds unitedBounds = renderers[0].bounds;

                for (int i = 1; i < rendererCount; i++)
                {
                    unitedBounds.Encapsulate(renderers[i].bounds);
                }

                currentTargetCenter = unitedBounds.center; // 結合したバウンディングボックスの中心を新たな中心点とする
            }
            else
            {
                currentTargetCenter = currentTarget.position; // レンダラーがないオブジェクトの場合、positionを中心とする
            }
        }
    }
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.47%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • C#

    7437questions

    C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

  • Unity

    4203questions

    Unityは、ユニティテクノロジーが開発したゲームエンジンです。 主にモバイルやブラウザ向けのゲーム製作に利用されていましたが、3Dの重力付きゲームが簡単に作成できることから需要が増え、現在はマルチプラットフォームに対応しています。 言語はC言語/C++で書かれていますが、C#、JavaScript、Booで書かれたコードにも対応しています。