ここでは、ダライアスを題材に、オブジェクト指向プログラミングにおけるクラス設計の方法論について展開します。
C++とDXライブラリでサンプルを書いてはいますが、できるだけ環境に関わらず参考になるように書きたいと思います。




以下のステップに分けて進めていきます。

1. オブジェクトの探し方
2. 継承関係にあるクラスの見つけ方
3. どのクラスに何のプロパティやメソッドを持たせるか

オブジェクト指向プログラミングについては、私はC++の入門書を多少読んだ程度であとは基本的に独学みたいなものですが、業務で行ったRubyでの実装や、PHPのクラスライブラリでの実装などに不自由した経験はなかったので、多分(笑)記事の内容については大きな問題はないと思います。
一部、メディア(HDD等)へのアクセスの効率、メモリ効率、CPU効率、あるいはカプセル化についてなどを意図的に無視した記述が出てくるかもしれませんが、この記事での主眼は、オブジェクト指向プログラミングの初心者がつまづきがちな、初めてクラスの設計をする際のガイドラインにしてもらうことにありますので、細かいところは笑って見逃してやってください。


1. オブジェクトの探し方

何のソフトウェアにしてもそうなのですが、まず、ソフトウェア自体が一つのオブジェクトです。
ここでいうと、ダライアスというゲーム自体が最大のオブジェクトです。
そんなにたくさんの書物を読んだ訳ではありませんが、このことに触れている書物を、なぜかほとんど読んだことがありません。
一番最初に大切になることなのに、どうしたものでしょうか。
日本ではこれが原因で、例えばC++でmainに当たる関数の中に、多くの処理を書いてしまっている方もおられることでしょう。
mainに当たる関数の中では、OSから渡された引数の処理や、ソフトウェアを実行した際の例外の処理などを除いて、ソフトウェアに当たるクラス(ここではクラスDarius)のインスタンスを生成する処理を書く程度にとどめるべきでしょう。
そうすれば、何も知らない人がコードを見ても、これはDariusという何かを作るためのプログラムだな、と一目瞭然になるという効果もあります。

さて、ダライアスというオブジェクトの中には何のオブジェクトが含まれるのかを、再帰的に探していきましょう。
まずはダライアスの画面を見てみましょう。目に見えるオブジェクトはおのずとそこから見つかります。


title

ダライアスを起動した時に最初に表示されるタイトル画面です。
Amebaブログの仕様上、これ以上は画像の解像度を高くできないのでご容赦ください。
とりあえず目に見えるモノは何でしょうか。画面上部から見ていきましょう。

画面上部左から、1Pショットゲージ、1Pボムゲージ、1Pアーム(バリア)ゲージ、1Pスコア、1P残機表示部、ゾーン表示部、2P残機表示部、2Pスコア、2Pショットゲージ、2Pボムゲージ、2Pアームゲージ。
ひとまず、これらは全て別々のオブジェクトとして見る事にしましょう。

さて、続いてはその下を見てみます。タイトル、開発会社、コピーライト。
この部分は、これらをまとめて表示しているだけなので、「タイトル画面」という一つのオブジェクトとして扱ったほうが効率はよさそうです。
ただ、動画ではDARIUSのDの上の部分に光が走っています。これはこれでオブジェクトとして捉えます。
「ダライアスというゲーム」のオブジェクトの中に、「タイトル画面」というオブジェクトがあって、その中に「走る光」というオブジェクトがあるイメージです。

そして右下に残クレジット(ゲーム参加可能回数の残り)。これもオブジェクトですね。

これらは全て、「ダライアスというゲーム」のオブジェクトの中に含まれる個々のオブジェクトと言う事です。

しかし、一つ一つのオブジェクトを別々のクラスとして記述するには、それぞれのオブジェクトの規模が小さすぎるような気がしなくもありません。
これについては、また後で考えることにしましょう。


demo1

次に表示される画面です。
画面上部は変化がありません。

画面右下の残クレジットにも変化がありません。

そして残った中央部分はデモ画面ですね。
オブジェクトの名前はデモ画面1とでもしましょうか。

デモ画面1の中には、INSERT COINの文字というオブジェクト、そして左から右に移動するプロコ(1P)とティアット(2P)の計3つのオブジェクトが含まれるようです。

