はじめに
他のデータベースとは異なり、ブロックチェーンへの書き込み(トランザクション)には、ガス代という形で費用がかかります。ブロックチェーンやWeb3ゲームを開発する際は、ガス代について考慮する必要があります。Sequenceのガススポンサーシップを利用すれば、エンドユーザー向けの複雑な処理の多くを自動化できますが、開発者としてもガス代についていくつか注意すべき点があります。
ゲーム開発時には、ブロックチェーンへトランザクションを送信する頻度を考慮し、ランタイムコストを最小限に抑えるようにしましょう。
ブロックチェーン特有の追加の難しさとして、ブロックチェーンへの書き込み(つまりトランザクション)は即時ではなく、非同期でネットワーク接続が必要です。
トランザクションは、インターネット接続の不具合や残高不足など、様々な理由で失敗することがあります。
まず、どの所有権(例:アイテム、パワーアップ、アンロックなど)をブロックチェーン上でトークン化すべきかを検討しましょう。
次に、ゲーム内で発生するトランザクションの「種類」を考えます。多くの場合、トランザクションは複数のカテゴリに分類できます。例えば、ピックアップ(コイン収集など)、クラフト、トレード、販売、購入などが挙げられます。
各トランザクションを分類したら、ユーザーの期待値や開発者としての期待値も考慮しましょう。ユーザー視点でどの程度の遅延が許容されるか?トランザクションが成功する前提で即時フィードバックを与えても問題ないか?もし失敗した場合、ユーザーや運営に悪影響を与えずにリカバリーできるか?
本ガイドの執筆者は、トランザクションを一般的に「高価値トランザクション」と「低価値トランザクション」に分類しています。
高価値トランザクションの場合は、ユーザーにフィードバックを返す前に確認を行う必要があります。トランザクションは、インターネット接続の不具合やガス不足、前提条件の誤りなど、さまざまな理由で失敗する可能性があります。高価値トランザクションが必ず成功すると仮定してすぐにユーザーへフィードバックを返してしまうと、後からトランザクションが失敗した場合、ユーザー体験や収益に悪影響を与えずにリカバリーすることができません。例えば、ゲーム内ストアでユーザーが「剣を購入」するトランザクションが失敗した場合、アカウントから剣を取り消す(プレイヤー体験の損失)か、売上を失う(収益の損失)かのいずれかになります。幸いにも、多くの高価値トランザクションはストアやクラフト、アップグレードなど、従来の(非ブロックチェーン)ゲームでも短い待ち時間が一般的なアクティビティと重なっています。
低額取引は、基本的に即座にユーザーへフィードバックを返すことが推奨されます。取引の確認を待たずに、ゲーム内のフィードバックを行って問題ありません。万が一取引が失敗しても、ほとんどの場合、ユーザー体験や開発者の収益に悪影響を与えることなく、簡単にリカバリーできます。従来のゲームでは、プレイヤーはこのようなアクションに対して即時のフィードバックを受け取ることに慣れています。例えば、ユーザーがプラットフォーマーゲームでコインを集めた場合、UIにすぐ反映されることを期待します。プレイヤーが次回のセッションで正確なコイン数を覚えていることはほとんどなく、また、コインをローカル保存してネットワーク復旧後に再送信しても、開発者の収益に影響することはほぼありません。
最後に、ゲーム内でどのくらいの頻度で取引を発生させるべきかも考慮しましょう。ゲームによっては、短時間に多くのアクションがゲーム状態に影響を与えることがあります。例えば、マリオがコインを取るたびにブロックチェーンへ取引を送信していたら…コストがすぐに高額になってしまいます。低額取引はまとめて処理しましょう!
Unityでこれを実装するには?
まず、ユーザーがオンチェーン上で保有しているもののローカルキャッシュを構築しましょう。これは簡単で、ブロックチェーンから読み取りを行い、ユーザーのトークン残高を都合の良い形式でローカルに保存するだけです。既存のゲームやプロトタイプでローカルストレージシステム(PlayerPrefsなど)やリモートストレージシステム(RDBMSなど)を使っている場合は、すでにローカルキャッシュが実装されていることが多く、アダプターを作成するだけで対応できるでしょう。
次に、Unity SDKが提供するTransactionQueuerおよびその継承クラスを活用することをおすすめします。TransactionQueuerは高い柔軟性があり、プレイヤーが多くの状態変更アクションを行うゲーム開発をサポートするために設計されています。例えば、ゲーム内でコインなどの低価値トランザクションを大量に集める場合、PermissionedMinterTransactionQueuer(mint関数に権限があり、サーバーからミントする場合のデフォルト)やSequenceWalletTransactionQueuer(誰でもミント可能な場合)を利用できます。これらを使うことで、複数のトランザクションを簡単にキューに追加でき、可能であれば自動的にまとめられます(例:‘mint(amount: 5, tokenId: 11)‘と’mint(amount: 3, tokenId: 11)‘が’mint(amount: 8, tokenId: 11)‘に統合されます)。また、トランザクションはx秒ごとや、関数呼び出し時など、任意のタイミングで送信できます(高価値トランザクションの場合は上書き可能)。TransactionQueuerの詳細についてはこちらのドキュメントをご覧ください。
最後に、取引の失敗を検知し、適切にエラー処理を行う必要があります。
if (transactionReturn is FailedTransactionReturn) {
// Handle the failed transaction
}
これらの概念をUnity SDKで実際に使っている例として、Jelly Forestガイドやサンプルコードをご覧ください。