A3サイズの升目帖(旧)

ゲーム制作やプログラミングに関する雑記

座標系をリファクタリングした

ゲーム本体の制作に目立った進捗はないなあ。

というのも、ここ4日ほどかけて座標周りのリファクタリングをしていたからだ。

このリファクタリングだけは今のうちにやっておかないと後々大きな負債になりそうだったので。


事の経緯

Point」型

Point という型があった。

プロジェクトの最初期のコミットで追加された構造体だ。

こいつは、平面上の座標をX軸とY軸で表現するいたってシンプルなものである。

struct Point
{
    float x;
    float y;
}


しかし、これは 大きな間違い だった。

Point は、その ほとんど無情報にも近いシンプルさ汎用的で甘美な響きの命名 ゆえにあちこちで乱用され、 ありとあらゆる概念がごちゃまぜになる というカオスを生じてしまっていた。


ゲーム上の座標も、クライアント上の座標も、テクスチャ上の座標も、十把ひとからげの無階級制。

「座標」という名のもと、みな等しく Point なのである。

しまいには、「表のセルの 番地 も、ある意味座標だよね(暗黒微笑)」とか言って Point 型を持ち出す始末。気でも触れたんか?


ブラックホールさながら座標の概念を遍く飲み込んだ Point は、コードのそこかしこに顔を出し、レイヤーの壁に大穴をあけ、上層から下層まで一気通貫の腐臭漂う依存関係を生み出していたのだ。


コード量が少ないうちはまだ何とかなっていたものの、拡大と膨張をするのがソフトウェアの定め。

プロジェクトが大規模になるにつれて、これらの概念の混乱が喫緊の問題として頭をもたげるようになってきた。


Size」型

悲しいかな、Size という型もあった。

プロジェクトの最初期のコミットで追加された構造体だ。

こいつは、平面上の矩形サイズを幅と高さで表現するいたってシンプルな(以下略


これは…マズい…

このように、本来は無関係の概念を容易に混同させてしまう「無駄に包容力の高い型」が、殊に座標系に多いという事実がプロジェクトを進めるうちに分かってきた。

このままコーディングを断行すると、遠からず自分の足を撃ち抜くことは明白だったので、いったん作業を止めてリファクタリングを行っていたというわけだ。


せっせとリファクタリング

ゴールの設定

PointSize を解体してそれぞれのコンテキストに沿った表現力の高い型に作り替えてやらなきゃいけない。少なくとも異なる概念の値の取り違えは無くすこと。ここがゴール。

ゲーム座標はゲーム座標、テクスチャ座標はテクスチャ座標という具合に、ちゃんと専用の型を作っていく。


不思議なもので、今回の Point とか Size のような極度に汎用的でヤバげなやつらは、大抵 util とか common のような極度に汎用的でヤバげなフォルダに存在するもんだ。

最終的にはこのフォルダごと解体していけるといいが。

一足飛びにやったらダメ

慌てて一気に進めようとすると、そのうちゴールを見失ってトンチンカンなことをやりだすので、小さなステップでこなしていこう(1敗)

コミットできていないファイルがウン十単位でフワフワ漂流していると(個人的に)精神衛生上よろしくないというのもある。

(まあ今回は座標系というインフラレベルの大工事になってしまったので、息継ぎなしの対応になった点はある意味やむなしという感じもするが…)


で、どうにか完了

途中で設計方針に悩んで気が狂いそうになったり、「SOLIDとは…カプセル化とは…」などと考えすぎて気が狂いそうになったり、紆余曲折あって気が狂いそうになったが、どうにかリファクタリングを完了させた。

まあそれなりにましなコードに生まれ変わったかな。

少なくとも、目標としていた「座標系の値の取り違えリスク撲滅」は、このリファクタリングによってほぼ達成できたと自負している。やったぜ。


あと、こんなこともできるようになった。

演算子オーバーロードを用いた直感的な座標計算である。

// 速度 * フレーム数 = 距離
Velocity v;
Frame f;
Distance d = v * f;
// 座標の平行移動。
Position p;
Distance d;
p += d;
// 2点間の距離を得る。
Position p1;
Position p2;
Distance d = p1 - p2;

座標計算をまるで数式を書くかのごとくコーディングできる。至福である。

しかも、X軸とY軸はそれぞれ別の型になっているので、計算や比較処理でアホみたいな取り違えをして時間を溶かすリスクを低減できる。

Position p1;
Position p2;
if (p1.x < p2.y) // X軸とY軸は比較不能。コンパイルエラー!
{
...
}


ひとまず一件落着

懸案事項はこれにて片が付いたので、今度こそゲーム本体の制作に戻ろう。

リファクタリングだけで4日かかったのは正直痛いが、杜撰な設計のツケだと思って甘んじて受け入れるしかないな。

とりとめのない文章になりましたが、現場からは以上です。