Discord ActivityをUnityで作る(備忘録)
後に公開チュートリアルを作成するが、ひとまずメモ用
参考リンク
公式
Unityでの先駆者
基本的に先駆者を参考にしつつ、足りないところをもう少し丁寧に残す
DiscordでUnity(WebGLビルド)が動くまで
基本環境
Windows 10
WSL2 (Ubuntu)
Unity2023.2.20f1(Win)
React Unity WebGL
VSCode
Unityビルド
Unity環境構築
Unity Hubを使用。Unityエンジニアを対象として記事のため省略。
Unity2022LTS以降なら恐らく問題ないはず。
Unityビルド
先駆者を参考に作成。チュートリアル時はもう少し変える
環境構築
WSLの環境構築
WSLのインストールは下記参照。今回はUbuntuを使用。
VSCodeを使用して基本作業するので下記を参考にRemote Development拡張機能パックをインストール
node.jsの環境構築
node.jsのバージョンを切り替える可能性を考慮してnvmを使用(rbenv的なヤツ)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
nvm --version
nvm install --lts
node -v
Cloudflareの環境構築
以下でシステム種別を確認(筆者はx86_64)
uname -m
Cloudflare Zero Trustダウンロードページから、対象のシステム種別の.debのダウンロードリンクをコピーしてwgetでダウンロード。
下記はその例
wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
cloudflared --version
ブラウザでの実行
Reactプロジェクトの作成
Viteでのプロジェクト作成
mkdir discord-unity-app
cd 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
追記予定:この時点でのフォルダ構造
Unity WebGLビルドの移動
以下のフォルダ構造になるように
discord-unity-app/client/public/
├── Build/
├── TemplateData/
├── index.html
└── vite.svg #vite生成時に生成されたアイコン
Reactプロジェクトの編集
- discord-unity-app/client/src/下に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;
- client/src/App.tsxを下記のように変更
import UnityComponent from "./UnityComponent";
function App() {
return <UnityComponent userName={"test"} />;
}
export default App;
- client/src/index.cssに下記のように変更
body {
margin: 0;
overflow: hidden;
}
#unity-canvas {
width: 100vw;
height: 100vh;
}
- clientディレクトリで下記を実行
npm install
npm dev
-
http://localhost:5173/など、表示されたURLにアクセス。
ブラウザで実行出来れば成功
Discordアプリの作成
チームの作成
Discordの開発者ポータルへアクセス
アプリ単位でテストユーザーを最大50人まで追加できるが、チームを作成しそのチームのアプリとした方がユーザー管理/アプリの共同管理が楽なのでチームを作成することを推奨。複数人でのテストのため、この段階で自分のサブアカウント、あるいは協力者を招待しておくのが吉(RoleはRead Onlyで良い)
アプリケーションの作成
Applications->New Application
チーム選択するのを忘れないように気を付ける
続いて、アクティビティの設定をする。
サイドバーから「ACTIVITIES」→「Getting Started」をクリック。
認証情報(OAuth2)の確認
認証形式には OAuth2 が使われている。
サイドバーから「OAuth2」をクリックし、認証情報画面を開く。
- Redirects->Add Redirectでhttp://localhostを記入
- Client ID, Client Secretをコピー、メモに残しておく
アクティビティでUnityを実行する
アクティビティ実行のための事前準備
- 下記リポジトリをクローンする
https://github.com/discord/getting-started-activity - クローンしたリポジトリから、serverディレクトリとexample.envをdiscord-unity-app/に配置する
- example.envを.envに名前変更する
ここまで行うと以下のようなフォルダ構造になっている
discord-unity-app
├── client/
├── server/
└── .env
- .envに先ほどメモしたClient ID, Client Secretを記載する。
VITE_DISCORD_CLIENT_ID=<client_id>
DISCORD_CLIENT_SECRET=<client_secret>
アクティビティのバックエンドの起動
- アクティビティサーバー用のターミナルを新規で立ち上げる
- serverにディレクトリを移動
cd server
- バックエンドの起動
npm install
npm run dev
起動したままにする。認証などDiscord APIでやり取りするのに使用する。
フロントエンドの修正
- discord-unity-app/client/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,
},
},
});
- discord-unity-app/client/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",
"guilds"
],
});
// サーバーから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");
}
// チャンネル名の取得
let activityChannelName = 'Unknown';
// Requesting the channel in GDMs (when the guild ID is null) requires
// the dm_channels.read scope which requires Discord approval.
if (discordSdk.channelId != null && discordSdk.guildId != null) {
// Over RPC collect info about the channel
const channel = await discordSdk.commands.getChannel({channel_id: discordSdk.channelId});
if (channel.name != null) {
activityChannelName = channel.name;
}
}
console.log(`[Debug]チャンネル:${activityChannelName}`);
await discordSdk.commands.getInstanceConnectedParticipants()
// ユーザー情報の取得
const user: { global_name: string } = await fetch(
`https://discord.com/api/users/@me`,
{
headers: {
Authorization: `Bearer ${auth.access_token}`,
"Content-Type": "application/json",
},
}
).then((reply) => reply.json());
console.log(`[Debug]名前:${user.global_name}`);
// ユーザー名の設定
//setUserName(user.global_name);
}
return <UnityComponent userName={userName} />;
}
export default App;
フロントエンドをcloudflareを使用して実行
- フロントエンドを実行(clientディレクトリ)
npm run dev
以下のようにURLが表示される
VITE v5.0.12 ready in 100 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help
- cloudflare用の新規ターミナルを作成
- ネットワークトンネルを先ほどのURLで開始(別ターミナル)
cloudflared tunnel --url http://localhost:5173
以下のようなURLが表示されるのでURLをコピーする
Your quick Tunnel has been created! Visit it at (it may take some time to be reachable):
https://funky-jogging-bunny.trycloudflare.com
- URLマッピングの設定
Application->URL Mappings->Target
Discordでの確認
- 開発者コンソールのため、Canary版Discordをダウンロードする
- 開発者モードを有効にする
ユーザー設定->詳細設定->開発者モード
- ボイスチャットに参加
- アクティビティを起動
起動に時間がかかるが、しばらく待ってUnityが起動したら成功。
トラブルシューティング
ブラウザで実行できない
前半のブラウザ表示に失敗する
- URLをミスしていないか
- フロントエンド=clientは実行(nvm run dev)されているか
- Unityのビルド名とReact内でのUnity名にミスがないか
- 本記事ではDiscordAppというディレクトリにUnityビルドする前提です
- F12でブラウザのコンソールで表示されるエラーにヒントがないか
アクティビティでの確認後実行できない
終盤のApp.tsx改変後はブラウザでは実行できず、真っ白になるのが正しいです。
アクティビティで実行できない
- Discordのサーバー設定でアクティビティを有効にしているか
- cloudflareは実行されているか
- URLマッピングを忘れていないか
- cloudflareで発行後のURLと一致することを確認する
- 500が出る場合、フロントエンド=clientは実行(nvm run dev)されているか
認証ダイアログが出てこない、ユーザー名が取得できない
- バックエンド(server)は実行されているか
- 許可ダイアログは許可をしたか
マルチプレイヤー関係
他のユーザーがアクティビティを起動できない
- 「権利がない」と表示される場合、アクティビティを起動しようとしているユーザーは内部テストユーザー、あるいはチームのメンバーか確認する