ゲームエンジンがなかった時代、ゲーム会社はどうやって「手作り」でゲームを作っていたのか?
この質問の答えは、ほとんどの人が想像するよりはるかに野蛮なものだ。
今のゲーム開発者はUnityやUnrealを開けば、エディタがあり、物理エンジンがあり、レンダリングパイプラインがあり、ライティングシステムがある。開発者の仕事は、すでに組み立てられたフレームワークの中にコンテンツを詰め込むことだ。しかし、1980年代にはそんなものは存在しなかった。フレームワークもエンジンも、再利用可能なインフラも何もなかった。ゲームを作りたければ、まずそれを動かすマシンを作り、そのマシン上でアセンブリ言語を使って、すべてのピクセルの色を一つ一つ指定しなければならなかった。
すべてはピクセルから始まった。
初期のゲームグラフィックは直接メモリに書き込まれていた。プログラマーは画面上のすべての位置に対応するメモリアドレスを知っている必要があった。ある場所に点を描きたければ、そのアドレスに値を書き込む。その値が色を表す。線を描きたければループを書き、CPUにこのアドレスからあのアドレスまで何バイトおきに書き込むよう指示する。キャラクターを描きたければ、ピクセルの各行を16進数に変換し、一行ずつ書き込み、画面上の正しいオフセットを見つけてその数値を配置する。
この操作は今日では「ソフトウェアレンダリング」と呼ばれている。当時は名前すらなかった。代替手段がなかったからだ——単に「画面がどうやって表示されるか」と呼ばれていた。
カーマックが最初のWolfensteinに取り組んでいたとき、彼は具体的な問題に直面した。IBM PCのグラフィックアーキテクチャはスムーズな横スクロールをサポートしていなかった。画面はフレーム全体を一度に更新する方式で、画像を動かすとティアリングが発生し、震えているように見えた。彼はGPUの低レベルレジスタを膨大な時間をかけて研究し、ハードウェアがネイティブでサポートしていない機能をソフトウェアでシミュレートする、ハードウェアを騙す方法を見つけ出し、画面をスムーズにスクロールできるようにした。
これに関するドキュメントも前人による経験もなかった。彼はただそこに座って、ハードウェアマニュアルを隅々まで読み込み、試行錯誤で少しずつ解き明かしていった。
これこそが、その時代のプログラマーの基本的な仕事の状態だった。あなたが向き合っていたのは説明書のあるシステムではなく、ハードウェアそのものだった。あなたはハードウェアよりもハードウェアに詳しくなり、その隙間の中にスペースを見つけ出さなければならなかった。
サウンドはまったく別次元の問題だった。
初期のPCのサウンドシステムはブザー——PCスピーカーだった。それは「ビー」という単一の音しか出せなかった。それで音楽を再生するには、このブザーの振動周波数を制御し、異なる周波数を極めて高速に切り替え、人間の聴覚の残響効果を利用してメロディが流れているように聞かせる必要があった。この技術はPWM、パルス幅変調と呼ばれる。原理は音響物理学であり、実装はマイクロ秒単位の正確なタイミング制御である。少しでも間違えば、ただのノイズになる。
当時、PCスピーカーからより多くの音の層を引き出す方法を專門的に研究するプログラマーがいた。その方法は、極めて短い時間枠内で異なる周波数を切り替え、擬似ステレオ効果を生み出すというものだった。この技術は当時のデモシーンで黒魔術のように扱われていた。「ビー」としか鳴らないブザーで、音楽に近いものを生み出したのだ。聞いた他のプログラマーは、どうやってやったのか見当もつかなかった——原理が直感的ではないからだ。これを実現するには、タイミング制御に対する極めて正確な理解が必要だった。
AdlibとSound Blasterが登場すると、FM合成音がもたらされた。これもゼロから学ぶ必要がある別のシステムだった。FM合成の原理は、ある周波数の波形を使って別の周波数の波形を変調し、複雑な音色を生成するというものだ。変調パラメータの違いによって異なる楽器の音色が生まれる。プログラマーは自分でパラメータを調整し、どのパラメータの組み合わせがピアノのように聞こえるか、どの組み合わせがバイオリンのように聞こえるか、どの組み合わせが爆発音のように聞こえるかを自分でテストしなければならなかった。標準的な答えは存在せず、すべては耳と経験に頼るものだった。当時、各社のサウンドプログラマーは独自に蓄積したパラメータテーブルを持っていた。それらは真の意味で企業秘密だった。
ゲームロジックのレベルでのデザイン感覚は、また別の形態だった。
エンジンがないということは、既成の衝突判定も、既成の物理シミュレーションも、既成のAIフレームワークもないということだ。キャラクターが壁にぶつかったときに壁をすり抜けずに止まってほしければ、自分で衝突判定を書き、何を「衝突」とするかを自分で定義し、衝突後にキャラクターの速度と位置がどう変化するかを自分で決めなければならなかった。
初期のゲームの衝突判定は単純で野蛮だった。キャラクターと障害物の両方を矩形と見なし、二つの矩形が重なっているかどうかをチェックし、重なっていれば止まる。この方式は高速だったが、問題は実際のゲームキャラクターが矩形ではないことだ——形のあるスプライトだ。矩形の衝突ボックスと実際のグラフィックの間には常に誤差があった。だから初期のゲームでは、明らかに弾が外れているのに命中と判定されたり、キャラクターが明らかに壁に触れる前に止まってしまったりすることがよくあった。
当時のプレイヤーはこれをバグとは呼ばなかった。ゲームの特徴と呼んだ。他にやりようがあることを知らなかったからだ。
もう一つ特筆すべきことがある。初期のPCのメモリは極めて限られていた。640KBが10年にわたるハードウェアの上限だった。この数字は今日ではスクリーンショット一枚すら保存できない。プログラマーはゲームのすべて——グラフィック、効果音、マップ、ロジックコード——をこの640KBの中に詰め込み、さらに実行時データのためのスペースも確保しなければならなかった。
どうやって詰めるのか?圧縮、分割読み込み、再利用。
グラフィックデータの圧縮は独立した技術分野だった。プログラマーは独自の圧縮アルゴリズムを発明し、グラフィックデータを保存時には可能な限り小さくし、表示が必要なときには即座に解凍しなければならなかった。マップが大きすぎて入りきらなければ、チャンクに分割し、プレイヤーがいるチャンクだけを読み込み、離れたらアンロードする。今日この技術はストリーミングと呼ばれる。当時は「仕方ない」と呼ばれた。
メモリのすべてのバイトは奪い合いだった。コードを書くとき、プログラマーは常に考えなければならなかった。この変数にもっと小さいデータ型を使えないか?このロジックにもっと少ない一時変数を使えないか?このループのメモリアクセス回数を減らせないか?このような考え方は、今日のプログラマーにはほとんど必要ない。現代のハードウェアはメモリに十分な余裕があり、コンパイラの最適化も十分に賢い。一バイト一バイトを節約する必要はない。
しかし、その時代のプログラマーはコンピュータ全体の動作を頭の中に詰め込んでいた。CPUの命令サイクルからメモリレイアウト、GPUレジスタまで、すべての層を理解していなければならなかった。なぜなら、すべての層が、彼らが利用する必要のある隙間になり得たからだ。
カーマックはかつて逆平方根を計算するコードを書き、その中に“what the fuck”というコメントを残した。このコードは直感に反するビット操作のトリックを使って逆平方根を高速に近似計算し、標準の数学ライブラリよりはるかに速かった——すべて3Dレンダリングの計算時間を節約するためだ。このコードは後にプログラマー文化における伝説となり、繰り返し引用された。なぜなら、それはハードウェアの制約の中で数学と創造性を使ってパフォーマンスを絞り出すという、すでに失われた思考様式を象徴していたからだ。
その時代のゲームプログラマーは、エンジニアと科学者のハイブリッドに近かった。彼らが解決していたのは「どうやって機能を実装するか」という問題だけではない。「このマシンの物理的限界の中で、その機能は実装できるのか」という問題だった。
この二つの問題は大きく異なる。
最初の問題には標準的な答えがある。ドキュメントを調べ、APIを呼び出し、手順に従う。二番目の問題には標準的な答えがない。自分で答えを発明しなければならない。時には答えが存在し、時には存在しない。まず自分がどちらのケースにいるのかを突き止め、それからどうするかを決めなければならなかった。
今日のゲーム産業は巨大な協力システムだ。エンジン企業、ミドルウェア企業、ツールチェーン企業——すべての層に誰かが取り組んでいる。開発者はこのシステムの頂点に立ち、コンテンツそのものに集中する。これは進歩であり、良いことだ——より多くの人がゲームを作れるようになり、ゲームの規模と複雑さは過去をはるかに超えた。
しかし、ピクセルからゲームを作り始めたあの時代には、今日ではもう存在しないものがあった。
あなたがゲームを開くと、画面上のすべての点がどこから来たのか、すべての音がどのように作られたのか、すべての衝突がどのように判定されたのかがわかる。なぜなら、それらはすべてあなたが一行ずつ書き出したものであり、他の誰かが代わりにやったものは何ひとつないからだ。
この感覚には名前がある。それは完全な掌握感と呼ばれる。
だから、あの時代にゲームを開発した大神たちは——本当の大神だった。本当にすごかった。