絶対にバグってほしくない!安全なゲームコードの書き方

1.4K Views

October 20, 25

スライド概要

本スライドは2025年8月30日(土)に開催したゲーム開発者向けのリアルイベント『ゲームメーカーズ スクランブル2025』で行われた講演のスライドとなります。

タイトル:
絶対にバグってほしくない!安全なゲームコードの書き方

内容:
ゲーム開発において、楽しさや勢いに任せてコードを書いてしまう人もいると思います。しかしそういった部分は往々にして不具合の原因となり、デバッグの際に地獄を見ます。この講演では、コードを手堅く、わかりやすく書くためのノウハウを紹介します。
初学者向けの書き方のお作法から、言語機能を活かした高度な設計にも触れるので、幅広いレベルの方のステップアップや振り返りの機会になれば幸いです。

登壇者:
有限会社デジタルサポート
エンジニア
竹内 亮太 氏

講演動画も公開中!

【アーカイブ記事】https://gamemakers.jp/article/2025_10_20_119845/
【イベントページ】https://gamemakers.jp/scramble2025/
【イベントレポート記事】https://gamemakers.jp/article/2025_09_24_116494/

profile-image

ゲームづくりに役立つ情報をお届けする「ゲームメーカーズ」の資料公開用アカウント。 WEBメディア「ゲームメーカーズ」では、ゲーム開発TIPSや”作り手目線”のインタビュー、お得なセール情報などを毎日更新! http://gamemakers.jp

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

(ダウンロード不可)

関連スライド

各ページのテキスト
1.

絶対バグって欲しくない! 安全なゲームコードの書き方 有限会社デジタルサポート 竹内 亮太(a.k.a. rita)

2.

アジェンダ • 安全なコードとは何か • シンプルな関数設計 • アサートの活用 • 型安全を利用した工夫

3.

プロフィール • 竹内 亮太 / Ph.D rita • 2013年 シリコンスタジオ • ゲームエンジン・ミドルウェア開発に従事 • 2016年 ゲームフリーク • Switchのポケットモンスターシリーズでライブラリ・エンジン開発のリード • 2020年 デジタルサポート • 様々な開発業務や技術記事の執筆など

4.

ゲーム業界でやったこと ©Silicon Studio Corp. ©2022 Pokémon. ©1995-2022 Nintendo/Creatures Inc. /GAME FREAK inc.

5.

ゲームーメーカーズで書いた記事 • CEDECのレポート • カプコンのREエンジンオープン カンファレンスの取材 • 「最適化とは何か」の連載

6.

おことわり • 本講演の内容は、これまでの業務内容とは関係ありません • あくまでソフトウェア開発の一般論です • 正直言っていることは地味かもしれません • 規模の大きな開発にステップアップする際の心がけとしてどうぞ • 例示コードはC++だったりC#だったりします • どちらにも通じるような内容しか扱ってないです

7.

安全なコードとは、何か

8.

ゲーム開発は楽しい! • ノリに乗っている時のコーディングのライブ感! • AIなんかにやらすのはもったいない! • 思っていた通りに、いや、 思った以上のインタラクションが得られた時の快感! • ゲームプログラミング最高!

9.

そんな時間はほんのひととき • コードを書くとバグが生まれます • 絶対に生まれます • それらはリリースという締め切りが近づいてから牙を剝く • ああ、ゲームプログラミングつらい……

10.

なぜつらいのか? • どこが間違っているのかが わからない • 何をやっているのかが わからない • なんとなく全体的に 疑わないといけない

11.

ここでの「安全なコード」とは • コードを適切な単位(関数)にわける • 関数単位で何をやっているかを明確にする • 正しい入力と出力がなにかを定義する • そうすれば信頼できる部分とできない部分を区別できる

12.

イメージとしてはこんな感じ too_large_function() { small_functions_orchestrator() { 処理A 綺麗な関数1呼び出し 条件分岐B 綺麗な関数1 ループC … 綺麗な関数2呼び出し … 綺麗な関数2 … } } 全体的にもやっと怪しい状態 処理を綺麗に分けておいて利用することで 挙動不明瞭、要調査ポイント多数 信頼できる領域が増える

13.

全部を信頼できるようにするのは無理 • 最上位層が複雑になるのは仕方ない • 少しでも信頼できる領域を増やす • その努力が将来の自分や仲間を救う

14.

シンプルな関数設計

15.

シンプルな関数を実現するための要素 • 読みやすい • 複雑すぎない • 予想外のことが起こらない

