[Unity]ワールド座標からスクリーン座標に変換したが、想定した動作にならない

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
sadora3
記事: 175
登録日時: 11年前

[Unity]ワールド座標からスクリーン座標に変換したが、想定した動作にならない

#1

投稿記事 by sadora3 » 6年前

まずは下のGIFを見て欲しいです。
https://dotup.org/uploda/dotup.org1378844.gif.html
今ロックオンをしている敵の位置に、ロックオンマーカーを表示させたいのですが、カメラが動くとロックオンマーカーの表示が敵からずれてしまいます。

CanvasのRenderModeは「ScreenSpace Camera」です。
そして、PlaneDistanceは1のまま、ロックオンマーカーをずれることなく表示させたいです。

今、スクリプトは下記のようになっています。

コード:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraManager : MonoBehaviour {
    [SerializeField]
    private GameObject PlayerObj;   //プレイヤーのゲームオブジェクト
    [SerializeField]
    private GameObject[] EnemyObj;  //敵のゲームオブジェクト
    [SerializeField]
    private GameObject LockOnObj;   //ロックオンのゲームオブジェクト
    [SerializeField]
    private Canvas mainCanvas;      //キャンバス

    private GameObject TargetObj;   //ロックオン対象のゲームオブジェクト

    void Start() {
        //CameraControlerオブジェクトをプレイヤーの位置へ
        transform.position = PlayerObj.transform.position;

        //カメラの位置調整と、角度をプレイヤーへ向かせる
        Camera.main.transform.position = new Vector3(0.0f, 5.0f, -10.0f);
        Camera.main.transform.LookAt(PlayerObj.transform);

        //ロックオン対象を決め、その方に向く
        TargetObj = EnemyObj[0];
        transform.LookAt(TargetObj.transform);

        //ロックオンマーカーの表示ON
        LockOnObj.SetActive(true);
    }

    void Update() {
        //CameraControlerオブジェクトをプレイヤーの位置へ
        transform.position = PlayerObj.transform.position;

        //右にいる敵にロックオンを切り替える
        if (Input.GetKeyDown(KeyCode.D)) {
            F("right");
        }
        //左にいる敵にロックオンを切り替える
        else if (Input.GetKeyDown(KeyCode.A)) {
            F("left");
        }

        //ロックオン対象の方にカメラを旋回させる
        transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(TargetObj.transform.position - transform.position), 0.1f);

        //ロックオンマーカーの座標更新
        LockOnObj.transform.GetComponent<RectTransform>().localPosition = ToScreenPositionCaseScreenSpaceCamera(TargetObj.transform.position, mainCanvas);
    }

    //ワールド座標から、スクリーン座標に変換
    private Vector2 ToScreenPositionCaseScreenSpaceCamera(Vector3 position, Canvas canvas) {
        var p = RectTransformUtility.WorldToScreenPoint(Camera.main, position);
        var retPosition = Vector2.zero;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(
            canvas.GetComponent<RectTransform>(),
            p,
            Camera.main,
            out retPosition
        );
        return retPosition;
    }

    //ロックオンの切り替え処理
    private void F(string str) {
        Vector3 TargetScreenPos = Camera.main.WorldToScreenPoint(TargetObj.transform.position);
        float TempPosX = TargetObj.transform.position.x;
        GameObject TempObj = null;

        for (byte i = 0; i < EnemyObj.Length; i++) {
            //プレイヤーの後ろにあるオブジェクトは対象にしない
            Vector3 Vec = EnemyObj[i].transform.position - PlayerObj.transform.position;
            float dot = Vector3.Dot(Camera.main.transform.forward.normalized, Vec.normalized);
            if(dot < -0.8f) {
                continue;
            }

            Vector3 ScreenPos = Camera.main.WorldToScreenPoint(EnemyObj[i].transform.position);

            //右にロックオンを切り替える
            if (str == "right" && TargetScreenPos.x < ScreenPos.x) {
                if (TempObj == null) {
                    TempObj = EnemyObj[i];
                } else if (Camera.main.WorldToScreenPoint(TempObj.transform.position).x > ScreenPos.x) {
                    TempObj = EnemyObj[i];
                }
            }
            //左にロックオンを切り替える
            else if (str == "left" && TargetScreenPos.x > ScreenPos.x) {
                if (TempObj == null) {
                    TempObj = EnemyObj[i];
                } else if (Camera.main.WorldToScreenPoint(TempObj.transform.position).x < ScreenPos.x) {
                    TempObj = EnemyObj[i];
                }
            }
        }

        if (TempObj != null) {
            TargetObj = TempObj;
        }
    }
}
どうすれば、ロックオンマーカーをずれることなく表示させることが出来るのでしょうか?

・Unityのプロジェクト
http://firestorage.jp/download/ba138d67 ... 8d649f7e74

・環境
ゲームエンジン:Unity2017.2.0f3
エディター:VisualStudio2015
OS:Windows10
言語:C#

sadora3
記事: 175
登録日時: 11年前

Re: [Unity]ワールド座標からスクリーン座標に変換したが、想定した動作にならない

#2

投稿記事 by sadora3 » 6年前

現状ロックオンマーカーをUIではなく、ビルボードとして扱うことでこの問題を回避出来ました。
が、根本的な解決にはなっていないので、もし分かる方がいれば、引き続き回答の方よろしくお願いします。

ちょっと質問が分かりにくかったかもしれないので補足です。
現状知りたいのは、CanvasのRenderModeが「ScreenSpace Camera」で、PlaneDistanceが1のときの、正しい「ワールド座標からスクリーン座標に変換」する方法です。
PlaneDistanceを100まで上げることで、ロックオンマーカーがずれることはなくなりますが、UIすべてが3Dオブジェクトよりも後ろに描画されてしまう問題が新たに発生してしまいます。
あと、上に提示してあるワールド座標からスクリーン座標に変換する処理は、カメラが動いてしまうとずれてしまいますが、ロックオン対象が動くのは、ずれることなく正常な変換が出来ています。
[追記]
最後の2行についてですが、主語がないため分かりにくくなってしまい申し訳ありません。
何がずれてしまうのかというと、ロックオンマーカー(つまり変換されたスクリーン座標)です。

返信

“C言語何でも質問掲示板” へ戻る