はじめに

他のデータベースと異なり、ブロックチェーンへの書き込み(トランザクション)にはガス代という費用がかかります。ブロックチェーンやWeb3ゲームを開発する際は、ガス代を考慮する必要があります。Sequenceのガススポンサー機能により、ユーザー側の複雑さは大幅に軽減されますが、開発者としてもガス代についていくつか配慮すべき点があります。

ゲーム開発時には、ブロックチェーンへトランザクションを送信する頻度を考慮し、ランタイムコストを最小限に抑えるようにしましょう。

ブロックチェーン特有の追加の難しさとして、ブロックチェーンへの書き込み(つまりトランザクション)は即時ではなく、非同期でネットワーク接続が必要です。

トランザクションは、インターネット接続の不具合や残高不足など、様々な理由で失敗することがあります。

まず、どの所有権(例:アイテム、パワーアップ、アンロックなど)をブロックチェーン上でトークン化すべきかを検討しましょう。

次に、ゲーム内で発生するトランザクションの「種類」を考えます。多くの場合、トランザクションは複数のカテゴリに分類できます。例えば、ピックアップ(コイン収集など)、クラフト、トレード、販売、購入などが挙げられます。

各トランザクションを分類したら、ユーザーの期待値や開発者としての期待値も考慮しましょう。ユーザー視点でどの程度の遅延が許容されるか?トランザクションが成功する前提で即時フィードバックを与えても問題ないか?もし失敗した場合、ユーザーや運営に悪影響を与えずにリカバリーできるか?

本ガイドの執筆者は、トランザクションを一般的に「高価値トランザクション」と「低価値トランザクション」に分類しています。

高価値トランザクションの場合は、ユーザーにフィードバックを返す前に確認を行う必要があります。トランザクションは、インターネット接続の不具合やガス不足、前提条件の誤りなど、さまざまな理由で失敗する可能性があります。高価値トランザクションが必ず成功すると仮定してすぐにユーザーへフィードバックを返してしまうと、後からトランザクションが失敗した場合、ユーザー体験や収益に悪影響を与えずにリカバリーすることができません。例えば、ゲーム内ストアでユーザーが「剣を購入」するトランザクションが失敗した場合、アカウントから剣を取り消す(プレイヤー体験の損失)か、売上を失う(収益の損失)かのいずれかになります。幸いにも、多くの高価値トランザクションはストアやクラフト、アップグレードなど、従来の(非ブロックチェーン)ゲームでも短い待ち時間が一般的なアクティビティと重なっています。

低額取引は、基本的に即座にユーザーへフィードバックを返すことが推奨されます。取引の確認を待たずに、ゲーム内のフィードバックを行って問題ありません。万が一取引が失敗しても、ほとんどの場合、ユーザー体験や開発者の収益に悪影響を与えることなく、簡単にリカバリーできます。従来のゲームでは、プレイヤーはこのようなアクションに対して即時のフィードバックを受け取ることに慣れています。例えば、ユーザーがプラットフォーマーゲームでコインを集めた場合、UIにすぐ反映されることを期待します。プレイヤーが次回のセッションで正確なコイン数を覚えていることはほとんどなく、また、コインをローカル保存してネットワーク復旧後に再送信しても、開発者の収益に影響することはほぼありません。

最後に、ゲーム内でどのくらいの頻度で取引を発生させるべきかも考慮しましょう。ゲームによっては、短時間に多くのアクションがゲーム状態に影響を与えることがあります。例えば、マリオがコインを取るたびにブロックチェーンへ取引を送信していたら…コストがすぐに高額になってしまいます。低額取引はまとめて処理しましょう!

Unityでこれを実装するには?

まず、ユーザーがオンチェーンで保有しているものをローカルでキャッシュする仕組みを作ります。これは簡単で、ブロックチェーンからデータを読み取り、ユーザーのトークン残高を都合の良い形式でローカル保存するだけです。既存のゲームやプロトタイプでPlayerPrefsなどのローカルストレージや、RDBMSのようなリモートストレージを使っている場合は、すでにローカルキャッシュの仕組みがあるはずなので、アダプターを作成するだけで十分です。

次に、Unity SDKが提供するTransactionQueuerおよびその継承クラスを活用するのがおすすめです。TransactionQueuerは高い柔軟性があり、プレイヤーが頻繁に状態を変更するゲームの開発をサポートするために設計されています。例えば、ゲーム内でコインなどの低額取引を大量に集める場合、PermissionedMinterTransactionQueuermint関数に権限が必要な場合、デフォルトでサーバーからミントする場合)や、SequenceWalletTransactionQueuer(誰でもミントできる場合)を利用できます。これらを使えば、複数の取引をキューに追加するだけで、可能であれば自動的にまとめてくれます(例:‘mint(amount: 5, tokenId: 11)‘と’mint(amount: 3, tokenId: 11)‘が’mint(amount: 8, tokenId: 11)‘にまとめられる)。取引はx秒ごと、または関数呼び出し時に送信できます。また、最短でもy秒ごと(高額取引の場合は上書き可能)といった設定も可能です。TransactionQueuerの詳細はこちらのドキュメントをご覧ください。

最後に、取引の失敗を検知し、適切にエラー処理を行う必要があります。

if (transactionReturn is FailedTransactionReturn) {

    // Handle the failed transaction

}

これらの概念をUnity SDKで実際に使っている例として、Jelly Forestガイドサンプルコードをご覧ください。