ReactとUnityを連携させよう！

## 目的
大学の友達とwebアプリを作成することになったのですが、ReactとUnity6を連携する方法がなかったので残しておこうと思いました。

## 使用技術
Unity6000.0.32f1
React
Vite
react-unity-webgl


## フォルダ構成

```text
.
├── Build              #Unityをビルドしたフォルダ
└── src/               
    ├── App.tsx 
    ├── App.css
    ├── index.css
    ├── main.tsx
    ├── UnityBuild.css
    └── vite-env.css
```

## 環境構築
Viteを用いてReactの環境を構築しました！
一応手順を残しておきます。
```bash
npm create vite
```

下記にように表示されるのでプロジェクトの名前を入力しましょう
```bash
Project name:
```

下記のように表示されるのでReactを選択
```bash
◆  Select a framework:
│  ● Vanilla
│  ○ Vue
│  ○ React
│  ○ Preact
│  ○ Lit
│  ○ Svelte
│  ○ Solid
│  ○ Qwik
│  ○ Angular
│  ○ Marko
│  ○ Others
```

下記のように表示されるのでTypeScript + SMCを選択
```bash
◆  Select a variant:
│  ● TypeScript
│  ○ TypeScript + SWC
│  ○ JavaScript
│  ○ JavaScript + SWC
│  ○ React Router v7 ↗
│  ○ TanStack Router ↗
│  ○ RedwoodSDK ↗
```

これで環境構築ができました。
最後に下記のコマンドを実行して試してみよう
```
npm i
```
```
npm run dev
```
実行できることが確認出来たらライブラリをインストールしよう

```
npm install react-unity-webgl
```


## Unity側でやること
```CubeController.cs
using UnityEngine;

public class BallController : MonoBehaviour
{
    public void Move(int position)
    {
        gameObject.transform.position += new Vector3(position, 0, 0);
    }
}

```

- HierarchyでCubeを作成し、名前をCubeに変更
- CubeController.csをCubeにアタッチ

### Buildをする！
webGLとしてBuildします。
Project SettingsからCompression FormatがDisabledになっているかを確認！

:::note warn
BrotliとGzipだと圧縮された状態でデプロイされるので読み込むことができなくなるので注意
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4058543/d0507e38-3a8b-40ef-a637-860c230c83ce.png)

## React側でやること

Pathを設定し、ビルドしたwebGLをUnityコンポーネントとして表示
CSSを適用してサイズなどを変更してます。。。
```tsx
const App = () => {
    const { unityProvider ,sendMessage} = useUnityContext({
    loaderUrl: "Build/Build/Build.loader.js",
    dataUrl: "Build/Build/Build.data",
    frameworkUrl: "Build/Build/Build.framework.js",
    codeUrl: "Build/Build/Build.wasm",
  })
  return(
  <>
  <div>
  <Header/>
  <UnityButton sendMessage={sendMessage}/>
  <Unity unityProvider={unityProvider} className="unity-canvas-large-and-centered"/>
  </div>
  
  </>
  )}
```

``` UnityBuild.css
/* UnityStyles.css など */
.unity-canvas-large-and-centered {
  width: 80vw;  /* 画面の幅の80% */
  height: 70vh; /* 画面の高さの70% */
  /* 必要に応じて最大のピクセルサイズも指定できます */
  /* max-width: 1200px; */
  /* max-height: 800px; */
  border: 1px solid #ddd; /* 任意: 境界線を見やすくするため */
}

/* App.css */
body, html {
  margin: 0;
  padding: 0;
  height: 100%;
  font-family: sans-serif; /* 任意: 基本フォント */
}

.app-layout {
  display: flex;
  flex-direction: column; /* ヘッダーとUnityラッパーを縦に並べる */
  min-height: 100vh;     /* 画面全体の高さを最低でも確保 */
}

.unity-wrapper {
  flex-grow: 1; /* ヘッダー以外の残りの垂直スペースをすべて使う */
  display: flex;
  justify-content: center; /* 水平方向の中央揃え */
  align-items: center;    /* 垂直方向の中央揃え */
  padding: 20px; /* 任意: Unityコンテンツの周りに少し余白 */
  box-sizing: border-box; /* paddingを含めてサイズ計算 */
}

/* Headerのスタイル (任意、調整用) */
.app-layout > div:first-child { /* Headerコンポーネントを指す想定 */
  background-color: #f0f0f0;
  padding: 10px 20px;
  text-align: center;
}
```

ボタンを押したらhandleClickに引数を渡して実行します。
sendMessageによってReactからUnityへと実行する関数を伝えます。
```tsx
sendMessage("Unity内のオブジェクト名","実行したい関数",渡したい引数);
```

``` tsx
//ボタンをクリックしてキューブを移動
const UnityButton = ({sendMessage}:UnityButtonProps) => {
  function handleClick(position: number){
    sendMessage("Ball","Move",position);
  }
  return(
    <>
    <button onClick={() => handleClick(-1)}>左へ移動</button>
    <button onClick={() => handleClick(1)}>右へ移動</button>
    </>
  )
}
```


<details>
<summary>App.tsxのコード全体</summary>


``` App.tsx
import { Unity, useUnityContext} from "react-unity-webgl";
import './UnityBuid.css';

// sendMessageプロップの型定義
interface UnityButtonProps {
  sendMessage: (
    gameObjectName: string,
    methodName: string,
    parameter: number 
  ) => void;
}

//ボタンをクリックしてキューブを移動
const UnityButton = ({sendMessage}:UnityButtonProps) => {
  function handleClick(position: number){
    sendMessage("Ball","Move",position);
  }
  return(
    <>
    <button onClick={() => handleClick(-1)}>左へ移動</button>
    <button onClick={() => handleClick(1)}>右へ移動</button>
    </>
  )
}

//ヘッダー
const Header = () =>{
  return(
  <div>
    <p>Mix Leap 2025/05/30</p>
  </div>
  )}


const App = () => {
    const { unityProvider ,sendMessage} = useUnityContext({
    loaderUrl: "Build/Build/Build.loader.js",
    dataUrl: "Build/Build/Build.data",
    frameworkUrl: "Build/Build/Build.framework.js",
    codeUrl: "Build/Build/Build.wasm",
  })
  return(
  <>
  <div>
  <Header/>
  <UnityButton sendMessage={sendMessage}/>
  <Unity unityProvider={unityProvider} className="unity-canvas-large-and-centered"/>
  </div>
  
  </>
  )}

export default App

```
</details>

## 実行してみると!
![無題の動画-‐-Clipchampで作成.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4058543/c0a58ee9-35c2-431b-96da-19c3df621bd7.gif)

## 参考文献
https://react-unity-webgl.dev/

https://logmi.jp/main/technology/323164