VRChatのワールドでジャンプスケアを実装する

# ジャンプスケアを作ってみよう

色々とやってきましたがそろそろ実際に使えるやつ作っていきたいですよね

ホラワの定番のジャンプスケアを作ってみましょう。

ジャンプスケアと言っても2種類あるのかな

- 特定のエリアに入ったときとかに怖い顔とかがいきなり出てくる
- なにかアクションを起こしたときとかに目の前になんか出てくる

前者は**OnPlayerTriggerEnter**で怖い顔とかのオブジェクトをActiveにすればOKですね。
目の前になんか出てくるやつをやってみましょう。

## 1.画像とSEの準備

ということで怖い画像用意しました、今回はこれを使います。こわいですね。
![_001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2708808/bba66139-a156-eab6-aec3-8fc031187cce.png)
これを目の前に表示させるようにしましょう。
音も大事だと思うので、**SE**も用意しましょう。※1
フリー素材をダウンロードするのもいいですが、著作権の都合で一旦後回し。
Windowsのシステム音を使いましょう。
このファイルを使いましょう。多分みんな怖がります。

C:\Windows\Media\Windows システム エラー.wav
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2708808/fb874c21-28f2-559b-e0bc-edd4daca5e20.png)

**パス**はここになります。※2
場所だけ覚えといてください、ファイルをプロジェクトに入れるのは後でやります。

ではプロジェクトの方に移動します。
前回のプロジェクトを開いてください。
前回使ったオブジェクトは消すなり非アクティブにするなりしておいてくださいね

## 2.コードの準備

今回のスクリプト名はjumpscareにしときましょう。

~~~C#
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;

public class jumpscare : UdonSharpBehaviour
{
    private GameObject parentObj;
    private GameObject childObj;
    private VRCPlayerApi players;
    private VRCPlayerApi.TrackingData headData;
    void Start()
    {
        parentObj = this.gameObject.transform.GetChild(0).gameObject;
        childObj = parentObj.transform.GetChild(0).gameObject;
        players = Networking.LocalPlayer;
        headData = players.GetTrackingData(VRCPlayerApi.TrackingDataType.Head);
        childObj.SetActive(false);
        SendCustomEventDelayedSeconds(nameof(Activate), 5);
        SendCustomEventDelayedSeconds(nameof(Deactivate), 10);

    }
    void Update()
    {
        headData = players.GetTrackingData(VRCPlayerApi.TrackingDataType.Head);
        parentObj.transform.position = headData.position;
        parentObj.transform.rotation = headData.rotation;
    }
    public void Activate()
    {
        childObj.SetActive(true);
    }
    public void Deactivate()
    {
        childObj.SetActive(false);
    }
}


~~~

5秒後に目の前になんか怖い画像が出てきて、10秒後に消えます。っていうスクリプトです。

今回も説明が多少長くなりますので、最後に追記します。
では、Unity側での操作に戻ります。

1. Hierarchy上で右クリック→**Create Empty**を選択※3
1. 右側Inspectorの欄でTransform Positionをx=0,y=0,z=0に
1. 上で作成したEmpty(名前は**GameObject**になっているはず)の上で右クリック→Create Emptyを選択
1. 3で作成したEmpty(名前はGameObject)の上で右クリック→UI→**Image**を選択※4

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2708808/42e30a38-932d-1794-fb8b-f3568877cfe5.png)

左側のHierarchyウィンドウがこんな感じになってればOKです。

1. GameObjectの下のCanvasを選択し、右側のRender modeをWorld Spaceに
1. Rect TransformのPos x, Pos y, Pos Z, Width, Heightをすべて0に

![js1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2708808/48ae28f4-068d-0500-3d87-5c74157b6a1d.png)

1. Canvasの下のImageを選択し、Rect TransformのWidthとHeightの値を1にする。
1. Pos Zの値を0.5にする。

![js2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2708808/5a0e9600-0cf6-7cd6-40fa-6e11a53cfba5.png)

1. 作成した一番上のEmpty(表示はGameObject)にAdd Component→UdonBehaviourを追加
1. jumpscare.assetをProgram Source欄にドラッグ＆ドロップ

### ファイルの追加

1. Explorer上からWindows システム エラー.wavを選択し、UnityプロジェクトのAsset欄(のどこでも)にドラッグ＆ドロップ
1. 同じようにして、上に貼り付けていたピンク色一色の画像をドラッグ＆ドロップでプロジェクトに追加

![js3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2708808/420203f2-fbc2-c57e-29ad-577ac46c61c2.png)

### 画像の貼り付け

3. ピンク色の画像をクリックして選択し、右側のウィンドウの一番上の方、Texture TypeをDefault→Sprite(2D and UI)に変更
4. 確認のダイアログが出るので、『Apply』を選択
4. Hierarchy上の、Canvasの下のimageを選択して、右側Image欄、Source Imageの欄に先程の画像ファイルをドラッグ＆ドロップ

![js4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2708808/117b9966-6a6e-5ce5-ef0a-847fa55ca02e.png)

### 効果音の設定