さらにプロコとティアットのアフターバーナが2個ずつアニメーションしています。
これらも先ほどのタイトル画面の走る光と同様に、プロコとティアットのオブジェクトに属するオブジェクトになります。


demo2

デモ画面2です。

画面左と中央に動きのないシルバーホークの設計図があるので、2つまとめて1つのオブジェクトと言えるでしょう。

画面右にはシルバーホークの仕様らしきテキストがタイピングされるように表示されています。
これは左の設計図とは独立した一つのオブジェクトとして扱ったほうがよさそうです。


game

さあ、いよいよオブジェクトがたくさん動いているゲーム画面です。

目に見える自機、自機の弾、敵、敵の弾はもちろんそれぞれオブジェクトです。

背景、地形ももちろんオブジェクトです。

しかし、ここには目に見えないいくつかのオブジェクトがひそんでいます。

まず、タイトル画面、デモ画面には無かった「音」です。
オブジェクト指向では、BGM、効果音の一つ一つもオブジェクトとみなします。
ショットの発射音はレーザ等の個々の武器に属します。
爆発音は、破壊された個々の自機や敵機に属します。
パワーアップの球体を取った時の効果音は、その球体に属します。

そしてこのゲームの特徴である「ゾーン」も、オブジェクトとみなします。
他の「物」ならイメージしやすいかもしれませんが、「ゾーン」というのは概念に近いです。
ゾーンのBGM、背景、地形、敵の出現座標、画面上部のゾーン表示などの各オブジェクトは、このゾーンというオブジェクトに属します。
WARNING画面や、ボスの次元気流エリアもオブジェクトとして扱いますが、これらは各ゾーンに属したオブジェクトとして扱ったほうが都合がいいかもしれません。

オブジェクト指向では、こういった目に見えない抽象的な事柄であっても、オブジェクトとみなす時があるのです。

後の画面は、これまで見てきた画面とそう大きな違いはないので省略します。


ここまで紹介したように、オブジェクト指向プログラミングでは、ソフトウェア自身、グラフィック、アニメーション、文字、数字、音楽、効果音、なんらかの実体を伴うもの、概念的なもの、そういった一切合切のものをオブジェクトとして捉えます。
そしてそれら個々のオブジェクトで、何らかの動きや振る舞いを伴う物を、それぞれのクラスとして実装していきます。
個々のクラスの中では、自身の持つプロパティを加工して表示させたり、効果音を鳴らしたり、あるいは他のクラスを実体化したインスタンスやプロパティの相互作用をさせたり、等々をメソッドとして記述していきます。
最終的に、例外処理などの一部の例外を除いて、実装部分は全てクラスを記述することで完結します。

そのため、どれが何のオブジェクトであるか、もしくは何のオブジェクトの一部か、を仕分けるオブジェクトの分離が、クラス設計そのものということになります。

オブジェクトの分離部分を間違えると、とたんに間違えた部分の実装がエレガントに記述できなくなります。
これは、互いに別のクラスとして定義されなければいけない内容が1つのクラスの中に混在してしまったり、1つのクラスで実装しなければならない内容が複数のクラスに分割されてしまうことが原因です。
逆に言うと、クラスの実装がエレガントに記述できていれば、その部分のオブジェクトの分離は成功していると見ていいでしょう。
メインループなどのどうしても長大になりがちな箇所以外は、意外と細かい意味単位でシンプルに実装できるものです。
答えは実際に実装したあとのコードが教えてくれることでしょう。

要するに、オブジェクトをうまく分離することこそが、クラス設計における最大の肝となるのです。
クラスの実装がうまくいかない場合、大抵の原因はオブジェクトの分離の失敗なので、またそこに戻って設計し直しになります。
オブジェクト指向プログラミングの経験を積むほど、経験則によりこのオブジェクトの分離での失敗が少なくなるので、開発効率が上がります。

話は少々それますが、オブジェクトの分離で問題になりそうなケースの解決例が、「デザインパターン」として確立しています。
まだ先の大規模なソフトウェア開発をする段階での話になるでしょうが、どうしても自力でオブジェクトの分離がうまくいかないケースが出てきた場合には、そういった経験則に頼るのもよいかもしれません。
目に見えるオブジェクトだけなら楽なのですが、時には概念的なことをオブジェクトとして捉えたほうが成功する場合があることも頭の片隅に置いておいてください。




