🎙️

UnityアプリをDiscordアクティビティとして実装してみる

2024/03/23に公開

1.embedded-app-sdkについて

3/18にアクティビティ(Discord上で動くアプリ)を個人で開発できるembedded-app-sdkが公開されました。
https://discord.com/build/embedded-app-sdk
こちらはウェブページをアクティビティとして組み込めるsdkで
nodeで動く環境であれば割となんでも動かせそうです。

なので今回はUnityアプリをWebGLビルドで出力しアクティビティとして組み込みたいと思います。

基本的なDiscordの設定等の内容は既にzennに投稿してくださっている方がいるののでそちらを参考にしてください
https://zenn.dev/ulxsth/articles/30a19eb0b50153

2.今回作成するもの

今回はDiscordのユーザー名をUnity上に表示する単純なアプリを作成します。

環境

下記環境での今回の記事は執筆しています。

node v20.11.1
npm 10.2.4
Unity 2022.3.4f1

3.Unityプロジェクトの作成

Unityのバージョンは下記を使用しています。

2022.3.4f1

プロジェクトの作成

プロジェクトをDiscordAppという名前で作成します。

UIの設置

下記のような形で名前を表示するUIを作成します。

テキスト変更用のスクリプトを作成

  1. C#スクリプトをNameViewer.csという名前で作成
  2. 下記のスクリプトを記述
NameViewer.cs
using TMPro;
using UnityEngine;

public class NameViewer : MonoBehaviour
{
    public void SetText(string text)
    {
        var textUi = GetComponentInChildren<TextMeshProUGUI>();
        textUi.text = text;
    }
}
  1. Canvasにスクリプトをアタッチ

プロジェクトの出力

  1. プロジェクト設定→Player→WebGLの設定→圧縮形式を無効に変更する
  2. WebGLビルドで出力

    今回はDiscordAppという名前でディレクトリを作成し、そこに出力しました。
    下記の通りに出力されました。
DiscordApp/
├── Build/
├── TemplateData/
└── index.html

4.Reactプロジェクトの作成

次に出力したUnityアプリを今回はReact上に表示したいと思います。
プロジェクトはembedded-app-sdkのチュートリアルでも用いられたviteを使用します。
それとUnityアプリをReact上に表示するために下記ライブラリを使用します。
https://react-unity-webgl.dev/

viteでのプロジェクトの作成

  1. 親ディレクトリをdiscord-unity-appという名前で作成します。
  2. discord-unity-appディレクトリ内で下記コマンドを実行します
$ npm create vite@latest client -- --template react-swc-ts
$ cd client
$ npm i
$ npm i react-unity-webgl
$ npm i @discord/embedded-app-sdk

3.discord-unity-app/client/public下に出力したUnityアプリを配置
下記の様な配置になりました。

discord-unity-app/client/public/
├── Build/
├── TemplateData/
├── index.html
└── vite.svg #vite生成時に生成されたアイコン

Unityアプリを表示するためのコンポーネントを作成

  1. discord-unity-app/client/src/下にUnityComponent.tsxという名前でコンポーネントを作成
  2. UnityComponent.tsxに下記を記述
UnityComponent.tsx
import { useEffect } from "react";
import { Unity, useUnityContext } from "react-unity-webgl";

interface UnityCompoonentProps {
  userName: string;
}

const UnityComponent = (props: UnityCompoonentProps) => {
  // UnityContextを準備、表示するUniyアプリを指定
  const { unityProvider, sendMessage, isLoaded } = useUnityContext({
    loaderUrl: "Build/DiscordApp.loader.js",
    dataUrl: "Build/DiscordApp.data",
    frameworkUrl: "Build/DiscordApp.framework.js",
    codeUrl: "Build/DiscordApp.wasm",
  });

  // useEffectの対象にisLoadedを含めない場合
  // 環境によってはsendMessageが動作しない問題がある
  useEffect(() => {
    if (isLoaded) {
      // Unityアプリに対してメッセージを送信
      // sendMessage("オブジェクト名", "関数名", 引数)
      sendMessage("Canvas", "SetText", props.userName);
    }
  }, [isLoaded]);

  return <Unity id="unity-canvas" unityProvider={unityProvider} />;
};

