VRCでUdonSharpのギミックを作るに当たっての基礎的な部分③【PlayerのTrigger関連】

前回はObjectのTrigger判定について軽く動作を確認しました。
今回はプレイヤーのTrigger判定について書いていきます。

# OnPlayerTrigger～　Triggerに入ったPlayerに対して行う処理

主に

**OnPlayerTriggerEnter(VRCPlayerApi player)※1
OnPlayerTriggerExit(VRCPlayerApi player)
OnPlayerTriggerStay(VRCPlayerApi player)**

の3種類があります。
オブジェクトのときと似ていますね。**挙動もほぼ一緒です。**
**引数**になんかめんどくさそうなのが入ってますね。あとでざっと書きます。

実は範囲内に入ったらONになるミラーの記事で軽くやってるけど、もうちょっと踏み込んでみましょう。

では引き続き前回のワールドを流用しつつ、コードを書いていきましょう。
スクリプトの名前は**PlayerTriggerTest**にしましょう。

ではちょっと背伸びしてjoinログ(みたいなもの)でも作ってみましょうか。
処理が煩雑になるので**同期**はしません。**Local**です。※2、※3
ほぼコピペで完結するのでコードに関しては大丈夫なはず。
**上の方が一部変わっていますので**コピペする方はちゃんと全文コピーしてくださいね

~~~C#

using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
using System;
using UnityEngine.UI;

public class PlayerTriggerTest : UdonSharpBehaviour
{
    public Text text;
    private DateTime dt;
    private string str;
    void Start()
    {
        str = "";
        dt = DateTime.Now;
    }
    public override void OnPlayerTriggerEnter(VRCPlayerApi player)
    {
        dt = DateTime.Now;
        str = dt.ToString("yyyy/MM/dd HH:mm:ss");
        str += " : 【";
        str += player.displayName;
        str += "】...Entered \n";
        text.text += str;
    }
    public override void OnPlayerTriggerExit(VRCPlayerApi player)
    {
        dt = DateTime.Now;
        str = dt.ToString("yyyy/MM/dd HH:mm:ss");
        str += " : 【";
        str += player.displayName;
        str += "】...Exited \n";
        text.text += str;
    }
   void OnPlayerTriggerStay(VRCPlayerApi player)
    {
        player.TeleportTo(player.GetPosition(), Quaternion.Euler(player.GetRotation().eulerAngles + new Vector3(0f, 1f, 0f)));
    }


}
~~~

ちょっとコードが長いし説明が冗長になるので、一旦動かしてみましょう。

**エリア内に入る、もしくはエリアから出るとログが表示されます。
エリア内に居ると勝手に回転します。**

ではUnityに戻ります。前に使ったオブジェクトは**床(Plane)以外**は全部非アクティブにしといてくださいね

1. Hierarchyで右クリック→3D Object→Cubeを行ってください。**Cubeを選択した状態にしてください。**
1. 右側Inspectorウィンドウ、Transform→Positionをx=0,y=0,z=0
1. 右側Inspectorウィンドウ、Transform→Scaleを   x=2,y=2,z=2
1. 右側Inspectorウィンドウ、一番下のAdd Component→検索欄にUdon→候補で出てきたUdon Behaviourを選択して追加
1. 右側Inspectowウィンドウ、Udon BehavourのProgram Sourceの欄に先ほど作成したPlayerTriggerTest.assetをドラッグ＆ドロップ

一旦ここまででエリアの設定は完了です。**赤枠の場所の数字とかが同じであればOKです。**
![_ptri1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2708808/9def55f3-9a39-55ff-a259-47f1020713a0.png)

次はログを表示する場所を作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2708808/a0b5e7cd-22bd-9521-8473-539bf83576af.png)

Hierarchy上で右クリック→UI→Textを選択してください。

**Canvas**と**Text**というものが作成されます。
HierarchyからCanvasを選択して、赤枠の場所を以下のように設定してください。
1. 赤枠1の場所のRender modeをWorld Spaceに(変えないと以下の部分が変更できません、ついでに見えません)
2. 赤枠2の場所の数値をそれぞれPos x=0,Pos y = 0 Pos z =5, Width = 0, Height = 0　に設定
![_ptri2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2708808/25e69d48-c619-bb4e-7a83-66293992f1e7.png)

