Jelly Forest 入門 - Unity ゲームガイド
Jelly Forest 入門 - Unity ゲームガイドでは、ソーシャルサインインやアップグレード、コスメティックアイテムなどのブロックチェーン機能を備え、これらのアイテムがすべてスマートコントラクトウォレットに保存される2Dランナーゲームを紹介します。
Jelly Forest はブロックチェーン対応の2Dランナーゲームです。ゲームにはソーシャルサインイン、複数段階のアップグレード(上位のアップグレードには下位のアップグレードを材料として使用)、コスメティックアップグレードがあり、すべてが組み込み型のノンカストディアルスマートコントラクトウォレットに保存されます。プレイヤーに対してトランザクション署名のポップアップやガス代の支払いは発生しません。
Google Play からこちらでダウンロードできます!
このガイドでは、Jelly Forest をどのように開発したか、そしてSequence の Unity SDKを使ってご自身の web3 ゲームを作る方法を解説します!
1. ゲームループを作成する
最初のステップは基本的なゲームループの構築です。まずはマネタイズ戦略や、どのように web3 要素を活用するかも考えておきましょう!
ゲームループには、Unity Asset Store で Infinite Runner Engine を購入しました。このアセット内のデモシーン JellyForest
を少し調整することで、iOS と Android で動作するビルドを作成できました。
2. ソーシャルサインインと Sequence の Embedded Wallet ソリューションを統合する
設定
- Package Manager を使って Sequence の Unity SDK をインストールする
- Sequence Builder Console にサインインする
- Builder Console でゲーム用のプロジェクトを作成する
- Builder Console で Embedded Wallet をセットアップする
SequenceConfig
スクリプタブルオブジェクト(インストール時に Package Manager の Samples メニューからインポート)に、Builder で追加した Google および Apple のクライアントIDと、WaaSConfigKey
に設定キーを入力してください。- Android と iOS のクライアントIDは、それぞれのプラットフォームに正しく設定するのを忘れずに!
- Builder Console から取得した Builder API キー を
Settings > API Access Keys
のprod
キーに入力してください。
ソーシャルサインイン
- プレイヤーがログインするための基本的なシーンを作成しましょう。
- 私たちは新しいシーンを作成し、背景画像を追加しました。
Canvas
を作成し、Canvas Scaler
コンポーネントを追加して「Scale with Screen Size」UIスケールモードを使用します。これにより、LoginPanel や他の UI 要素がビルドターゲットを切り替えても自動的にスケーリングされます。LoginPanel
プレハブを Canvas の下にシーン階層へドラッグします。これは Project ウィンドウのPackages > Sequence Embedded Wallet SDK > SequenceFrontend > Prefabs
にあります。- UI マネージャーを作成し、
LoginPanel
のOpen
を呼び出せるようにします。実装例はこちら:
- 階層内で
LoginPanel
プレハブとの参照を切り離し、シーンビューで自由に編集できるようにします。- 階層内で
LoginPanel
ゲームオブジェクトを選択します。 - 階層内で
LoginPanel
ゲームオブジェクトを右クリックします。 Prefab > Unpack Completely
を選択します。
- 階層内で
- LoginPanel をゲームのテーマに合わせてカスタマイズしましょう。
LoginPanel はソーシャルサインインのロジックをすべて自動で処理します。実装方法が気になる場合は、LoginPage
や OpenIdAuthenticator
の実装を確認してみてください。
認証は Open ID Connect Implicit Flow を利用しています。
Sequence API でセッションを登録する
ソーシャルサインインが完了すると、自動的に Sequence WaaS(Wallet as a Service)API へセッション登録リクエストが送信されます。仕組みは以下の通りです:
ソーシャルサインインが完了すると、OpenIdAuthenticator.SignedIn
イベントが発火します。これにより SequenceLogin.ConnectToWaaS
で認可プロセスが始まります。
ユーザーのウォレットを取得する
ウォレットを取得するには、SequenceWallet.OnWalletCreated
イベントに購読する必要があります。
「Sequence Embedded Wallet SDK」の Package Manager ページの Samples から「Useful Scripts」経由で SequenceConnector
をインポートすることを強くおすすめします。初期コードが多数含まれており、SDK とのやりとりに便利なインターフェースとして機能します。JellyForest でもこのコードを多用しました。
JellyForest では、SequenceWallet.OnWalletCreated
イベントが発火した際に次のシーンを読み込む LevelLoader MonoBehaviour も作成しました。
Sequence の Embedded Wallet ソリューションの認証の仕組みについては、ドキュメントやブログ記事もご覧ください。
3. コレクティブルコントラクトをデプロイする
プレイヤーがサインインしてウォレットを持てるようになったので、コレクティブルを追加しましょう!
ERC1155 コントラクトの利用を強くおすすめします。ゲームに最適な柔軟なトークン規格です。Builder Console から監査済みの ERC1155 実装を簡単にデプロイできます:
Jelly Forest でもこの方法を採用しました。
スマートコントラクトをデプロイしたら、「Gas Sponsoring」ページでコントラクトアドレスを Sponsored Address として追加するのを忘れずに!これにより、ゲームのスマートコントラクトとやりとりする際、ユーザーのガス代が自動的にあなたのコンピュートクレジットで肩代わりされます。
4. リモートミンターをデプロイする
Builder Console でデプロイした ERC1155 コントラクトは、デフォルトでトークンをミントするための権限が必要です。一見面倒に思えるかもしれませんが、これはとても重要です!これがなければ、誰でもコントラクトの mint メソッドを呼び出して無限にゲーム内アイテムを手に入れることができてしまいます。
Sequence ウォレット(または他のウォレット)を持つサーバーをデプロイし、Builder でミント権限を付与しましょう。
Jelly Forest での実装例
Jelly Forest では、ゲームプレイ中に集めたコインがすべて ERC1155 トークンとしてミントされます。実装方法は以下の通りです:
- Cloudflare に登録します(ミントサービスのコードをホストするためですが、他の方法でも構いません)
- ターミナルやコマンドラインを開きます
git clone https://github.com/0xsequence-demos/cloudflare-worker-sequence-relayer.git
を実行し、続いてcd cloudflare-worker-sequence-relayer
git checkout permissionedMinter
pnpm install
で依存関係をインストールします- wrangler をインストールします
そしてログインします
wrangler.toml
を開きますname
の文字列を変更してサーバー名を設定します- 新しいEOAウォレットを作成し、秘密鍵をエクスポートします。どのEOAウォレットでも構いません。Metamaskを使えば簡単にウォレットのセットアップや秘密鍵のエクスポートができます。秘密鍵の取り扱いには十分ご注意ください。パソコンに平文で保存したり、バージョン管理にコミットしたりしないでください!この秘密鍵を
PKEY
に設定します。 CONTRACT_ADDRESS
を設定します。PROJECT_ACCESS_KEY
を設定します。これは、先ほどBuilder ConsoleでSequenceConfig
スクリプタブルオブジェクトをセットアップした際に取得した本番用APIキーです。CHAIN_HANDLE
を設定します。これが何かわからない場合は、Builder ConsoleのNode Gatewayページで各ネットワークのCHAIN_HANDLE
を確認できます。
pnpm dev
- これでサーバーがローカルにデプロイされます。どのlocalhostでデプロイされたかはコマンドラインに表示されます。- 別のコマンドラインウィンドウを開きます。
curl http://localhost:8787
- 表示されたlocalhostに置き換えてください。これでサーバーにリクエストを送ります。- ローカルサーバーが動作しているコマンドライン上で、ミンターのウォレットアドレスがログに表示されるはずです。
- このアドレスにBuilder Consoleでミント権限を付与します。
Contracts
から該当するコントラクトを探し、クリックして開きます。Write Contract
をクリックします。grantRole
を展開します。role
には0x9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6
を入力します。これはMINTER_ROLE
のKeccak-256ハッシュ値です。account
にはミンターのウォレットアドレスを貼り付けます。
wrangler deploy
- これでCloudflare Workerにコードがデプロイされ、ミント用のURLが発行されます。
これで準備完了です!サーバーにPOSTリクエストを送るときは、C#で定義されたボディを使います。proof
はミントリクエストを送るクライアント側で生成されます。Unity SDKでは、MintingRequestProverによって実装されています。
5. ゲーム内トークンをプレイヤーのインベントリにミントする
パーミッション付きミンターサーバーのセットアップが完了したので、クライアント側(Made With Unityアプリ)を連携させ、プレイヤーにゲームプレイを通じてトークンを付与できるようにします。Unity SDKのPermissionedMinter.MintToken
メソッドを呼び出すことで、パーミッション付きミンターにリクエストを送信できます。
Jelly Forestでは、プレイヤーがレベルを進むごとに多くのコインを集めます。これらはすべてERC1155トークンです。プレイヤーに快適なUXを提供するために、まだ解決すべき課題がいくつかあります。
- ユーザーのインベントリにどのトークンや権利があるかを、どのようにチェーンから読み取るのでしょうか?
- ブロックチェーンのトランザクションは、Arbitrumのような一部のチェーンでは高速ですが、即時ではありません。コイン(または他のアイテム)を集めてから、ゲーム内のインベントリに反映されるまで数秒待つ必要があるのは、一般的に良いユーザー体験とは言えません。
- 一見すると、ユーザーがゲーム内でトークンを獲得するたびにトランザクションを送信したくなるかもしれません。しかし、特にJelly Forestのように大量のコイン(トークン)を集めるゲームでは、膨大な数のトランザクションが発生し、ガス代が非常に高額になってしまいます!
それでは、Jelly ForestでUnity SDKを使ってこれらの課題をどのように解決したか見ていきましょう!
1. チェーンの読み取り
特定ユーザーのウォレット内トークンを読み取るのは複雑な作業ですが、SequenceのIndexerを使えば簡単です。Unity SDKでも実装されています。
こちらはJelly Forestで、Indexerを使ってプレイヤーのウォレットからゲームのERC1155コントラクト内の全トークンを読み取るコード例です。
2. キャッシュの構築
ブロックチェーンのトランザクションは即時反映されませんが、ユーザーには即時のフィードバックを提供したいので、シンプルなインメモリキャッシュを活用します。
Jelly Forestで最初にSequenceWallet
を受け取った際、SequenceConnector(ゲーム内でSequence SDKとやり取りする主なインターフェース)がInventory
を作成します。
このInventory
は、ゲーム内でシンプルなキャッシュとして使われます。作成時や必要に応じて、Indexerを使ってユーザーのウォレット内トークンを取得します。その後、ユーザーがトークンを獲得するたびにキャッシュ(Inventory
)とオンチェーンデータを更新します。
Inventory
の全実装はこちらでご覧いただけます
3. トランザクションキューの活用
SequenceのUnity SDKには、とても柔軟なトランザクションキューイングシステムが用意されています。
Jelly Forestでは、PermissionedMinterTransactionQueuerのMonoBehaviourをSequenceConnector
のGameObjectにアタッチし、Awakeで参照を取得しています。
この設定が完了したら、トークンを集めたときに「ミントトークン」を呼び出すだけです。
これによりInventory
が更新され、ミントトランザクションがPermissionedMinterTransactionQueuer
のキューに追加されます。PermissionedMinterTransactionQueuer
は、可能な限りトランザクションを自動的にまとめて、ガス代を最小限に抑えます。
Jelly Forestでは、プレイヤーがゲームオーバーになるたび、ただし、30秒未満の間隔では送信されないように設定しています。
どのくらいの頻度でトランザクションを送信するべきでしょうか?
Unity SDKを使えば、これは技術的な問題というよりゲームデザインの問題になります。
TransactionQueuers
は、X秒ごとに自動で送信、関数呼び出しで促されたときに送信(ただしY秒未満では送信しない)、または促されたときに最小間隔(Y秒)を無視して即時送信、など柔軟に設定できます。
トランザクションキューアの設定を決める際に考慮すべきポイントをいくつか挙げます:
- トランザクション送信頻度が高いほど、ガス代も多くかかります。もちろん、選択するEVM互換ブロックチェーンによって、コストが許容範囲かどうかや送信できるトランザクション数・複雑さは大きく変わります。
- トランザクション送信頻度が低いほど、ゲーム内の状態(キャッシュ)とオンチェーン情報のズレが大きくなります。もしトランザクションが失敗した場合、プレイヤー体験を損なわずに復旧する方法が必要です。
Jelly Forestの例では、ショップでのトランザクションはユーザーにとって非常に重要だと考えました。ユーザーがアップグレードや帽子を手に入れたと思っても、トランザクションが失敗して取り消す必要が出たり、正当に獲得していないアイテムを余分にミントする事態は避けたいと考えました。そのため、ショップページでは購入トランザクション(およびTransactionQueuers内の全トランザクション)が完了するまでユーザーに待ってもらう仕様にしました。
6. ゲーム内トークンを他のトークンと交換してバーンする
Jelly Forestでは、コインや(場合によっては)下位のパワーアップをバーンすることで、パワーアップやコスメティックアイテムを購入できます。
この仕組みを有効化し、強制するために、シンプルなBurnToMintスマートコントラクトをデプロイしました。このコントラクトでは、特定のトークンIDに対してミント要件(必要なトークンIDとその数量)を指定できます。ERC1155トークンのバッチを受け取り、送信者がdata
パラメータでミントしたいトークンIDを指定すると、コントラクトは各トークンIDの必要数を受け取ったかどうかを確認します。条件を満たしていれば、コントラクトはトークンをバーンし、指定されたトークンIDを送信者(ユーザー)にミントします。条件を満たさない場合は、トランザクションが失敗し、ロールバックされます。
このコントラクトには、Builder Consoleでゲームコントラクトのミント権限を付与しています。
Contracts
から該当するコントラクトを探し、クリックして開きます。Write Contract
をクリックします。grantRole
を展開します。role
には0x9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6
を入力します。これはMINTER_ROLE
のKeccak-256ハッシュ値です。account
にはミンターのウォレットアドレスを貼り付けます。
:::danger
警告:上記で共有したBurnToMint
スマートコントラクトは、第三者による監査を受けていません。再利用する際はご注意ください!
:::
ユーザーがショップでアップグレードやコスメティックアイテムを購入すると、PurchaseShopItemQueueableTransactionをSequenceConnector
内のSequenceWalletTransactionQueuerに追加し、BurnToMint
スマートコントラクトへトランザクションを送信します。
7. ショップページの構築とミント要件の設定
Jelly Forestのショップページを作成し、各アップグレードや帽子の価格・ミント要件を設定する際、Scriptable Objectsを使ってShopItemsを定義することにしました。これにより、Inspectorでシリアライズできるため、調整や可視化が簡単です。また、これらのScriptable Objectを使って、各Itemの内容やトークンIDとの紐付けも行っています。
しかし、Scriptable Objectで定義したミント要件と、オンチェーンのBurnToMint
コントラクトで定義したミント要件を同期させるのは手間がかかり、バグの原因にもなりやすいことが分かりました。
そこで、ShopItem
のScriptable Object用にエディタ拡張を作成し、ボタンを追加しました。このボタンを押すと、オンチェーンで定義されているミント要件とScriptable Objectの内容が一致しているかを確認し、異なっていればScriptable Objectに合わせてBurnToMint
コントラクトのミント要件を更新するトランザクションを送信します。トランザクションは、開発者のマシンに環境変数として保存された秘密鍵から作成されたEOAウォレットを使って送信されます。このEOAウォレットは、このコントラクトのオーナーです。
実際、ショップページは60秒ごと(およびページを開くたび)にスマートコントラクトへミント要件の変更を問い合わせ、UIを自動で更新しています。これにより、ゲームの経済バランスをアップデートなしでリアルタイムに調整できます!
下の動画をクリックしてください
ShopItemEditorExtensionの実装はこちらをご覧ください。
8. 購入したアイテムをゲーム内で活用する
これで、プレイヤーはログインしてウォレットを取得し、トークンを獲得し、そのトークンでアイテムを購入できるようになりました。あとは、プレイヤーがアイテムを欲しくなる理由を作るだけです。つまり、ここからはゲーム開発者としての腕の見せ所です。魅力的なパワーアップやコスメティックを作りましょう!
ゲーム内でトークンを活用するには、ユーザーが指定したトークンIDを十分に所有しているかを確認し、そのトークンの効果を適用するだけです。
Jelly Forestでは、いくつかのPowerUpTypesを定義し、各Item
にPowerUpType
とティアを割り当てています。そして、プレイヤーが所有する各タイプの最強パワーアップをInventory
から検索する仕組みを作っています。