16.

くらべてみよう

17.

悪い例 • インデントが深くて読みたくない • 無駄にねちょねちょした条件分岐 • 返値がいつ決まるのか分からない ハラハラ感

18.

マシな例 • インデントが浅くてまだ読む気になる • 早期リターンとelse不使用で流れが明確 • 同じ事を表現できるなら短い方がいい • 謎の圧縮言語みたいになるのはNG ※マジックナンバーが良くないとかは別の話

19.

シンプルな関数の基本 • ネストを深くしない • 早期リターンの積極活用 • 1つの変数へ何回も代入することは避ける • どこでどう更新されているのかを追うのがしんどい • インクリメント・デクリメント操作も箇所を絞る

20.

複雑度を意識する • 循環的複雑度という指標がある • 関数に存在する経路数のこと • 分岐やループの個数が多いと複雑度が高い • 理想としてはすべての関数はテストが書かれているべきだが… • 分岐やループが多く、入り組んでいるとテストを書くことすらできない • デバッグ難易度も跳ね上がる

21.

複雑度の求め方 • 右の関数だと複雑度5 • 初期値が1で、 分岐が3つ、ループ1つで合計5 • Visual Studioで測定できる • 分析→コードメトリクス • ざっくり分岐やループの個数と 考えていい

22.

複雑度の指標 循環的複雑度 状態 ~10 シンプルで 理解しやすい 11~20 ちょっと複雑だが 保守可能 21~50 非常に複雑で保守や テストの作成が困難 51~ 複雑すぎてほぼ確実 にバグが存在する • 理想は10以下 • 妥協しても20以下をキープしたい • ノリで書くと20超え50超えはザラ • 秘伝の継ぎ足しをやっていると じわじわ増えて首を絞めていく

23.

複雑度を下げるには • ループや分岐を簡略化して減らす • 関数単位で計測するので、 1つの関数に処理を詰め込むと否応なしに上がる • 1つの関数をコンパクトにすることが重要 • 巨大な1つの関数をデバッグするより、 シンプルな関数の集合体になっている方が良い

24.

参照透過性を意識する • 以下の条件を満たす関数を「参照透過性がある」という • 同じ入力に対して常に同じ出力を返す • 処理の中で出力を返すこと以外の操作を行わない • いわゆる「副作用」がない • クラスのメンバ変数や、グローバル変数へのアクセスを伴うと これは満たせない • ゲームプログラムだと結構厳しい、と感じる人も多いはず

25.

くらべてみよう 参照透過性あり 参照透過性なし

26.

ないよりは、あったほうがいいけれど • 同じ入力に対して常に同じ結果を返すのは分かりやすいし安心 • だけどゲームは巨大な状態遷移装置なので、 状態を変化させる処理は必須 • 状態を変更しうる関数、しない関数が区別できると良い • クラスメンバ関数のうち、メンバ変数を触らないものはstaticにする • C++ならconstメンバ関数を利用する • 抜け道は色々あるので注意する(mutableとかえげつないキャストとか) • グローバル変数の利用を極力避ける

27.

クラスメンバが複雑化しそうになったら • メンバ変数をあちこちで操作しない • 非staticメンバ関数をしぼり、操作に 必要な処理は細かいstatic関数に砕く • 命名で状態操作の有無が推測できる ようにしておくのも大事 • Updateは変更しそう • GetやCalcはしなさそう

28.

関数を作る上でのポイントまとめ • 早期リターンなどを駆使してネストを抑えて読みやすく • 複雑度を抑える • そのためにもサイズ自体を小さくする • 参照透過性をできるだけ持たせる • メンバ変数の変更箇所を限定する • グローバル変数の利用を避ける

29.

アサートの活用

30.

アサートとは • その行に処理が到達した時点で 「この条件が真であるはず」ということを表明する • 条件式は実行時に評価され、偽の場合は実行を停止する • 開発中は停止するが、製品コードにおいては行ごと無視される

31.

利用例 • キャラクタークラスがあって、 生きている時しか呼んでほしく ない処理があるとする • 死んでるときにそれを呼んだら 明確にバグなのですぐ気付きたい • 「こうあるべき」条件を記述して、 それに違反していたら即停止!

32.

エラー処理とアサートを明確に区別しよう • エラー処理は「起こり得ることに対応する」ためのもの • 仕様上起こり得るなら当然必要 • アサートは「あり得ないことを排除する」ための表明 • 仕様上起こり得ないこと=バグなので、 エラーチェックによる早期リターンと混同してはいけない