2. 継承関係にあるクラスの見つけ方

入力される値が違うだけで、やっている処理はほとんど一緒の一連の処理があるとします。
こういう場合、BASICならサブルーチンで、C言語なら関数で処理を共通化させたいところです。
しかし、オブジェクト指向プログラミングではここがちょっと違います。
何らかの共通のプロパティや共通のメソッドなどがある場合、まず継承関係がないか疑ってみるべきです。

ここでは、自機、敵機、地上物、ボス、各種弾を例に解説していきたいと思います。

見つけ方も何も、もう最初から答えを言ってしまえば、これらは全て同じ基底クラスを持つはずです。
なぜなら、全てのキャラクタに座標や当たり判定が定義されているからです。
もちろん、C++のほんとど素人な自分には、記事を書き下ろしてる段階では、本当にそんな実装をしてシューティングゲームに必要な速度が出せるかどうかはまったくわかっていません。
しかし、オブジェクト指向的には思考過程は間違っていないはずなので、記事にして損することはないでしょう。
まあ、経験上きっと大きな問題はないと思います。
実装してみなければわからないのは、これに限ったことではありませんし。

さて、これらはクラスObjectとしてくくるには少々範囲が狭いです。
ダライアスでは自然物を破壊したりすることなどがないので、クラスArtifact(人工物)などの名前でもいいのかもしれません。
しかし、さまざまな物体に座標や当たり判定があったりする他のシューティングゲームへの応用を考えてObjectとしておきましょう。

クラスObjectは大きく分けて2つに継承されるでしょう。
一つは各種弾を意味するクラスWeapon。
これは最終的にクラスWaveやクラスEnemyMissileなどの自機や敵の武器に継承させて実装します。
一つは自機、敵機、地上物、ボスなどのクラスStructure(構造物)。

クラスStructureも大きく分けて2つに継承されるでしょう。
一つは自機であるクラスSilverhawk。
これは最終的にクラスProcoとクラスTiatに継承させて実装するということは言うまでもないでしょう。
一つは敵機全体を示すクラスEnemy。
これは最終的にクラスサイクリンややクラスゾイドなどの雑魚敵に継承させて実装します。
最初、Bellserにしようか迷ったのですが、BellserMissileとかで、'sが付けられないので語呂が悪くてやめました。

雑魚敵の英名は公式設定資料集のDARIUS ODYSSEYにも、どうやら掲載されていないようです。
一次資料の開発時の原稿にもボスしか英名は書かれていません。
雑魚敵は全て異なるクラスになると思うので、名前の由来になっている薬品名の英名や開発者の名前などを元に自分で類推するしかなさそうです。

一見、Enemyは地上物と飛行物に別れそうですが、ここまでくるともう振る舞いであるメソッドの違いだけのような気がします。

ボスは?と思うかもしれませんが、ボスは雑魚敵に似た振る舞いを複数くっつけて、いくらか専用の振る舞いをしているだけに見えます。
そのため、クラスEnemyを継承した攻撃発生個所や当たり判定箇所のクラスを設計し、それらのインスタンスをプロパティとして何個も所持するクラスBossを設計すると予想されます。
そのクラスBossを継承させて、クラスKingFossilやクラスGreatThingなどを設計することになるのではないでしょうか。
そういう理由で、ボスのパーツはクラスEnemyを継承させたクラスになるでしょうが、ボス自身はこの継承関係のツリーには含まれません。
と思っていましたが、Boss自身の大きな座標と当たり判定を忘れていました。ひょっとしたら、クラスBossはクラスStructureを継承するのかもしれません。

また、クラスTwinとクラスMultiは、クラスBombの複数のインスタンスをプロパティに持つ設計になると思うので、このツリーには含まれません。

とりあえずここまでにしておきます。
本当に上記のようになるのかは、実装結果のコードだけが知っています。