1. Imageを選択した状態で、右側Inspectorウィンドウで一番下のAdd Component→検索欄にAudio Sourceと入力→出てきた**Audio Source**を追加※5
1. Audio Clipの欄にWindows Critical Stop.wavをドラッグ＆ドロップ(なんか勝手に英語表記になってしまってるけど一緒)
1. 下の方の**Play On Awake**がついてない場合は☑にチェックを入れる。(Activeになったら音がなる設定)

![js5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2708808/6f925373-6a77-4f42-821c-6b26f3a18f8f.png)

これで設定は完了です。動作の確認をしてみましょう。

![VCC_Test-s-PC_-Mac-_-Linux-Standalone-Unity-2019.4.31f1-Personal_-_DX11_-2023-03-05-14-43-28.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2708808/32754ecb-0b72-8bec-2dab-5115e63ec123.gif)

目の前に画像が出ていて、視線を動かしても追従していますね。
音はGIFアニメなので入れられませんが、おそらく出ていると思います。

## コードの内容について


    private GameObject parentObj;
    private GameObject childObj;
    private VRCPlayerApi players;
    private VRCPlayerApi.TrackingData headData;

今回はpublicではなくて**private**と書いてありますね。※7
VRCPlayerApi.TrackingData headDataは頭の向きを取得するためのものだと思ってください。
わざわざ覚える必要もないと思います。

### Start()の中身

        parentObj = this.gameObject.transform.GetChild(0).gameObject;

GetChild(int number)はそのオブジェクトのnumber番目の**子オブジェクト**、という意味です。※8
上から一番目を取得したい場合は0が入ります。2番目は1という風に、1ずつずれます。

        childObj = parentObj.transform.GetChild(0).gameObject;

今度はparentObjから見た子オブジェクトなので、更に1つ下のオブジェクト、ここでは**Canvas**が当てはまります。

        players = Networking.LocalPlayer;

Playersにはローカルプレイヤー、つまり操作主が当てはまります。

        headData = players.GetTrackingData(VRCPlayerApi.TrackingDataType.Head);

headDataを自分の頭の位置などの情報で**初期化**します。

        childObj.SetActive(false);

子オブジェクトを非アクティブにします。(ジャンプスケアはいきなり出るから怖いんです)

        SendCustomEventDelayedSeconds(nameof(Activate), 5);

5秒後にActivateメソッドを呼び出します。(子オブジェクトをアクティブにします)

        SendCustomEventDelayedSeconds(nameof(Deactivate), 10);

10秒後にDeactivateメソッドを呼び出します。(子オブジェクトを非アクティブにします。)
**5とか10とかの数字の位置に、自分の好きな数字を入れれば、自由に時間は変えられます。**

### Update()の中身

        headData = players.GetTrackingData(VRCPlayerApi.TrackingDataType.Head);

この時点での頭の位置などの情報を取得します。

        parentObj.transform.position = headData.position;

親オブジェクトの位置を、頭の位置と同じにします。

        parentObj.transform.rotation = headData.rotation;

親オブジェクトの回転を、頭の回転と同じにします。

**子オブジェクトは、親オブジェクトに追従して動き、かつUnity側でZ=0.5という数値を設定しているため、
必ず親オブジェクトから0.5離れた位置に表示されます。
そして親オブジェクトはUpdateメソッドによって常に頭の位置と同じ場所になるため、
必然的に頭の位置から0.5離れた位置、つまりほぼ目の前にでてくるということになります。**

0.5より近づければもっと大きく、離せばもっと小さく表示されます。
Pos X, Pos Yの値をいじれば表示位置がそれぞれ左右、上下にずれます。

Deactivateメソッドの中身を消して、再生している最中にPos X,Pos Yの値を変えてみると、
リアルタイムで変化が確認できるのでわかりやすいかもしれません。

今回はここまでです。ではまた次回にー。

>※1　**SE** Sound Effect(効果音)のこと。

>※2　**パス(Path)**　PC上などでファイルのある位置のこと。フォルダパス。
>インターネットでいうURLみたいなもの。

>※3　**Empty**　空のGameObject。当たり判定もなにもないけど、座標とか回転とかはある。
>色々使い道は多いのでこれから多用していきます。

>※4　**Image**　画像などの表示に使う。使えるのは**Texture TypeがSprite(2D and UI)の画像のみ。多分。**
>pngで入れた画像はTexture TypeがDefaultになっているので変えましょう。
>これを使うと自分のワールドに写真とか貼れるね。

>※5　**Audio Source**　音やBGMなどを使用する際に使う**Component**。※6
>AudioClip(音源)を設定することで音が鳴らせたりする。

>※6　**Component(コンポーネント)** GameObjectにアタッチできる追加要素的なもの。
>Collider(当たり判定)、UdonBehaviour(動作制御その他)、VRCPickup(VRC上で持てるようになる)など。
>UdonSharpを使う上で非常によく出てくるし大事なので覚えて。

>※7 **private** **スコープ**の一種。他の場所からは参照できないということ。
>Udonsharpだと割とガバガバ。

>※8 **子オブジェクト** Unity上で階段状に表示されているオブジェクトで、あるオブジェクトの右下に表示されているもの。
>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2708808/f758d185-42e7-a5ff-98c7-a9e7e234c5ad.png)
>左上に表示されているものは**親オブジェクト**。