世界を動かす技術を、日本語で。

C言語でゲームを作ります(はい、C言語です)(2016)

概要

  • 筆者がゲーム開発にC言語を選ぶ理由 の説明
  • 信頼性・移植性・シンプルさ を重視する姿勢
  • 他の言語(C++、C#、Java、Go、Haxe等)との比較
  • C言語の危険性と利点 の両面を評価
  • 個人的な選択であり、他人に強制しない 立場

私がゲームをC言語(そう、C)で書く理由

  • 最近の個人プロジェクトゲーム はすべて 「バニラ」C言語 で開発
  • 一般的でない選択、理由の共有

言語に求める絶対条件

  • 信頼性重視、自分で起こしていないバグに時間を割きたくない
  • Flash向けゲーム の経験、Flashの終焉による教訓
  • 長期間存続するプラットフォーム の必要性
  • 特定OSへの依存回避、コンソール開発の選択肢確保
  • 移植性と移植性の高いライブラリサポート の重要性

言語に望むこと

  • シンプルさ重視、複雑な言語機能やAPIに疲弊
  • 覚えやすい言語、何度も調べる手間の削減
  • 厳格な型付け、強力な警告、静的解析 によるバグ削減
  • 優れたデバッガや動的解析 でバグ発見容易化
  • 高精細リアリズムは不要 だが、パフォーマンスは少し気にする
  • コンパイラの速度重視、10秒以上待つと集中力が切れる
  • OOP(オブジェクト指向)への懐疑、データとコードの柔軟な分離志向

他の選択肢との比較

  • C++

    • ゲーム開発で主流、契約仕事でも多用
    • 複雑さ・バグの発生しやすさ・コンパイルの遅さ が大きな不満
    • 不要な機能が多く、シンプルさに欠ける
  • C#・Java

    • 冗長・複雑、OOPに強く誘導
    • 隠蔽された複雑性 が時に問題化
  • Go

    • Cの現代版 という印象で好感
    • GC(ガーベジコレクション)による停止 がゲーム開発に致命的
    • ゲーム向けライブラリ不足、Cラッパー作成の手間
    • ニッチさ による将来性への懸念
  • Web(JavaScript、Haxe等)

    • Web環境の変化の速さ、Flash終了の恐怖
    • JavaScriptの緩さ に不信感、大規模開発への不向き
    • Haxe は将来性に不安も、Web開発時に有力候補
  • 自作言語

    • 独自言語開発への憧れ はあるが、ライブラリ資産の喪失や互換性維持の負担
    • 本業はゲーム制作、言語開発に本腰を入れる気はなし

C言語が今も最適な理由

  • 危険だが信頼できる、鋭い包丁のような存在
  • 高速な実行とコンパイル、開発効率の高さ
  • ほぼ全ての環境で動作可能、移植の容易さ
  • 強力なライブラリとツールサポート の継続
  • 特別推奨はしない、個人的な快適さと経験による選択
  • 他人の選択を否定しない姿勢、自分のスタイルへの納得

まとめ

  • C言語の選択は極めて個人的なもの
  • シンプルさ・信頼性・移植性・高速性 を重視
  • 他言語の複雑さや制約に対する違和感
  • 「Cを使うべき」とは言わないが、自分には最適

Hackerたちの意見

自分の根本的な部分は変わらないよ。Cは俺の言語で、何十年も役に立ってきた。大きな問題はないけど、2つのうちのどちらか(または両方)に達すると痛みが出てくる。Cのコードベースでグループで作業すると、他の言語とは違っていろんなレベルで苦労することが多いんだ(そう、C++も含めてね)。もう一つは、現代の代替手段と比べて何をするにも時間がかかること。特にゲームを開発している場合は、これが問題になるかもしれない。とはいえ、シンプルさの魅力には引かれるから、反論はできないな。

なんでCのコードベースでグループ作業すると他の言語とは違って痛みが出ると思う?グループで作業すること自体はいつも苦労を伴うけど、Cの場合はC++よりもその苦痛はずっと軽いと感じたよ。

とはいえ、反論はできないな。さっきも言ったけど、俺も「少ない言葉で多くを語る」ことに引かれているから。

Cのコードベースでグループ作業すると、他の言語とは違っていろんなレベルで苦労する。Linuxは2025年に2,134人の開発者を惹きつけたから、その主張はちょっと弱まるね。

投稿の著者には完全に共感する。言語を深く楽しむための主な要件はシンプルさだから、CやGolang、Odin、Zigみたいな言語が大好きなんだ。ただ、時には必要な複雑さを受け入れつつも、エレガントに問題を解決する言語が必要だと認めている。特にコードの正確性、特にメモリや並行性の安全性を優先する必要があるときは、OOPではなく主に関数型のパターンを使うけど、Haskellほど極端にはならない。そんな時は、俺の好きな複雑な言語であるRustを選ぶ。ネットワークコードを扱うことが多くて、すごく並行性が高く、できるだけ正確で、良いパフォーマンスが求められるから、Rustはここでは自然に感じる。一方で、シンプルなインディーゲームを作るのが好きで、その場合は命令型でOOPでないスタイルのシンプルでパフォーマンスの良い言語が好きだ。俺の意見では、C、特に最近のOdinはかなり良い選択だと思う。もしジョナサンがこのコメントを読んでいるなら、彼がGolangを挙げたことを考えると、CとGolangのいいとこ取りのOdinを勧めたいな。Golangのシンプルさを持ちながら、ガベージコレクタがなくて、Raylibを使ってゲームを作るのもかなり簡単だよ。

言語の名前はGoで、golangはウェブサイトのドメインだよ。

フラッシュの死 >ゲーム用のライブラリサポートはかなり貧弱で、Cライブラリをラップするのはそれほど難しくないけど、そうすると余計な作業が増える。これがいつ書かれたのかは分からないけど、2015年頃だと思う。つまり、約10年前だね。彼の意見は今どうなっているんだろう。

インターネットアーカイブのWayback Machineでのページの最初のキャプチャは2016年1月9日だよ。だから、少なくともそれくらい古い。あと、その時の彼のウェブサイトのメインページのスナップショットもあるよ。ゲームのスクリーンショットが載ってて、ブログ投稿が書かれた時にどんなゲームを作って公開していたのかのコンテキストを提供してる。https://web.archive.org/web/20160110012902/http://jonathanwh... これも3Dっぽくて、かなりユニークなスタイルだね:https://web.archive.org/web/20160112060328/http://jonathanwh...

「誰もこれをやらない」 まあ、これは少し言い換えた方がいいね。Cを使うのは普通じゃないけど、かつてはそうだったし、今でも多くの人がゲームを書くためにCを使っている。俺もその一人だよ。

人々が「誰も…」や「みんな…」って言うとき、彼らはしばしば、たぶんほとんどの場合、100.000000%の人を文字通り意味してるわけじゃないんだよね。君は例外だよ。彼が「誰もこれをやらない」と言うのは正しい。

文字通り何千ものゲームがCで書かれていて、すべてのグラフィックスAPI(OpenGL、Vulkan、DX)はCのAPIだから、全然変じゃないよ。主要なゲームエンジンもC/C++で書かれているし。

DirectXはC++(技術的にはCOMインターフェースのセット)で、ほとんどのゲームエンジンもC++だよ。LinuxプログラミングみたいにCが標準の環境とは違って、ほぼすべてのゲームは長い間C++で書かれてきたから、もう30年くらいになるんじゃないかな。

KhronosのAPIはC、DirectXはCOMやWinRTを通じて公開されたC++、Metalはシェーダー用のC++とSwiftバインディングを使ったObjective-Cだよ。NintendoやPlayStationは、どのコンソール世代について話すかによるね。

それに、SDL3もCだし、Box2d物理ライブラリの最新バージョンもCで書き直されたんだよね。

僕は主にCみたいに書くけど、必要に応じてC++の機能も使うんだ。目を細めて見ると、Rustに似てるかも。よく「Cでゲームを書く」って言ってる人たちがC++の機能について文句言って、結局は構造体のヘッダーや大きなswitch文を使って仮想インターフェースを手動で再実装して、自分を良く見せようとするのが面白い。Cでゲームを書くのが難しいわけじゃないよ。現代の言語機能を手動で実装する必要があるだけ。自分が欲しくない機能がある言語について文句を言うのは馬鹿げてる。テンプレートを乱用しなければ、C++のコンパイル時間は長くならないよ。

そうだね、Cを選ぶってことはC++の特定のサブセットを選んでるって言えるかも。例えば「Google C++」(つまり、Googleのスタイルガイドに従ってC++を書く)と違うサブセットを選ぶ主な違いは、コンパイラがそのサブセットに従うことを強制するところだね。

これ、よく見かける光景だよ。人々は「Cでゲームを書く」って言って、結局はvtableを構造体に入れたり、巨大なswitch文を使ったりしてC++の半分を再構築してるんだよね。コンパイラの助けなしでね。それが幸せならそれでいいけど、明らかにシンプルでも安全でもないよ。あと、C++のコンパイル時間は主にテンプレートやメタプログラミングによる自己負傷であって、仮想関数を持つことの本質的なコストじゃないんだ。

自分が欲しくない機能がある言語について文句を言うのはバカみたいだ。自分だけで作業しているならそれはバカかもしれないけど、価値のあるソフトウェアは通常、チームによって開発され、プロジェクトの期間中にメンバーやリーダーシップが変わりながら進化していくんだ。使われる機能は、年月を経て使われた全ての機能の合併で、チームリーダーが前任者よりも多くの機能を許可するのは簡単だけど、減らすのはかなり難しいんだ。また、使いたい機能を持つライブラリが使っている場合、欲しくない言語機能を使わざるを得ないこともあるよ。例えば、低レベルのプログラミングをしているとき、ページ上で明確に見えない暗黙の呼び出し(デストラクタやオーバーロードされた演算子など)は好きじゃない。でも、使いたいライブラリがそれを使っているなら、そういう暗黙の呼び出しは避けられないよね。もし言語にその機能がなければ、ライブラリも当然それを使わないし。

テンプレートを乱用しなければ、C++のコンパイル時間は長くならない。驚くことに、これは真実じゃない。C++のファイルを書いた後で、結局C++の機能を一切使っていないことに気づいたことがあるよ。ファイル名を.cに変更したら、コンパイル時間が半分になった。

一度測ったことがあって、驚いたことに、テンプレートが(直接的に)長いコンパイル時間の原因ではないんだ。ヘッダー内の関数本体が原因で、テンプレートはヘッダーにあり、他のテンプレート関数やクラスを呼び出すから、コード生成と時間が爆発的に増えるんだ。でも、数行だけで他のテンプレート関数を呼び出さなければ、たぶん大丈夫だよ。ここに書いたことがあるよ https://bolinlang.com/wheres-my-compile-time それを書いた後、自分の標準ライブラリを作ったんだけど(vector、hashmap、setsみたいなデータ構造、スライス、文字列、rng、print、いくつかのio関数などがある)、たくさんのテンプレートを使っていて、clangとgcc両方で200ms未満でコンパイルできるんだ。多くの標準ライブラリのヘッダーはそれよりもずっとコンパイルに時間がかかるよ。早いコンパイル時間が必要なら、自分の標準ライブラリを持つのは悪くないアイデアだね。

ネットで「C++のサブセットを使えばいい」って言う人がいる一方で、「Cは悪いC++のサブセットだ」って主張する人もいるんだよね。だから、CコードをC++コンパイラでコンパイルすると、「良いCコード」から「悪いC++コード」に昇格しちゃう(ほとんどのCコードは「例外安全」じゃないし)。これで言語を評価するのは理不尽かもしれないけど、「このコードはもっと良くなる可能性がある」と考えるのはちょっとした気晴らしだね。C++はこういう気晴らしがたくさんある。

もちろん、Cでも「仮想関数」を使う人はいるけど、これはCの特性とは関係ないと思う。C++で仮想を作るのがめっちゃ簡単だから、みんなそれを乱用しちゃうんだよね。これがコードの読みやすさや理解、デバッグをすごく難しくする(特にテンプレートと混同すると)。Cでは、仮想を使うことはできるけど、複雑になるから、使う前にちょっと考えることになるよ。

C++は、Cでやってることを関数ポインタで再実装しつつ、実際に何が起こっているのかを90年代のオブジェクト指向パラダイムに基づいた重たい構文で隠してるんだよね。

結局、C++の動的ディスパッチ(君の「仮想インターフェース」)は、すべての型にvtableをくっつけて、その型のインスタンスにそのvtableへのポインタを提供することで実現される。もし90%のコードがガチョウや白鳥、アヒル、カモメみたいな特定の型を扱っていて、10%だけが広い「鳥」カテゴリで動く必要があるなら、残念ながら、すべてのガチョウ、白鳥、アヒル、カモメはそのvtableポインタを持ち歩くことになる。これで君の「鳥」コードはC++で「うまく動く」ってわけ。でも、これが唯一の解決策じゃない。イディオマティックなRustのアプローチでは、vtableは鳥のコードでだけ使われて、他のところでは存在しないから、常にアヒルであるものにはスペースを取らない。でもその代わり、考える時間が増える。デフォルトではvtableがないから、動的ディスパッチは全くできない。だから、Cのプログラマーは手動で機能を実装しなきゃいけないけど、少なくとも自分が欲しかった機能を具体的に実装できるんだ。Bjarne Stroustrupが先世紀にやりやすいようにしたものじゃなくてね。

自分が欲しくない機能がある言語に文句を言うのはバカげてる。良い言語の基準が「どれだけ機能があるか」なら、確かにC++が勝つよ。一方で、「その言語にどれだけの足元の銃があるか」が基準なら、C++はほとんどの他のメインストリーム言語に負ける。Cもその中に含まれてるしね。時には、足元の銃がないのがプラスになることもある。

それ自体は珍しくないけど、2026年にこれをやるにはちょっと狂ってるかもね。僕はChrysalisを完全にCで開発したよ(GLFW3とFMODを音声に使ってる):https://store.steampowered.com/app/1594210/Chrysalis/

ここ2年間、Jedi Academyのコードベース(CとC++)に熱心に取り組んできたんだ。それはRavensoftのidtech3エンジンのバリアントで、ゲームの戦闘が精度やタイミングの変化にどれだけ脆弱かは本当に驚きだよ。ライトセーバーの戦闘の特性を壊さずに追加できることはほとんどないんだ。i++カウンターを追加することすらできない場所もあって、ちょっとした遅延を引き起こしたり、何かがずれたりして、ゲームプレイ全体に影響を与える原因を追跡できていないんだ。でも、22年前の古いコンパイラを使って、ゲームのFPU特性を保つようにしてるんだ。最近のツールを使ってこのコードベースを使おうとする現代的な試みもあるけど、全部を改造してしまって、なんか違和感があってバランスが崩れてる感じがする。idtech3はCへの素晴らしい挑戦で、本当に特別なもので、カーマックとチームは当時すごいことをやってたんだ。

僕はCが好きだった。 brutalな感じが楽しかったけど、プリプロセッサは除いてね。だから、zigは神のような存在なんだ。Cよりもシンプルで、Cよりも正確なんだよ!例えば、zigは単一要素へのポインタと、未知の長さの配列へのポインタを区別できる。一方でCのABIでは、すべてがT*なんだ。Cライブラリをインポートするとき、Cそのものよりも使いやすくできるんだ。Cライブラリを簡単にインポートできるのはゲーム開発にとって特に重要で、ほとんどのC++ライブラリもCヘッダーをエクスポートしてるのは、その重要性を知ってるからだよ。https://github.com/zig-gamedevには、ゲームで使われるzig化されたCライブラリのリポジトリがたくさんあるよ。プリプロセッサについては、zigのコンパイルタイムは本当に素晴らしい。コンパイル時に動くのがもっとzigなんだ。

ほとんどのいわゆるC++ライブラリもCヘッダーをエクスポートしてるのは、重要性を知ってるからだ。ゲーム開発の分野では、そういうのが少なすぎると思う。

Cはエントリーレベルが非常に低いけど、メモリ管理についての知識が必要だよ。Java開発者として、与えられた.hと.soを使って急いで交換コネクタを作らなきゃいけなかったとき、Cを選んだんだ。C++はエントリーレベルが高すぎたからね。Cが鋭いナイフなら、C++は鋭いナイフでいっぱいの回転するポストみたいなもんだ。安全だと思ってても、自分を切っちゃうことがある。でも、Cの文字列管理はひどいと思うから、C++から借りたいな。文字列管理だけは。

それがC++のいいところだよ。使いたくない機能は使わなくていいんだから。

Cが好きだな。メモリ管理を全部取り除いても(そう、隠れたメモリ管理を含むglibcの危険な呼び出しもね)、すごくスムーズでクリーンになるんだ。MISRAみたいなルールが事前のメモリ割り当てを要求するから、使ってる分はしっかり管理できる。どこにもサプライズが欲しくない(または必要ない)なら、すごく役立つよ。ハードウェアに結びついたサプライズ(例外やエラーなど)が欲しいなら、それもいいしね。ユニットテストを書くのも結構簡単だし。