さて、複雑になってしまったので、継承関係を図にしてまとめてみましょうか。

  • Object - 物体クラス 座標や当たり判定等の枠組みを提供
    • Weapon - 武器クラス いらないかも?
      • Bomb - 自機のボムクラス 画像、方向、軌道、速度、敵機との当たり判定等を定義
      • Shot - 自機のショットクラス 方向、軌道、敵機との当たり判定等の枠組みを提供
        • Missile - ミサイルクラス 画像、速度、効果音、地形との当たり判定等を定義
        • Laser - レーザクラス 画像、速度、効果音、地形との当たり判定等を定義
        • Wave - ウェーブクラス 画像、速度、効果音等を定義
      • EnemyShot - 敵のショットクラス 自機との当たり判定等の枠組みを提供
        • EnemyMissile - 敵の通常ミサイルクラス 画像、速度、軌道等を定義
        • 破壊可能な回転するエネルギー弾 - アミドの弾クラス
        • 垂直上昇するミサイル - メズヒの弾クラス
        • 虹色の弾 - KingFossilやDualShears等の弾クラス
        • 青いレーザ - DualShearsのレーザクラス
    • Structure - 構造物クラス 耐久力、破壊判定、アニメ等の枠組みを提供
      • Silverhawk - 自機クラス 2機の共通点となる枠組みを提供
        • Proco - プロコ(1P)クラス 操作系、画像等の相違点を定義
        • Tiat - ティアット(2P)クラス
      • Enemy - 敵クラス 敵機の共通点となる枠組みを提供
        • サイクリン - 雑魚クラス 敵機の画像、効果音等を定義
        • ゾイド - 雑魚クラス
        • タノール - 雑魚クラス
        • ネシウム - 雑魚クラス
        • ブラーボ - 雑魚クラス
        • KingFossilの頭部 - 攻撃を行い耐久力を持たないクラス
        • KingFossilの背びれ - 攻撃を行わず耐久力を持つクラス
        • KingFossilの胸びれ - 攻撃を行わず耐久力を持つクラス
      • Boss - ボスクラス ボスの共通点となる枠組みを提供
        • KingFossil - KingFossilクラス 頭部耐久力、無敵処理、アニメ等を定義
        • DualShears - DualShearsクラス



3. どのクラスに何のプロパティやメソッドを持たせるか

まずはもう一度ダライアスの画面を見てみましょう。


demo1

画面上部左から、
1Pショットゲージ、1Pボムゲージ、1Pアーム(バリア)ゲージ、
1Pスコア、1P残機表示部、
ゾーン表示部、
2P残機表示部、2Pスコア、
2Pショットゲージ、2Pボムゲージ、2Pアームゲージ。

これらの表示を実行するC言語的な関数に名前を付けるとしたら、さしずめ次のようになるでしょうか。
drawShotGaugeProco, drawBombGaugeProco, drawArmGaugeProco,
drawScoreProco, drawLifeProco,
drawZoneName,
drawLifeTiat, drawScoreTiat,
drawShotGaugeTiat, drawBombGaugeTiat, drawArmGaugeTiat


なんだか、いろいろ共通した部分があります。
それぞれは、Proco(1P)やTiat(2P)が持つプロパティを表示させたものです。
これらがProcoやTiatが持つオブジェクトであることは、ゲームの画面的にはわかりにくいかもしれませんが、コックピットにある計器をイメージすればよいでしょう。
プロパティも一種のオブジェクトです。
オブジェクトが持つ性質は、名前にしてみるとわかりやすいですね。
オブジェクトの正体が何であるかは、オブジェクト自身が持っているのかもしれません。

名前という点について着目してみると、ProcoとTiat以外にまだ重複しているものがいくつかあります。
そう、draw, Shot, Bomb, Arm, Gauge, Score, Lifeです。

Scoreについては、プロコやティアット自身が持っているscoreというプロパティを表示するので、以下のようなメソッドを用意することになるでしょう。
proco.drawScore();

Lifeも同様です。
proco.drawLife();

Gaugeについては、3つ並んでいるゲージは、性質的になんだか似ているように見えます。
ここではGaugeというクラスを作って、それを継承させたShotGauge, BombGauge, ArmGaugeクラスを作成すれば、うまくいきそうな気がしてきます。
そして、それらを画面に表示させるときに、drawというメソッドを用意すれば、エレガントに書ける気がしてきます。
proco.shotGauge.draw();
プロコというオブジェクトが持っているショットゲージというオブジェクトのdrawメソッドを実行する。
つまり、「プロコのショットゲージを表示する。」です。
なんだか、見た目にも何をやっているかわかりやすい気がしませんか?