次はHierarchyからTextを選択して、赤枠の場所を以下のように設定してください。
1. Rect Transformの値をPos X = -1.75,Pos Y = 3 ,Pos Z = 0, Width = 0,Height = 0に
1. Scaleのx,y,zの値をすべて0.001に
1. その下のテキストボックスに入っているNew Textの文字を削除
1. Font sizeを100に
1. ParagraphのHorizontal OverflowとVertical OverflowをそれぞれOverflowに設定



![_ptri3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2708808/d22e5a71-91a8-65c9-0c09-2841c70bda77.png)



はじめに作成したCubeのTextの欄に、今作成したTextをドラッグ＆ドロップすれば完了です。
![_ptri4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2708808/df2d6d81-f018-38f8-cbd7-f661a2e59ac6.png)

では起動してみましょう。
エリア内に入るとプレイヤーが回転します。そして時間と名前がログに表示されます。
エリアから出ると回転が止まります。時間と名前がログに表示されます。

![VCC_Test-s-PC_-Mac-_-Linux-Standalone-Unity-2019.4.31f1-Personal_-_DX11_-2023-02-28-23-51-26.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2708808/17c55c64-f67b-c8df-3030-3943be290466.gif)

ちゃんと動いていますね！


## 省略していたコードの内容解説

    using System;
    using UnityEngine.UI;

using System 現在時刻を取得するメソッドを使用するために記載します。※4
using UnityEngine.UI UI関連のメソッドを使用するために記載します。

    public Text text;
    private DateTime dt;
    private string str;

ワールド上で文字を表示するために、textという名前の**Text型のオブジェクトを作成する。**※5
時刻を取得するために、dtという名前の**DateTime型の変数を作成する。**※6
名前などを記録しておくために、**string**型の変数strを作成する。※7

別に名前は好きにつけちゃっていいです。全角はだめです。頭文字だけ取ったり最初の方の文字だけ取ることが多いです。
**大きいプロジェクトのときはちゃんと名前付けないとよくわからなくなるのでおすすめしません。**

~~~C#

    void Start()
    {
        str = "";
        dt = DateTime.Now;
    }
~~~
文字列strを""(空白)で**初期化**※8
変数dtを現在時刻で**初期化**。

    public override void　OnPlayerTriggerEnter(VRCPlayerApi player)
    {
    }

OnTriggerEnterとかのときと違って**public overrride**※9が頭についていますね。
Player系のメソッドにはだいたいついてるって思ってくれればいいです。
付けないと動作が変になるとか聞いた気がします

        dt = DateTime.Now;
        str = dt.ToString("yyyy/MM/dd HH:mm:ss");
        str += " : 【";
        str += player.displayName;
        str += "】...Entered \n";
        text.text += str;
 
1. 今の日付を取得する
1. 日付をyyyy/MM/dd HH：mm:ss 形式にして、文字列型の変数strに**代入*※10*する。(例2022/02/03 05:06:07みたいな表示)
1. 変数strの末尾に「　：【」を追加する
1. エリア内に入ったプレイヤーの名前を変数strの末尾に追加する
1. 変数strの末尾に「】...Entered」と追加し、末尾に**改行コード**※11を入力する
1. **テキストオブジェクトの、テキスト部分**の末尾に str の文字列を追加※12

これらの文字列をつなげると、**【日時:名前:Entered】** という表示になります
なお、最後に入力した改行コードのお陰で、出たり入ったりを繰り返すたびに、結果が改行されて追加されていきます。


    public override void OnPlayerTriggerExit(VRCPlayerApi player){
    }
次にこちらですが、中身はほぼ一緒なので解説する必要ないです。
Enterがエリア内に入ったときに処理が走りますが、こちらはエリア外に出たときに処理が走ります。

最後ですが

    player.TeleportTo(player.GetPosition(), Quaternion.Euler(player.GetRotation().eulerAngles + new Vector3(0f, 1f, 0f)));

長いですね。

    player.TeleportTo(Vector3 position,Quaternion rotation);※13、※14

**rotationの方向を向かせてpositionの位置にプレイヤーを飛ばす。**

そして式の中身ですが、

    player.GetPosition()

プレイヤーの位置を取得します。

    Quaternion.Euler(player.GetRotation().eulerAngles + new Vector3(0f, 1f, 0f))

1. プレイヤーの位置を取得し、Vector3に変換します。
1. Vector3 (0f,1f,0**f**) を加算します。y軸に加算しているので、上から下の軸を中心に回す感じ。※15
1. Quaternion型に直します。

