Unity WebGLゲームにランキングを付ける(不正対策と送る条件)
ブラウザで動くゲームから送られる記録は、いくらでも書き換えられます。弾くのではなく「ありえない値は保存して無効にする」設計と、送るのは自己ベスト更新時だけという条件、EventSystem が無いシーンで名前を入力させる方法を、実装と実測つきで残します。
公開 2026年9月18日
この記事の要点4 points
- ブラウザから送られる値は信用できない。 弾くのではなく、保存したうえで無効印を付ける。無効の理由も残す。
- 「ありえない値」の線引きは、ゲームの数値から計算する。 このゲームなら登る速さの上限から高さの上限が出る。
- 送るのは自己ベストを更新して50点以上のときだけ。 毎回送ると、短い記録で表が埋まる。
- 名前は3文字の選択式にした。 WebGL のスマホでは文字入力が出せず、画面に日本語フォントも積んでいないため。
目次7章
検証した環境最終確認 2026年9月18日
- Unity 6.6(6000.6.0f1)・WebGL ビルド
- Next.js 16 / PostgreSQL 18(同じドメインで配信)
- Claude Code(Opus 5 / 思考レベル 高)
書いてあるのは、この環境で実際に試した結果です。ツールの仕様や料金は変わるので、重要な判断の前には公式の情報も確認してください。
自作のゲームにランキングを付けました。ブラウザで動くゲームから送られてくる値は、いくらでも書き換えられます。 それを前提に、どこまでやるかを決めた記録です。
5件/分
同じ相手からの上限
1時間なら60件
12字
名前の長さ
ASCII のみ・ゲーム内は3文字
50点
送る下限
自己ベスト更新時のみ
71点
通しの確認で送った記録
自動操作で実際に遊んだ
弾かずに「保存して無効にする」
最初に決めたのはここです。ありえない値が来たとき、エラーを返して捨てるのではなく、保存したうえで無効の印を付けます。
弾いて捨てる
保存して無効にする(採用)
返すのは 200 のままで、accepted: false と理由を返します。ゲーム側は「ランキングに載らなかった」とだけ出せば済みます。
$ curl -X POST … -d '{"name":"CHEAT","score":999999,"playMs":5000,
"detail":{"heightM":9000,"coins":500}}'
{"ok":true,"accepted":false,"reason":"高さ 9000m は 5秒では届かない"}「ありえない値」は、ゲームの数値から計算する
線引きは勘ではなく、そのゲームの数値から出します。 このゲームは壁を跳んで登るので、登る速さの上限が決まっています。跳び続けても毎秒 5.5m を超えられません。
const seconds = input.playMs / 1000
if (height > seconds * 5.5 + 30) return `高さ ${height}m は ${Math.round(seconds)}秒では届かない`
if (coins > seconds * 2 + 10) return `コイン ${coins}枚は ${Math.round(seconds)}秒では取れない`
if (input.score > height + coins * 150 + 50) return `スコア ${input.score} は 高さ${height}m・コイン${coins}枚では出ない`
余裕(+30 や +10)を足しているのは、正当な記録を落とさないためです。ここを攻めても不正は止まりません(上限ぎりぎりの値を送られたら通ります)。目的は「桁違いの値を表から外す」ことだけです。
⚠️ ゲームの数値を変えたら、この式も見直す
遊びの数値(跳ぶ速さ・コインの点数)を変えると、上限も変わります。式はゲームごとに1か所へ置き、ゲーム側の数値と対応していることをコメントに書いておくのが現実的でした。
残りの3つ:連投・送信元・名前
- 1
連投を止める
同じ相手から 1分5件・1時間60件 まで。超えたら 429 を返す - 2
送信元を確かめる
他サイトからの POST はOriginで拒否する。ゲームは同じドメインから配信しているので、これで困らない - 3
名前を絞る
ASCII の英数字と_ . -と空白だけ・12字まで。HTML も URL も入らない。NG ワードは既定の名前に置き換える
IP アドレスは保存していません。連投の判定に使うのは HMAC でハッシュにした値だけです。
送るのは「自己ベストを更新したときだけ」
サーバー側の対策より、こちらのほうが表の見た目に効きました。
プレイのたびに送ると、数秒で終わった記録まで表に並びます。 このゲームは1プレイが30〜90秒と短く、失敗も多いので、そのままだと大半が低い記録になります。
そこで、送信は「自己ベストを更新して、かつ50点以上」のときだけにしました。結果として、送信の回数も1プレイごとではなく、上達したときだけになります。
// 🔴 送るのは自己ベストを更新したときだけ(短い記録で表を埋めない)
if (_isNewBest && Session.Score >= 50) SendScore();
エディターからは送らない
作っている最中に何度も遊ぶので、エディターから本番の表に試し撃ちが残ると面倒です。送信のコードは #if UNITY_EDITOR で切り、エディターではログに出すだけにしました。
EventSystem の無いシーンで、名前を入力させる
このプロジェクトのシーンには EventSystem がありません(入力は自分で読んでいるため、外してあります)。つまり uGUI のボタンは押せません。
名前の入力をどうするかで、2つ決めました。
- 3文字を
+/-で選ぶ形にする。 WebGL のスマホでは文字入力が期待どおりに出ず、画面に日本語フォントも積んでいないためです - 押した場所が、その文字の枠に入っているかを自分で判定する
bool Hit(Text text, Vector2 screenPoint)
{
if (text == null || !text.gameObject.activeInHierarchy) return false;
return RectTransformUtility.RectangleContainsScreenPoint(text.rectTransform, screenPoint, _camera);
}
押した座標は、新しい Input System から取ります(Input.mousePosition は使えません)。タイトルでのタップは、先に名前のボタンを見て、当たっていなければゲームを始めるという順番にしました。
うまくいかない
- uGUI の Button を置く(EventSystem が無いので押せない)
- タップでいきなりゲームが始まり、名前を変えられない
- 文字入力を出す(スマホの WebGL で出ない)
この形にした
- 文字の枠に当たっているかを自分で判定する
- 名前のボタンに当たったときはゲームを始めない
- 3文字を選ぶだけ。英数字なので追加のフォントも要らない
通しの確認は、自動で遊ばせて行った
人が遊んで確かめると、失敗したときに何が起きたか分かりません。自動操作のブラウザで実際に遊ばせて、送信まで通ることを確かめました。
{ "method": "GET", "status": 200, "body": "{\"scores\":[]}" }
{ "method": "POST", "status": 200, "body": "{\"ok\":true,\"accepted\":true,\"rank\":1}" }
{ "method": "GET", "status": 200, "body": "…\"name\":\"AAA\",\"score\":71…" }画面には RANKED #1 が出て、紹介ページの一覧にも並びました。確認に使った記録は、そのあと消しています。