33.

取り違えるとどうなってしまうのか • アサートにするべきものを早期リターンしてしまうと 開発中にバグを見過ごしやすくなる • エラー処理で対処すべきものをアサートにすると リリース後にエラーケースが見過ごされるバグが発生する • いずれにしてもバグを埋め込むことになる

34.

正しくエラー処理とアサートを区別するには • 仕様を正しく定義すること • 例えば • 「大事なアイテムを捨てようした」時に…… • 「それを捨てるなんてとんでもない!」で捨てる処理を止める • 大事なアイテムには「捨てる」こと自体をできなくしている場合 • 捨てる処理に大事なアイテムが渡されること自体がおかしいからアサート • 仕様の定義が曖昧だと区別が難しくなる

35.

契約による設計(Design by Contract)に基づく アサートの仕方 • 事前条件(関数利用側が満たすべき責務) • 関数呼び出し時の引数、メンバ変数などの入力となる値に対する条件 • 事後条件(関数提供側が保証する結果) • 関数正常終了時の返値、変更したメンバ変数などの出力となる値に対する条件 • 不変条件(関数利用側・提供側が共に満たすべき責務) • 関数の呼び出し前後で変更されないメンバ変数を示す条件 • 言語によってはこれらの記述を構文として書けるものもある • D, Kotlinなど

36.

アサートの利用方法 • C#/Unityの場合 • C++/UEの場合 • 基本はDebug.Assertメソッドを使う • 基本はassertマクロを使う • UnityだとUnityEngine.Assertions以下に IsTrue, IsFalseなどの便利メソッドがある • UEだと動作が異なるバリエーションがある • check • ほぼassertと同じ • verify • ほぼcheckと同じだが 製品ビルドでも消えない • ensure • ログ出力のみを行い、処理は止めない • デバッガが繋がっている時は止まる

37.

アサート、使いましょう • 起こりえない状況を早めに検出できる • エラー処理と正しく使い分けるために 仕様を明確に定義する • 環境に応じたアサートの利用方法を調べて活用しましょう

38.

型安全を利用した工夫

39.

アサートは便利、だが…… • プログラムを実行しないと間違いが検出されない • 検出漏れが心配で夜も眠れない • ならば実行する前に間違いが検出できればより安心! • ≒間違ったコードがビルドエラーになればよい • 型を駆使して静的に(ビルド時に)間違いを検出する • 元々C++やC#は静的型付け言語なのでその恩恵は受けている • そこからさらに踏み込む

40.

同じ数値型でも別の型に分けることで誤用を防ぐ • 同じ整数型で表現するパラメータを 別々の型として定義しておくことで、 取り違えを防ぐ • float3が指すものは多岐にわたるため、 細分化した型で管理するアプローチも ある • Color • Position • Scale • etc…

41.

状態管理はバグの宝庫 • 巨大なswitch-caseは いつか破綻する • 判定があっちこっちに 存在するのも悲惨 • 遷移もどこで行われているか わかったもんじゃない

42.

型を状態ごとに細分化する • 状態ごとに型を分ける • その状態で行う処理は すべてその型の中で実装する • 状態遷移を1箇所に集約させる

43.

更にステップアップ • 前のスライドの例だと、 SetStateに渡す型によっては 異常な遷移が発生しうる • 状態型を遷移可能な型からのみ 生成できるようにすれば 間違った遷移が書けなくなる • データドリブンで状態管理した部分には 不向きなので、適材適所 • その場合は読み込ませるデータ自体を 検証(バリデーション)する

44.

原理主義にはならない • 型安全を追求しだすとパズル的に延々やれてしまう • だがゲーム開発という領域ではどうしても無理が出てくる • 静的にできるところはする、できないところはアサートで • 完全じゃなくても、できるだけ頑張っておけば楽になる

45.

まとめ

46.

バグと向き合うための心得 • 安全地帯を作る努力をする • 複雑度の低い関数 • アサートによる実行時ガード • 型安全で間違いが書けない領域を広げる • でも安全地帯を過信しない • アサートの内容が間違っている可能性もある • 型安全にしたつもりがなってない可能性もある

47.

“良い仕事は全て単純な作業の堅実な積み重ね” • スケジュールに追い立てられると忘れがちですが、 結局これです • 私自身書いていてブーメランが刺さりまくってます • このスライドでも目新しいことや特別なことは そう書いてないと思います • Tidyにやっていきましょう

48.

おわり さあ、明日も楽しく安全なコードを書こう