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

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

ただいまの
回答率

88.81%

unity 地面までの方向ベクトルを表示させたい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,257

eggpol

score 60

やりたいこと↓
イメージ説明

カメラからみたターゲットから地面(X-Z平面)までの方向ベクトルを取得しUIに矢印として表示させたいです。
Vectro3.downで取得した方向を2Dに変換すればいいのは分かりますが具体的な方法が分かりません。
画面に表示する矢印はなるべく端に表示させたいです。

よくわからないの教えて下さい。

  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

質問への追記・修正、ベストアンサー選択の依頼

  • eggpol

    2019/05/10 23:44

    だめでした・・・
    同じところでエラーが出ますね。
    unityのバージョンは2017.2.0f3 Visualstudioを使っています。
    Vector2同士って掛け算できないんでしょうか
    リファレンス見る限りだとpublic static Vector2 operator * (Vector2 a, float d);
    となっていました。

    Assets/teach2/Graphic.cs(106,22): error CS0019: Operator `*' cannot be applied to operands of type `UnityEngine.Vector2' and `UnityEngine.Vector2'
    Assets/teach2/Graphic.cs(114,21): error CS0019: Operator `*' cannot be applied to operands of type `UnityEngine.Vector2' and `UnityEngine.Vector2'

    var p = (vertex * headScale) + headOffset;
    var p = vertex * shaftScale;

    キャンセル

  • Bongo

    2019/05/11 00:28 編集

    Vector2のリファレンスを一番下までスクロールすると出てくるpublic static Vector2 operator * (Vector2 a, Vector2 b);を使いたかったのですが、先ほど過去バージョンのリファレンスを見てみたところ2017.4以前には載っていませんね...

    ともかくエラーが出るからには、Vector2同士をそのまま掛け算するのはあきらめて
    var p = new Vector2(vertex.x * headScale.x, vertex.y * headScale.y) + headOffset;
    var p = new Vector2(vertex.x * shaftScale.x, vertex.y * shaftScale.y);
    でどうでしょう。Vector2同士の乗算演算子でも内部的には同様になっているようですので、このように書き換えても動作上問題はないかと思います。

    キャンセル

  • eggpol

    2019/05/11 18:13

    できました。ありがとうございます。
    Vector2同士の掛け算は
    リファレンスを確認してみたところ 2017.4verではできなくて2019.1verではできるんですね。
    バージョンが違うことで演算子のオーバーロード?が変わることは今まで遭遇したことがなかったのでいい経験になりました。

    ここも直しました。
    自分用
    p = new Vector2((((p * 0.5f) + new Vector2(0.5f, 0.5f)) - pivot).x * size.x, (((p * 0.5f) + new Vector2(0.5f, 0.5f)) - pivot).y * size.y);

    p = new Vector2((((p * 0.5f) + new Vector2(0.5f, 0.5f)) - pivot).x * size.x, (((p * 0.5f) + new Vector2(0.5f, 0.5f)) - pivot).y * size.y);

    キャンセル

回答 1

checkベストアンサー

+1

ワールド空間における方向ベクトルがすでにVector3.downだと決定しているのなら、おっしゃる通りそれをカメラやスクリーンに合わせた座標系に変換してやればいいでしょう。これはUnityが提供している座標系変換用メソッドを使えばよさそうですが、悩みどころはむしろUI上に矢印をどうやって描くかという点かもしれませんね。

一例として、矢印描画用の独自UI要素を作る方針でやってみました。

#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

[ExecuteInEditMode]
public class DirectionArrow : Graphic
{
    // 矢印の原型となる頂点の位置、およびそれらを繋ぐ順番
    private static readonly Vector2[] headVertices =
    {
        Vector2.up + (Vector2.left * 0.5f),
        Vector2.right,
        Vector2.zero,
        Vector2.down + (Vector2.left * 0.5f)
    };
    private static readonly int[][] headIndices =
    {
        new[] {0, 1, 2},
        new[] {3, 2, 1}
    };
    private static readonly Vector2[] shaftVertices =
    {
        Vector2.zero,
        Vector2.up + Vector2.right,
        Vector2.down + Vector2.right
    };
    private static readonly int[][] shaftIndices =
    {
        new[] {0, 1, 2}
    };