ティアットのアームゲージを表示する場合はこうですね。
tiat.armGauge.draw();
プログラミングを何も知らない方でも、ダライアスというゲームを知っている方なら、何をやっているか程度なら見たらわかるかもしれません。

そうです。完璧に設計されたクラスによる実装は、見た目が非常にわかりやすいんです。エレガントなんです。
手続き型プログラミングに慣れた方が、初めて完璧に設計できたクラスによるソフトウェアを実装することができたときは、自分で作ったコードなのに、その美しさに感動すら覚えてしまうことでしょう。
そして「オブジェクト指向プログラミングとはこういうことだったのか」と思うわけです。わかってしまえば意外と簡単なことだったのです。
なぜなら、各種入門書に書いてある個々のことは理解できていたはずだからです。それらが有機的に結びついたイメージを持てていなかっただけです。
つまり、完成されたコードを読む機会に恵まれていなかったということですね。
学習当初から完成されたコードを読んで勉強できれば手っ取り早いのですが、今の日本ではそういう環境が少ないのかもしれません。
ベーマガは偉大だったということでしょうか。

さて、ShotGauge, BombGauge, ArmGaugeクラスの共通項は何でしょうか。
まずは相対的な位置情報が考えられます。
左アイコンの左上x座標、左アイコンの左上y座標、文字の左上x座標、文字の左上y座標、メータの左上y座標、メータ1の左上x座標、メータ2の左上x座標、…、メータ7の左上x座標。
こんなところでしょうか。

他に共通項はあるでしょうか?
見たところ、左アイコンの枠は共通です。
アイコンの中身のグラフィックは枠と同一の大きさにすると、枠が上書きされて消えてしまうので、枠の大きさより小さくする必要があります。
そのため、アイコンの大きさは枠より小さくする必要があり、その大きさを揃えるとすれば、アイコンの左上x座標、アイコンの左上y座標も必要になります。
と考えてみたものの、なんだかエレガントな気がしません。アイコンデータに枠を含めてしまったほうが楽そうなのでそうしましょう。
他には、メータ1つの形、大きさは共通です。それのオン、オフ時の色で塗りつぶされた2パターンがあります。矩形塗りつぶしで実装してしまいましょう。

メソッドの共通項は思いつきますか?
以下の項目が考えられます。

アイコンのグラフィック番号を引数に渡して、その内容を表示させる処理(protected)。
ラベル番号を引数に渡してラベルを書き換える処理(protected)。
メータの目盛りを引数に渡して書き換える処理(protected)。
そしてその3つをまとめて、ゲージを画面に表示する処理(public)。

アイコンのグラフィックについては、その中身はShotGauge, BombGauge, ArmGaugeクラスが持つべき内容なので、ここではその受け皿のグラフィックハンドルをプロパティに定義だけして内容は入れずにおきます。

ラベルの文字はセンタリングされて表示されていますから、一見センタリング処理が必要そうに見えますが、等幅フォントなので、元データの左に適切な数のスペース文字を入れればよいだけです。
そして、本来はフォントに縦横8ピクセルの横長のフォントが必要なのですが、ひとまず普通のフォントで代用しておきます。
ラベルの内容もゲージごとに違うので、ここでは受け皿のポインタだけ用意します。

メータの段階については、+1ずつしか増えない仕様ではあるのですが、できればシルバーホークのクラスが持つべきパワーアップの段階情報をゲージクラスのプロパティに持たせたくないので、都度メータ7つ全書き換えで実装します。
もちろん、呼ばれるごとにメータを+1し、8つになったらメータをクリアしてゲージのグレードをアップさせる、という処理をゲージクラスのメソッドで書けなくもありません。
ただ、ゲージクラスのインスタンスを利用するシルバーホークの各オプションクラス(クラスShot, クラスBomb, クラスArm)でも、それと同等の処理がゲージを操作する時以外にもどうしても必要です。
当たり前ですが、メータの示す位置や武器グレード等の情報は、各オプションのクラスが所持して操作しないと、実際にショットやボムやアームを変更する処理などが行えません。
つまり、ゲージクラスで勝手にメータの示す値を所持したり、ゲージのグレードアップの処理などの自分自身の管理を行ってしまえば、各オプションのクラスで行わなければならない処理と同じことを二度行うことになってしまいます。
そのため、ゲージクラスではゲージを利用するための最低限のインタフェースを提供して、ゲージクラスをどのように利用するかの判断は、シルバーホークの各オプションのクラスに任せるべきです。

