個人開発をしています。無料の計算ツール集 ShibaHub、補助金を検索できる 補助金ナビ を運営していて、いまは3DのゲームをSteamに出す準備をしています。
そのゲームの通信を、Steamの仕組みに載せ替える作業をしていたときの話です。
初期化は成功している。ログにも「接続OK」と出ている。なのに、肝心の機能だけがエラーで落ちる。
原因の切り分けにかなり時間を使ったので、同じ形で詰まる人のために書いておきます。ゲームの話として書いていますが、外部サービスを使うプログラム全般で起きる話です。
何が起きたか
やりたかったのは「オンラインの部屋(ロビー)を作る」という処理です。
コードはこんな流れでした。
# ① 初期化する
var res = Steam.steamInitEx(app_id, false)
if int(res.get("status", -1)) == 0:
print("接続OK") # ← ここは通る
# ② 部屋を作る
Steam.createLobby(FRIENDS_ONLY, 8)
# → 失敗。エラーコード 3
①は成功します。ログにもちゃんと「接続OK」と出ます。
なのに②だけが 3 というコードで失敗する。
しかも困ったことに、少し前までは同じコードで成功していました。
やった切り分け
「さっきまで動いていたものが動かない」ので、まず自分が直した箇所を疑いました。ここが遠回りの始まりでした。
- 直前のコミットを戻して試す → やっぱり失敗する
- 別のもっと単純なテストで試す → やっぱり失敗する
ここでようやく「自分のコードじゃないかもしれない」と考えを切り替えました。
そこで、状態を全部そのまま出すだけの小さなスクリプトを書きました。
print("isSteamRunning = ", Steam.isSteamRunning())
print("loggedOn = ", Steam.loggedOn())
print("initResult = ", Steam.get_steam_init_result())
結果がこれです。
isSteamRunning = true
loggedOn = false ← ★これ
initResult = { "status": 0, "verbal": "" }
Steamは起動している。初期化も成功している。でもログインしていない。
エラーコード 3 は「接続がない」という意味でした。つまりコードは正しくて、土台のほうがログアウトしていたわけです。
なぜログアウトしていたのか
犯人は、その少し前に自分で実行したコマンドでした。
ゲームのビルドをSteamにアップロードするために、steamcmd というコマンドラインツールで同じアカウントにログインしていました。これが、起動中のSteamクライアントのセッションを切っていたのです。
つまり自分で自分の足を撃っていました。
学んだこと1:「初期化できた」と「使える」は別物
ここがいちばんの学びでした。外部サービスを使うとき、状態は1つではなく段階になっています。
| 段階 | 確認する方法(Steamの例) | これがfalseだと |
|---|---|---|
| ライブラリが読めている | ClassDB.class_exists("Steam") |
何も始まらない |
| アプリが起動している | isSteamRunning() |
初期化が失敗する |
| 初期化に成功した |
steamInitEx() の戻り値 |
APIが呼べない |
| ログインしている | loggedOn() |
APIは呼べるが、通信する機能だけ失敗する |
自分は「初期化に成功した=使える」と思い込んで、available = true という1つのフラグにまとめていました。最後の段だけが崩れている状態を表現できていなかったわけです。
直したあとはこうしました。
# 「初期化できた」だけを持つフラグ
func logged_on() -> bool:
return available and Steam.loggedOn()
# 通信が要る機能は、こちらで判断する
func usable() -> bool:
return logged_on()
そして、使えないときは理由を文字列で返すようにしました。
func offline_reason() -> String:
if not available:
return status
if not Steam.loggedOn():
return "ログインしていません(オフラインか、ログインが切れています)"
return ""
「使えません」だけだと、遊ぶ人は直しようがありません。何が起きているかまで出して、初めて対処できます。
学んだこと2:ログが親切だと、かえって疑えなくなる
今回いちばん厄介だったのは、ログに「接続OK」と出ていたことでした。
これは嘘ではありません。初期化は本当に成功しています。ただその先の段階までは保証していないだけです。
でも人間は「接続OK」と書いてあるものを疑いません。だから土台ではなく、自分の書いたコードを延々と読み直すことになりました。
いま思うのは、ログの文言は「何を確認したか」まで書いたほうがいいということです。
❌ 接続OK
⭕ 初期化OK(ログイン状態は未確認)
たった1行ですが、次に詰まった人の向く方向が変わります。
学んだこと3:迷ったら「状態を全部出すだけ」のスクリプトを書く
今回いちばん効いたのは、何も直さず、状態をそのまま出すだけの小さなスクリプトでした。
デバッグをしていると、つい「ここを直したらどうなるか」を試したくなります。でもそれは原因の見当がついているときにだけ有効です。見当が外れていると、直すたびに変数が増えて、ますます分からなくなります。
- 直す前に、いま何がどうなっているかを全部並べる
- 出す項目は多くていい。答えが分からないから調べているので
このスクリプトは捨てずに tools/ に置いて、手順書からも呼べるようにしました。同じことは必ずまた起きます。
まとめ
- 外部サービスの状態は段階になっている。「初期化できた」と「使える」を分けて持つ
- 使えないときは理由まで返す。「使えません」だけでは直せない
- ログの「OK」は、どこまでのOKなのかを書く
- 詰まったら、直す前に状態を全部出すだけのスクリプトを書く
ちなみに今回の件、ゲーム側は「Steamが使えないときは今までのやり方に自動で切り替わる」作りにしてあったので、遊べなくなること自体は起きませんでした。土台が落ちても止まらない道を用意しておくのは、やはり効きます。