    // 各部位の大きさ
    // RectTransformの半分の大きさを1.0とする比率で指定する
    public float arrowLength = 1.0f; // 矢印全体の長さ
    public float shaftThickness = 0.0625f; // 軸の太さ(逆三角形のため、矢じりとの接続部の太さということになる)
    public float headLength = 0.25f; // 矢じりの先端から矢じりと軸の接続部までの長さ
    public float headSize = 0.25f; // 軸に対して垂直な方向の矢じりの大きさ

    public Vector3 directionVector = Vector3.down; // ワールド座標系における矢印の向き
    public Transform directionCameraTransform; // ワールド方向をビュー方向に変換するのに使う(シーンを撮影しているカメラをセットしておく)

#if UNITY_EDITOR
    // メニューの「GameObject」→「UI」→「Direction Arrow」でオブジェクト作成
    [MenuItem("GameObject/UI/Direction Arrow")]
    public static void CreateDirectionArrow()
    {
        var canvas = FindObjectOfType<Canvas>();
        if (canvas == null)
        {
            canvas = new GameObject("Canvas", typeof(Canvas)).GetComponent<Canvas>();
            canvas.gameObject.AddComponent<CanvasScaler>();
            canvas.gameObject.AddComponent<GraphicRaycaster>();
            canvas.renderMode = RenderMode.ScreenSpaceOverlay;
        }

        if (FindObjectOfType<EventSystem>() == null)
        {
            var eventSystem = new GameObject("EventSystem", typeof(EventSystem));
            eventSystem.AddComponent<StandaloneInputModule>();
        }

        var directionArrow = new GameObject("Direction Arrow", typeof(CanvasRenderer), typeof(DirectionArrow));
        directionArrow.transform.SetParent(canvas.transform, false);
        Selection.activeGameObject = directionArrow;
    }

#endif

    /// <inheritdoc />
    protected override void OnPopulateMesh(VertexHelper vh)
    {
        vh.Clear();
        var vectorLength = this.directionVector.magnitude;
        if ((this.directionCameraTransform == null) || Mathf.Approximately(vectorLength, 0.0f))
        {
            return;
        }

        // directionVectorの向きをビュー空間の向きに変換し、さらに大きさと向きに分離する
        var arrowVector = this.directionCameraTransform.InverseTransformDirection(this.directionVector);
        var arrowDirection = ((Vector2)arrowVector).normalized;
        var arrowMagnitude = ((Vector2)arrowVector).magnitude * this.arrowLength;

        // 先端の奥行きに合わせた拡縮率を求める
        // 矢印が画面の奥・手前に傾いている場合、それをアピールするためスケールにこれを上乗せする
        var zScale = 1.0f / (1.0f + (arrowVector.z * 0.5f));

        // 矢印の原型の座標を目的の形に加工するための各種パラメーターを用意する
        var pivot = this.rectTransform.pivot;
        var size = this.rectTransform.rect.size;
        var shaftLength = Mathf.Max(arrowMagnitude - (this.headLength * zScale), 0.0f);
        var shaftScale = new Vector2(shaftLength, this.shaftThickness * zScale);
        var headScale = new Vector2(this.headLength, this.headSize) * zScale;
        var headOffset = new Vector2(shaftLength, 0.0f);
        var rotationX = new Vector2(arrowDirection.x, -arrowDirection.y);
        var rotationY = new Vector2(arrowDirection.y, arrowDirection.x);

        // 矢印の原型を加工し、vhに追加していく
        var vert = UIVertex.simpleVert;
        vert.color = this.color;
        foreach (var vertex in headVertices)
        {
            var p = (vertex * headScale) + headOffset;
            p = new Vector2(Vector2.Dot(rotationX, p), Vector2.Dot(rotationY, p));
            p = (((p * 0.5f) + new Vector2(0.5f, 0.5f)) - pivot) * size;
            vert.position = p;
            vh.AddVert(vert);
        }
        foreach (var vertex in shaftVertices)
        {
            var p = vertex * shaftScale;
            p = new Vector2(Vector2.Dot(rotationX, p), Vector2.Dot(rotationY, p));
            p = (((p * 0.5f) + new Vector2(0.5f, 0.5f)) - pivot) * size;
            vert.position = p;
            vh.AddVert(vert);
        }
        foreach (var index in headIndices)
        {
            vh.AddTriangle(index[0], index[1], index[2]);
        }
        var indexOffset = headVertices.Length;
        foreach (var index in shaftIndices)
        {
            vh.AddTriangle(index[0] + indexOffset, index[1] + indexOffset, index[2] + indexOffset);
        }
    }