という意味です。


    player.TeleportTo(player.GetPosition(), Quaternion.Euler(player.GetRotation().eulerAngles + new Vector3(0f, 1f, 0f)));

最終的に、このコードは
**プレイヤーの位置を『プレイヤーの現在いる位置、プレイヤーの現在向いている向きをy方向に少しずらした状態にして』ワープさせる**
ということになります。
現在位置が変わらないので単に回転してるだけのように見えますが、実際は現在地点と同じ場所にワープさせられています。

>※1 VRCPlayerApi
>https://docs.vrchat.com/docs/players
>↑公式の説明書。英語強い人ならいけるとおもう。
>名前とか位置、VRかどうかなど色々な情報が取得できる。

>※2 同期
> 2人以上のプレイヤーで状態が同じに保たれること。みたいな感じの意味。

>※3 ローカル(Local)
> 同期が取れていない、同期を取っていない状態。それぞれのプレイヤーでオブジェクトの位置が別々になっていたりする。
> VRCでプレイヤーが無を持っていたりしたらそのせいかも。

>※4 using 初期状態だと最低限使用するものしか定義されていないので、必要に応じて使えるメソッドなどを増やしたいとき、
>using ○○とコードの先頭(**クラス名**の前)に記載するとその記載した**クラス**に応じてメソッドが使えるようになる。
>
>**クラス(Class)**
>ググったら雛形って書いてあったけどどうにもしっくりこないので、属性とか種類とかそういうイメージのものって感じ。
>非常に長くなるので省略します。勉強したい方、もしくは説明してほしい！っていう方いればtwitterにでも書いといて
>オブジェクト指向でググれば出てくるので説明しなくてもいいって思ってます

>※5 **Text型**
>その名前の通り。テキストの表示ができる。
>サイズ・フォント・色などが変更できる。
>デフォルトのフォントサイズだとガビガビになるので、フォントサイズを大きくしてScaleでサイズ調整するといいかも

>※6 **DateTime型**
>日時を保存できる。
>.ToStringと後ろにつけると文字列にできるので、上記のTextに入れられるようになる。

>※7 **string型**
>文字列を保存できる。名前とか数字とか。数字は文字列にすると足し算が変になりますので注意。

>※8 **初期化**
>開始時、**宣言**した変数・オブジェクトに何かしらのデータを入れること。
>publicとつけてる場合はUnity側で入れられるので気にしなくていいです。
>というかUnity側で好きなの入れたいときだけpublicにしといたほうがいいと思う
>
>privateの場合は基本的に初期化すること。初期化しないと変なエラー出ます。

>※9 override 別のClassのメソッドを上書きするという意味。
>大本の処理は○○だけど△△の処理に直します！的な感じ。
>なんでPlayer系のメソッド全部に付いてるのかはわかりません。

>※10 **代入** 変数に値を入れること。初期化も代入と似た感じ。

>※11 文字列では改行は行えないので、改行コードを入力することで無理やり改行させる。

>※12　textオブジェクトのtext要素、つまりここに文字列を追加していくということ。
>![_ptri5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2708808/209897ab-e9b8-2e11-acdd-bce067ae1d3d.png)

>※13 Vector3　(x,y,z)で表される3つの数値。
>足し算や引き算ができる。掛け算はややこしいしあまり使わないのでまだ不要です。
>例　(1,2,3) + (3,5,7) = (4,7,10)みたいな。
>宣言は　**Vector3 name = new Vector3(x,y,z)で行える。**(nameの欄に好きな名前使って)

>※14 **Quaternion** Unityでオブジェクトの回転を取得すると必ずこいつで入ってきます。
>　説明が非常に厄介で筆者も勉強する気が起きないので、一旦は無理やりVector3型に直しましょう。
>**Quaternion qua = Quaternion.Euler(Vector3 vec)**　で直すことが出来ます。
>逆に、Vector3型をQuaternion型に変換したい場合は
>**Vector3 vec = qua.eulerAngles** のように変換したいQuaternion型変数の後ろに**.eulerAngles**とつければOK！

>※15 **f** float型(小数)を使用する際に数字の末尾につける。
>**Udonsharpで小数を使うときにfを付けないとUnityに怒られるので、必ず付けましょう。
>整数表記でも小数として使いたいときは付けましょう。**