Reactを使用してWeb上でUnityのシーンの中のカメラを動かす (Unity 2019.4.29 LTS)

## はじめに
今回React.jsを使用してUnityのシーンの中にあるカメラを動かしていこうと思います。
![master.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1462552/dc76d374-b785-0976-cacf-35fa633cc8ff.gif)

## 環境
Unity 2019 4.29 LTS (私がただ好きなバージョンです。)
React 18.2.0
node 16.17.1

## Unity側
まず、Assetから新規でC#スクリプトファイルを作成します。
![スクリーンショット 2023-12-20 172621.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1462552/bd1f00a1-7c1d-81e1-1ab6-88b141164890.png)

次に、Main Cameraにアタッチします。
![スクリーンショット 2023-12-20 172907.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1462552/13d6d8b2-f6a1-dd6b-f826-7aecb0f3fb1b.png)

### 

### スクリプトを以下のような感じで書きます。

```C#:CameraController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraController : MonoBehaviour
{
    void Update()
    {
        if (Input.GetKey(KeyCode.RightArrow))
        {
            MoveRight(1);
        }
        else if (Input.GetKey(KeyCode.LeftArrow))
        {
            MoveLeft(1);
        }
        else if (Input.GetKey(KeyCode.UpArrow))
        {
            MoveUp(1);
        }
        else if (Input.GetKey(KeyCode.DownArrow))
        {
            MoveDown(1);
        }
    }
    public void MoveRight(int position)
    {
        this.gameObject.transform.position += new Vector3(position, 0, 0);
    }
    public void MoveLeft(int position)
    {
        this.gameObject.transform.position -= new Vector3(position, 0, 0);
    }
    public void MoveUp(int position)
    {
        this.gameObject.transform.position += new Vector3(0, position, 0);
    }
    public void MoveDown(int position)
    {
        this.gameObject.transform.position -= new Vector3(0, position, 0);
    }
    public void SetPosition(int x, int y, int z)
    {
        this.gameObject.transform.position = new Vector3(x, y, z);
    }
}
```
### 何をしているのか

このコードはUnity内でのシーンでMain Cameraを動かすためのコードです。
ざっくりいうと、Main Cameraを動かしています。

ここでは、キーボードの矢印を使用して移動できるように書いています。
また、Web上にReactで上下左右のボタンを出し、押した時に移動するようにもなります。


### WebGLでビルドする
Add Open Sceneでビルドしたいシーンを選択すしてBuildをクリックします。
![WebGLBuilding-BuildPlayerOptions.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1462552/fe5404c7-9f93-5e92-6123-989aebd06d80.png)



## React側

react appを作成

```
$ npx create-react-app unity-to-web
$ npm run build
$ npm start
```

### スクリプトを以下のような感じで書きます。

```App.js 
import React, { useEffect, useState } from "react";

function UnityComponent() {
  const [position] = useState(1);

  useEffect(() => {
    const script = document.createElement("script");

    script.src = "/Build/UnityLoader.js";
    script.onload = () => {
      if (typeof window.UnityLoader !== "undefined") {
        window.UnityInstance = window.UnityLoader.instantiate(
          "unityContainer",
          "/Build/Downloads.json",
          {
            onProgress: (progress) => {
              console.log(`Loading progress: ${progress}`);
            },
            Module: {
              onRuntimeInitialized: () => {
                console.log("Unity runtime initialized.");
              },
            },
          }
        );
      } else {
        console.error("UnityLoader is not defined");
      }
    };

    document.body.appendChild(script);
  }, []);

  const moveCamera = (direction) => {
    if (typeof window.UnityInstance !== "undefined") {
      window.UnityInstance.SendMessage(
        "Main Camera",
        `Move${direction.charAt(0).toUpperCase() + direction.slice(1)}`,
        position
      );
    } else {
      console.error("UnityInstance is not defined");
    }
  };

  return (
    <div>
      <h1>ReactでUnityのシーンの中のカメラを動かす</h1>
      <div id="unityContainer" style={{ width: "960px", height: "600px" }} />
      <div
        style={{
          display: "flex",
          flexDirection: "column",
          alignItems: "center",
        }}
      >
        <button onClick={() => moveCamera("up")}>Move Up</button>
        <div style={{ display: "flex", flexDirection: "row" }}>
          <button onClick={() => moveCamera("left")}>Move Left</button>
          <button onClick={() => moveCamera("right")}>Move Right</button>
        </div>
        <button onClick={() => moveCamera("down")}>Move Down</button>
      </div>
    </div>
  );
}

export default UnityComponent;

```