まとめ
- ブラウザゲームのランキングで不正は防げない。ありえない値を保存して無効にする、連投を止める、ページに「参考記録」と書く
- 「ありえない」の線引きはゲームの数値から計算する。余裕を持たせて、正当な記録を落とさない
- 送るのは自己ベスト更新時だけ。表の中身と送信の回数が、これだけで変わる
- 作っている最中の試し撃ちが本番に残らないよう、エディターからは送らない
- EventSystem が無いなら、押した座標が枠に入っているかを自分で判定すればボタンは作れる
FAQ
よくある質問
ブラウザゲームのランキングで不正は防げますか?
防げません。ゲームはブラウザの中で動いているので、送信の中身は書き換えられます。できるのは、ありえない値を無効にすること、連投を止めること、そしてページに「参考記録」と書いて期待値を下げることです。
不正な記録は削除したほうがいいですか?
消さずに、無効印(is_valid = false)と理由を付けて残しています。どんな値が来たのかが分かると、上限の式を直すときの材料になります。表示するのは有効な記録だけです。
スコアはいつ送るべきですか?
このゲームでは、自己ベストを更新して50点以上のときだけにしました。プレイのたびに送ると、数秒で終わった記録まで表に並び、サーバーへの送信も無駄に増えます。
Unity の WebGL で名前を入力させるには?
スマホのブラウザでは文字入力(TouchScreenKeyboard)が期待どおりに出ません。3文字を + / - で選ぶ昔ながらの形にすると、キーボードが無くても入力でき、日本語フォントも要りません。
この記事は役に立ちましたか?
NEXT
あわせて読む
Unity WebGLゲームを自前サーバーで公開する方法(Caddyの設定)
Unity の WebGL ビルドを自分のサーバーで配信するときに必要な設定を実測で残します。.br に Content-Encoding と application/wasm を付ける、末尾スラッシュの 308、前段の圧縮との二重がけ、アップロードと反映にかかった時間。
効果音と画像を素材ファイルなしで作る(Unityでコードから生成)
AI にゲームを作らせると、コードは書けても素材が出てきません。画像も音もファイルを1つも置かず、四角・丸・三角と、計算した波形だけで1本作りました。実装と、増えたビルドサイズ(+111KB)と、この方法でできないことを残します。
UnityのWebゲームの公開先を比較(自前サーバー・unityroom・itch.io)
Unity 6.6 で作った WebGL ゲームをどこに出すか、3つの選択肢の条件を確認しました。unityroom は 6.6 にまだ対応していません。itch.io は展開後 500MB までで、AI で作ったことの申告欄があります。自前サーバーは制約がない代わりに、圧縮ファイルのヘッダーを自分で設定します。