    private void Update()
    {
        this.SetVerticesDirty();
    }
}

メニューからDirection Arrowを生成して色をつけ、メインカメラをセットし、背景となる円形イメージの上に配置して実行したところ、カメラの向きに応じて矢印の向きが変化しました。
実際のゲームに使うには矢印がシンプルすぎる気がしますので、もっと見栄えをよくする必要があるでしょう。

結果

bochan2さんからご指摘のあった凹凸のあるメッシュに対応するには、ターゲットに最も近いメッシュ上の点を見つけて、DirectionArrowdirectionVectorをターゲットから最近点への向きに書き換えることになるでしょうが、これはなかなかやっかいそうですね。
Collider.ClosestPointで3D空間上の1点に最も近いコライダー表面上の1点を得られるようですが、残念ながらこれは凹部分を持つメッシュには使用できないようです。地形メッシュはおそらく凹部分を持つでしょうから、別の手を使う必要があると思います。
Finding the closest point on a concave mesh (Unity C#) - Game Development Stack Exchangeでは...

  1. メッシュ上のすべての頂点を走査し、ターゲットに最も近い頂点を見つける。
  2. メッシュ上のすべてのポリゴンを走査し、その最も近い頂点を含んでいる三角形を抜き出す。
  3. 抜き出された三角形について、それぞれターゲットに最も近い三角形上の点を求め、それらのうち最もターゲットに近いものを選ぶ。

といった手順が提案されていました。凹凸付きメッシュに対応できないとゲームデザイン上まずいようでしたら、こういった方法を検討してみてはいかがでしょうか。ただし地形メッシュが多数の頂点を含む場合は、提案者の方が言及しているように不要な候補を早期に除去できるような最適化を行わないと、毎フレーム実行するには少々きついかもしれません。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/05/08 14:42

    回答ありがとうございます。まさにこの挙動が望んでいたものです。
    矢印の拡大縮小についてなのですが、これは何を意味しているのでしょうか?

    キャンセル

  • 2019/05/08 15:52

    視点を回したときに矢印の先端が大きくなったり小さくなったりする挙動のことでしょうか?

    ちょっと蛇足だったかもしれませんが、地面の方向が2Dの画面に対して奥にあるか手前にあるかを区別できるような、多少3Dっぽい表現にした方がいいかな...と思って付け加えたものです。とはいえ、方向の手かがりが乏しい宇宙空間や海中ならともかく、回答に載せた図のような一般的な地上なら「画面が地面を映していれば地面方向はカメラの前方、空を映していれば地面方向はカメラの背後」ということは映像を見れば伝わるでしょうから、わざわざ矢印を拡大縮小する必要はなかったかもしれませんね。

    拡大縮小をなくす場合はzScaleを削除して、さらにその少し後で出てくるshaftLength、shaftScale、headScaleの式の中で「 * zScale」とスケールを掛けている部分も削除してしまってください。

    キャンセル

  • 2019/05/08 21:21

    なるほど
    つまり空を向く(カメラの後方に地面が存在)ほど矢印が大きくなるということですね

    キャンセル

  • 2019/05/09 05:01

    はい、そういうことになりますね。

    それと、追記修正欄にもコメントしましたように視線が地面に対して垂直に近づくほど矢印が短くなるようにしてみましたが、
    var arrowMagnitude = ((Vector2)arrowVector).magnitude * this.arrowLength;

    var arrowMagnitude = this.arrowLength;
    に変更すると、この効果がなくなって矢印が常に円の外周を指すようになるはずです。

    キャンセル

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

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

関連した質問

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