次の記事→【ゲーム開発】Unity設計完全に理解した! 〜 設計の役割・性質・コスト【C#】
はじめに
私はUnityを使ったゲーム開発に携わるクライアントエンジニアです。エンジニア歴はトータルで7年くらいで、言語やフレームワークが提供する仕組みについてよく学び、ある程度使いこなしている自信はあります。
ですが、ゲームクライアント全体の設計となると途端にどうしてよいかわからず、
- Railsの経験があるからMVCにしてみたけど、なんかしっくりこない
- MVVM、MV(R)Pとかいろいろあるけどメリット・デメリットがよくわからん
という状態でした。
最近になって設計に関する様々な概念について学び、自分なりの原則やルールを見いだせるようになった上で自信マンマンで社内勉強会で発表したのですが、まだどうにもしっくりこない感じがありました。自分の説明に対して「いやでもこれは違うじゃん」という話になって、平行線のまま終わる、みたいな感じ。
今になってよく考えてみると
- 私自身の意見にバイアスがかかっていた
- 相手の言い分も矛盾しているわけではなく、筋が通っている
- でも噛み合わない、なんで?
という未解決の問題があったように感じました。
上記の反省を踏まえ、本エントリは具体的な設計を考える前の「良い設計とは何か?」という問いに対し、さらに前段階のそもそも「良い」「悪い」をどうやって評価すべきか?という点について述べるものです。
書いてある内容は言われてみれば当たり前のことかもしれませんが、明文化されることによって例えば「なぜか話が通じない!」と感じたときにどうすべきかのヒントになるかもしれません。
* * *
また、本記事は以下の記事に多大なインスパイアを受けています。
- 私的コードレビュー お作法( @hadashiA さん)
- 役割駆動設計で巨大クラスを爆殺する( @MinoDriven さん)
どちらもかなりためになる内容なので、一読されることをおすすめします(本エントリより先・後どちらでも構いません)。
それでは説明に移ります。
評価対象のコンテキスト
物事を評価する際には、評価対象の「コンテキスト」についてよく考えなければいけません。
ここでいうコンテキストとは、ざっくり「対象の背後にある事情」と言い換えられます。具体例を挙げて考えてみましょう。
Aliceの例
例えばAliceが会社の査定評価で最低のEを受け取ったとします。その背景には
- 定時に会社に来ない、遅刻ばかりしている
- 勤務態度がよくない、就業時間中に仕事以外のことをしている
といった問題があるとします。この場合、Aliceの評価Eは至極妥当なものでしょう。
しかし、実はAliceは凄腕仮想通貨トレーダーだったのです!
帰宅後は夜な夜なトレードや銘柄の研究に明け暮れ、寝るのは朝5時。これでは始業時間の9時までには到底起きられません。
また、Aliceは仮想通貨ブログと仮想通貨情報を発信するTwitterアカウントを運営し、仮想通貨界隈ではかなりの高い評価と知名度を誇っています。
* * *
この例ではAliceという人物が少なくとも「会社員」「仮想通貨トレーダー」という2つのコンテキストを有しており、それぞれのコンテキストで評価が真逆であることがわかります。
さらにもう一つ条件を付け加えて、Aliceは仕事で会社からもらえる給料よりも、仮想通貨関連の収入のほうがはるかに多い
とします。この場合についてAliceの立場で考えると、仕事と仮想通貨の優先度は明らかに仕事 < 仮想通貨
でしょう。
また、Aliceの時間は限られているため、仕事と仮想通貨のコンテキストは時間的リソースの取り合いを通じて互いに影響しあっていると言えます。
この状態を図にすると以下のようになります。
Aliceの例で重要な点を整理して短く書くと、
- 対象には異なる複数のコンテキストがある
- 対象の評価はコンテキストによって異なる
- コンテキストには優先度が存在する
- コンテキストは互いに影響し合う
ということになります。
クソコードが生まれる背景
もっと具体的なプログラムの書き方の話をしましょう。一般的にクソコードと呼ばれるものの代表例をいくつか挙げます。
- コピペで量産された似たような処理がたくさんある
- マジックナンバーが直書きされていて意味が読み取れない
- 例外を正しくハンドリングせず、握りつぶしている
- グローバル変数や
public static
を多用している(いわゆる「staticおじさん」のコード)
今思いつく限りの4つを挙げましたが、実際にはもっとたくさんあるでしょう。
では、こういったクソコードが生まれるのはなぜでしょうか?考えられる原因を挙げます。
- プログラマ自身の技術力の低さ
- 納期の短さ
- コードを書いた日の体調や精神的な問題
- 悪いコード規約や年上エンジニアの間違ったコーディング指南
とりあえず4つほど挙げてみました。
1,2はため息と同時に出るくらい当たり前の原因なのですが、3,4については言われてみて初めて「たしかにそういう場合もあるよな」と思われたのではないでしょうか。
一般的に「プログラマ自身の技術力の低さ」からプロダクトの品質を守るには、ペアプログラミングなどの手法が使われます。新米エンジニアや新しくチームに参加したエンジニアが書いたコードを、経験豊かなエンジニアやプロダクトに詳しいエンジニアがレビューするのです。
しかし、実際の現場で目にするのはその程度の「経験を積めば良し悪しの判断がつく」というクソコードばかりではありません。
コードを書いている本人が「これは二通りの書き方があるけどどっちがいいんだ?」と悩むコードがあり、ネット上には「これはクソ」「いや逆にそっちがクソだろ」みたいなやり取りが多くあります。
私自身の経験で言わせてもらうと、ゲーム業界のエンジニア、とりわけクライアントエンジニアは、私から見て「どうみてもクソコードだろ...」というコードを平気で書いていると感じることが多いです。
具体的には先に挙げた「クラスをコピペして内容がほとんど同じコードを量産している(DRY原則に違反している)」ことが多い印象です1。
ゲーム開発歴の長いベテランエンジニアが書いたコードなのに、どうしてそんなことになるのでしょうか?
また、クソコードが生まれる原因として挙げた「プログラマ自身の技術力の低さ」以外の残りの3つについてはどう捉えたら良いのでしょうか?
コンテキストごとの優先度
Aliceの例を思い出してください。
- 対象には異なる複数のコンテキストがある
- 対象の評価はコンテキストによって異なる
- コンテキストには優先度が存在する
- コンテキストは互いに影響し合う
これを実際の問題に当てはめて考えましょう。
1. プログラマ自身の技術力の低さ
2. 納期の短さ
3. コードを書いた日の体調や精神的な問題
4. 悪いコード規約や年上エンジニアの間違ったコーディング指南
1についてはペアプログラミングが有効ということがわかっているので、2,3,4についてエンジニアに関係するそれぞれ異なるコンテキストを通じて説明します。
まずは2,3の場合をもっと具体化してみましょう。
- コードを書く日にたまたま体調が悪かった
- かつ、新機能のリリースを間近に控えていた
上記のコンテキストの場合、エンジニア内の優先度はコード品質 << 納期 ≦ 体調
という具合になり、仕事を消化しつつも早く帰るために優先度が最も低いコード品質が損なわれることになります。
4の場合も具体化してみます。
- 実際はもっと高品質なコードが書ける
- 守らないといけないコーディングルールがクソ
この場合も、エンジニア内の優先度はコード品質 < 自身の高い技術力から生み出されるコード < コーディングルール
となり、問題のあるコーディングルールが最優先されるためにコード品質が犠牲になります。
技術力を抜きにして考えると、いずれの場合もコード品質よりも優先度の高い存在があり、そちらが優先された結果としてコード品質に悪影響が出ています。
考えてみたら当たり前の話です。
クソコードはそれを書いたエンジニアという存在がいて、それを取り巻くコンテキストが互いに作用しあった結果として生まれるのです。
設計の評価の観点
本題の設計の話に移りましょう。設計は今現在私が思いつく限り、かつ大雑把には以下の観点・評価軸から評価が可能です2。
- 機能追加のしやすさ
- 機能変更のしやすさ
- スケールしやすさ
- パフォーマンス
- 安定性・可用性
また、設計を制限する条件として
- ハードウェア(HW)制約:CPU性能やメモリ容量など
- ソフトウェア(SW)制約:開発言語やフレームワークの選択肢など
- 納期
の3つを挙げることができます。これらとアプリケーションの関係を図にしてみました。
一番外側の「HW/SW制約」は初めから大きさが決まっています3。その内側の「納期」は場合によっては大きくなったり小さくなったりある程度の弾力性があり4、それらの制約により最終的なアプリケーションの規模・形を制限する成長限界が決まります。
アプリケーションは成長限界の中で評価軸が影響しあいながら成長が促進され、求められる仕様の形に定まっていきます。「影響し合いながら」の部分がしっくりこない場合は、パフォーマンスを限界まで引き上げようとするとその他多くが犠牲になることを考えるとわかりやすいです。
「良い設計」とは、こういった設計のコンテキストを洗い出した上で総合的にその良し悪しを判断されるべきもののはずです。
では、「総合的に良し悪しを判断」とはいったいどうやればいいでしょうか?
アプリケーションごとのコンテキスト
アプリケーションにもそれぞれコンテキストがあります。
それは使われる場面や機能、提供されるインターフェースなどによって分類でき、Webサービスであったりスマホゲームであったり、銀行の預金や振込を扱うシステムであったり業務改善用の労務管理ソフトウェアであったりします。
注意しなければいけないのは、Aliceの場合と同様アプリケーションにもそのコンテキストごとに優先度があるということです。別の言葉で言い換えると、各評価軸に対してコンテキストごとに暗黙的な重み付けがあるということです。
Webサービスとスマホゲームについて、評価軸ごとに求められる品質をまとめてみました。
Webサービス
- 機能追加のしやすさ:★★★★★
- 機能変更のしやすさ:★★★★☆
- スケールしやすさ: ★★★☆☆
- パフォーマンス: ★★★☆☆
- 安定性・可用性: ★★★★☆
スマホゲーム
- 機能追加のしやすさ:★★★★☆
- 機能変更のしやすさ:★★★☆☆
- スケールしやすさ: ★★★★☆
- パフォーマンス: ★★★★★
- 安定性・可用性: ★★★☆☆
これらは今思いついたそれぞれの典型と思われる値をざっくりと表現したものですが、実際はサービスやゲームごとにまったく異なってくるでしょう(分類の仕方はこれ以外にも多種多様にあるはず)。
私はWebとゲームの開発にしか関わったことがないので想像になりますが、銀行システムでは大体こんな感じでしょうか。
銀行システム
- 機能追加のしやすさ:★★☆☆☆
- 機能変更のしやすさ:★☆☆☆☆
- スケールしやすさ: ★★★★★
- パフォーマンス: ★★☆☆☆
- 安定性・可用性: ★★★★★★★★★★
(全然違ってたらすいません)
簡素化のため3つの制約については考えていませんが、こうしてみるとコンテキストごとにアプリケーションに求められる品質の要件が全く異なっていることがわかります。
それぞれのコンテキストにおけるアプリケーションの評価軸の優先度は全く異なるので、Webサービス開発では短時間で機能追加が可能なスクリプト言語が好まれ、銀行システム開発ではCOBOLが採用されたわけです。
ゲーム開発の特殊性
実はゲーム開発は他の業界のアプリケーション開発と比べると毛色が違っていて、これは
- 厳しいハードウェア制約
- 高い専門性
- アップデート可能性の低さ
の3つで説明できます。
厳しいハードウェア制約
ゲームを遊ぶにはそれを動かす何らかのハードウェアが必要で、それらは必ずしも潤沢なリソースを持ち合わせてはいません。一般消費者の手元にないといけないものなので、基本的にはビジネス用途のものと比べて安価かつ大量生産されます。
制限を超えるものが作れない以上、ゲーム開発においてはこの「比較的低いハードウェア上の限界」を守ることが最重要課題と言えます。この狭い道を通れなければ品質云々の前にリリースできないのです。
昔のゲーム開発を取り巻くコンテキストを以下に図示します。先程の図とは納期とHW/SW制約の大きさが逆転していることがわかります。
参考までに、ファミコンソフトを作る上でどういった技術が使われているかを紹介している記事のリンクを貼っておきます。
最近のゲーム機器は時代の歩みとともに著しく進化しているので、設計の評価軸と制約の図は先に挙げたほうの形になってきています。スマホゲーでかなりリッチもしくは物量過多な表現をしない限り、ハードウェアの制約はあまり気にする必要がなくなりつつあります。
ですが、歴史的にみるとゲーム開発と厳しいハードウェア制約とは切っても切れない関係であり、エンジニアはこのことを常に意識しなければならなかったわけです。
高い専門性
画像を移動・回転・せん断するにはベクトルを用いたアフィン変換が必要で、誘導弾の実装には三角関数が使われます。3Dで物を表示するには頂点,メッシュ,テクスチャ,マテリアル,ライト, ...すいません不勉強でよく知りませんが、とにかく多くの知識が必要とされます。
これらの技術が業務用アプリケーションで求められることは比較的少ないでしょうし、割と近しいと思われるWebサービスであっても高い頻度ではお目にかかれないでしょう。
さらにこれらの技術知識は開発するゲームごとに求められるものが全く異なり、音ゲーから3Dアクションゲーまで多種多様なゲームが存在していることを考えると、一人のゲームエンジニアの脳内は多方面に渡る専門的な技術知識で満たされていると言えます。
最近では誰でも触れるUnityなどのゲームエンジンの登場により、ゲーム開発に要求される知識レベルは格段に低くなりました。クォータニオンを知らなくても3Dオブジェクトを回転できるし、物理計算ができなくてもRigidbody
コンポーネントをつければ球を転がせます。良い時代になったものです。
ゲーム開発で求められる技術は民主化されたゲームエンジンがその複雑さを担うようになり、自前のゲームエンジンを持っていなかった開発者は自身が作りたいゲームの内容に注力できるようになってきています。
このことは、ゲーム開発において重要視されるスキルが時代で変化してきていることを意味しています。
そういった時代の流れの中で、ActionScriptやUnityで手軽にゲーム開発してきたぺーぺーエンジニアの隣の席にはゴリゴリのアセンブラでゲームを開発していた熟練ゲームエンジニアが座っていて、同じチームでゲーム開発をしていたりするわけです。たぶん。
アップデート可能性の低さ
最近ではハードウェアがインターネットに接続されることも多く、ネットを通じたゲームのアップデートも一般的になりました。しかし昔のコンシューマーゲームはいわゆるパッケージ販売なわけで、一度マスターアップしたゲームのソースコードを修正することは今と比べて稀であったと思います。
先述の厳しいハードウェア制限と高い専門性により、ゲームエンジニアは歴史的に制限回避と表現技術に対して心血を注いできたのだと思います。一方で、先程の設計の評価軸でいう「機能追加のしやすさ」「機能変更のしやすさ」は以後のアップデート可能性の低さから軽んじられることが多く、リリース時の仕様を満たすためにコピペで実装された処理も多くあったのではないかと推察されます。
クソコードに対する自分なりの考え
長くなりましたが、「熟練ゲームエンジニアがなぜクソコードを書くのか?」という問いに対する自分なりの答えをまとめます。
- コードの評価に用いるコンテキストが異なっている
- 私:Webっぽいコンテキストを持っている
- 熟練ゲームエンジニア:特殊なゲーム開発のコンテキストを長く持っている
- コンテキストにおける優先度が違う
- 自分 :
(ハードウェア制約) < パフォーマンス < 追加/拡張のしやすさ(DRY潔癖症),納期
- 熟練ゲームエンジニア:
追加/拡張のしやすさ < 納期 <<< パフォーマンス,ハードウェア制約
- 自分 :
- 自分のコンテキストを通して見たコードAと熟練ゲームエンジニアのコンテキストを通して見たコードAは優先度が異なり、評価も異なっている
割と当たり前の事実にたどり着くためにだいぶくどくど書いてきましたが、モヤモヤした何かの輪郭をはっきりと捉え、その実体を暴けたのではないでしょうか。
まとめ
本エントリでは「良い設計とは?」を考える上での前提として
- 一つの対象には複数のコンテキストがある
- それぞれに優先度がある
- それぞれの評価は異なる
- それぞれに影響しあう
- 設計の評価軸
- 機能追加のしやすさ
- 機能変更のしやすさ
- スケールしやすさ
- パフォーマンス
- 安定性・可用性
- 業界やアプリケーションごとの優先度の違い
- ゲーム開発における歴史的な特殊性
について述べました。
次回以降のエントリでは以上の考え方を基礎として、ゲーム開発において複雑な設計を行う際に直面する実際の問題や解決方法などについて「このやり方は〇〇を改善できるが△△には悪影響がある」といった多面的な捉え方でまとめていくことになると思います。
最終的には具体的な設計事例やDI Containerの必要性についても解説したいのですが、だいぶ先の長い話になると思われます。
そこに辿り着く前にGWが残っていることと、自身のモチベーションが保てていることを祈ります🙏
最後に
「一つの対象には複数のコンテキストとそれぞれの優先度がある」というのは割と応用が利く考え方です。
例えばAliceのような人物の全体像を会社側が正しく認識・把握するには、本人との面談などを通じて会社以外のコンテキストについても共有される必要があります。
そうした結果、今まで任せていた仕事では持ち味を発揮できていなかったAliceをリーダーとし、新たに仮想通貨ビジネスを立ち上げることでAliceの良い面を会社として取り込む、というやり方によって会社のAliceに対する評価は逆転するでしょう。
また、ゲームの仕様を決めるミーティングやその他何らかの議論の前には、事前に互いの意見と解決すべき課題の優先順位を共有することで納得の行くゴールがはっきりし、限られた時間を有意義に使えるでしょう。
Twitterを見ていると映像提供のお願いについてテレビ局が批判されているのを見ることが多いですが、テレビ局のコンテキストを「報道機関」「営利組織」と分けて考えることで少しはマシな議論ができるかもしれません。
世の中には「ケース・バイ・ケース」という便利な単語がありますが、この言葉を使うと前提となるコンテキストの共有が省略される恐れがあり、その結果着地点を見失うなど議論の上ではとても危険です。あくまでも個々のケースと重み付けをはっきりさせた上で、それぞれに応じて良し悪しを議論すべきです。
* * *
ちなみに今現在私はJava界隈の設計が強そうな人をTwitterでフォローしてるのですが、そういった方々の発言はコンテキストが共有されていない(自分の知らない専門用語が多い)ので1ミリも理解できません。辛い
また、本エントリを書くに当たっては自分の脳内から溢れ出てきた内容をできるだけ整理してアウトプットしたつもりですが、如何せん既存理論のサーベイが十分でない部分があります。
なので「これ言ってることは〇〇理論じゃん」みたいな場合はコメントなどで教えてください。単純に参考になりそうだと感じたものでも構いません。急にマサカリぶん投げられると怖いので優しい感じで教えてもらえると嬉しいです。
よろしくお願いします。
次の記事→【ゲーム開発】Unity設計完全に理解した! 〜 設計の役割・性質・コスト【C#】