サウンドの設計 (音情報管理設計)
前回にて音の発生タイミングと種類の概要を考えてみましたが、今回はもう少し具体的なレベル(プログラムでいうクラス化等)で検討してみたいと思います。
ここでポイントなのは、前回でも述べたようにサウンドの設計というのは、映像の設計に比べてハードウェア、OS、ライブラリ、言語により設計がかなり変化してしまうことが挙げられます。
そして本ブログの現時点では「Java」と「XNA(C#)」の標準ライブラリを用いての解説となるので、次回はこれらの実装について具体的に検討できればと思っています。
ちなみに私個人の印象ですが、設計の難易度は「Java > XNA」という考えがあり、難易度の高いもの程、設計が大変(面倒)になります。
しかし、ここではできるだけ抽象化したフレームワーク設計を行い、言語が変化してもできるだけ設計が変化しないようなものを目指して行ければと思います。
ということで、検討してみましょう。
【プリミティブな音情報】
前回でも説明しましたが、サウンドには効果音とBGMという物に分類が行え、且つこれらの属性によりデータフォーマットが違うケースが多いということを説明しました。
そこで以下のプリミティブな音情報をクラス化します。
1.音の名前を判別するID。
2.音の種別(SE, BGM)。
3.音のデータフォーマット(WAVE, MIDI, MP3等)。
4.音のファイルの存在する場所。
5.ループ回数。
6.音声発声開始位置。
7.音声発声終了位置。
このうち「3」は言語がサポートしている物に依存します。
次に「4」はファイル名そのままの場合もありますし、サウンドファイルを読み込んだ際に取得できるハンドルであったりします。これは言語のサウンドライブラリの仕様により改変すると良いと思います。今回の設計ではファイル名のフルパスを入れて置くという設計となります。
そして、「6」「7」に関しては、サウンドファイルが1つで全てを表す仕様のもの(例えはCDで1トラックに全ての曲が連続して収録されている場合等)や、MIDIファイルのようなトラック先頭には無音を入れる(べき)仕様(これはMIDIモジュールの初期化を行うためとされています)のファイルであるが、先頭の無音をスキップ(できる場合はしたい)したいケースを考慮しての設計となります。
単純なコーディング例(Java)
【プリミティブな音管理】
音の情報が定義できたので、次は音の管理です。管理といっても発声の実施が主となり、サウンドデバイスの管理や設計により音情報の管理を行います。
後者の音情報の管理というのは、「ゲーム上の音情報を全て内包し、タスクから呼び出しを容易にする」事を意味し、タスクから発声の際にワザワザ上述の7種類程の情報を渡さないで呼び出させる必要のないようにできる仕組みの事となります。
例えば音管理を経由して戦闘機から弾を発射するSEを発声させたい時に、以下のコーディングを行う場合(以下は例えです)、
// 現時点で発声させるための情報を渡す
SoundItem se = new SoundItem(
SoundItem.SE,
SoundItem.WAV,
"Sounds/ShipMissile.wav");
m_soundManager.play(se);
と記述よりも、
// ID 指定
m_soundManager.play(SoundID.ShipMissile);
の方が分り易いので、このように先に音管理の中でデータベースを持つという設計をする方法もあります。
しかしながら、ある程度柔軟に設計をしたいと考えるので、今回は上述の2つのインターフェースを持ったものを検討します。
音情報はどこかのタイミング(主にゲームの開始になるとは思います)で音管理に全て登録され、タスクからは音管理にリクエストを行えば良いだけになるように設計します。
今回の設計では、音情報はマップを用いて管理してみましょう。
単純なコーディング例(Java)
というような感じで、音情報と音管理を設計してみました。
ソースから見て判るとおり、音管理内の肝心な発声を行う個所が完全に抜けている状況です。
これ以降の設計は言語依存となってくるので、まずはJavaの方の実装を次回より検討して行こうかと思います。
ここでポイントなのは、前回でも述べたようにサウンドの設計というのは、映像の設計に比べてハードウェア、OS、ライブラリ、言語により設計がかなり変化してしまうことが挙げられます。
そして本ブログの現時点では「Java」と「XNA(C#)」の標準ライブラリを用いての解説となるので、次回はこれらの実装について具体的に検討できればと思っています。
ちなみに私個人の印象ですが、設計の難易度は「Java > XNA」という考えがあり、難易度の高いもの程、設計が大変(面倒)になります。
しかし、ここではできるだけ抽象化したフレームワーク設計を行い、言語が変化してもできるだけ設計が変化しないようなものを目指して行ければと思います。
ということで、検討してみましょう。
【プリミティブな音情報】
前回でも説明しましたが、サウンドには効果音とBGMという物に分類が行え、且つこれらの属性によりデータフォーマットが違うケースが多いということを説明しました。
そこで以下のプリミティブな音情報をクラス化します。
1.音の名前を判別するID。
2.音の種別(SE, BGM)。
3.音のデータフォーマット(WAVE, MIDI, MP3等)。
4.音のファイルの存在する場所。
5.ループ回数。
6.音声発声開始位置。
7.音声発声終了位置。
このうち「3」は言語がサポートしている物に依存します。
次に「4」はファイル名そのままの場合もありますし、サウンドファイルを読み込んだ際に取得できるハンドルであったりします。これは言語のサウンドライブラリの仕様により改変すると良いと思います。今回の設計ではファイル名のフルパスを入れて置くという設計となります。
そして、「6」「7」に関しては、サウンドファイルが1つで全てを表す仕様のもの(例えはCDで1トラックに全ての曲が連続して収録されている場合等)や、MIDIファイルのようなトラック先頭には無音を入れる(べき)仕様(これはMIDIモジュールの初期化を行うためとされています)のファイルであるが、先頭の無音をスキップ(できる場合はしたい)したいケースを考慮しての設計となります。
単純なコーディング例(Java)
// 音情報 public class SoundItem { // 音声種別 public enum Type { SE, BGM, } // 音声ファイル種別(これは状況により改変) public enum DataFormatType { WAV, MIDI, MP3, } // ID public int m_id; // 種別 (enum Type) public Type m_type; // データフォーマット種別 (enum DataFormatType) public DataFormatType m_dataFormatType; // ファイル public String m_filename; // ループ回数 (0で無限) public int m_loopCount = 0; // ループ再生回数 public int m_playCount = 0; // 再生開始シーク位置 public long m_startSeekPoint = 0; // 再生終了位置 (-1で最後まで) public long m_endSeekPoint = -1; } |
【プリミティブな音管理】
音の情報が定義できたので、次は音の管理です。管理といっても発声の実施が主となり、サウンドデバイスの管理や設計により音情報の管理を行います。
後者の音情報の管理というのは、「ゲーム上の音情報を全て内包し、タスクから呼び出しを容易にする」事を意味し、タスクから発声の際にワザワザ上述の7種類程の情報を渡さないで呼び出させる必要のないようにできる仕組みの事となります。
例えば音管理を経由して戦闘機から弾を発射するSEを発声させたい時に、以下のコーディングを行う場合(以下は例えです)、
// 現時点で発声させるための情報を渡す
SoundItem se = new SoundItem(
SoundItem.SE,
SoundItem.WAV,
"Sounds/ShipMissile.wav");
m_soundManager.play(se);
と記述よりも、
// ID 指定
m_soundManager.play(SoundID.ShipMissile);
の方が分り易いので、このように先に音管理の中でデータベースを持つという設計をする方法もあります。
しかしながら、ある程度柔軟に設計をしたいと考えるので、今回は上述の2つのインターフェースを持ったものを検討します。
音情報はどこかのタイミング(主にゲームの開始になるとは思います)で音管理に全て登録され、タスクからは音管理にリクエストを行えば良いだけになるように設計します。
今回の設計では、音情報はマップを用いて管理してみましょう。
単純なコーディング例(Java)
// 音管理 public class SoundManager extends HashMap<Integer, SoundItem> { //---- // 音の許可 //---- // システムとして public boolean m_enableSystem = true; // SE public boolean m_enableSE = true; // BGM public boolean m_enableBGM = true; // デバイス // SE デバイス //SoundDeviceSE m_deviceSE; // BGM デバイス //SoundDeviceBGM m_deviceBGM; // 初期化を行います public boolean initialize() { // デバイスの初期化を行います return true; } // 項目を追加します public void add( int id, SoundItem.Type type, SoundItem.DataFormatType dataFormatType, String filename) { add(id, type, dataFormatType, filename, 1, 0, -1); } // 項目を追加します public void add( int id, SoundItem.Type type, SoundItem.DataFormatType dataFormatType, String filename, int loopCount) { add(id, type, dataFormatType, filename, loopCount, 0, -1); } // 項目を追加します public void add( int id, SoundItem.Type type, SoundItem.DataFormatType dataFormatType, String filename, int loopCount, long seekStartPoint, long seekEndPoint) { // システムとして音が許可されていない場合は抜けます if(m_enableSystem == false) { return; } // 既に登録されいているアイテムを削除します remove(id); // アイテム情報を設定します SoundItem item = new SoundItem(); item.m_id = id; item.m_type = type; item.m_dataFormatType = dataFormatType; item.m_filename = filename; item.m_loopCount = loopCount; item.m_startSeekPoint = seekStartPoint; item.m_endSeekPoint = seekEndPoint; // アイテム登録 put(id, item); return; } // 音を再生します public void play(int id) { SoundItem item = get(id); if(item == null) { return; } play( item.m_id, item.m_type, item.m_dataFormatType, item.m_filename, item.m_loopCount, item.m_startSeekPoint, item.m_endSeekPoint); } // 音を再生します public void play(int id, int loopCount) { SoundItem item = get(id); if(item == null) { return; } play( item.m_id, item.m_type, item.m_dataFormatType, item.m_filename, loopCount, item.m_startSeekPoint, item.m_endSeekPoint); } // 音を再生します public void play( int id, SoundItem.Type type, SoundItem.DataFormatType dataFormatType, String filename, int loopCount, long seekStartPoint, long seekEndPoint) { // システムとして音が許可されていない場合は抜けます if(m_enableSystem == false) { return; } // 種別毎にサウンドデバイスにリクエストします if(type == SoundItem.Type.SE) { if(m_enableSE == true) { //m_deviceSE.pause(item.id, ...); } } else { if(m_enableBGM == true) { //m_deviceBGM.pause(item.id, ...); } } } // 音を一時停止します public void pause(int id) { SoundItem item = get(id); if(item == null) { return; } pause(item.m_id, item.m_type, item.m_dataFormatType, item.m_filename); } // 音を一時停止します public void pause( int id, SoundItem.Type type, SoundItem.DataFormatType dataFormatType, String filename) { // 種別毎にサウンドデバイスにリクエストします if(type == SoundItem.Type.SE) { //m_deviceSE.pause(item.id, ...); } else { //m_deviceBGM.pause(item.id, ...); } } // 音を停止します public void stop(int id) { SoundItem item = get(id); if(item == null) { return; } stop(item.m_id, item.m_type, item.m_dataFormatType, item.m_filename); } // 音を停止します public void stop( int id, SoundItem.Type type, SoundItem.DataFormatType dataFormatType, String filename) { // 種別毎にサウンドデバイスにリクエストします if(type == SoundItem.Type.SE) { //m_deviceSE.stop(item.id, ...); } else { //m_deviceBGM.stop(item.id, ...); } } // 音を全て一時停止します public void stopPause() { // 登録されているアイテムを全て走査 Iterator it = keySet().iterator(); while(it.hasNext() == true) { SoundItem item = get(it.next()); // 種別毎にサウンドデバイスにリクエストします if(item.m_type == SoundItem.Type.SE) { //m_deviceSE.pause(item.id, ...); } else { //m_deviceBGM.pause(item.id, ...); } } // もしくは一気に...(これはライブラリによります) //m_deviceSE.pause(); //m_deviceBGM.pause(); } // 音を全て停止します public void stopAll() { // 登録されているアイテムを全て走査 Iterator it = keySet().iterator(); while(it.hasNext() == true) { SoundItem item = get(it.next()); // 種別毎にサウンドデバイスにリクエストします if(item.m_type == SoundItem.Type.SE) { //m_deviceSE.stop(item.id, ...); } else { //m_deviceBGM.stop(item.id, ...); } } // もしくは一気に...(これはライブラリによります) //m_deviceSE.stop(); //m_deviceBGM.stop(); } } |
というような感じで、音情報と音管理を設計してみました。
ソースから見て判るとおり、音管理内の肝心な発声を行う個所が完全に抜けている状況です。
これ以降の設計は言語依存となってくるので、まずはJavaの方の実装を次回より検討して行こうかと思います。
サウンドの設計 (音の発生タイミングと種類)
今回は久々に「共通」カテゴリの設計に入ります。
フレームワーク上のサウンド処理というのは1/4で検討したようにタスク処理、もしくは描画処理等の全ての処理が完了した後に発声させる設計を行います。
この場合、前者はタスク処理とともにタスクの処理時にその都度発声、後者はタスク処理内で発声を依頼(キューイング)しておいて、全ての処理が完了した際に一括発声(これはハードウェアの制限によるミキシング機構等を自前で行う場合は特に)というように設計できます。
幸い今日では(Windows上の)JavaやXNAのような高級な言語にて高級なサウンドライブラリを実装したものでは後者のような制限は仕様上特に謳われていないので、多少柔軟に設計ができます。
【タスクの処理時にその都度発声】

【全ての処理が完了した際に一括発声】

次に「ゲーム上のサウンド」というとこれには内訳ができ、分類すると効果音(SE{Sound Effect})と、BGM(Back Ground Music)の2つに分類できます。
これらの特徴は、
【効果音】
・ゲーム上のキャラクターが発する音声。
・主に発声時間は短い。
・発声は基本的に繰り返さない(場合により多少繰り返すような
表現が必要な場合はある)。
【BGM】
・ゲームの背景音楽として流れる音声。
・主に発声時間は長い。
・曲を繰り返す(場合により長編楽曲により繰り返す
必要の無い物もある)。
となり、この特徴によりデータの持ち方も検討するケースがあります。
現在ではハードウェアの制限が皆無となってきているため、サウンドフォーマットやデータ形式の敷居がなくなってきてはいますが、昔からの考えではおおよそ以下のような事が考えられていました。
【効果音】
・WAVファイル(PCM音源)のような生音がメイン。
1サンプリングのデータサイズが大きくても、再生時間が短いもの。
【BGM】
・MIDIや内蔵音源命令等の比較的再生時間が長くてもファイルサイズが
大きくならないもの。
というのがセオリーでした。
これにより、情報の持ち方や実現方式が違ってくる(場合がある)ので、次回はこのデータの持ち方の設計から検討して行きたいと思います。
しかし、近年ではメディアの大容量化に伴い、どちらもPCM(に近い生音)のフォーマットで実現する方式が非常に多く見受けられ、SE,BGMのデータフォーマットは共通というケースが多くなってきているので、時代の流れを感じます。
(これは一般にPCでリリースされるゲームや、現時点で次世代機と呼ばれるコンシューマー向けのゲーム機が対象となります)
フレームワーク上のサウンド処理というのは1/4で検討したようにタスク処理、もしくは描画処理等の全ての処理が完了した後に発声させる設計を行います。
この場合、前者はタスク処理とともにタスクの処理時にその都度発声、後者はタスク処理内で発声を依頼(キューイング)しておいて、全ての処理が完了した際に一括発声(これはハードウェアの制限によるミキシング機構等を自前で行う場合は特に)というように設計できます。
幸い今日では(Windows上の)JavaやXNAのような高級な言語にて高級なサウンドライブラリを実装したものでは後者のような制限は仕様上特に謳われていないので、多少柔軟に設計ができます。
【タスクの処理時にその都度発声】

【全ての処理が完了した際に一括発声】

次に「ゲーム上のサウンド」というとこれには内訳ができ、分類すると効果音(SE{Sound Effect})と、BGM(Back Ground Music)の2つに分類できます。
これらの特徴は、
【効果音】
・ゲーム上のキャラクターが発する音声。
・主に発声時間は短い。
・発声は基本的に繰り返さない(場合により多少繰り返すような
表現が必要な場合はある)。
【BGM】
・ゲームの背景音楽として流れる音声。
・主に発声時間は長い。
・曲を繰り返す(場合により長編楽曲により繰り返す
必要の無い物もある)。
となり、この特徴によりデータの持ち方も検討するケースがあります。
現在ではハードウェアの制限が皆無となってきているため、サウンドフォーマットやデータ形式の敷居がなくなってきてはいますが、昔からの考えではおおよそ以下のような事が考えられていました。
【効果音】
・WAVファイル(PCM音源)のような生音がメイン。
1サンプリングのデータサイズが大きくても、再生時間が短いもの。
【BGM】
・MIDIや内蔵音源命令等の比較的再生時間が長くてもファイルサイズが
大きくならないもの。
というのがセオリーでした。
これにより、情報の持ち方や実現方式が違ってくる(場合がある)ので、次回はこのデータの持ち方の設計から検討して行きたいと思います。
しかし、近年ではメディアの大容量化に伴い、どちらもPCM(に近い生音)のフォーマットで実現する方式が非常に多く見受けられ、SE,BGMのデータフォーマットは共通というケースが多くなってきているので、時代の流れを感じます。
(これは一般にPCでリリースされるゲームや、現時点で次世代機と呼ばれるコンシューマー向けのゲーム機が対象となります)
描画の設計 (基礎設計)
描画の設計はハードウェア、OS、プログラム言語により大きく異なるもので、更に2D、3Dゲームによりアプローチが変ります。
今回からしばらくの間はこれらを抽象化したレベルの設計(つまり共通で検討できる内容)を説明し、その後に別カテゴリにて言語別に説明ができれば良いなと思っています。
【流れ】
本カテゴリー
「ゲームプログラミング [共通] 」
にて概要設計を説明。
↓
別カテゴリー
2D→「ゲームプログラミング [Java] 」
3D→「ゲームプログラミング [XNA] 」
にて具体的な設計を説明。
という感じでしょうか。
◇ ◇ ◇
さて、抽象化レベルでの描画の設計というで、どのように検討すべきかを考えると、まず描画というのは、
・なにかしらのイメージを画面に表示する。
・イメージを表示する場所は演算の結果(タスク処理の結果)を元に表示する。
・内容によっては表示しなくても良いものがある。
・描画するものはできるだけ一括で描画した方が理解しやすいし効率が良い。
という事柄があると考えられます。
この事から2008/01/04にてイメージで表わしたように、

タスク(集合)処理の後に描画処理を行うのがと良いと考えます。
そして前回「キャラクタータスクに描画属性を持たせた設計」にて説明したとおり、描画属性を持つキャラクタークラスから派生したクラス(タスク)のみを描画対象とし、そのクラスが持つ描画情報を描画するという設計を行えば良いと考えます。
次に一括で描画する仕様を考案するのですが、単純にタスク集合の配列を走査してキャラクタークラスより派生したタスクを順番に描画するという考え方もあるのですが、これだとタスクが配列に登録された順番となり、優先順位を持つことができません。

※これは特に2Dのゲーム描画の際に如実に発生します。
3Dの場合はZソート、Zバッファの考え方があるので、このイメージとは考え方が異ります。
これをタスク集合の配列の登録順序を変更して実現を行うという仕組みを検討する方法もあるのですが、できるだけタスク処理と描画処理とは関係を切り離したいことから、描画専用の配列を用意すると関係を切り離すことができます。
この配列を実現した場合、配列への登録タイミングと抹消タイミングは、タスクの登録、抹消タイミングと同じで構わなく、タスクの実行中などに任意に順序を切り替えることができる仕組みを導入します。
これをイメージすると以下のような設計になります。

タスクの登録はJavaやC#の言語にある「関数のオーバーロード」として作成すると、シームレスに登録が実装できます。
この例では、タスク管理クラスのaddメソッドにより、キャラクタータスククラスより派生していないタスククラスは「タスクの登録」のメソッドが呼び出され、キャラクタータスククラスより派生しているタスククラスは「キャラクタータスクの登録」のメソッドが呼び出されます。
後者の場合は、タスク管理クラスの登録と同時に描画管理の配列"DrawManager"に登録されます。
このDrawManagerは描画処理時に参照され、描画対象となるタスクを描画します。
もしも描画の順序を変更したい場合はこの配列の中に登録されているタスク情報の順番を変更すれば実現が可能となります。
次回は2Dの描画処理設計をJava言語を基に説明したいと思います。
今回からしばらくの間はこれらを抽象化したレベルの設計(つまり共通で検討できる内容)を説明し、その後に別カテゴリにて言語別に説明ができれば良いなと思っています。
【流れ】
本カテゴリー
「ゲームプログラミング [共通] 」
にて概要設計を説明。
↓
別カテゴリー
2D→「ゲームプログラミング [Java] 」
3D→「ゲームプログラミング [XNA] 」
にて具体的な設計を説明。
という感じでしょうか。
◇ ◇ ◇
さて、抽象化レベルでの描画の設計というで、どのように検討すべきかを考えると、まず描画というのは、
・なにかしらのイメージを画面に表示する。
・イメージを表示する場所は演算の結果(タスク処理の結果)を元に表示する。
・内容によっては表示しなくても良いものがある。
・描画するものはできるだけ一括で描画した方が理解しやすいし効率が良い。
という事柄があると考えられます。
この事から2008/01/04にてイメージで表わしたように、

タスク(集合)処理の後に描画処理を行うのがと良いと考えます。
そして前回「キャラクタータスクに描画属性を持たせた設計」にて説明したとおり、描画属性を持つキャラクタークラスから派生したクラス(タスク)のみを描画対象とし、そのクラスが持つ描画情報を描画するという設計を行えば良いと考えます。
次に一括で描画する仕様を考案するのですが、単純にタスク集合の配列を走査してキャラクタークラスより派生したタスクを順番に描画するという考え方もあるのですが、これだとタスクが配列に登録された順番となり、優先順位を持つことができません。

※これは特に2Dのゲーム描画の際に如実に発生します。
3Dの場合はZソート、Zバッファの考え方があるので、このイメージとは考え方が異ります。
これをタスク集合の配列の登録順序を変更して実現を行うという仕組みを検討する方法もあるのですが、できるだけタスク処理と描画処理とは関係を切り離したいことから、描画専用の配列を用意すると関係を切り離すことができます。
この配列を実現した場合、配列への登録タイミングと抹消タイミングは、タスクの登録、抹消タイミングと同じで構わなく、タスクの実行中などに任意に順序を切り替えることができる仕組みを導入します。
これをイメージすると以下のような設計になります。

タスクの登録はJavaやC#の言語にある「関数のオーバーロード」として作成すると、シームレスに登録が実装できます。
単純なコーディング例(Java)
// タスク管理 // 描画管理 // タスクの登録 // キャラクタータスクの登録 } |
この例では、タスク管理クラスのaddメソッドにより、キャラクタータスククラスより派生していないタスククラスは「タスクの登録」のメソッドが呼び出され、キャラクタータスククラスより派生しているタスククラスは「キャラクタータスクの登録」のメソッドが呼び出されます。
後者の場合は、タスク管理クラスの登録と同時に描画管理の配列"DrawManager"に登録されます。
このDrawManagerは描画処理時に参照され、描画対象となるタスクを描画します。
もしも描画の順序を変更したい場合はこの配列の中に登録されているタスク情報の順番を変更すれば実現が可能となります。
次回は2Dの描画処理設計をJava言語を基に説明したいと思います。
キャラクタータスクに描画属性を持たせた設計
前回のタスクの派生設計の説明に、「キャラクター」クラスというものがありましたが、これは画面で動きまわる「登場人物(キャラクター)の基底クラス」として考えることができます。
登場人物は画面で描画されて初めて意味があるものとなり、これが実現されると可視的に「登場」することができます。
この考えを設計に用いると、キャラクタークラスから派生されたクラスは「描画されるべき対象となるもの」という設計にすることができます。
そして、このクラスから派生した、"自機"や"敵"はその性質を受け継ぐことになるので、「画面に描画される(登場する)」ことになります。
一方「ステージ管理」クラスのような、画面に表示されなくても良い「裏方」の場合は、キャラクターから派生せずに設計することで描画が行われることが無いものと考えることができます。
(舞台などで考える「裏方」のようなものです)
このように、まずは画面に表示される/されないという属性を派生により実現し、表示する(描画される)側は、これら描画に必要な情報を持つといったことを設計します。
次に描画される側として何の情報を持って描画すべきかという設計になるのですが、これはゲームの内容や表現方法により決定され、おおよそ以下のようなものに分かれると思います。
【2Dゲームの開発の場合の情報】
・ビットマップ(これは1枚~n枚)
・アニメーション情報(現在何枚目を示しているか等)
【3Dゲームの開発の場合の情報】
・モデルデータ(これは単純な物の場合は1つ)
・アニメーション(モーション)情報
【共通で必要であろう情報】
・描画する大きさ(どの程度の大きさで描画するか)
・中心座標(このキャラクターが存在する位置)
・ヒットする領域(描画している表現とは別に、どの程度の大きさで当たるか)
・表示の可否(表示したくない場合などに使用)
これらの情報がキャラクタークラスに存在し、派生したクラスはもれなくこの情報が継承されることになります。
そして派生したクラスはこれら情報をリアルタイムに演算し、座標の位置などを変化させて、まるで生きているように動かします。
これらの情報を描画するのは、今後説明する描画処理にて行われ、キャラクタークラスから派生したタスクは、目に映らない中の情報を変化させることのみを行います。
(タスク処理と描画処理との分業となります)

登場人物は画面で描画されて初めて意味があるものとなり、これが実現されると可視的に「登場」することができます。
この考えを設計に用いると、キャラクタークラスから派生されたクラスは「描画されるべき対象となるもの」という設計にすることができます。
そして、このクラスから派生した、"自機"や"敵"はその性質を受け継ぐことになるので、「画面に描画される(登場する)」ことになります。
一方「ステージ管理」クラスのような、画面に表示されなくても良い「裏方」の場合は、キャラクターから派生せずに設計することで描画が行われることが無いものと考えることができます。
(舞台などで考える「裏方」のようなものです)
このように、まずは画面に表示される/されないという属性を派生により実現し、表示する(描画される)側は、これら描画に必要な情報を持つといったことを設計します。
次に描画される側として何の情報を持って描画すべきかという設計になるのですが、これはゲームの内容や表現方法により決定され、おおよそ以下のようなものに分かれると思います。
【2Dゲームの開発の場合の情報】
・ビットマップ(これは1枚~n枚)
・アニメーション情報(現在何枚目を示しているか等)
【3Dゲームの開発の場合の情報】
・モデルデータ(これは単純な物の場合は1つ)
・アニメーション(モーション)情報
【共通で必要であろう情報】
・描画する大きさ(どの程度の大きさで描画するか)
・中心座標(このキャラクターが存在する位置)
・ヒットする領域(描画している表現とは別に、どの程度の大きさで当たるか)
・表示の可否(表示したくない場合などに使用)
これらの情報がキャラクタークラスに存在し、派生したクラスはもれなくこの情報が継承されることになります。
そして派生したクラスはこれら情報をリアルタイムに演算し、座標の位置などを変化させて、まるで生きているように動かします。
これらの情報を描画するのは、今後説明する描画処理にて行われ、キャラクタークラスから派生したタスクは、目に映らない中の情報を変化させることのみを行います。
(タスク処理と描画処理との分業となります)

タスクの派生設計
C++やJava、C#等のクラスの継承という概念を持つプログラミング言語にてゲームを開発する場合、この継承を用いてタスク設計を行うことができます。
この継承を用いると、後にゲーム開発を行う際、非常にシンプルに、しかも処理の流用が行えるようになります。
例をインベーダーのようなシューティングゲームとして、自機、自機の弾、敵、敵の弾、爆発、ステージ管理といった、幾つかのタスクを考えた場合の派生設計をしてみましょう。
ここで前提として、自機・敵の弾は真っ直ぐ飛ぶものとし、ステージ管理というのは敵が全滅したらステージクリアというようなものとします。
派生図で表すと以下のようになります。

ここで、上述のタスククラス以外に「キャラクター」「弾」というタスククラスがあるかと思いますが、これらは派生先のクラスが共通して使用できる処理を実現するための設計となります。
このように共通となりうるもの、しかしそのクラスが持っている基本的な質(事象)が変化しないものは、基底クラスとして設計しておくことにより、将来新しいクラスを追加する際に簡単な派生をするだけで、追加のクラスを設計することが容易となります。
例を考えるとすると、「敵クラス」に将来、「ザコ」や「ボス」といったタスク(クラス)を追加したいと考えた時、当然動作などが変化する訳ですが、「強さは違えど敵は敵」ということより、敵タスクから「ザコ」「ボス」を派生させて作成し、これらの動作をそれぞれ派生先で実現すれば、良いだけのものとなります。
また、上の図では「敵の弾クラス」がまっすぐ移動する「弾クラス」から派生していますが、これを将来「自機に向って飛ぶ」ように改造したい場合は、「弾クラス」とは別に「指定の相手に飛ぶ弾クラス」を作成し、このクラスから派生しなおすようにすれば、敵の弾はまっすぐから、「指定の相手に飛ぶ弾(指定の相手は自機)」に変化します。
このようにクラスの継承を活用して設計を行うと、ゲームのシステムを様々な形でカスタマイズしやすくなり、また流用を多岐に行えるために、プログラムのボリュームを抑えてプログラムを作成することができます。
この継承を用いると、後にゲーム開発を行う際、非常にシンプルに、しかも処理の流用が行えるようになります。
例をインベーダーのようなシューティングゲームとして、自機、自機の弾、敵、敵の弾、爆発、ステージ管理といった、幾つかのタスクを考えた場合の派生設計をしてみましょう。
ここで前提として、自機・敵の弾は真っ直ぐ飛ぶものとし、ステージ管理というのは敵が全滅したらステージクリアというようなものとします。
派生図で表すと以下のようになります。

ここで、上述のタスククラス以外に「キャラクター」「弾」というタスククラスがあるかと思いますが、これらは派生先のクラスが共通して使用できる処理を実現するための設計となります。
このように共通となりうるもの、しかしそのクラスが持っている基本的な質(事象)が変化しないものは、基底クラスとして設計しておくことにより、将来新しいクラスを追加する際に簡単な派生をするだけで、追加のクラスを設計することが容易となります。
例を考えるとすると、「敵クラス」に将来、「ザコ」や「ボス」といったタスク(クラス)を追加したいと考えた時、当然動作などが変化する訳ですが、「強さは違えど敵は敵」ということより、敵タスクから「ザコ」「ボス」を派生させて作成し、これらの動作をそれぞれ派生先で実現すれば、良いだけのものとなります。
また、上の図では「敵の弾クラス」がまっすぐ移動する「弾クラス」から派生していますが、これを将来「自機に向って飛ぶ」ように改造したい場合は、「弾クラス」とは別に「指定の相手に飛ぶ弾クラス」を作成し、このクラスから派生しなおすようにすれば、敵の弾はまっすぐから、「指定の相手に飛ぶ弾(指定の相手は自機)」に変化します。
このようにクラスの継承を活用して設計を行うと、ゲームのシステムを様々な形でカスタマイズしやすくなり、また流用を多岐に行えるために、プログラムのボリュームを抑えてプログラムを作成することができます。