ところで、大規模な多人数で開発するようなアプリケーションや、不特定の人に利用されるようなクラスライブラリなどについては、利用する側のクラスとチェックがかぶっても構わないので、安全のために利用される側のメソッドの引数チェックも行うように設計するのが一般的です。
いわゆるカプセル化というオブジェクト指向の考え方の一部です。
しかし、個人で把握できる範囲のプログラムに厳密なカプセル化が必要かというとそうでもありません。マジックナンバーの定数化などにしてもそうですが、そういうノウハウが存在することを頭に入れておいて、必要になってから厳密に行えばよいのです。
特に、今回のようなシューティングゲームは、最終的な実行速度の余裕が大きな問題になってくるので、カプセル化をやりすぎて速度が出なくなるようでは本末転倒です(とはいっても最近のパソコンでは、その程度で速度の問題が出るとも思えませんが)。
利用する側のクラスで引数の値の範囲を超えないような処理を入れるなどの必要最低限の実装を行ったほうが、本来ならどこで何の処理を行う必要があるのかを把握することができるので、クラス設計に慣れてないうちはむしろそうしたほうが望ましいのではないかと個人的には思います。

さて、先に挙げた共通項をGaugeクラスとして実装していきます。
派生クラスのShotGauge, BombGauge, ArmGaugeでしか使われない項目は、protectedに指定します。
ここでは、C++で、DXライブラリを使用して実装した例を紹介します。

// ゲージクラスのプロトタイプ
class Gauge
{
protected:
  int xPosIcon = 0;       // アイコンx位置
  int yPosIcon = 0;       // アイコンy位置
  int iconGr[3];         // アイコングラフィックへのハンドル

  int xPosLabel = xPosIcon + 16; // ラベル(LASER等の文字)x位置
  int yPosLabel = yPosIcon;   // ラベルy位置
  unsigned int labelCr;     // ラベル色
  char* label[3];        // ラベル文字列

  int xPosMeter[7];       // メータのx位置
  int yPosMeter = yPosIcon + 8; // メータのy位置
  unsigned int meterOnCr;    // メーターON色
  unsigned int meterOffCr;    // メーターOFF色

  void drawIcon(int grade);    // アイコン描画
  void drawLabel(int grade);   // ラベル描画
  void drawMeter(int indication); // メータ描画

public:
  Gauge();            // コンストラクタ
  void draw(int grade, int indication); // ゲージを画面に表示する
};

// ゲージクラスのコンストラクタ
// プロトタイプ宣言で初期化できていないプロパティを初期化
Gauge::Gauge()
{
  for (int i = 0; i < 7; i++)
  {
    xPosMeter[i] = xPosIcon + 16 + i * 8; // メータの各x位置を取得
  }

  labelCr = GetColor(255, 255, 255);  // ラベル色を取得
  meterOnCr = GetColor(255, 255, 0);  // メータオン時の色を取得
  meterOffCr = GetColor(128, 128, 128); // メータオフ時の色を取得
}

// ゲージを画面に表示する
void Gauge::draw(int grade, int indication)
{
  setIcon(grade);
  setLabel(grade);
  setMeter(indication);
}

// アイコンを表示
// grade - アイコンのグレード 0:初期 1:上位 2:最上位
void Gauge::drawIcon(int grade)
{
  DrawGraph(xPosIcon, yPosIcon, iconGr[grade], FALSE);
}

// ラベルを表示
// grade - ラベルのグレード 0:初期 1:上位 2:最上位
void Gauge::drawLabel(int grade)
{
  // フォントサイズ約8ピクセル
  SetFontSize(8);

  // ラベル表示
  DrawString(xPosLabel, yPosLabel, label[grade], labelCr);
}

