Help us understand the problem. What is going on with this article?

OPALゲームライブラリ

More than 1 year has passed since last update.

C++のゲームプログラムの練習用に作った自家製のフレームワークです。
Perlのスローガンに感銘を受け、それに倣うべくOPALと名付けました。

「簡単な事は簡単に、難しい事は可能に」

開発開始は2006-7年頃(あいまい)、現在の形になったのが2011年あたりです。

概要

基本的にテキストエディタとコマンドラインだけの簡素な開発環境です。

項目 内容
OS Windows7以降
言語 C++14
ベースシステム DirectX 9
コンパイラ MinGW-w64、VisualStudio2015
文字コード Shift-jis
スタイル オブジェクト指向
スレッド 基本シングル
座標系 左手系

使い方

下記は実行可能な最も短いコードとなります。

main.cpp
#include <Opal.hpp>

int main(){

    return Opal();
}

実行すると3つのウインドウが起動します。

ゲーム画面ウインドウ
1.jpg
ウインドウのタイトルや大きさ、背景の色等は変更できます。

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

デバッグ用ウインドウその2
3.jpg
printfなどの標準出力への出力はここに表示されます。

実際のゲームのプログラムは関数にして引数で渡します。

main.cpp
#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ループを握っています。そしてそのループはいわゆるタスクシステムで管理されています。ライブラリのコアシステムであるFILEXTHRDXはこのタスクシステムを利用しています。タスクが処理の状態を保持・監視できるので、通常のライブラリ単体では実装が難しい非同期読み込みやコルーチンも実装できました。

クラス 概要
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
20161211-112729-7b24e487-6eef-4669-b193-87927aa0ca72_R.jpg
Aキーで弾道ミサイル、Sキーでスマートボムが飛来します。
マウスで照準を移動し、Z/X/Cで迎撃ミサイルを発射します。
未完成なので誰かここから完成させてください。

以上

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした