C++のゲームプログラムの練習用に作った自家製のフレームワークです。
Perlのスローガンに感銘を受け、それに倣うべくOPALと名付けました。
「簡単な事は簡単に、難しい事は可能に」
開発開始は2006-7年頃(あいまい)、現在の形になったのが2011年あたりです。
概要
基本的にテキストエディタとコマンドラインだけの簡素な開発環境です。
| 項目 | 内容 |
|---|---|
| OS | Windows7以降 |
| 言語 | C++14 |
| ベースシステム | DirectX 9 |
| コンパイラ | MinGW-w64、VisualStudio2015 |
| 文字コード | Shift-jis |
| スタイル | オブジェクト指向 |
| スレッド | 基本シングル |
| 座標系 | 左手系 |
使い方
下記は実行可能な最も短いコードとなります。
# include <Opal.hpp>
int main(){
return Opal();
}
実行すると3つのウインドウが起動します。
ゲーム画面ウインドウ

ウインドウのタイトルや大きさ、背景の色等は変更できます。
デバッグ用のウインドウその1

ライブラリで用意しているデバッグ関数printdの出力がここに表示されます。
デバッグ用ウインドウその2

printfなどの標準出力への出力はここに表示されます。
実際のゲームのプログラムは関数にして引数で渡します。
# include <Opal.hpp>
void game_init(void){} // 初期化
void game_exec(void){} // メインループ
void game_free(void){} // 後始末
int main(){
return Opal( game_init, game_exec, game_free );
}
これはおおよそ下記と同じように呼び出されます。
main(){
game_init();
while ( 1 ){
game_exec();
}
game_free();
}
構成
このライブラリの名前空間はopalです。moduleXというのはクラスの名前になります。
プログラムファイルはおよそ下記の命名に従います。
| ファイル種類 | ファイル名 |
|---|---|
| ソース | directory/module\w+.cpp |
| ヘッダ | Inc/directory/module\w+.hpp |
Lib/
このライブラリが使うライブラリです。
| モジュール | スコープ | 内容 |
|---|---|---|
| Std/* | opal | ゲームに限らずよく使うような機能 |
| Ext/* | opal | ゲームの計算などでよく使うような機能 |
| Usr/* | (N/A) | ライブラリ利用者が追加する場所 |
| Game/* | (N/A) | ゲームの演出などでよく使うような機能 |
Sys/
このライブラリのコアのシステムです。
| モジュール | スコープ | 内容 |
|---|---|---|
| Opal | OPAL | アプリの概要の設定 |
| Time | opal::TIME | 時間に関する機能 |
| Rand | opal | 乱数に関する機能 |
| Window | opal::WINDOWX | ウインドウに関する機能 |
| Widget | opal::WIDGETX | GUIパーツに関する機能(未実装) |
| System | opal::SYSTEMX | ライブラリのメインループ本体 |
| game | opal::GAMEX | DLLゲームモジュールの実行 |
| heap | opal::HEAPX | ヒープメモリの管理 |
| new | (N/A) | new/deleteのオーバーロード |
| memory | opal::MEMORYX | メモリ管理に関する機能 |
| sig | opal::SIGX | システムイベントに関する機能 |
| pad | opal::PADX | 入力に関する機能 |
| thrd | opal::THRDX | マルチスレッドに関する機能 |
| file | opal::FILEX | ファイルに関する機能 |
| netw | opal::NETWX | ネットワークに関する機能(未完成) |
| srvc | opal::SRVCX | ユーザー定義サービスの実行 |
| rsrc | opal::RSRCX | オブジェクトのリソース管理 |
| prim | opal::PRIMX | オブジェクトのプリミティブ管理 |
| obj | opal::OBJX | タスクシステムの汎用オブジェクト |
| super | opal::SUPERX | ゲーム用タスクオブジェクトの共通基底クラス |
| work | opal::WORKX | ゲーム用タスクオブジェクト(計算用)クラス |
| draw | opal::DRAWX | ゲーム用タスクオブジェクト(グラフィック用)クラス |
| call | opal::CALLX | ゲーム用タスクオブジェクト(サウンド用)クラス |
| taskl | (N/A) | ゲーム用タスクオブジェクトのリンクリストの明示的インスタンス化 |
| debug | opal::DEBUGX | デバッグに関する機能 |
| Obj/* | (N/A) | トランスフォームサブクラス |
| Draw/* | (N/A) | Camera,Light,Fogの各サブクラス |
| ビルド時オプションのOPAL_HEAPを削除(未定義)にすると、new/deleteのオーバロードは無効になります。 | ||
| ビルド時オプションのOPAL_DEBUGを削除(未定義)にすると、デバッグに関する機能は無効になります。 |
Usr/
ここはライブラリを利用した派生プログラムの位置づけのため、ライブラリの名前空間にはいれていません。ライブラリ利用者が作成したモジュールを追加する場所となります。
| モジュール | スコープ | 内容 |
|---|---|---|
| Draw/* | (N/A) | 四角形、線分、文字など |
| Call/* | (N/A) | WAVE、MIDI(未完成)など |
| このライブラリはコアシステムは何も表示(鳴らす)することはできません。ライブラリ利用者がゲームで使うオブジェクトを実装する必要があります。とはいっても四角形とかよく使うような基本的なものはとりあえずいくつかいれてあります。 |
デバッグ
int printd( const char* format, ... );
int printf( const char* format, ... );
printdはデバッグ用ウインドウその1に文字列を表示します。デバッグ用ウインドウその1はメインループが更新されると表示がクリアされるので常に呼び出しをする必要があります。これは頻繁に値が更新される変数を監視するような場合に使用します。
printfはデバッグ用ウインドウその2ゲームに文字列を表示します。デバッグ用ウインドウその2は標準出力で出力された表示は消えずにスクロールアウトしていきます。初期化や後始末など1回だけ通過するような場所で変数の変更状態を表示するような場合に使用します。ちなみにstd::coutも使えます。
デバッグ用にいくつかのキーがデフォルトで予約されています。
| キー | 機能 |
|---|---|
| 1~0,F1~F12 | システム情報を表示 |
| テンキー | システム情報の詳細表示用のサブキー |
| ESC | フルスクリーン⇔ウインドウモードの切替 |
| Insert | スクリーンショット |
| Delete | ポーズのON/OFF |
| End | リセット/乱数シードは保存 |
| End+LShift | リセット/乱数シードは初期化 |
| Home | ウインドウの位置と大きさを初期化 |
| PageUp/PageDown | システム情報をスクロール |
| BackSpace | 実行のコマ送り(Deleteで解除) |
| Enter | 実行を2倍の速度にする |
| Enter+RShift | 実行を半分の速度にする |
| メモリーリークがあるとリセット時にデバッグ用ウインドウその2に警告が表示されます。 |
タスクシステム
このライブラリはいわゆるゲームプログラムのwhileループを握っています。そしてそのループはいわゆるタスクシステムで管理されています。ライブラリのコアシステムであるFILEXやTHRDXはこのタスクシステムを利用しています。タスクが処理の状態を保持・監視できるので、通常のライブラリ単体では実装が難しい非同期読み込みやコルーチンも実装できました。
| クラス | 概要 |
|---|---|
| TASK | タスク本体を表すクラス、双方向リンクリストを作る |
| LINX | タスクリストのスタート位置を表す |
タスクリストは循環リストのため、LINXがスタート位置となるTASKを保持しています。
LINX⇒TASK1⇔TASK2⇔TASK3⇔TASK4⇔...⇔TASK1(最初に戻る)
TASKクラスはLINXクラスのポインタをメンバ変数に持っています。そのポインタがnullでない場合、深さ優先でそのLINXの探索を開始します。そしてその先のTASKでまたLINXのポインタがあれば、さらに深く探索は分岐していきます。TASKに対する特定の操作はそのLINX配下のTASKに伝播します。例えば描画用TASKの表示をOFFにすれば、そのLINX配下のTASKはすべて表示OFFとなります。
LINXクラスには分岐に入る前と分岐から出た後に呼ばれる特別なコールバックが用意されています。このコールバックを使うとそのLINXに登録されているTASKだけに影響する操作を入れることができます。例えば、レンダーターゲットの切り替え/復帰などを簡単に実装できます。
// タスクリストを探索・実行する関数
static const std::function<void(LINX* const)> func = [&]( LINX* const linx ){
linx->LinxFuncBegin(); // 分岐前コールバック
ITER it( linx ); // タスクリストのイテレータ
for ( it.Begin(); it.End(); it.Next() ) {
if ( const auto tp = *it ) {
tp->TaskFunc(); // タスクの更新関数
if ( const auto bl = tp->Branch() ) { // 分岐(LINXクラスのポインタ)があるか
func( bl ); // 分岐探索(再帰)
}
}
}
linx->LinxFuncEnd(); // 分岐後コールバック
};
オブジェクト
このライブラリではゲームに登場するモノや処理をオブジェクトと呼んでいます。オブジェクトはTASK型から派生してタスクシステムで管理しています。このライブラリではゲームの特性を鑑みて、オブジェクトを以下のように派生・細分化しています。
TASK>OBJ>>>SUPER>{WORK,DRAW,CALL}
| クラス | 概要 |
|---|---|
| OBJ | ゲーム世界の外の計算を行う(システムに近い処理) |
| SUPER | WORK/DRAW/CALLの基底の仮想クラス |
| WORK | ゲーム世界の中の計算を行う(AIとスコア計算とか) |
| DRAW | 描画されるモノ |
| CALL | 音を鳴らすモノ |
OBJは純粋に何らかの処理を行うだけなので、ゲーム世界に必須といえる座標情報を持っていません。OBJからSUPERまでの間に以下のような座標情報に関するクラスがあります。
OBJ>(OOBJ>SOBJ>VOBJ>XOBJ)>SUPER
| クラス | 概要 |
|---|---|
| OOBJ | 座標変換のためのマトリックス |
| SOBJ | ローカル座標の位置、回転、拡縮のベクトル |
| VOBJ | 向きを表すローカル軸の単位ベクトル |
| XOBJ | ゲーム特有の何かをさせる場合のための予備 |
これらの●OBJはシステム内部で使用しており、ゲーム内で直接扱うことはありません。
プリミティブ
オブジェクトは抽象的なモノでしたが、プリミティブはより具体的なモノを表します。例えば四角形とか文字とかです。四角形クラス(RECT型)は以下のようにDRAW型から派生しています。
DRAW>DRAW2>RECT2
DRAW>DRAW3>RECT3
DRAW2とDRAW3は2D用/3D用に区別するためのクラスです。
# include "Usr/Draw/prim/rect2.hpp"
static RECT2 rect2; // RECT2型プリミティブのインスタンス
rect2.Open(); // インスタンスの初期化・タスクシステムに登録
rect2.Close(); // タスクシステムから削除、インスタンスの後始末
ステート
プリミティブを構成する要素のうち、座標や時間などの状態に関する情報をステートと呼ぶことにしています。座標についてはOBJ型から継承されて組み込まれています。この項目はまだ整理の途中です。
アトリビュート
プリミティブを構成する要素のうち、カラーやマテリアルなどの性質に関する情報をアトリビュートと呼ぶことにしています。必須かというとやや微妙な情報も多いかなと思っています。この項目はまだ整理の途中です。
リソース
プリミティブを構成する要素のうち、テクスチャ―やメッシュなどの構造に関する情報をリソースと呼ぶことにしています。リソースは各プリミティブが保持するのは無駄が多いので、別途管理されポインターで参照します。四角形や線分であっても構造の情報は必要ですが、単純でサイズが小さいものはプリミティブに組み込んでいます。
パラメーター
キャラの名前やHPなど純粋にゲーム世界の情報をパラメーターと呼ぶことにしています。セーブ/ロードの対象になるデータという位置づけです。この項目はまだ整理の途中です。
アクター
ゲーム世界のプレイアブルなオブジェクトを特にアクターと呼ぶことにしています。アクターは複数のプリミティブとパラメーターで構成されています。FSM(FiniteStateMachine)によるアクターシステムを研究中です。
サンプル
ミサイルを迎撃するゲームのサンプルコード(エターナル版)です。
github

Aキーで弾道ミサイル、Sキーでスマートボムが飛来します。
マウスで照準を移動し、Z/X/Cで迎撃ミサイルを発射します。
未完成なので誰かここから完成させてください。
以上