// メータを表示
// indication - メータの目盛り 0~6
void Gauge::drawMeter(int indication)
{
  int i;

  // 7*7ピクセルの矩形塗りつぶしを計7回行う.隙間は1ピクセル
  for (i = 0; i < indication; i++)
  {
    // 目盛りの分だけメータON(黄色)
    DrawBox(xPosMeter[i] + 1, yPosMeter,
      xPosMeter[i] + 8, yPosMeter + 7, meterOnCr, TRUE);
  }
  for (; i < 7; i++)
  {
    // 残りをメータOFF(灰色)
    DrawBox(xPosMeter[i] + 1, yPosMeter,
      xPosMeter[i] + 8, yPosMeter + 7, meterOffCr, TRUE);
  }
}

Amebaブログの仕様上、半角空白やタブは左詰めされてしまうので、全角空白が入っています。
コピペしてもそのままでは使えませんので悪しからず。
と思っていたら、Visual Studioではコピペすると勝手に全角空白を除去して整形してくれました。
そういえば15年以上前にも確かこんな機能あったよなぁとしみじみしてしまいました。
もっとも、この後にサンプルプログラムをダウンロードできる形で提供するので、わざわざコピペする必要はないでしょう。

さて、このクラスGaugeを継承させて、クラスShotGauge, BombGauge, ArmGaugeを実装していきます。
Gaugeクラスのプロパティやメソッドはこのクラスで使用するので、publicで継承します。
というか、大抵の場合publicで問題ないので、この段階では気にせずいきましょう。

Gaugeが持つべき情報ではなかったプロパティの中身(実際に表示される画面の位置、アイコンのグラフィック、ラベルの内容)を、差分としてコンストラクタで初期化するという感じになりました。
手続き型プログラミングならば、これらのプロパティの中身を引数にして関数を実行するのでしょうが、オブジェクト指向プログラミングでは、このように共通部分を書いた基底クラスを継承して、基底クラスとの差分という形で実装されるのです。
注:本来なら実際のゲージの表示座標はクラスProcoやクラスTiatを実装する段階で決定するので、ここに処理を書かないほうがよいです。詳細はサンプルプログラムのコードでご確認ください。

// ショットゲージクラスのプロトタイプ
class ShotGauge : public Gauge
{
public:
  ShotGauge(int id);      // コンストラクタ
};

// ボムゲージクラスのプロトタイプ
class BombGauge : public Gauge
{
public:
  BombGauge(int id);      // コンストラクタ
};

// アームゲージクラスのプロトタイプ
class ArmGauge : public Gauge
{
public:
  ArmGauge(int id);      // コンストラクタ
};

// ショットゲージクラスのコンストラクタ
// 基底クラスで初期化できていないプロパティを初期化
// id - ゲージを所有するシルバーホークの識別番号 0:Proco, 1:Tiat
ShotGauge::ShotGauge(int id)
{
  int xPosAbs;

  if (id == 0) xPosAbs = 48; // Procoのショットゲージの絶対座標xを決定
  else     xPosAbs = 568; // Tiatのショットゲージの絶対座標xを決定

  xPosIcon += xPosAbs;     // ショットゲージのアイコンのx座標を決定
  xPosLabel += xPosAbs;    // ショットゲージのラベルのx座標を決定

  for (int i = 0; i < 7; i++)
  {
    xPosMeter[i] += xPosAbs; // ショットゲージのメータのx座標を決定
  }

  // ショットゲージのアイコングラフィック読み込み
  iconGr[0] = LoadGraph("darius_shot0.png");
  iconGr[1] = LoadGraph("darius_shot1.png");
  iconGr[2] = LoadGraph("darius_shot2.png");

  // ショットゲージのラベル定義
  label[0] = "MISSILE";
  label[1] = " LASER ";
  label[2] = " WAVE ";
}

// ボムゲージクラスのコンストラクタ
// 基底クラスで初期化できていないプロパティを初期化
// id - ゲージを所有するシルバーホークの識別番号 0:Proco, 1:Tiat
BombGauge::BombGauge(int id)
{
  int xPosAbs;

  if (id == 0) xPosAbs = 136; // Procoのボムゲージの絶対座標xを決定
  else     xPosAbs = 656; // Tiatのボムゲージの絶対座標xを決定

  xPosIcon += xPosAbs;     // ボムゲージのアイコンのx座標を決定
  xPosLabel += xPosAbs;    // ボムゲージのラベルのx座標を決定

  for (int i = 0; i < 7; i++)
  {
    xPosMeter[i] += xPosAbs; // ボムゲージのメータのx座標を決定
  }

  // ボムゲージのアイコングラフィック読み込み
  iconGr[0] = LoadGraph("darius_bomb0.png");
  iconGr[1] = LoadGraph("darius_bomb1.png");
  iconGr[2] = LoadGraph("darius_bomb2.png");

  // ボムゲージのラベル定義
  label[0] = " BOMB ";
  label[1] = " TWIN ";
  label[2] = " MULTI ";
}