export default UnityComponent;
  1. client/src/App.tsxを下記のように変更
App.tsx
import UnityComponent from "./UnityComponent";

function App() {
  return <UnityComponent userName={"test"} />;
}

export default App;
  1. client/src/index.cssに下記のように変更
index.css
body {
  margin: 0;
  overflow: hidden;
}

#unity-canvas {
  width: 100vw;
  height: 100vh;
}
  1. clientディレクトリ内で下記コマンドを実行
$ npm run dev

表示されるURLにアクセスし下記のような表示がされれば成功

Discordアクティビティとして動かすために処理を追加

  1. discord-unity-appディレクトリ下にgetting-started-activityリポジトリ[1]の一部ファイルを追加

https://github.com/discord/getting-started-activity
リポジトリ下のserverディレクトリとexample.envdiscord-unity-appに配置後、
example.env.envに変更する

下記の様な配置になりました。

discord-unity-app
├── client/
├── server/
└── .env
  1. .envファイルとseverディレクトリ内をセットアップする
    冒頭に紹介した記事を参考にしてください
  2. discord-unity-app/client/vite.config.tsを下記のように変更
vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  envDir: "../",
  server: {
    proxy: {
      "/api": {
        target: "http://localhost:3001",
        changeOrigin: true,
        secure: false,
        ws: true,
      },
    },
    hmr: {
      clientPort: 443,
    },
  },
});

  1. discord-unity-app/client/App.tsxを下記のように変更
App.tsx
import { useEffect, useState } from "react";
import UnityComponent from "./UnityComponent";
import { DiscordSDK } from "@discord/embedded-app-sdk";

function App() {
  let auth;

  const discordSdk = new DiscordSDK(import.meta.env.VITE_DISCORD_CLIENT_ID);

  const [userName, setUserName] = useState("");

  useEffect(() => {
    setupDiscordSdk();
  }, []);

  async function setupDiscordSdk() {
    await discordSdk.ready();

    // Discordクライアントの認証
    const { code } = await discordSdk.commands.authorize({
      client_id: import.meta.env.VITE_DISCORD_CLIENT_ID,
      response_type: "code",
      state: "",
      prompt: "none",
      scope: ["identify"],
    });

    // サーバーからaccess_tokenを取得
    const response = await fetch("/api/token", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        code,
      }),
    });
    const { access_token } = await response.json();

    // access_tokenを用いた認証
    auth = await discordSdk.commands.authenticate({
      access_token,
    });

    if (auth == null) {
      console.log("Authenticate command failed");
      throw new Error("Authenticate command failed");
    }

    // ユーザー情報の取得
    const user: { username: string } = await fetch(
      `https://discord.com/api/users/@me`,
      {
        headers: {
          Authorization: `Bearer ${auth.access_token}`,
          "Content-Type": "application/json",
        },
      }
    ).then((reply) => reply.json());

    // ユーザー名の設定
    setUserName(user.username);
  }

  return <UnityComponent userName={userName} />;
}

export default App;

5.アクティビティの起動

冒頭に紹介した記事を参考にDiscordアクティビティを起動
下記のような表示がされれば成功

デバッグについて

PTBまたはCanary版のいずれかであれば開発者ツールが以下のショートカットで開けます
https://support.discord.com/hc/ja/articles/115001239472-コンソールログエラーのトラブルシューティング

Windows
Ctrl + Shift + I
Mac
⌥+⌘+I

脚注
  1. getting-started-activityリポジトリはembedded-app-sdkのチュートリアルプロジェクトのようなもので冒頭で紹介したzennの記事でも使用している ↩︎

Discussion