### 何をしているのか
ReactのuseStateフックを使用して新しいpositionという名前の状態変数を作成し、初期値を1にします。
positionの値が一度設定された後で変更されることがないため、配列の分割代入を使用し、positionのみを取得するようにします。

``` react
const [position] = useState(1);
```


##### script.src = "/Build/UnityLoader.js";で、作成したスクリプト要素のsrc属性にUnityのローダースクリプトのパスを設定します。

UnityLoader.jsは、JavaScriptからWebGLコンテンツと直接通信するためのAPIを提供します。これにより、JavaScriptからUnityのゲームオブジェクトや関数を操作することが可能になります。

##### script.onloadは、スクリプトが完全にロードされたときに実行されるイベントハンドラーです。
この中で、window.UnityLoader.instantiate関数を使用してUnityのゲームエンジンを初期化します。この関数は、UnityのゲームエンジンをWebページにロードし、指定したコンテナ（この場合は"unityContainer"）に表示します。


##### window.UnityLoader.instantiate関数の第二引数には、ゲームのビルドデータのパス（この場合は"/Build/Downloads.json"）を指定します。

<br />
＊ Unityのバージョンが2020年以降の場合、この辺りのやり方が異なるため、丁寧で神様のような公式ドキュメントを読んでみてください。

https://docs.unity3d.com/ja/2021.3/Manual/webgl-templates.html

私は2019年バージョンと20年バージョンで、使用が変わっていたことに気が付かずにいました......
ずっとcreateUnityInstanceが未定義ですと怒られ続けました...

普通にUnityLoader.js内でgrepすれば良いだけの話だったんですけどね。


##### window.UnityLoader.instantiate関数の第三引数には、オプションを指定します。このオプションには、ゲームのロード進行状況を表示するonProgress関数と、ゲームエンジンが初期化されたときに実行されるonRuntimeInitialized関数が含まれています。


``` react
 useEffect(() => {
    const script = document.createElement("script");

    script.src = "/Build/UnityLoader.js";
    script.onload = () => {
      if (typeof window.UnityLoader !== "undefined") {
        window.UnityInstance = window.UnityLoader.instantiate(
          "unityContainer",
          "/Build/Downloads.json",
          {
            onProgress: (progress) => {
            },
            Module: {
              onRuntimeInitialized: () => {
              },
            },
          }
        );
      } else {
        console.error("UnityLoader is not defined");
      }
    };

```
2019.4バージョンのドキュメント
https://docs.unity3d.com/ja/2019.4/Manual/webgl-templates.html

<br />

ここからはUnityをReactで動かすためのコードになります。
``` react
 const moveCamera = (direction) => {
    if (typeof window.UnityInstance !== "undefined") {
      window.UnityInstance.SendMessage(
        "Main Camera",
        `Move${direction.charAt(0).toUpperCase() + direction.slice(1)}`,
        position
      );
    } else {
      console.error("UnityInstance is not defined");
    }
  };
```

moveCamera関数は、Unityのゲームエンジン内の特定のゲームオブジェクト（この場合は"Main Camera"）にメッセージを送信して、その動作を制御します。

if (typeof window.UnityInstance !== "undefined")は、UnityInstanceが定義されているかどうかをチェックします。これは、Unityのゲームエンジンが正しくロードされ、初期化されていることを確認します。

window.UnityInstance.SendMessage関数は、Unityのゲームエンジン内の特定のゲームオブジェクトにメッセージを送信します。

第一引数は、メッセージを送信するゲームオブジェクトの名前（"Main Camera"）です。
第二引数は、ゲームオブジェクトに送信するメソッドの名前です。

第三引数は、メソッドに渡す値（position）です。

<br />

## 重いシーンでできない？
![画面収録 2023-12-21 12.40.41.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1462552/ee7f3e45-f66a-1d84-f020-9f4dde9d9779.gif)
このシーンでやってみたところ、、、
![スクリーンショット 2023-12-21 4.40.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1462552/c717432a-dbb1-7b55-ca42-133f27792902.png)

Main Cameraを移動するのにシーンが重くてできない？みたいなことが起き、成功できませんでした。


<br />

## 最後に
昨年のアドベントカレンダーでは、一回きりの切り札"ポエム"を発動してしまったので、今年は技術系の何かを書きました。

<br />

結論
### ポエムは伸びる。