// アームゲージクラスのコンストラクタ
// 基底クラスで初期化できていないプロパティを初期化
// id - ゲージを所有するシルバーホークの識別番号 0:Proco, 1:Tiat
ArmGauge::ArmGauge(int id)
{
  int xPosAbs;

  if (id == 0) xPosAbs = 224; // Procoのアームゲージの絶対座標xを決定
  else     xPosAbs = 744; // Tiatのアームゲージの絶対座標xを決定

  xPosIcon += xPosAbs;     // アームゲージのアイコンのx座標を決定
  xPosLabel += xPosAbs;    // アームゲージのラベルのx座標を決定

  for (int i = 0; i < 7; i++)
  {
    xPosMeter[i] += xPosAbs; // アームゲージのメータのx座標を決定
  }

  // アームゲージのアイコングラフィック読み込み
  iconGr[0] = LoadGraph("darius_arm0.png");
  iconGr[1] = LoadGraph("darius_arm1.png");
  iconGr[2] = LoadGraph("darius_arm2.png");

  // アームゲージのラベル定義
  label[0] = " ARM ";
  label[1] = " SUPER ";
  label[2] = " HYPER ";
}

これでショットゲージ、ボムゲージ、アームゲージというシルバーホークの部品の設計図が完成しました。
オブジェクト指向プログラミング初心者は、まずこのようにして、末端の細かい部品の設計図をたくさん作っていくことから始めたほうがが楽でしょう。
次はその細かい部品を組み込んだオブジェクトを設計する段階になるでしょうか。
慣れてしまうと、打ち合わせもそこそこに部品ごとに分業したり、トップダウンでの書き下ろしなんていう芸当もできるようになるかもしれません。

また、部品ごとに単体テストを行うツールなんていうのもあります。
作り始めは、テストの定義を作成するのに時間を取られますが、最終的には生産性を向上させてくれるでしょう。
これも頭の片隅に置いておいてください。

さて、意図的に避けてきたdrawZoneNameですが、先の例にならえば、zone.drawNameのようなメソッドを実行するような形にすれば、うまくいきそうな気がしてきます。
本当にうまくいくかどうかは今の私のC++の技量では実装してみないとわかりませんが、おそらくZoneというクラスを継承させた、ZoneA, ZoneB, ZoneC…といったクラスのdrawNameメソッドを用意することになるのではないかと思います。

しかし、ここでは問題があります。
ZoneAクラスのインスタンスを生成していないタイトル画面の段階で、Aというゾーン名を表示してしまっています。
元のダライアスの仕様自体が構造的でない、と言ってしまえばそれでおしまいなのですが、完全移植を目指すなら何とかしないといけません。
今のところの自分のアイデアでは、継承させる前のZoneクラスなら中身がスカスカなので、ここのnameプロパティの値を"A"にして、ZoneクラスのインスタンスからdrawNameすれば、ZoneAの大きなインスタンスをこのためだけに生成しなくてもよさそうな気がします。
と思っていたのですが、昔の低速なハードウェアでは、処理能力に余裕のあるデモ画面中にゾーンAのデータを読み込んでおく処理などがあるのはある意味当然かな、なんて思いました。
そもそもデモ画面上のプレイでゾーンAなども登場しますしね。

このようにして、完成したソフトウェアの動作をイメージしながら、オブジェクトに何のプロパティやメソッドが必要かを類推していきます。
その内容をクラスとして記述していくわけです。



こちら←に上記の内容をテストする簡単なサンプルプログラムを用意しました。
このブログを書いた時点から、ゲージ周りはそれなりに実装内容が変わっているのでご了承ください。
サンプルプログラムが、そのままゲームのひな型のようなものになっていると思うので、ここから追加して実装していくのもよいかもしれません。
なお、画像は微妙に劣化させてあります。