Unity / Godot / JavaScript SDK ガイド
GameRush のゲームから、プレイヤーの疑似ID・ランキング・公開プレイヤー状態と相手探し・受信箱・交換所・アナリティクス・ゲームからの共有・表示言語・起動パラメータ・リアルタイムマルチプレイを扱うための SDK です。Unity には UPM パッケージ、Godot 4 にはアドオン、エンジンを使わない素の index.html のゲームには JavaScript ライブラリを配布しています。
ここで扱うのは、書き出したゲームから GameRush の API を呼ぶ方法です。どのエンジンからどうやって GameRush に載る形を書き出すかは 対応エンジンと書き出しガイド、ロード時間と処理負荷の詰め方は ゲームの最適化ガイド、作者の手元や CI から呼ぶ HTTP API は API リファレンス にあります。開発者向けの文書は 開発者向けページ から一覧できます。
1. 共通の約束
入力の検証・再送・通信方式の選択・プロトコル差分の吸収は GameRush 側のランタイムが持っていて、SDK が持つのは型のついた呼び出しと待ち合わせだけです。そのため、作者が SDK を更新しなくても GameRush 側の改善はそのまま既存のビルドへ届きます。
SDK の更新が要るのは、新しい API を使いたいときだけです。
- 例外を投げません。すべての呼び出しが結果オブジェクトを返します。GameRush の外(作者の自サイトなど)で動かした場合も
unsupportedを返して静かに縮退します。ただし 9. アナリティクス の呼び出しは、結果オブジェクトではなく真偽値を同期で返します。 - 起動時に
protocolVersionを読みます。SDK が要求する版数を満たさない環境では、その機能を無効にします。例外は JavaScript 版のクラウドセーブのfallbackで、無効にせず、渡さないときと同じ呼び出しになります。 - 知らないイベントは黙って捨てます。ランタイムが SDK より新しいことは常に起こり、
closeのreasonのように既知のイベントへ新しい値が増えることもあります。 - メッセージは 8KB 上限です。WebRTC のデータチャネルの実用上限に合わせてあり、緩める予定はありません。
channelと宛先は必須の概念です。いまの WebSocket 中継ではunreliableも落ちずに順序どおり届きますが、将来 WebRTC へ切り替わると本当に落ちて並び替わります。落ちる前提で書いてください。- join は置き換えです。部屋にいる状態で
joinを呼ぶと、先に現在の部屋が閉じてclose(reasonはreplaced)が届き、それから新しい部屋を取りに行きます。joinが失敗しても元の部屋には戻らないので、続けたいときはcloseを受けてからjoinし直してください。 - 部屋が1通を捨てると
error(codeはmessageTooLarge・rateLimited・badFrame)が届きます。部屋はつながったままで、続けて送れます。部屋が閉じるときはcloseだけが届きます。codeは増えることがあります。 - 同じプレイヤーが別のタブや端末から同じ部屋へ入ると、新しい接続が残り、前の接続には
close(reasonはjoinedElsewhere)が届きます。このときは自動でjoinし直さないでください。し直すと2つのタブが互いを追い出し合い、joinの回数制限に当たるまで続きます。
2. Unity
導入
Package Manager の Add package from git URL... に https://github.com/gamerush-cc/grush-sdk-unity.git#v1.11.1 を入れてください。# の後ろが版で、新しい版へ上げるときはここを書き換えます。Unity 2021.3 以降、ビルドターゲットは WebGL です。パッケージ識別子は cc.gamerush.sdk です(ソースは grush-sdk-unity)。
入れると GameRush メニューが増え、Unity から出ずにログインしてビルドを上げられます(14. Unity Editor からアップロードする)。
API
using GRushSdk;
var self = await GRush.Player.GetSelfAsync();
if (self.Ok) Debug.Log(self.Value.PseudoId);
var joined = await GRush.Net.JoinAsync("duel");
if (!joined.Ok) return;
var room = joined.Value;
room.Message += message =>
{
if (message.IsStale) return;
Apply(message.From, message.Payload);
};
room.PeerJoined += peer => Debug.Log(peer.Index);
room.TransportChanged += transport => Debug.Log(transport);
room.Error += error => Debug.LogWarning(error.Code);
room.Closed += reason => Debug.Log(reason);
room.Send(bytes, GRushChannel.Unreliable, GRushRoom.Everyone);
var now = room.ServerTimeMs();
await room.LeaveAsync();| 型 | 内容 |
|---|---|
GRushResult<T> | Ok / Value / Code(GRushErrorCode)/ Message |
GRushPlayer | PseudoId / IsGuest / ProfileConsent / DisplayName / AvatarUrl(ProfileConsent は、GameRush の設定で名前とアイコンの表示がオンのとき true。ゲストは常に false) |
GRushPeer | Index / PseudoId / DisplayName / AvatarUrl(表示名とアイコンは、その相手が GameRush の設定で表示をオフにしていないときだけ入る。設定オフとゲストでは null) |
GRushMessage | From / Channel / Seq / Payload / IsStale |
GRushRoom | LocalPeerId / HostPeerId / IsHost / Peers / Stats.Transport / RoomCode / RoomEpoch |
JS からのコールバックは次の Update で発火します。イベントハンドラの中は Unity のメインスレッドなので、そのまま Unity の API を触って構いません。
3. Godot 4
導入
grush-sdk-godot のリリース(v1.9.1)を取得し、addons/grush_sdk/ をプロジェクトの addons/ へコピーし、Project Settings > Plugins で有効にしてください。autoload シングルトン GRush が自動登録されます。書き出しは Web です。
API
var self_result: Dictionary = await GRush.player.get_self()
if self_result["ok"]:
print(self_result["value"]["pseudo_id"])
var joined: Dictionary = await GRush.net.join("duel")
if not joined["ok"]:
return
var room: GRushRoom = joined["value"]
room.message_received.connect(func(message: Dictionary) -> void:
if message["is_stale"]:
return
apply(message["from"], message["payload"]))
room.peer_joined.connect(func(peer: Dictionary) -> void: print(peer["index"]))
room.transport_changed.connect(func(transport: String) -> void: print(transport))
room.error_received.connect(func(error: Dictionary) -> void: push_warning(error["code"]))
room.closed.connect(func(reason: String) -> void: print(reason))
room.send(bytes, GRush.CHANNEL_UNRELIABLE, GRush.EVERYONE)
var now := room.server_time_ms()
await room.leave()結果はすべて {"ok": bool, "value": Variant, "code": String, "message": String} の形で返ります。Unity 版と同じ形を GDScript の慣習(snake_case・Dictionary・signal)に置いたものです。
4. JavaScript(素の index.html)
エンジンを使わず HTML と JavaScript だけで書いたゲーム向けです。バンドラは要りません。<script> タグ 1 行でも、ES Modules の import でも読めます。
導入
grush-sdk-js の grush-sdk.js(グローバル版)か grush-sdk.mjs(ES Modules 版)をゲームのフォルダへコピーするか、CDN から読んでください。依存はありません。CDN から読むときは版を固定してください。版を固定しないと、アップロード済みのビルドの挙動が後から変わります。
<script src="https://cdn.jsdelivr.net/gh/gamerush-cc/grush-sdk-js@v0.11.0/grush-sdk.js"></script>
<script>
GRush.leaderboards.submit("high_score", 12000).then((result) => {
if (result.ok) console.log("rank", result.value.rank);
else console.log("not sent", result.code);
});
</script>グローバル版は window.GRush に入ります。同じ名前の変数が既にあるときは上書きせず、window.GRushSdk だけに入ります。
<script type="module">
import GRush from "./grush-sdk.mjs";
const self = await GRush.player.getSelf();
if (self.ok) console.log(self.value.pseudoId);
</script>API
const self = await GRush.player.getSelf();
const profile = await GRush.player.requestProfile();
const saved = await GRush.cloudSave.save({ stage: 3 }, "default");
const loaded = await GRush.cloudSave.load("default");
const joined = await GRush.net.join({ mode: "duel" });
if (joined.ok) {
const room = joined.value;
room.on("message", (message) => {
if (message.isStale) return;
apply(message.from, message.json());
});
room.on("peerjoin", (peer) => console.log(peer.index));
room.on("error", ({ code }) => console.warn(code));
room.on("close", ({ reason }) => console.log(reason));
room.sendJson({ x: 1, y: 2 }, { channel: GRush.CHANNEL_UNRELIABLE, to: GRush.EVERYONE });
const now = room.serverTimeMs();
await room.leave();
}結果はすべて { ok, value, code, message } の形の Promise で返り、reject しません。code は Unity の GRushErrorCode と同じ名前を小文字始まりにした文字列(invalidParams など)です。JavaScript 版だけにある conflict は、クラウドセーブの版ずれ(baseRevision の不一致)です。
- 送れるのは
ArrayBuffer・型付き配列・文字列です。sendJsonは値を JSON 文字列にして送ります。受け取ったメッセージはtext()とjson()で読めます。 GRush.cloudSaveは、本人だけが読むクラウドセーブです。他プレイヤーへは見えません。ゲストはsignInRequiredになります。ゲストの分を端末に置くときは、下の「クラウドセーブの端末への保存」のfallback: "local"を使います。- GameRush は、プレイヤーが次のゲームへ送り始めたときや一覧を開いたときにゲームを止めます。止めている間は
document.visibilityStateがhiddenになり、visibilitychangeが届きます。セーブはこのときに送ってください。次のゲームへ送った後のゲームはまもなく閉じられるので、時間を置いてからの保存は届かないことがあります。 - 止めている間は
Date.now()も止まり、再開すると止めていた時間の分だけ進みます。フレームの経過時間はrequestAnimationFrameの時刻かperformance.now()で測ってください。こちらは止めていた時間を含みません。 GRush.backendは、GameRush 上ではweb、localhost・127.0.0.1・file://ではmock、それ以外のサイトではnoneになります。noneのときは、どの呼び出しもunsupportedを返します。
クラウドセーブの端末への保存
load・save・remove の最後の引数に { fallback: "local" } を渡すと、GameRush がプレイヤーのサインインを確かめられないとき(渡さなければ signInRequired になるとき)だけ、セーブを端末に読み書きします。JavaScript 版だけの機能です。
const loaded = await GRush.cloudSave.load("default", { fallback: "local" });
if (loaded.ok) {
const revision = loaded.value?.revision ?? 0;
const saved = await GRush.cloudSave.save({ stage: 3 }, "default", {
baseRevision: revision,
fallback: "local",
});
if (saved.ok) console.log(saved.value.storage); // "cloud" か "local"
}- 返る項目の
storageは、クラウドに置いたときはcloud、端末に置いたときはlocalです。localは、GameRush がサインインを確かめられず端末に保存したことを表します。端末のセーブのrevisionは常に0です。 - 端末のセーブは、その端末のそのアプリ(ブラウザ)の中にだけあります。別の端末やアプリからは読めず、ブラウザが消すこともあります。
- サインインしても、端末のセーブは自動ではクラウドへ上がりません。サインイン後の
loadでクラウドに無ければ端末のセーブが返るので、それをsaveするとクラウドへ上がります。クラウドにあればクラウドのセーブが返ります。 - 端末のセーブには、保存した時点で GameRush が分かっていたアカウントを記録します。サインイン済みの
loadでは、別のアカウントのものと分かっている端末のセーブは返りません。どのアカウントか分からないまま保存した端末のセーブは、サインインしたどのアカウントにも返ります。 - サインインを確かめられないときの
loadとremoveは、どのアカウントで保存したかに関係なく、その端末のセーブを返し、消します。共用の端末では、前に使った人の端末のセーブが返ることがあります。 fallback: "local"付きのsaveとremoveがクラウドで成功すると、同じスロットの、そのプレイヤーの端末のセーブと、どのアカウントのものか分からない端末のセーブが消えます。別のアカウントのものは残ります。- 一度サインインを確かめられなくなった後でサインインが戻ると、クラウドのセーブが優先されます。その間に端末へ保存した新しい分は、クラウドにセーブがあれば
loadで返りません。fallback: "local"付きの保存がクラウドで成功すると、そのプレイヤーの端末のセーブは消えます。 - 端末への書き込みでは
baseRevisionを見ず、最後の書き込みが残ります。端末のセーブのrevision(0)をそのままbaseRevisionに渡すと、クラウドが空なら書け、クラウドにあればconflictになります。 - サインインの直後の
saveがconflictになったら、loadしてから保存し直してください。 - 切り替えるのはサインインを確かめられないときだけです。通信の失敗・版ずれ・上限超えなど、ほかの失敗では端末に保存せず、そのまま失敗を返します。
fallbackに"local"とnull以外の値を渡すとinvalidParamsです。nullは渡さないときと同じです。protocolVersionが 5 未満の GameRush ではfallbackを無視し、渡さないときと同じ呼び出しになります(ゲストはsignInRequired)。
5. ランキング
ランキングの定義は作者が宣言するもので、ゲームからは作れません。
ゲームからできるのは、宣言済みの枠へ投稿することと読むことだけです。宣言は Studio の画面、ビルドに同梱する grush.json(下の「grush.json で宣言する」)、API(13. API トークン)のどれでもできます。
スコアはゲームからの自己申告値で、GameRush は値を検証していません。
応答の Verified / verified は常に false です。これをゲーム内で表示する義務はありません — 使いたいときのために値を渡しているだけで、GameRush 側の画面では GameRush が自分で明示します。
entry の DisplayName / AvatarUrl は、その相手がサインイン済みで、GameRush の設定「ゲームで名前とアイコンを表示する」をオフにしていないときに入ります。この設定は既定でオンで、全ゲームに共通です。ゲストと、設定をオフにした相手は null です。
名前を出すためにゲームから許可を求める必要はありません。RequestProfileAsync / request_profile / requestProfile はダイアログを出さず、自分の表示名を返します。ゲストなら SignInRequired、設定がオフなら ConsentDeclined です。RevokeProfileAsync / revoke_profile / revokeProfile は設定を変えず、いまのプレイヤー情報を返すだけです。
// Unity
var boards = await GRush.Leaderboards.ListAsync();
var posted = await GRush.Leaderboards.SubmitAsync("high_score", 12000);
var page = await GRush.Leaderboards.TopAsync("high_score", 20);
var near = await GRush.Leaderboards.AroundMeAsync("high_score", 5);
foreach (var entry in page.Value.Entries)
{
var label = entry.IsGuest ? "ゲスト" : (entry.DisplayName ?? "匿名");
}# Godot
var boards: Dictionary = await GRush.leaderboards.list()
var posted: Dictionary = await GRush.leaderboards.submit("high_score", 12000)
var page: Dictionary = await GRush.leaderboards.top("high_score", {"limit": 20})
var near: Dictionary = await GRush.leaderboards.around_me("high_score", {"range": 5})// JavaScript
const boards = await GRush.leaderboards.list();
const posted = await GRush.leaderboards.submit("high_score", 12000, { operationId: runId });
const page = await GRush.leaderboards.top("high_score", { limit: 20 });
const near = await GRush.leaderboards.aroundMe("high_score", { range: 5 });| 投稿が弾かれる条件 | HTTP | 返る GRushErrorCode |
|---|---|---|
宣言されていない key / 停止中の枠 | 404 | InvalidParams |
| 作者が宣言した値域の外 | 400 | InvalidParams |
| 値域を宣言していない枠 | 400 | InvalidParams |
| 有効プレイ 10 秒未満のセッション | 400 | InvalidParams |
session が見つからない / 別ゲーム・別プレイヤーの session | 403 | ConsentDeclined |
| session あたり・毎分あたりの投稿上限 | 429 | RateLimited |
| ホストに再生中のセッションが無い(投稿がサーバへ届かない) | — | Unavailable |
集約が best の枠で、自己ベストを超えない値域内の値を metadata なしで投稿したときは、何も保存しないので、有効プレイ・session・投稿上限の条件では弾かれません。成功として今の値と順位が返り、Updated / updated は false です。投稿上限にも数えません。自分のエントリが作者に非表示にされているときは、この扱いになりません。
ConsentDeclined は session が拒否されたときと FriendsAsync / friends の同意拒否のときの両方で返り、コードもメッセージも同じなので結果からは区別できません。投稿の失敗を Code だけで一括に捌くと、切れたセッションを同意拒否と誤診します。
session はゲームが指定できません。信頼済みの親 UI が live なセッションを付け、そのセッションがこのゲーム・この相手のものかをサーバが検証します。
AroundMeAsync / around_me は、自分がまだ載っていないときに空の一覧を返します。先頭を返すとゲーム側が「自分は1位」と誤読するためです。
FriendsAsync / friends は相互フォローに絞った一覧ですが、ゲームごとのフレンド同意がまだ無いため、現在は常に ConsentDeclined を返します。
エントリの metadata は、Godot はそのまま辞書で返しますが Unity は返しません。metadata が要る Unity のゲームは、応答の生 JSON である GRushLeaderboardPage.RawJson を自前のパーサへ渡してください。
grush.json で宣言する
ビルドのいちばん上(index.html と同じ階層)に grush.json を置くと、そのビルドが最新になったときに中のランキングの定義が反映されます。Studio からアップロードするだけで定義でき、API トークンは要りません。公開中のゲームでは、新しいビルドは審査で承認されてから配信されるので、定義もそのビルドが承認されて配信される時点で反映されます。それまでは、配信中のビルドの定義がそのまま使われます。
{
"leaderboards": [
{
"key": "high_score",
"title": "ハイスコア",
"sort": "desc",
"valueType": "int",
"aggregation": "best",
"period": "all_time",
"minValue": 0,
"maxValue": 1000000
},
{
"key": "weekly_time",
"title": "今週のタイムアタック",
"sort": "asc",
"valueType": "duration_ms",
"aggregation": "best",
"period": "weekly",
"minValue": 0,
"maxValue": 3600000,
"enabled": true
}
]
}| 項目 | 必須 | 値 |
|---|---|---|
key | 必須 | 英数字と . _ : - で 1〜64 文字。. と .. だけのキーは使えない。ゲームから投稿するときの識別子 |
title | 必須 | 1〜60 文字。改行と制御文字は空白にして保存します |
sort | 必須 | desc(大きいほど上位)か asc(小さいほど上位) |
valueType | 必須 | int / float / duration_ms |
aggregation | 必須 | best / last / sum |
period | 必須 | all_time / daily / weekly / monthly |
minValue / maxValue | 任意 | 数値。両方そろえて書き、minValue を小さくします。省くと値域なしで、その枠は投稿を受けません |
enabled | 任意 | 省くと true。false で停止します |
最上位に書けるのは leaderboards と $schema だけです。知らない項目・重複した key・21 個以上の定義・64KB を超えるファイルは、綴りの誤りを見逃さないよう拒みます。Studio はアップロードの前に同じ規則で確かめます。
反映は key ごとで、同じ内容なら何も書きません。grush.json に書かなかった key の枠は、消えも止まりもしません。止めるときは "enabled": false と書きます。消すときは、Studio の画面か API で行ってください。grush.json に書いた key の枠の title・値域・enabled は、Studio の画面や API で変えても、grush.json を含むビルドを次に上げたときにファイルの値へ戻ります。
スコアが投稿された枠の sort・valueType・aggregation・period を変える宣言と、すでにある枠と合わせて 1 ゲーム 20 個を超える宣言は、ビルドごと失敗になります。変えたいときは新しい key で宣言してください。
grush.json はほかのファイルと同じく配信されるので、ゲームから読んでも構いません。gzip で事前圧縮したものは読めますが、brotli で圧縮したものは受け付けません。API トークンで grush.json を含むビルドを上げるときは、builds:write に加えて leaderboards:write が要ります。
| エンジン | 置く場所 | ビルドの最上位へ届く方法 |
|---|---|---|
| Unity | プロジェクトの直下(Assets/ の隣)の grush.json | WebGL でビルドすると、SDK が出力先の最上位へ写します。ファイルが無いのに出力先に grush.json が残っていれば、消さずに警告を出します。Assets/ や StreamingAssets/ に置いても最上位には出ません |
| Godot | res://grush.json | Web で書き出すと、SDK の書き出しプラグインが index.html の隣へ写します。ファイルが無いのに書き出し先に grush.json が残っていれば、消さずに警告を出します。.pck の中のファイルは読みません |
| JavaScript | index.html の隣の grush.json | そのままアップロードします |
エディタとローカルのモックも同じ grush.json を読み、最初のランキングの呼び出しの前にその枠を宣言します。JavaScript 版が読むのはローカルサーバから開いたときだけで、3 秒以内に読めなければ読まなかったものとして扱います。file:// では GRush.mock.loadLeaderboards(manifest) に中身を渡してください。中身を渡すときは Unity が GRushMockLeaderboards.LoadManifest(json)、Godot が GRushMockLeaderboards.load_manifest(json_text) で、どれも読むのはアップロードと同じ規則です。
モックは書式の誤りをアップロードと同じ文言で警告し、その中身を使いません。中身を渡したときの誤りは、Unity の LoadManifest が GRushErrorCode.InvalidParams、Godot の load_manifest と JavaScript の loadLeaderboards が invalidParams で返します。enabled: false の枠は一覧に出ず、投稿と取得は宣言していない key と同じ失敗(Unity は InvalidParams、Godot と JavaScript は invalidParams)です。grush.json で minValue / maxValue を書かなかった枠は、GameRush と同じく投稿を受けません。period が daily / weekly / monthly の枠は GameRush と同じ期間キー(UTC)を返し、期間が変わると空からやり直します。ファイルを自動で読むときは、同じ key を Define / define / defineLeaderboard で宣言してあればそちらを使います。LoadManifest / load_manifest / loadLeaderboards に渡した定義は、それらの宣言を置き換えます。
集約が sum のランキングを再送するときは識別子を渡す
SubmitAsync(key, value, metadataJson, operationId) / submit(key, value, metadata, operation_id) の第4引数は、同じ投稿の再送を弾くための識別子です。
加算は冪等ではありません。
投稿がサーバに届いて保存された後で応答だけが失われた場合、そのまま送り直すと二重に足され、集約後の値からは元へ戻せません。再送するなら同じ operationId を渡してください。best と last は同じ値を再適用しても結果が変わらないので、省略して構いません。
6. 公開プレイヤー状態
他プレイヤーへ見せる進捗(称号・ひとことなど)の入れ物です。クラウドセーブとは別で、本人だけが読む非公開データはこの経路から一切読めません。
ここに置いたものは他プレイヤーへ見えるユーザー生成コンテンツで、通報とモデレーションの対象になります。そのため payload に制約があります。
| 制約 | 理由 |
|---|---|
| JSON オブジェクトに限る(裸の文字列・配列は不可) | 実質バイナリの詰め込み場所になる |
| 4KB 以下 | — |
| base64 のような読めない長い文字列を含まない | 通報されたとき人間が読めないとモデレーションが成立しない |
Unity の payload は生の JSON 文字列、Godot は辞書、JavaScript はオブジェクトです。
// Unity
await GRush.PlayerState.SetMineAsync("{\"title\":\"初心者\",\"level\":3}");
var mine = await GRush.PlayerState.GetMineAsync();
var others = await GRush.PlayerState.GetAsync(new[] { pseudoIdA, pseudoIdB });# Godot
await GRush.player_state.set_mine({"title": "初心者", "level": 3})
var mine: Dictionary = await GRush.player_state.get_mine()
var others: Dictionary = await GRush.player_state.get_many([pseudo_id_a, pseudo_id_b])// JavaScript
await GRush.playerState.setMine({ title: "初心者", level: 3 });
const mine = await GRush.playerState.getMine();
const others = await GRush.playerState.get([pseudoIdA, pseudoIdB]);Godot 側だけメソッド名が get_many なのは、get が Godot 側の Object.get と衝突してパースエラーになるためです。通信上のメソッド名は Unity と同じ playerState.get です。
運営が伏せた状態は返りません。要求した疑似IDの数だけ返ってくる前提で書かないでください。エディタのモックでも GRushMockPlayerStates.Define(id, json, hidden: true) / define(id, payload, true) で同じ挙動を試せます。
SetMine に baseRevision を渡すと楽観ロックになり、他所で更新されていれば InvalidParams で戻ります。省略すれば常に上書きします。
相手探し
非同期対戦の相手を探すために、同じゲームの他プレイヤーの公開状態をランダムに引けます。公開状態には相手探しに使う数値 rating を持たせられ、rating の範囲で絞れます。
// Unity
await GRush.PlayerState.SetMineAsync("{\"deck\":\"fire\",\"level\":12}", -1, 1520);
var found = await GRush.PlayerState.SampleAsync(5, 1400, 1600);
if (found.Ok)
{
foreach (var rival in found.Value.States) Debug.Log(rival.PseudoId + " " + rival.Rating);
}# Godot
await GRush.player_state.set_mine({"deck": "fire", "level": 12}, null, {"rating": 1520})
var found: Dictionary = await GRush.player_state.sample({"count": 5, "min_rating": 1400, "max_rating": 1600})
if found["ok"]:
for rival in found["value"]:
print(rival["pseudo_id"], rival["rating"])// JavaScript
await GRush.playerState.setMine({ deck: "fire", level: 12 }, undefined, { rating: 1520 });
const found = await GRush.playerState.sample({ count: 5, minRating: 1400, maxRating: 1600 });
if (found.ok) for (const rival of found.value) console.log(rival.pseudoId, rival.rating);| 呼び出し | Unity | Godot | JavaScript |
|---|---|---|---|
| 相手を引く | PlayerState.SampleAsync(count, minRating, maxRating) | player_state.sample({ count, min_rating, max_rating }) | playerState.sample({ count, minRating, maxRating }) |
| rating を書く | PlayerState.SetMineAsync(json, baseRevision, rating) | player_state.set_mine(payload, base_revision, {"rating": r}) | playerState.setMine(payload, baseRevision, { rating }) |
- 1 回に引けるのは 1〜20 件で、省略すると 10 件です。自分の状態と運営が伏せた状態は返りません。
- 返るのは疑似ID・payload・rating などの状態だけで、名前とアイコンは返りません。
- rating の範囲は最小と最大を一緒に渡します。範囲を渡すと、rating を書いていない状態は返りません。
- rating は -1e9 より大きく 1e9 より小さい数値です。
nullを渡すと消え、渡さなければ今の値のまま payload だけを書き換えます。 - rating を書いていない状態の rating は、Unity では
HasRatingがfalse、Godot と JavaScript ではnullです。 - 相手探しは
protocolVersion7 以上の GameRush で使えます。それより古い GameRush では、相手探しはUnsupportedになり、rating は送らずに payload だけを書きます。Unity はGRush.IsMatchmakingAvailableで確かめられます。 protocolVersionが 7 以上でも、アプリの更新の間などは rating が書かれないことがあります。そのときも書き込みは成功し、payload は書かれます。rating が書けたかは、setMine が返す状態の rating で確かめてください。
7. 受信箱
同じゲームのプレイヤーの疑似ID宛てに、小さい構造化メッセージを送れます。非同期対戦の結果の通知などに使います。ゲストも送受信できます。
payload で文章や画像は送れません。プレイヤーどうしの文章や画像のやりとりには使わないでください。
| payload の形 | 上限 |
|---|---|
| JSON のオブジェクト(Unity は JSON の文字列、Godot は辞書) | 詰めた JSON で 1KB |
文字列の値とキーは英数字と _ . : - だけ(空白・日本語・ほかの記号は不可) | 32 文字 |
| ほかは数値・真偽値・null と、それらの配列とオブジェクト | 値は合計 64 個 |
| 入れ子 | 最上位を 1 段目として 4 段 |
識別子(アイテムの ID・ステージの番号・スコアなど)は運べても、文章は運べない形です。合わない payload は InvalidParams になります。kind も英数字と _ . : - の 32 文字までです。
// Unity
await GRush.Inbox.SendAsync(rivalPseudoId, "battleResult", "{\"stage\":3,\"win\":true}");
var inbox = await GRush.Inbox.ListAsync(true);
if (inbox.Ok)
{
foreach (var message in inbox.Value.Messages)
{
var result = JsonUtility.FromJson<BattleResult>(message.PayloadJson);
}
}# Godot
await GRush.inbox.send(rival_pseudo_id, "battleResult", {"stage": 3, "win": true})
var inbox: Dictionary = await GRush.inbox.list({"unread_only": true})
if inbox["ok"]:
for message in inbox["value"]["messages"]:
print(message["from"], message["kind"], message["payload"])// JavaScript
await GRush.inbox.send(rivalPseudoId, "battleResult", { stage: 3, win: true });
const inbox = await GRush.inbox.list({ unreadOnly: true });
if (inbox.ok) {
for (const message of inbox.value.messages) console.log(message.from, message.kind, message.payload);
await GRush.inbox.markRead(inbox.value.messages.map((message) => message.id));
}| 呼び出し | Unity | Godot | JavaScript |
|---|---|---|---|
| 送る | Inbox.SendAsync(to, kind, json) | inbox.send(to, kind, payload) | inbox.send(to, kind, payload) |
| 一覧 | Inbox.ListAsync(unreadOnly, limit, cursor) | inbox.list({ unread_only, limit, cursor }) | inbox.list({ unreadOnly, limit, cursor }) |
| 既読にする | Inbox.MarkReadAsync(ids) | inbox.mark_read(ids) | inbox.markRead(ids) |
| 消す | Inbox.RemoveAsync(ids) | inbox.remove(ids) | inbox.remove(ids) |
| 通報する | Inbox.ReportAsync(messageId) | inbox.report(message_id) | inbox.report(messageId) |
- 一覧は新しい順で、1 回 1〜50 件(省略すると 20 件)です。続きは、返った次のカーソルを渡して読みます。未読の数は絞り込みによらず返ります。
- メッセージには、送り手の疑似ID・
kind・payload・届いた日時・既読にした日時(未読なら null)が入ります。Unity の payload は JSON の文字列(PayloadJson)で、JsonUtility.FromJsonなどでゲームの型に読めます。 - 既読と削除は 1 回 50 件までで、更新した件数が返ります。
- 自分宛てには送れません。同じゲームの疑似IDでない宛先は
InvalidParamsです。 - 送りすぎ(1 人毎時 60 件・毎日 300 件、同じ宛先へは毎時 10 件まで)は
RateLimitedで、時間が経てば回復します。待つ時間が分かるとき(ゲストの短い間の送りすぎ)は、JavaScript の結果に待ち時間retryAfterMsが入ります。 - 受信箱は
protocolVersion7 以上の GameRush で使えます。それより古い GameRush では、どの呼び出しもUnsupportedです。Unity はGRush.IsInboxAvailableで確かめられます。
通報
ゲームは通報の画面を描けません。通報を呼ぶと GameRush が確認を出し、プレイヤーが承諾したときだけ送られます。返るのは「送られたか」の真偽値です。ゲストは SignInRequired です。
通報すると送り手をブロックします。その相手からのメッセージと、その相手の出品は見えなくなります。確認には、GameRush が取り直したメッセージの中身と、送り手の疑似IDの先頭 8 文字と受け取った日時が出ます。
8. 交換所
プレイヤーがゲームのアイテムを出品し、同じゲームの別のプレイヤーが受けて交換できます。出品も受諾もゲストができます。
GameRush はアイテムの正当性を確かめません。出品の中身はゲームの申告のままです。保証するのは、交換が片方だけ成立しないことと、終わった取引を取りこぼさないことだけです。課金アイテムには使わないでください。
出すもの(offer)・欲しいもの(want)・受ける側が渡すもの(give)は、7. 受信箱 の payload と同じ形で、文章や画像は送れません。大きさは詰めた JSON で offer 2KB・want 1KB・give 2KB までです。プレイヤーどうしの文章や画像のやりとりには使わないでください。
// Unity
// 出品: 出すアイテムをセーブで仮置きにし、operationId と一緒に保存してから呼ぶ。
var created = await GRush.Trade.CreateAsync(
"{\"item\":\"sword\",\"count\":1}",
"{\"item\":\"gem\",\"count\":3}",
save.EscrowOperationId,
new GRushTradeOptions { OfferKey = "sword", WantKey = "gem" }
);
// 受ける
var accepted = await GRush.Trade.AcceptAsync(tradeId, "{\"item\":\"gem\",\"count\":3}");
if (accepted.Ok && accepted.Value.Outcome == GRushTradeOutcome.Taken) ShowTaken();
// 受け取り: 反映して保存してから Ack する
var mine = await GRush.Trade.MineAsync();# Godot
var created: Dictionary = await GRush.trade.create(
{"item": "sword", "count": 1}, {"item": "gem", "count": 3}, save["escrow_operation_id"],
{"offer_key": "sword", "want_key": "gem"}
)
var accepted: Dictionary = await GRush.trade.accept(trade_id, {"item": "gem", "count": 3})
var mine: Dictionary = await GRush.trade.mine()// JavaScript
const created = await GRush.trade.create({
offer: { item: "sword", count: 1 },
want: { item: "gem", count: 3 },
offerKey: "sword",
wantKey: "gem",
operationId: save.escrow.operationId,
});
const accepted = await GRush.trade.accept(tradeId, { item: "gem", count: 3 });
if (accepted.ok && accepted.value.outcome === "taken") showTaken();
const KNOWN_ACTIONS = ["receiveGive", "returnOffer", "receiveOffer"];
const mine = await GRush.trade.mine();
if (mine.ok) {
const done = mine.value.pending.filter((trade) => KNOWN_ACTIONS.includes(trade.action));
for (const trade of done) {
if (save.appliedTrades.includes(trade.id)) continue;
applyTrade(save, trade);
save.appliedTrades.push(trade.id);
}
const saved = await GRush.cloudSave.save(save);
if (saved.ok && done.length) await GRush.trade.ack(done.map((trade) => trade.id));
}| 呼び出し | Unity | Godot | JavaScript |
|---|---|---|---|
| 出品する | Trade.CreateAsync(offer, want, operationId, options) | trade.create(offer, want, operation_id, options) | trade.create({ offer, want, operationId, … }) |
| 受付中の出品の一覧 | Trade.ListAsync(offerKey, wantKey, limit, cursor) | trade.list({ offer_key, want_key, limit, cursor }) | trade.list({ offerKey, wantKey, limit, cursor }) |
| 自分の出品と、終わった取引 | Trade.MineAsync() | trade.mine() | trade.mine() |
| 受ける | Trade.AcceptAsync(tradeId, give) | trade.accept(trade_id, give) | trade.accept(tradeId, give) |
| 取り下げる | Trade.CancelAsync(tradeId) | trade.cancel(trade_id) | trade.cancel(tradeId) |
| 受け取りを確かめる | Trade.AckAsync(ids) | trade.ack(ids) | trade.ack(ids) |
| 通報する | Trade.ReportAsync(tradeId) | trade.report(trade_id) | trade.report(tradeId) |
交換所は protocolVersion 7 以上の GameRush で使えます。それより古い GameRush では、どの呼び出しも Unsupported です。Unity は GRush.IsTradeAvailable で確かめられます。
取りこぼさない使い方
通信が途中で切れても、アイテムが消えたり増えたりしないよう、次の順で使ってください。
- 1. 出品の前に、出すアイテムを手元のセーブで仮置き(使えない状態)にします。
- 2. 出品の識別子
operationId(英数字と._:-の 64 文字まで)をセーブに保存してから出品します。 - 3. 応答が失われたら、同じ
operationIdで出品を呼び直します。同じoperationIdの再送には、中身によらず先に作った出品が返ります。 - 4. 自分の取引(Unity は
MineAsync、Godot と JavaScript はmine)の、終わった取引(pending)をセーブに反映し、クラウドセーブに保存してから、その取引 ID で受け取りを確かめます(ack)。ack するまで、終わった取引は pending に残ります。 - 5. 反映済みの取引 ID をセーブに持ち、同じ取引を二度反映しないようにします。
| pending の action | 誰が | セーブへの反映 |
|---|---|---|
receiveGive | 出品者 | 成立した。受けた側の give を足し、仮置きの offer を消す |
returnOffer | 出品者 | 取り下げ・期限切れ・運営の非表示で終わった。仮置きの offer を手元へ戻す |
receiveOffer | 受けた側 | 成立した。offer を足す |
知らない action は反映せず、ack もしないでください。ゲストのときに出品・受諾した取引の pending は、同じゲストとして開いたときに返り、サインインしている間は返りません。
出品と受諾
- 出品は、誰でも受けられる公開の出品と、相手の疑似IDを指定する指名の出品の2種類です。
offerKey/wantKeyは一覧の絞り込みに使う識別子(英数字と._:-の 32 文字まで)で、省略できます。- 期限は 600〜604800 秒(10 分〜7 日)で、省略すると 3 日です。受付中の出品は 1 人 10 件までです。
- 受付中の出品が 10 件あるときの出品は
InvalidParamsです。待っても直らないので、取り下げるか、成立・期限切れを待ってから出品してください。 - 出品は 1 人毎日 30 件、成立させた受諾は毎時 30 件までで、超えると
RateLimitedです。時間が経てば回復します。待つ時間が分かるとき(ゲストの短い間の送りすぎ)は、JavaScript の結果に待ち時間retryAfterMsが入ります。 - 一覧に出るのは、他人の公開の出品と自分宛ての指名の出品のうち、受付中のもの(新しい順、1 回 1〜50 件)です。
- give は当事者(出品者と受けた側)にだけ入り、ほかの人には null です。
| 受諾の結果(outcome) | 意味 |
|---|---|
matched | 成立した。同じ取引への再送も matched |
taken | 先に別のプレイヤーが受けた |
expired | 期限が切れていた |
cancelled | 出品者が取り下げた |
notFound | 見えない出品(伏せられた・自分の・自分宛てでない指名の・ブロック関係にある相手の) |
受諾の結果はエラーではなく outcome で返ります。成立した取引は matched のときだけ一緒に返ります。2人が同時に受けても、成立するのは片方だけです。
通報
ゲームは通報の画面を描けません。通報を呼ぶと GameRush が確認を出し、プレイヤーが承諾したときだけ送られます。返るのは「送られたか」の真偽値です。ゲストは SignInRequired です。通報すると出品者をブロックし、その相手のメッセージと出品は見えなくなります。
確認には、GameRush が取り直した出品の中身と、出品者の疑似IDの先頭 8 文字と出品の日時が出ます。プレイヤーは画面の出品と見比べて通報します。自分の出品は通報できず、確認を出さずに InvalidParams です。
9. アナリティクス
ゲームの中で起きたことを送ると、Studio の作品詳細のアナリティクスに集計が出ます。送れるのは、オンボーディングとファネルのステップ(「ファネル」のタブ)、ゲームが決めた出来事(「カスタムイベント」のタブ)、ゲーム内の通貨の入手と消費(「エコノミー」のタブ)です。Unity は v1.6.0、Godot は v1.5.0、JavaScript は v0.7.0 以降の SDK で使えます。導入の節の版(Unity v1.11.1・Godot v1.9.1・JavaScript v0.11.0)は、どれも対応しています。
1. 共通の約束 の例外です。どの呼び出しも結果オブジェクトではなく真偽値を同期で返し、例外を投げません。待たずに、どこからでも呼べます。true は GameRush へ渡したことで、保存されたことではありません。
// Unity
GRush.Analytics.LogOnboardingStep(1, "tutorial_start");
GRush.Analytics.LogFunnelStep("shop", "visit-1", 2, "item_selected");
GRush.Analytics.LogCustomEvent(
"boss_defeated",
1,
new Dictionary<string, string> { { "stage", "3" } }
);
GRush.Analytics.LogEconomyEvent(
GRushEconomyFlowType.Sink,
"coin",
50,
120,
GRushEconomyTransactionType.Shop,
itemSku: "sword"
);# Godot
GRush.analytics.log_onboarding_step(1, "tutorial_start")
GRush.analytics.log_funnel_step("shop", "visit-1", 2, "item_selected", {"tab": "weapons"})
GRush.analytics.log_custom_event("boss_defeated", 1, {"stage": "3"})
GRush.analytics.log_economy_event(
GRush.AnalyticsApi.FLOW_SINK, "coin", 50, 120, GRush.AnalyticsApi.TRANSACTION_SHOP, "sword"
)// JavaScript
GRush.analytics.logOnboardingStep(1, "tutorial_start");
GRush.analytics.logFunnelStep("shop", "visit-1", 2, "item_selected", { fields: { tab: "weapons" } });
GRush.analytics.logCustomEvent("boss_defeated", 1, { fields: { stage: "3" } });
GRush.analytics.logEconomyEvent("sink", "coin", 50, 120, "Shop", { itemSku: "sword" });| 送るもの | Unity | Godot | JavaScript |
|---|---|---|---|
| オンボーディングのステップ | Analytics.LogOnboardingStep(step, stepName, fields) | analytics.log_onboarding_step(step, step_name, fields) | analytics.logOnboardingStep(step, stepName, { fields }) |
| ファネルのステップ | Analytics.LogFunnelStep(funnel, funnelSessionId, step, stepName, fields) | analytics.log_funnel_step(funnel, funnel_session_id, step, step_name, fields) | analytics.logFunnelStep(funnel, funnelSessionId, step, stepName, { fields }) |
| カスタムイベント | Analytics.LogCustomEvent(name, value, fields) | analytics.log_custom_event(event_name, value, fields) | analytics.logCustomEvent(name, value, { fields }) |
| 通貨の入手と消費 | Analytics.LogEconomyEvent(flowType, currency, amount, endingBalance, transactionType, itemSku, fields) | analytics.log_economy_event(flow_type, currency, amount, ending_balance, transaction_type, item_sku, fields) | analytics.logEconomyEvent(flowType, currency, amount, endingBalance, transactionType, { itemSku, fields }) |
| GameRush の中で送れるか | Analytics.IsAvailable | analytics.is_available() | analytics.isAvailable() |
名前・値・fields に、個人を識別できる情報を入れないでください。プレイヤーの名前・メールアドレス・アカウントの ID のような値は、集計のための名前や値として送るものではありません。
| 項目 | 制限 |
|---|---|
step | 1〜100 の整数 |
value(カスタムイベント) | 省略で 1。絶対値 1e9 まで |
amount(エコノミー) | 正の値で 1e12 まで |
endingBalance(エコノミー) | その後の残高。省略できません。絶対値 1e15 まで |
flowType(エコノミー) | source(入手)か sink(消費) |
transactionType(エコノミー) | 標準の値は TimedReward・Onboarding・Shop・Gameplay・ContextualPurchase。ほかの値も受けます |
イベント名・ファネル名・funnelSessionId | 英数字と . _ : - で 1〜64 文字 |
funnelSessionId(ファネル) | 省略できます。Unity は null、Godot は空文字、JavaScript は undefined を渡します |
通貨・transactionType | 英数字と . _ : - で 1〜32 文字 |
stepName・itemSku・fields の値 | 1行のテキストで 40 文字まで |
fields | 3つまで。キーは英数字と _ で 1〜32 文字 |
- 形が合わないときは
falseを返し、理由ごとに1回だけコンソールに警告が出ます。stepName・itemSku・fieldsの値は、改行・制御文字・双方向の上書きと isolate を空白に直し、前後の空白を落としてから数えます。直した結果が空のstepNameとitemSkuは省略と同じに扱い、空のfieldsの値は拒みます。 - オンボーディングとファネルの同じステップは、1セッションで最初の1回だけが残ります。セッションはページを読み直しても続きます。同じファネルを何度も通るなら、通るたびに別の
funnelSessionIdを渡してください。 - ゲームが前面に出るまでの分は、GameRush が溜めてから送ります。溜められるのは 30 件までで、超えた分は
falseです。 trueが返っても、GameRush が掛ける上限を超えた分は保存されません。前面に出た後は 500ms に1件、まとめて最大 30 件までを受け、送る前の列は合計 200 件までです。保存はセッションあたり、オンボーディングを含むファネルのステップ 200 件・カスタムイベント 500 件・エコノミー 500 件までです。送れなかった分は送り直しません。
古い GameRush(protocolVersion 6 未満)と GameRush の外では、どの呼び出しも false です。ゲームに読み込まれる GameRush のスクリプトは新しくても、アナリティクスに対応する前の古い GameRush のアプリ(ストアの更新を待つ iOS・Android のアプリなど)の上では、true が返っても送られません。そのぶん集計の人数は、遊んだ人数より少なくなることがあります。
エディタとモックでは、GameRush と同じ検査をして、通ったイベントを1件ずつコンソールに出し、直近の 200 件を持ちます(Unity は GRushMock.AnalyticsEvents、Godot は GRushMock.analytics_events、JavaScript は GRush.mock.analyticsEvents())。どこへも送りません。前面に出るまでの溜め・GameRush の上限・同じステップを最初の1回だけ残す扱い・セッションあたりの上限は再現しないので、モックでは true の分がすべて記録に残ります。
10. ゲームからの共有
ゲームから「いまの画面や結果を共有して」と頼めます。呼んだ時点では共有は開かず、GameRush の確認シートが出て、プレイヤーが送り先を押したときに開きます。シートを出している間、ゲームは一時停止します。
ゲームに返るのは「開いた(opened)」か「やめた(cancelled)」だけです。どこへ送ったか、実際に投稿されたかは GameRush にも分からないので返りません。
共有には GameRush が URL を付けます。そのゲームのページか、画像と本文を載せた共有ページの URL で、ゲームから URL は指定できません。ゲームが指定できるのは、URL に添える起動パラメータだけです(下の「起動パラメータを添える」)。
// Unity
if (await GRush.Share.IsAvailableAsync())
{
var shared = await GRush.Share.ShareScreenAsync("ステージ3をクリア");
if (shared.Ok && shared.Value == GRushShareStatus.Opened) ShowThanks();
}# Godot
if await GRush.share.is_available():
var shared: Dictionary = await GRush.share.share_screen("ステージ3をクリア")
if shared["ok"] and shared["value"]["status"] == "opened":
show_thanks()// JavaScript
if (await GRush.share.isAvailable()) {
const shared = await GRush.share.share({ text: "ステージ3をクリア", image: "screen" });
if (shared.ok && shared.value.status === "opened") showThanks();
}| 呼び出し | Unity | Godot | JavaScript |
|---|---|---|---|
| 本文と画像 | Share.ShareAsync(text, bytes) | share.share(text, image) | share.share({ text, image }) |
| 本文と画面のスクショ | Share.ShareScreenAsync(text) | share.share_screen(text) | share.share({ text, image: "screen" }) |
| いま共有できるか | Share.IsAvailableAsync() | share.is_available() | share.isAvailable() |
共有できるかは失敗せず、true か false で返ります。共有ボタンを出す前に確かめてください。GameRush の外、古い GameRush、埋め込み表示では false です。
起動パラメータを添える
共有に起動パラメータを添えると、GameRush が付ける URL に ?gp.dungeon=3 のように載ります。受け取った人がその URL からゲームを開くと、ゲームは 12. 起動パラメータ の呼び出しで値を読めます。
// Unity
var launchParams = new Dictionary<string, string> { { "dungeon", "3" } };
var shared = await GRush.Share.ShareScreenAsync("ダンジョン3に挑戦", launchParams);
if (!shared.Ok && shared.Code == GRushErrorCode.Unsupported)
{
shared = await GRush.Share.ShareScreenAsync("ダンジョン3に挑戦");
}# Godot
var shared: Dictionary = await GRush.share.share_screen("ダンジョン3に挑戦", {"dungeon": "3"})
if not shared["ok"] and shared["code"] == "unsupported":
shared = await GRush.share.share_screen("ダンジョン3に挑戦")// JavaScript
let shared = await GRush.share.share({
text: "ダンジョン3に挑戦",
image: "screen",
launchParams: { dungeon: "3" },
});
if (!shared.ok && shared.code === "unsupported") {
shared = await GRush.share.share({ text: "ダンジョン3に挑戦", image: "screen" });
}| 呼び出し | Unity | Godot | JavaScript |
|---|---|---|---|
| 本文と画像 | Share.ShareAsync(text, bytes, launchParams) | share.share(text, image, launch_params) | share.share({ text, image, launchParams }) |
| 本文と画面のスクショ | Share.ShareScreenAsync(text, launchParams) | share.share_screen(text, launch_params) | share.share({ text, image: "screen", launchParams }) |
- キーと値の制限は 12. 起動パラメータ と同じです。キーは英数字と
_-で 1〜32 文字(__proto__は使えません)、値は英数字と._~:,-で 0〜128 文字、8 個までです。 - URL から読むときと違い、1つでも合わない項目があると共有を開かずに
InvalidParamsを返します。合わない項目だけを落として共有することはしません。 - 空の起動パラメータは、渡さないときと同じです。
- 起動パラメータに対応する前の古い GameRush(
protocolVersion9 未満)では、共有を開かずにUnsupportedを返します。起動パラメータを外して呼び直せば共有できます。
共有した URL は誰にでも見え、そのまま転送されます。招待コードのような、プレイヤーを識別できる値を入れないでください。
受け取る側のゲームは値を信用せず、確かめてから使ってください。共有した URL の起動パラメータは、受け取った人が書き換えられます。
画像の形
- Unity は画像ファイルのバイト列(
Texture2D.EncodeToPNG()など)を渡します。 - Godot は
Imageを渡します。SDK が PNG にしてから渡します。 - JavaScript は
Blob・ArrayBuffer・型付き配列・data URL・HTMLCanvasElement・{ base64, mimeType }を渡せます。 - どれも
"screen"(Unity はShareScreenAsync、Godot はshare_screen)でゲームの画面のスクショを送れます。 - 受け付ける形式は PNG・JPEG・GIF・WebP です。SVG などほかの形式は
InvalidParamsになります。 - 4MB まで、縦横それぞれ 4096px まで、総画素数 16MP までです。GameRush が長辺 1600px の JPEG に作り直してから送ります(GIF は 1 コマ目、透過は黒)。
"screen" に写るのはゲームの canvas だけです。DOM で描いた文字や画像は "screen" に写りません。
HTML の要素でスコアや名前を重ねているなら、canvas に描いた画像を自分で作って渡してください。ゲームが最初に描画するより前の "screen" は Unavailable です。
画像が実際に送り先へ付くかは、プレイヤーの端末と送り先で決まります。どう送るかはシートに出ます。
- 画像ファイルを付けられるスマホの端末共有では、画像ファイルを付けます。
- LINE の送信先選択、画像ファイルを付けられないスマホの端末共有、PC の X 投稿画面では、画像と本文を載せた共有ページを作ってその URL を送ります。LINE には画像と「遊ぶ」ボタンの付いたメッセージで届き、X ではリンクのカードに画像が出ます。共有ページを作れなかったときは本文と URL だけになります。
- PC の X 投稿画面には画像ファイルを付けられません。画像つきのポストにしたいプレイヤーは、シートで画像を保存して投稿画面で添付できます。
- Android アプリでは本文と URL だけになります。
共有ページは 90 日間、URL を知っている人なら誰でも見られます。ゲームが渡した本文がそのまま載るので、プレイヤーに見られて困る文字を入れないでください。載せる画像は長辺 1024px・1MB までの JPEG に作り直されます。
本文
| 本文の条件 | 結果 |
|---|---|
| 100 文字を超える | InvalidParams |
URL を含む(https://・www.・example.com のような「英数字.英字2文字以上」) | InvalidParams |
@メンションを含む(全角の @ も同じ) | InvalidParams |
| 制御文字・ゼロ幅スペースなどの見えない文字 | 取り除いて送る |
| 空行 | 取り除いて送る。改行は 2 つまでで、3 行目以降は空白でつなぐ |
URL は形で判定するので、Lv.MAX のような英字どうしのドット表記も弾かれます(v1.5 のように数字を挟むものは通ります)。ハッシュタグは使えます。
X へ送るときは、GameRush が本文の末尾に #GameRush を付けます。本文に #GameRush があれば付けません。
エラーコード
| 返るコード | いつ |
|---|---|
Unsupported | GameRush の外で動かしたとき。共有に対応していない古い GameRush(protocolVersion 3 未満)。起動パラメータを添えた共有に対応していない古い GameRush(protocolVersion 9 未満) |
InvalidParams | 本文か画像か起動パラメータが上の条件に合わない |
Unavailable | いま共有できない(プレイヤーがフィードをめくるモードにいる・タブが裏にある・埋め込み表示・GameRush が共有を止めている・1 回の滞在で同じゲームから 5 回を超えた)。最初の描画より前の画面のスクショ |
RateLimited | シートを出している間の 2 回目、5 秒以内の続けての呼び出し |
結果が返るまで、プレイヤーがシートで迷うあいだ待ちます。14 分でシートは閉じて cancelled が返ります。opened を報酬の条件にするなら、同じ報酬を何度も渡さないようゲーム側で数えてください。
11. 表示言語
プレイヤーが GameRush をどの言語で使っているかを読めます。ゲームの文言をそれに合わせて切り替えるのに使います。
// Unity
GRush.Locale.Changed += (next) => ApplyLanguage(PickLanguage(next));
var locale = await GRush.Locale.GetAsync();
if (locale.Ok) ApplyLanguage(PickLanguage(locale.Value));# Godot
GRush.locale.changed.connect(func(next): apply_language(pick_language(next)))
var locale: Dictionary = await GRush.locale.fetch()
if locale["ok"]:
apply_language(pick_language(locale["value"]))// JavaScript
GRush.locale.onChange((next) => applyLanguage(pickLanguage(next)));
const locale = await GRush.locale.get();
if (locale.ok) applyLanguage(pickLanguage(locale.value));| 値 | 意味 |
|---|---|
locale | source が user か system なら GameRush の画面が表示している言語(いまは ja・en・ko・zh-Hans・zh-Hant・id・fil・vi・th のどれか)、device なら端末の言語の先頭(fr-FR など)。GameRush の言語は今後増えるので、知らない値が来ても動くように書いてください |
source | user はプレイヤーが GameRush で言語を選んだ、system は端末の言語に従っている、device は GameRush から答えが無く端末の言語だけで決めた。source は必ず入っています。source も今後増えるので、知らない値は device と同じに扱ってください |
languages | 言語の優先順。source が user なら先頭が locale で、続けて端末の言語が並びます。system と device では端末の言語の一覧そのままなので、locale が入っているとは限りません。空にはなりません(GameRush が空の一覧を渡したときは [locale]) |
ゲームが持っている言語の中から、languages の先頭から順に最初に一致したものを選ぶのがおすすめです(en-US が無ければ en のように、地域を落とした言語でも比べます)。中国語は zh-Hans-HK(簡体字・香港)のように用字の付いたタグで来ることがあり、その直後には用字を落とした zh-HK も並びます。簡体字と繁体字を分けて用意しているなら、用字(Hans・Hant)で比べてください。どれにも一致しなければ locale で比べ、それでも無ければゲームの既定の言語にします。GameRush に無い言語(フランス語など)を用意しているなら、端末の言語に従っているプレイヤーにはその言語で出せます。通知を受け取り損ねないよう、取得より先に変更の通知を購読してください。
| 呼び出し | Unity | Godot | JavaScript |
|---|---|---|---|
| いまの言語を取る | Locale.GetAsync() | locale.fetch() | locale.get() |
| 取れていれば同期で返す(まだなら null) | Locale.Current | locale.current() | locale.current() |
| 言語が変わったら知らせる | Locale.Changed | locale.changed | locale.onChange(handler) |
変更の通知は、言語が初めて決まったときと、プレイヤーがプレイ中に GameRush の言語を変えたときに届きます。同じ値のままなら届きません。GameRush の外と、ゲームに読み込まれる GameRush のスクリプトが表示言語に対応する前の古いものだったときは unsupported が返るので、エンジン自身の言語判定に戻してください。スクリプトは新しくても GameRush のアプリ側が古くて答えられないときは、unsupported ではなく source が device の値が返ります。Godot だけ get ではなく fetch なのは、Object.get と名前がぶつかるためです。バックエンドを差し替えても(Unity GRush.UseBackend、Godot GRush.use_backend、JavaScript GRush.configure)、変更の通知の購読は続きます。
12. 起動パラメータ
ゲームのページの URL に gp. を付けて載せたキーと値を、ゲームから読めます。シェアしたリンクから特定のアイテムやダンジョンへ直接飛ばすのに使います。https://gamerush.jp/g/<ゲームのコード>?gp.item=sword&gp.dungeon=3 なら、ゲームには item が sword、dungeon が 3 として渡ります。
ゲームから共有するときに起動パラメータを添えると、GameRush がこの形の URL を作ります(10. ゲームからの共有 の「起動パラメータを添える」)。
// Unity
var launch = await GRush.Launch.GetAsync();
if (launch.Ok && launch.Value.TryGet("item", out var item) && Inventory.Owns(item))
{
OpenItem(item);
}# Godot
var launch: Dictionary = await GRush.launch.fetch()
if launch["ok"]:
var item: String = launch["value"]["params"].get("item", "")
if item != "" and inventory.owns(item):
open_item(item)// JavaScript
const launch = await GRush.launch.get();
const item = launch.ok ? launch.value.params.item : undefined;
if (item && inventory.owns(item)) openItem(item);値を信用しないでください。リンクは誰でも作れます。アイテムが存在するか・プレイヤーが持っているか・ダンジョンが解放済みかをゲームの側で確かめ、確かめられなければ無視してください。
| URL の書き方 | 制限 |
|---|---|
gp.<キー>=<値> | ゲームに渡るキーは gp. を除いた部分です |
| キー | 英数字と _ - で 1〜32 文字(__proto__ は使えません) |
| 値 | 英数字と . _ ~ : , - で 0〜128 文字(空の値も渡ります) |
| 個数 | 8 個まで |
形の合わない項目は GameRush が1つずつ落とし、残りだけを渡します。同じキーを2回書いたときは、そのキーを落とします。有効な項目が 8 個を超えたときは全部を落とします。
| 呼び出し | Unity | Godot | JavaScript |
|---|---|---|---|
| 届くまで待って取る | Launch.GetAsync() | launch.fetch() | launch.get() |
| 届いていれば同期で返す(まだなら null) | Launch.Current | launch.current() | launch.current() |
| 1つのキーを読む | Current.TryGet("item", out var item) | params.get("item", "") | params.item |
起動パラメータが渡るのは、プレイヤーがそのリンクで最初に開いたスライドのゲームだけです。別のゲームへスワイプしてから戻ってきたときは、パラメータは空になります。フィードでページを読み直したときも空になります。単体表示や埋め込みでは、ページを読み直すと同じパラメータがまた渡ることがあるので、何度渡っても困らないように書いてください。パラメータの無い起動でも取得は失敗せず、空のパラメータが返ります。
GameRush の外と、ゲームに読み込まれる GameRush のスクリプトが起動パラメータに対応する前の古いもの(protocolVersion 8 未満)だったときは unsupported が返り、同期の取得は null です。スクリプトは新しくても GameRush のアプリ側が古くて答えられないときは、unsupported ではなく空のパラメータが返ります。Godot だけ get ではなく fetch なのは、Object.get と名前がぶつかるためです。
13. API トークン
Studio の API トークン から資格情報を発行すると、ランキングの宣言・ゲームの作成と設定変更(サムネイル・実況ポリシー・公開設定など)・ビルドのアップロード・アナリティクスの取得を HTTP で行えます。CI から流しても構いません。エンドポイントごとの詳細は API リファレンス にあります。
トークンをゲームのビルドへ入れないでください。ビルドに入れたものは公開したのと同じで、出回った後は取り消せません。
置き場所はエンジンプロジェクトの外側、つまり手元の環境変数か CI のシークレットです。
- GameRush Studio の API トークン から発行する
- 権限は要るものだけ選ぶ(宣言だけなら
leaderboards:readとleaderboards:write。ゲーム ID を調べるならgames:read) - 対象のゲームを絞れる。絞ったトークンを他のゲームへ向けると
404になる - 平文が出るのは発行直後の一度だけ。失くしたら失効させて出し直す
| 権限 | できること |
|---|---|
games:read | ゲームの一覧と1本の取得 |
games:create | ゲームの新規作成(特権) |
games:write | 作成後の設定変更。タイトル・説明・元 URL・サムネイル(正方形・16:9・9:16)・実況ポリシー・公開設定(特権) |
builds:write | ビルドの作成とアップロード(特権)。最上位に grush.json を含むビルドは leaderboards:write も要る |
leaderboards:read | ランキング定義の一覧 |
leaderboards:write | ランキング定義の作成・更新・削除 |
analytics:read | ゲーム別アナリティクスの取得 |
特権の3つは、作品そのものを増やす・差し替える・プレイヤーに見える姿を変える権限です。発行画面と承認画面で印が付くので、渡す相手をよく確かめてください。
curl -X PUT "https://gamerush.jp/api/games/$GAME_ID/leaderboards/high_score" \
-H "Authorization: Bearer $GRUSH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"ハイスコア","sort":"desc","valueType":"int","aggregation":"best","period":"all_time","minValue":0,"maxValue":1000000}'PUT は key での冪等な適用です。無ければ作り、あって内容が違えば更新し、同じ内容なら何もしません。同じコマンドを何度流しても結果は変わらないので、宣言をリポジトリのスクリプトに置いて毎回流して構いません。
- 一覧は
GET /api/games/$GAME_ID/leaderboards、ゲーム ID はGET /api/gamesで取れる - 投稿が1件でもある枠では
sort/valueType/aggregation/periodを変えられない(409)。保存済みの値の意味が後から変わるため。変えたいときは別の key を作る。同じ値を流し直す分は変更ではないので通る - 値域(
minValue/maxValue)は両方揃って初めて意味を持つ。宣言が無い枠は投稿を受け付けない
自分のツールから取る(loopback 承認)
Unity を使うなら自分で実装する必要はありません。14. Unity Editor からアップロードする の Editor 拡張がこの手順を持っています。ここは自前の CLI やエディタ拡張を書く人向けです。
手元でループバックのサーバを立てて /studio/authorize を開き、承認後に戻ってきたコードを交換します(RFC 8252 + PKCE)。手順とパラメータは API リファレンスの loopback 承認 にあります。
redirect_uriはhttp://127.0.0.1:<port>/http://localhost:<port>/http://[::1]:<port>だけ。ポートは任意で、クエリもフラグメントも付けられないcode_challenge_methodはS256のみ。code_verifierは 43〜128 文字- コードはワンタイムで 5 分。
code_verifierを間違えた時点でそのコードは死ぬので、失敗したら承認からやり直す - こうして取ったトークンは Studio の一覧に発行元のクライアント名つきで並ぶので、手発行と見分けられる
games:createはゲームの許可リストと併用できない(発行時に400、使用時に403)。まだ無いゲームは許可リストに載せられないため
14. Unity Editor からアップロードする
Unity から出ずに、ログインからビルドのアップロード、ランキングの宣言まで通せます。
13. API トークン のトークンを手で貼る必要はありません。Editor がブラウザ承認で自分で取ります。
| GameRush メニュー | 内容 |
|---|---|
| GameRush ウィンドウ | アップロードとランキング宣言の画面 |
| ログイン | 既定のブラウザで承認画面を開き、戻ってきたコードをトークンへ交換する |
| ログアウト | 保存したトークンを消す |
トークンはプロジェクトの外(%APPDATA%\GameRush\ / ~/Library/Application Support/GameRush/ / $XDG_CONFIG_HOME/GameRush/)に置きます。Assets/ にも EditorPrefs にも書かないので、リポジトリにもプロジェクト設定にも漏れません。Editor 側のコードは Editor 専用のアセンブリに入っており、書き出したビルドにはコードもトークンも含まれません。
アップロードの手順
- Unity で WebGL に書き出す(
index.htmlが直下にあること)。15. 推奨設定でビルドする を使うと、書き出した先がそのまま次の手順に入る - GameRush ウィンドウでゲームを選ぶ。新規作成もできる(
games:createを要求したトークンが要る。作成の前に、ゲームと第三者アセットの権利を確認するチェックを入れる。unlistedで作るときは、全年齢向けの内容であることを申告するチェックも入れる) - 書き出しディレクトリを選ぶと、上げる前に手元で検査する — 拡張子・パス・ファイル数・総量がサーバと同じ規則で見られるので、2000 ファイル上げ切ってから弾かれることがない
- アップロードする(進捗が出る。途中で止められる。失敗したファイルだけ再送する)
新規作成ではタイトル・説明・遊び方を入れ、可視性を選べます(private か unlisted)。遊び方は操作方法・目的・コツなどを複数行で書けます。既存ゲームの説明・遊び方・公開設定は Studio か API の PATCH /api/games/:id で変えてください。
unlisted は審査を経ずに選べます。アップロードが終わった時点で、URL を知っている人がすぐ遊べます。フィードと検索に載る public は審査を通るので、Studio か API で申請してください。
覚えておくこと
- 書き出しディレクトリの直下に
grush.jsonを置くと、そのビルドが最新になったときにランキングの定義も反映される(書式は 5. ランキング)。公開中のゲームでは、ビルドが審査で承認されて配信される時点で反映される。このときトークンにleaderboards:writeが要る - 公開中のゲームに上げたビルドは審査に積まれ、審査の間は前に承認されたビルドの配信が続く。承認されると新しいビルドに差し替わる
- Brotli / gzip で書き出したファイルはそのまま上げる。
Build/x.wasm.brは.brが付いた名前のまま登録され、配信側がその名前でContent-Encodingを決める - 作成の失敗を機械的に再試行しない。ゲームとビルドの作成には冪等キーが無く、再試行が2つ目を作る(個別ファイルの送り直しは安全)
- Editor 拡張が古いとサーバが操作を止める。止まったらパッケージを更新する
15. 推奨設定でビルドする
Unity と Godot の SDK は、ゲームの最適化ガイド のおすすめ設定をまとめて適用し、そのまま GameRush に上げられる形でビルドできます。エディタの画面からでも、CLI(CI)からでも使えます。
適用した設定は元に戻しません。Unity の Player Settings と Godot の export_presets.cfg に残る項目は、バージョン管理の差分で確かめられます。Unity の Build Settings 側の項目(Development Build など)は Library/ に残るので、差分には出ません。
| 終了コード | 意味 |
|---|---|
| 0 | ビルドできた(目安の 15MB を超えたときは警告だけ出す) |
| 1 | ビルド・書き出しに失敗した(Godot は Web の書き出しテンプレートが入っていないときもここ) |
| 2 | 引数・出力先・エンジンの環境が足りない(Unity の WebGL モジュールが無い、Godot が 4.3 より古い、Unity で GameRush のビルド以外のファイルがあるフォルダを出力先にした、Godot でプロジェクト直下や addons/ を出力先にした、など) |
| 3 | 想定外の例外(Unity のみ) |
| 4 | できたビルドが GameRush のアップロード条件に合わない(Unity はファイルの種類・index.html・2000 ファイル・50MB、Godot は 2000 ファイル・50MB を見る) |
Unity
- GameRush > GameRush ウィンドウ の「推奨設定でビルド」に、いまの設定とおすすめの値が並ぶ。ログインしていなくても使える
- 推奨設定を適用 で、変わる項目を確かめてから適用する。推奨設定でビルド は適用してから WebGL でビルドする
- ビルドが終わると、出力先がそのままアップロード欄に入る(14. Unity Editor からアップロードする)
- メニューの GameRush > 推奨設定を適用 / GameRush > 推奨設定でビルド からも呼べる
- Build Settings で有効にしたシーンをビルドする。出力先の既定は
Build/GameRushで、ビルドのたびに中身を作り直す(Assets/などプロジェクトの中の大事なフォルダは選べない)
Unity -batchmode -quit -projectPath . -buildTarget WebGL \
-executeMethod GRushSdk.Editor.GRushBuild.BuildRecommended \
-grushOutput Build/GameRushログは [grush-build] で始まり、変えた設定は set <項目>: <前> -> <後> の形で1行ずつ出ます。
Godot 4
- grush-sdk-godot の
addons/grush_sdk_build/もプロジェクトのaddons/へコピーし、Project Settings > Plugins で GameRush SDK Build を有効にする(grush_sdkとは別のプラグイン) - Project > Tools > GameRush: 書き出し設定を確認 で、いまの設定とおすすめの値を比べて適用できる
- Project > Tools > GameRush: 推奨設定で書き出す で、「GameRush Web」プリセットを作って書き出す。出力は
index.html/index.js/index.wasm/index.pckの名前になるので、改名せずにそのまま上げられる - Godot 4.3 以降と、Web の書き出しテンプレートが要る
- Export の画面を開いたままだと新しいプリセットが見えないので、開き直す
godot --headless --path . \
--script res://addons/grush_sdk_build/grush_build_cli.gd \
-- --output build/gamerush--no-mobile-textures を付けるとスマホ向けのテクスチャ圧縮(ETC2 / ASTC)を入れません。--preset-only を付けるとプリセットを書くだけで書き出しはしません。
16. エディタでの動作確認
WebGL / Web 書き出し以外では、SDK は自動的にモックへ落ちます。JavaScript 版は localhost・127.0.0.1・file:// で開いたときにモックへ落ちます。これが無いとエディタで一切テストできないため、モックは付属品ではなく SDK 本体の一部として扱っています。
| 切り替え | Unity | Godot | JavaScript |
|---|---|---|---|
| サインイン状態 | GRushMock.SignedIn | GRushMock.signed_in | GRush.mock.config.signedIn |
| 表示名 | GRushMock.DisplayName | GRushMock.display_name | GRush.mock.config.displayName |
| 名前とアイコンを表示する設定 | GRushMock.GrantProfileConsent | GRushMock.grant_profile_consent | GRush.mock.config.grantProfileConsent |
| unreliable の欠落率 | GRushMock.UnreliableDropRate | GRushMock.unreliable_drop_rate | GRush.mock.config.unreliableDropRate |
| 2人目の peer | GRushMock.AddPeer(name) | GRush.mock_add_peer(name) | GRush.mock.addPeer(name) |
| 部屋のエラー | GRushMock.RaiseRoomError(code, message) | GRush.mock_raise_room_error(code, message) | GRush.mock.raiseRoomError(code, message) |
| ランキングの枠 | GRushMockLeaderboards.Define(key, title, …) | GRushMockLeaderboards.define(key, title, …) | GRush.mock.defineLeaderboard(key, { … }) |
| ランキングの枠を grush.json の中身で宣言(5. ランキング) | GRushMockLeaderboards.LoadManifest(json) | GRushMockLeaderboards.load_manifest(json_text) | GRush.mock.loadLeaderboards(manifest) |
| ランキングの他プレイヤー | GRushMockLeaderboards.AddRival(key, name, value) | GRushMockLeaderboards.add_rival(key, name, value) | GRush.mock.addRival(key, name, value) |
| 他プレイヤーの公開状態 | GRushMockPlayerStates.Define(id, json, hidden) | GRushMockPlayerStates.define(id, payload, hidden) | GRush.mock.definePlayerState(id, payload, { hidden }) |
| rating 付きの公開状態(相手探し) | GRushMockPlayerStates.Define(id, json, hidden, rating) | GRushMockPlayerStates.define(id, payload, hidden, rating) | GRush.mock.definePlayerState(id, payload, { rating }) |
| モックの相手の疑似ID | GRushMock.Rivals | GRushMock.RIVALS | GRush.mock.rivals |
| 相手からのメッセージを届ける | GRushMockInbox.Receive(from, kind, json) | GRushMockInbox.receive(from, kind, payload) | GRush.mock.receiveInbox(from, kind, payload) |
| 相手が出品する | GRushMockTrades.Offer(from, offer, want, options) | GRushMockTrades.offer(from, offer, want, options) | GRush.mock.offerTrade(from, offer, want, options) |
| 相手が自分の出品を受ける | GRushMockTrades.Accept(tradeId, give, by) | GRushMockTrades.accept(trade_id, give, by) | GRush.mock.acceptTrade(tradeId, give, by) |
| 出品をいま期限切れにする | GRushMockTrades.Expire(tradeId) | GRushMockTrades.expire(trade_id) | GRush.mock.expireTrade(tradeId) |
| 受信箱・交換所の通報の確認の答え | GRushMock.ConfirmInboxReport / ConfirmTradeReport | GRushMock.confirm_inbox_report / confirm_trade_report | GRush.mock.config.confirmInboxReport / confirmTradeReport |
| プレイヤー状態の通報の確認の答え | GRushMock.ConfirmPlayerStateReport | GRushMock.confirm_player_state_report | GRush.mock.config.confirmPlayerStateReport |
| 共有の結果(opened / cancelled) | GRushMock.ShareStatus | GRushMock.share_status | GRush.mock.config.shareStatus |
| 共有できるか | GRushMock.ShareAvailable | GRushMock.share_available | GRush.mock.config.shareAvailable |
| 表示言語(空なら端末の言語)。変更の通知を出すのは Unity だけで、値が変わらなければ出しません | GRushMock.SetLocale(tag) | GRushMock.locale | GRush.mock.config.locale |
| 起動パラメータ(既定は空。GameRush の制限では確かめずにそのまま返す) | GRushMock.LaunchParams["item"] = "sword" | GRushMock.launch_params = {"item": "sword"} | GRush.mock.config.launchParams = { item: "sword" } |
AddPeer / mock_add_peer / mock.addPeer が返す相手は、同じプロセス内の2人目の peer として実際に部屋へ入り、送受信が往復します。実機やサーバへ繋がずに2人分の挙動を確認できます。
モックが再現するのは、実サーバの縛りの一部です。
- ランキング: 宣言していない key と、宣言した値域の外の値を
InvalidParamsで弾きます。止めた枠(enabled: false)への投稿と取得、grush.jsonでminValue/maxValueを書かなかった枠への投稿も、実サーバと同じく弾きます。集約(best/last/sum)も実サーバと同じように働きます。JavaScript 版は値の型も検査します。 - 公開プレイヤー状態: JSON オブジェクトでない payload と、実サーバと同じく UTF-8 のバイト数で 4KB を超える payload を
InvalidParamsで弾きます。Unity 版は文字列のエスケープ(\u3042など)を書いたまま数えるので、エスケープを含む payload を実サーバより大きく数えることがあります。JavaScript 版は base64 のような読めない長い文字列も弾きます。 - クラウドセーブ(JavaScript 版): ゲストは
signInRequired、スロット名と payload の誤りはinvalidParams、baseRevisionの食い違いはconflictになります。形式の検査はサインインの確認より先なので、ゲストでもスロット名や payload が誤っていればinvalidParamsになります。 - 相手探し: モックの相手3人は rating 付きの公開状態を持ち、自分と伏せた状態を除いてランダムに返します。件数と rating の範囲の誤りは
InvalidParamsです。 - 受信箱と交換所: payload・offer・want・give の形と大きさ、自分宛ての禁止、
operationIdでの冪等、受付中の出品の 10 件の上限、受諾の結果、pending と ack を実サーバと同じ規則で扱います。モックの相手は届いたメッセージを同じ kind と payload で送り返し、はじめは相手からのメッセージ1通と相手の受付中の出品2件があります。通報を承諾すると相手をブロックし、その相手のメッセージと出品は見えなくなります。送信と出品の頻度の上限は再現しません。 - クラウドセーブの
fallback: "local"(JavaScript 版):signedInがfalseのときはモックの中の端末の保存先に読み書きし、storage: "local"・revision: 0を返します。signedInをtrueに戻すと、loadはクラウドに無ければ端末のセーブを返し、saveとremoveはクラウドで成功すると端末のセーブを消します。端末の保存先はページを読み直すと空になり、GRush.mock.reset()でも空になります。
session 束縛・有効プレイ 10 秒・投稿上限はエディタでは意味を持たないので省いてあります。つまりエディタで通った投稿が実環境で 400 / 403 / 429 になることはあるので、投稿の失敗経路はゲーム側で必ず扱ってください。
共有のモックは確認シートを出さず、選んだ結果(既定は opened)をそのまま返します。添えた起動パラメータは GameRush と同じ制限で確かめ、合わなければ InvalidParams を返します。本文と画像の検査はしないので、弾かれる本文(10. ゲームからの共有)は GameRush 上で確かめてください。
UnreliableDropRate / unreliableDropRate は既定 0 ですが、出荷前に必ず 0 より大きくして試してください。
本番の WebSocket 中継は unreliable を落とさないため、パケットが落ちる前提で書けているかを確かめられる場所はここしかありません。確認を怠ったゲームは、WebRTC へ切り替わった時点で初めて壊れます。
17. サンプル
Unity と Godot は各2本、JavaScript は1本です。Unity と Godot のサンプルはシーンアセットを持たないので、空のシーンにノード(Unity は GameObject、Godot は Control)を1つ作ってスクリプトを付けてください。
| サンプル | 内容 |
|---|---|
| Score Attack | 疑似IDと表示名の取得。ベストスコアはローカル保存 |
| Score Attack(JavaScript) | 10 秒タップ。ランキングへの投稿と上位表示。localhost から開くとモックで動く |
| Duel | 2人対戦。paddle は unreliable、ボールはホストが送る。エディタではモックの相手が動く |
Unity と Godot のサンプルではランキングと公開プレイヤー状態を使っていないので、5. ランキング・6. 公開プレイヤー状態 の API を直接呼んでください。