【VRC】オブジェクトを自分の前に配置する。

# 1 この記事は何?

VRchatにて、自プレイヤーのz0[m]前、ピッチ角θ0の位置に常にオブジェクトを配置する方法を説明します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150223/553965de-a848-e43f-63d3-a780ef3d9ab6.png)

# 2　どのように動作するの？

２つのオブジェクト「TEST_PANEL」と「MYOBJ」をが配置されております。オブジェクト「TEST_PANEL」の表示ボタンをクリックすると「MYOBJ」がプレイヤーのz0[m]前、ピッチ角θ0、に常に表示されます。プレイヤーが移動しても常に「MYOBJ」がプレイヤーのz0[m]前、ピッチ角θ0に表示されます。オブジェクト「TEST_PANEL」の「表示」ボタンをクリックすると、オブジェクト「MYOBJ」を表示させます。再度「表示」ボタンをクリックすると、MYOBJは消えます。
「
![420.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150223/0ecbcc9a-3e8b-6c09-1100-d38a3df4ab26.jpeg)


# 3　設計方法

# 3-1 オブジェクトの設計方法

オブジェクトのデザイン方法は下記に詳しく記事にしております。

https://qiita.com/NT1123/items/f2b75382e6899f807e48


オブジェクト「TEST_PANEL」をデザインします。必要なボタン、テキストオブジェクトを配置したあと、InspectorにUdon Berhaiviorを配置し、TEST_PANEL.csを配置します。TEST_PANEL.csの中身は、次節の3-2コードの設計に記載しております。
![421.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150223/6b079494-a2ec-5f3d-e13f-f3e4243e5ddc.jpeg)
オブジェクト[TEST_PANEL」内に配置したボタンに対し、TEST_PANELをINspector内 On Click()内に配置、ボタンをクリックしたときに呼び出すメソッド「Pressed」を入力します。
![423.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150223/b232fa0a-50c0-fb8f-a47a-29f08d595176.jpeg)
オブジェクト「MYOBJ」をデザインします。必要なボタン、テキストオブジェクトを配置します。
![422.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150223/9697f692-46d4-b8b2-fd18-bae3bf9d5940.jpeg)
TEST_PANEL.cs内の変数と各種オブジェクトのアタッチメントを行います。
![424.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150223/0bf1b9d1-1f17-43a5-81d3-e6fe0520b47c.jpeg)


# 3-2 コードの設計方法

下記に記載のコードは、パネルの位置を制御するコードです。(TEST_PANEL.cs)
まず、プレイヤーのアバターheadの位置と角度(Yaw,Roll,Picth)がワールド座標原点を基準にして求めます。


```C#:TEST_PANEL
using UdonSharp;
using UnityEngine.UI;
using VRC.SDKBase;
using UnityEngine;


public class Tracecode : UdonSharpBehaviour
{

    [SerializeField] Text displayP1; //アバターheadの座標を表示するテキスト
    [SerializeField] Text displayP2; //アバターheadのオイラー角を表示するテキスト
    [SerializeField] Text displayP3; //対象オブジェクトの座標を表示するテキスト
    [SerializeField] Text displayP4; //オブジェクトの回転量
    [SerializeField] Text displayMode; //オブジェクトの回転量
    private Vector3 headP; //アバターheadの座標を格納する変数
    private Quaternion headR,rotOBJ; //headP:アバターheadのオイラー角, rotOBJ:オブジェクトの回転角
    public int RotX; //RotX:オブジェクト回転角(X軸)
    public GameObject Myobj;//オブジェクト自体を定義する
    public bool mode=false;

    //アバターの初期座標を取得する。
    void Start()
    {
        zahyouget();
        displayMode.text = mode.ToString();
        
    }


    void Update()
    {
        //初期位置の設定
        var player = Networking.LocalPlayer;
        var headData = player.GetTrackingData(VRCPlayerApi.TrackingDataType.Head); //自分自身の座標を取得する。

        headP = headData.position; //アバターheadの座標を返す。
        headR = headData.rotation; //アバターheadのQuaternionを返す。

        Vector3 rotEuler = headR.eulerAngles; //アバターheadのQuaternionをオイラー角に変換する
        rotEuler.x = RotX; //x軸オイラー角のみ、回転する角度を指定する。もしこの行がない場合、オブジェクトは常にアバターの視点方向に表示される。
        rotOBJ = Quaternion.Euler(rotEuler); //オイラー角をクォータニオ ンに戻し、プレイヤー座標とずらす距離を計算する
        Myobj.transform.position = headP + rotOBJ * new Vector3(0, 0, 0.5f); //アバターheadの位置を中心とし、z軸方向に0.5m移動、かつオイラー角(RotXで指定した数値,アバターの視点軸角度,アバターの視点軸角度)方向に回転する。
        Vector3 myObjloc = Myobj.transform.position;

        //座標表示
        Myobj.transform.rotation = headR;
        displayP1.text = headP.ToString();　 //アバターheadの座標を表示する
        displayP2.text = rotOBJ.ToString();　//アバターheadのオイラー角を表示
        displayP3.text = myObjloc.ToString();　 //対象オブジェクトの座標を表示
        displayP4.text = rotEuler.ToString();　//オブジェクトの回転量

    }

    public void Pressed()
    {
        mode = !mode;
        Myobj.SetActive(mode);
        displayMode.text = mode.ToString();
    }


    public void zahyouget()
    {
        //初期位置の設定
        var player = Networking.LocalPlayer;

        if (player != null)
        {
            var headData = player.GetTrackingData(VRCPlayerApi.TrackingDataType.Head); //自分自身の座標を取得する。
            headP = headData.position; //アバターheadの座標を返す。
            headR = headData.rotation; //アバターheadのオイラー角を返す。
        }
    }

}

```


- コードの解説
まず、プレイヤーのアバターheadの位置と角度(Pitch,Yaw,Roll)がワールド座標原点を基準にして求めます。           
プレイヤーのアバターheadの角度 headR=(Pitch,Yaw,Roll)の順番でベクトルとして並んでいます。


```C#:detail0
 var headData = player.GetTrackingData(VRCPlayerApi.TrackingDataType.Head); //自分自身の座標を取得する。
 headP = headData.position; //アバターheadの座標を返す。
 headR = headData.rotation; //アバターheadのオイラー角を返す。
```

(オブジェクトの表示位置)=ワールド原点 + プライヤーアバターhead座標 + オフセット値(x,y,z軸,Pitch,Yaw,Roll)で指定します。


ちなみに下記の図の場合のオブジェクトの配置位置は、①ワールド原点→ ②アバターhead位置 → オフセット移動(0,0,z0) → アバターheadを基準にしアバターheadの(Pitch,Yaw,Roll)と同じ量ほど回転させる 。


![419.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150223/aa545959-fea7-9528-3fa8-823a4d3849e9.jpeg)
これをコードで表現したものが下記になります。ここでは、Pitchは固定量(θ0), Roll,YawはアバターheadのRoll値,Yaw値と同じに設定すると
常にオブジェクトが正面からθ0ほどPitch方向に回転させた位置にオブジェクトが配置されます。

```C#:detail1
 Myobj.transform.position = headP + rotOBJ * new Vector3(0, 0, 0.5f);
```


オブジェクトは下記図のように表示されます。
![425.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150223/50c35086-3ceb-b3ae-f2aa-50577585122d.jpeg)