スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

テキスト差分アルゴリズムの実装方法

テキスト差分アルゴリズムはどのように実装すれば良いのだろう
と悩んでいたのですが、

ある程度やり方が確立出来て来たので、その方法を書いてみます。

~やり方~
【1】変更前、変更後それぞれの文字単位での出現数をカウント
【2】変更前、変更後での、文字の増減を算出
【3】任意の場所で、文字列を分割
【4】分割した変更前、変更後での、文字単位での出現数をカウント
【5】分割した変更前、変更後での、文字の増減を算出
【6】手順5と、手順2の増減を比較。比較結果に応じて、
   分割した境界をずらす
【7】分割された文字列に対して、手順1~7の操作を繰り返す


文章だと意味分からないですね。
具体例を書いてみます。

例:
変更前(ファイルA):ABCDEFZZ
変更後(ファイルB):ZZABCDEF


■手順1、手順2(文字をカウント。増減を出す)

まず文字の出現数をカウントします。
   前   後
A: 1個  1個  = 増減なし
B: 1個  1個  = 増減なし
C: 1個  1個  = 増減なし
D: 1個  1個  = 増減なし
E: 1個  1個  = 増減なし
F: 1個  1個  = 増減なし
Z: 1個  1個  = 増減なし


増減はありませんね。


■手順3~5(分割、分割後の増減を出す)
変更前(ファイルA):ABCD    EFZZ
変更後(ファイルB):ZZAB    CDEF


とりあえず、左半分を調べます。

   前   後
A: 1個  1個  = 増減なし
B: 1個  1個  = 増減なし
C: 1個  0個  = 1個減少
D: 1個  0個  = 1個減少
Z: 0個  2個  = 2個増加


で、これを分割前の増減と比較します。
※分割前は、どれも増減なしだったので、
C,Dが減少、Zが増加した
事になります。


■手順6(分割結果に応じて、境界をずらす)
自分が実装した差分作成アルゴリズムでは、
この境界変更こそが要です。

では、やっていきたいと思います。
比較結果では、C,Dが減少、Zが増加していました。

これはどういう事かと言うと、
-------------------------------------------
■分割したら文字数が減少した
⇒必要な文字が、変更後の右側に含まれてしまった

■分割したら文字数が増加した
⇒必要な文字が、変更後の左側に含まれてしまった

-------------------------------------------
↑こういう事になります。

つまり、減少している文字がある場合は、
変更の境界を左へ
ずらします。

変更前(ファイルA):ABCD    EFZZ
変更後(ファイルB):ZZAB    CDEF

        
変更前(ファイルA):AB      CDEFZZ
変更後(ファイルB):ZZAB    CDEF



そして、増加している文字がある場合は、
変更の境界を左へ
ずらします。

…と言いたいところですが、
今回の場合、「Z」を同じ分割グループに入れようとすると、
「A」「B」が異なる分割グル―プに属する事になってしまいます。

つまり、単純に考えると、
A削除、B削除、A追加、B追加、でコストが4つ増えるのです。

なので、今回は割に合わないので、
「Z」の方の境界変更はしません。

変更前(ファイルA):ABCD    EFZZ
変更後(ファイルB):ZZAB    CDEF

        
変更前(ファイルA):AB      CDEFZZ
変更後(ファイルB):         ZZABCDEF

↑ABのコストが増えるからダメ。

■手順7(今までの操作を繰り返す。)
境界の変更が終われば、
後は再帰的に分割された文字列をまた分割…という風に繰り返していきます。

※アンダーバーは空白を意味します。
つまり、下記の様に記載されていれば、空白⇒Z(Zが1個増加、の意味になります)
_


また、下記の様に記載されていれば、Z⇒空白(Zを1個削除、の意味になります)

_


■その後の処理
変更前(ファイルA):A  B     CDEFZZ
変更後(ファイルB):ZZ AB    CDEF

変更前(ファイルA):_ AB    CDEFZZ
変更後(ファイルB):ZZAB    CDEF

変更前(ファイルA):_ _ AB    CDEFZZ
変更後(ファイルB):Z Z AB    CDEF

変更前(ファイルA):_ _ A B    CDEFZZ
変更後(ファイルB):Z Z A B    CDEF

変更前(ファイルA):_ _ A B    CDE FZZ
変更後(ファイルB):Z Z A B    CD EF

変更前(ファイルA):_ _ A B    CD EFZZ
変更後(ファイルB):Z Z A B    CD EF

変更前(ファイルA):_ _ A B  C D EFZZ
変更後(ファイルB):Z Z A B  C D EF

変更前(ファイルA):_ _ A B  C D EF ZZ
変更後(ファイルB):Z Z A B  C D E F

変更前(ファイルA):_ _ A B  C D E FZZ
変更後(ファイルB):Z Z A B  C D E F

変更前(ファイルA):_ _ A B  C D E F ZZ
変更後(ファイルB):Z Z A B  C D E F _

変更前(ファイルA):_ _ A B  C D E F Z Z
変更後(ファイルB):Z Z A B  C D E F _ _


上記を見ても分かりますが、
最終的に、先頭にZを2つ追加、最後のZを2つ削除という結果が出ます。

これが自分の実装した差分ファイル作成アルゴリズムの概要です。
この方法を説明している参考文献が見当たらなかったのですが、
あまり確立されていない方法なのだろうか?
(処理速度が遅くて実用されてないのかもしれないけども)

確立されてなかったら、
文字列分割法とでも名付けておこう。
スポンサーサイト

テーマ : ゲーム製作 関連 - ジャンル : ゲーム

アクションゲームのカメラワークの実装方法 ~前編~

今回、現時点で最良の方法と思われる
カメラワークの実装方法について記載してみたいと思います。

まず、単純なカメラワークの実装方法。
-----------------------------------------
【1】カメラの座標を保持するクラスを作成
【2】カメラに影響を受ける描画クラスを作成
【3】主人公の座標から、カメラの座標を修正

-----------------------------------------

もしかしたら、クラスを使わずに
実装しようとする方も居る
かもしれないので、
より、概念だけ分かる様に単純化してみます。


単純化すると、下記になります。
------------------------------------------
【1】g_CameraPosX,g_CameraPosYなどのグローバル変数を用意
【2】カメラに影響を受ける描画オブジェクトは、
   描画する際に、オブジェクトの座標 - カメラの座標とする。
【3】主人公の位置から逆算して、カメラの座標を修正

------------------------------------------

分かりにくいので順に考え方から説明します。


■カメラワークの原理
例えば、実行ファイルでのウィンドウサイズが800×600で、
主人公がX座標800、Y座標800の位置に居るとし、(主人公の背丈は100ドット分)
また、カメラの左上の座標は、X座標0、Y座標300とします。

この場合、
広大なゲームの世界のうち、
カメラが、X座標0~800、Y座標300~900の部分を切り取って
実行画面に映し出している

という認識を持ってみて下さい。

【参考画像】
Sample_20131217_1.jpg

そう考えると、主人公を 実行画面の左端に映し出す為には、
カメラの左上の座標を、(800,300)にすれば良い
と分かると思います。

【参考画像】
Sample_20131217_2.jpg


■カメラワークの実装方法
では、上記の例えで言う「切り出す」という処理を
プログラムでは、どう実装すれば良いか。

下記の様に、単純にカメラ座標用の変数に値を入れただけでは、
主人公は表示されません。


g_CameraPosX = 800;
g_CameraPosY = 300;
g_PlayerPosX = 800;
g_PlayerPosY = 800;

DrawObject( g_PlayerPosX, g_PlayerPosY, g_Handle );

※DrawObject()は、画像ハンドルと、X,Y座標を指定すれば、
 該当の画像が、指定座標に描画される関数とする。


なぜなら、カメラ座標用の変数に値を入れようが、
主人公の描画位置は800
(ウィンドウの表示領域外)のままだからです。

では、どうすれば良いか、というと
カメラの座標を主人公の座標から引きます。

DrawObject(g_PlayerPosX - g_CameraPosX,  g_PlayerPosY - g_CameraPosY, g_Handle );

DrawObject(800 - 800, 800 - 300, g_Handle );

DrawObject(0, 500, g_Handle );




要は、ゲーム世界のX座標:800の地点を、
ウィンドウ上のX座標:0の所に表示させたい
訳です。

その為には、800を引けば(左方向にずらす)OKという事です。

※他の座標でも同じです。
ゲーム世界のX座標:1200の地点を、
ウィンドウ上のX座標:0の場所に表示させたい場合は、
1200を引けばOKです。


これで、カメラを基準とした描画方法は分かったと思います…!
カメラの影響を受けるオブジェクトは、
下記の様にカメラ座標を引いて
やれば良いのです。
DrawObject(g_PlayerPosX - g_CameraPosX,  g_PlayerPosY - g_CameraPosY, g_Handle );


【補足】
また逆に、「制限時間表示」「スコア表示」などは
カメラワークの影響で上下左右に動いてもらっても困る
ので、
カメラ座標は引かない様にしましょう。

※DrawObject(g_ScorePosX, g_ScorePosY, g_Handke);の様に、固定表示にする。



カメラワークに影響を受けるオブジェクトの表示方法は分かったと思うので、
次は、カメラ座標をどのように動かすか?を考えていきます。

これは、簡単。
カメラの座標 = 主人公の座標 - 300; と言う風に、
主人公の位置から逆算すればOKです。

【参考画像】
Sample_20131217_3.jpg

ただ、これだと、
主人公がどんなに移動しても、
主人公が中心に来るようにカメラ座標が修正
されます。

つまり、主人公が激しくジャンプしたりすると、
ガクガクとカメラも激しく上下に動くことになって
非常に見づらい状態
になります。


■カメラワークの修正その1
このカメラがガクガクと動いてしまう挙動の修正案としては、
カメラの座標修正を特定条件下のみに制限する事です。

左右移動であれば、
「左右の端付近に来た時に座標修正」という方法で対応します。

【参考画像】
Sample_20131217_4.jpg


で、「主人公が画面端付近に来た」という判定
プログラムでどう行うか、についてですが、

これは、主人公の座標とカメラ座標の差分を求めて行います。

例えば、
・ウィンドウサイズ … 横800×縦600
・主人公 … (X,Y) = (0, 500)
・カメラ左上 … (X,Y) = (0, 0)

とします。

この時、「画面右端付近」というと、X座標が600くらいでしょうか。
(つまり、主人公のX座標と、カメラのX座標との差が
 600以上の時にカメラ座標を修正する
、と考える。)

すると、下記の様になります。
if( g_PlayerPosX - g_CameraPosX > 600 ){
g_CameraPosX = g_PlayerPosX - 600;
}


また「画面左端付近」も同様に考えて、
主人公とカメラのX座標の差分が200未満の時に、
カメラ座標を修正
するようにします。

if( g_PlayerPosX - g_CameraPosX < 200 ){
g_CameraPosX = g_PlayerPosX - 200;
}


まとめると下記になります。
if( g_PlayerPosX - g_CameraPosX > 600 ){
g_CameraPosX = g_PlayerPosX - 600;
}else if( g_PlayerPosX - g_CameraPosX < 200 ){
g_CameraPosX = g_PlayerPosX - 200;
}


【補足】
「もっと早めにカメラが動いてくれないと、
進んだ先に何があるのか分からない!」と感じた場合
は、
上記の600を500にしてみたり、200を300にしてみたりして、
微調整すればOKだと思います。


■カメラワークの修正その2
で、あともう一つ条件を追加せねばなりません。
マップが存在しない場所にまで
カメラは動いてもらっては困る
ので、

【参考画像】
Sample_20131217_5.jpg


全体マップの外側をカメラが映すような状態になった場合は、
カメラの位置を調整する様にします。

右へ移動して、マップの終端が見える状態になる場合は、
下記の様に「カメラの映す領域の右端 = マップの末端」になるように調整すればOK。

【参考画像】
Sample_20131217_6.jpg


これも、どうプログラムを書けば実装出来るのか考えてみます。

これは簡単で、下記の様にすれば実装出来ます。
------------------------------------------------------
カメラの座標 + ウィンドウの横幅の値が、
マップの横幅を超えれば、

(マップの横幅 - ウィンドウの横幅)を、カメラ座標とする

------------------------------------------------------

マップの横幅 … 1000
カメラの座標 … 250
ウィンドウの横幅 … 800
とすると、

if( 250 + 800 > 1000 ){
g_CameraPosX = 1000 - 800;
}

となります。

全部変数にすると、
if( g_CameraPosX + WND_WIDTH > MAP_WIDTH ){
g_CameraPosX = MAP_WIDTH - WND_WIDTH;
}

になります。

左方向へ移動し、
左側にマップが表示されない領域が出来る場合
の処理は、
下記の様になります。
if( g_CameraPosX < 0 ){
g_CameraPosX = 0;
}

※マップの左端のX座標は、0とする。
つまり、マップの横幅が1000の場合は、
0~999までがX座標の取りうる範囲とする。

左にはみ出す場合は、判定方法が少し楽になります。
(右端の場合は、左上のX座標 + ウィンドウの横幅で、
  右端の座標を調べる手間が発生するのですが、左端はカメラ座標をそのまま使えば良い。)


まとめると、下記の様になります!
if( g_PlayerPosX - g_CameraPosX > 600 ){
g_CameraPosX = g_PlayerPosX - 600;
}else if( g_PlayerPosX - g_CameraPosX < 200 ){
g_CameraPosX = g_PlayerPosX - 200;
}

if( g_CameraPosX + WND_WIDTH > MAP_WIDTH ){
g_CameraPosX = MAP_WIDTH - WND_WIDTH;
}else if( g_CameraPosX < 0 ){
g_CameraPosX = 0;
}


※上記の判定方法は、ウィンドウサイズ < マップのサイズを想定しています。
なので、ウィンドウが800×600に対し、マップサイズが100×100しかないという、
意味不明な状況に関しては考慮していません。
(カメラの座標を移動させるまでも無く、マップ全体が実行画面に収まる場合。)


さて、これで、
必要な時にだけカメラワークが行われるし、
マップが存在しない部分が映されそうになった場合も、
マップが存在する範囲のみが映る様にカメラ座標が修正される
し、
問題ない、と考えられるはずです。

しかし…、結論から言います。
これだけでは、不十分です。

特にアクションゲームにおいては、
致命的に、おかしなカメラワークが行われる
パターンが発生
します。


さて、そのおかしなカメラワークが発生する条件とは何か。
また、その対象方法は何か。

カメラワーク実装方法 ~後編~ で纏めます…!
お楽しみに…!!(`・ω・´)

テーマ : ゲーム製作 関連 - ジャンル : ゲーム

ローグライクゲームを作ってみよう!  その9 ~アーカイブ読み込み+α~

さて、引き続きゲーム制作の前提となるシステム作りの解説を行ってまいります。

今回やるのは、下記の二つ!
-----------------------------------------
【1】アーカイブファイル読み込み
【2】入力管理システムの構築

-----------------------------------------

まず、アーカイブファイルの読み込みです。

※アーカイブファイルの作成、読み込み自体は前回解説したので、
こちらをお読みください。

【参考URL】アーカイブファイル作成ファイルを作ってみた!



■アーカイブファイルからの読み込み
読み込み方は極めて単純です。

まず、今までファイル読み込み用のCLoaderFileクラスを使用していたのですが、
同様に、アーカイブファイルから読み込む為のCLoaderArchiveクラスを作成します。
※CLoaderBaseを継承。

【参考画像】
SampleRogue4_1.jpg

で、読み込みクラスを生成する部分で、
CLoaderBaseにCLoaderArchiveをnewして代入する部分を作ります。
(ただのswitch文への追記ですね。)

int t_ClassType = t_ParamVec[ t_OffSet + 1 ];

std::tr1::shared_ptr< CLoaderBase > t_LoaderBase;
switch( t_ClassType ){
case 1: t_LoaderBase.reset( new CLoaderFile() ); break;
case 2: t_LoaderBase.reset( new CLoaderArchive() ); break;
default:break;
}


で、後はCLoaderArchiveクラスの中身を作るだけです。

CArchiveクラスという、アーカイブファイルを管理するクラスを作成しているので、
このクラスからファイルサイズとファイルポインタを取得し、
1)ファイルサイズを超えるまでint型の変数を読む
2)すべて読み込み終わったら、Factoryクラスに読み込んだ値を渡す

という部分を作れば完成です。

int CLoaderArchive::Init( std::vector< int >& t_ParamVec, int t_Offset )
{
int result = -1;

//変数4つ分の要素数があれば
if( ( int )t_ParamVec.size() > t_Offset + 3 ){
//各データを変数に代入
m_Priority = t_ParamVec[ t_Offset ];
m_LoadNum = t_ParamVec[ t_Offset + 1 ];
m_CreateNum = t_ParamVec[ t_Offset + 2 ];
int t_FileNameNum = t_ParamVec[ t_Offset + 3 ];

//ファイル名の分の要素数があれば。
if( ( int )t_ParamVec.size() > t_FileNameNum + t_Offset + 3 ){
//ファイル名のデータは、t_Offsetの位置から4つ後ろの変数から開始するので、t_Offset + 4と指定。
std::vector< char > t_FileNameVec;
ChangeIntToStr( t_FileNameVec, t_ParamVec, t_Offset + 4, t_FileNameNum );

CArchive& t_Archive = CArchive::getInstance();
m_FileSize = t_Archive.GetFileSize( &t_FileNameVec[ 0 ] );
m_pFile = t_Archive.GetPointer( &t_FileNameVec[ 0 ] );


//ファイルが正常に読み込めていれば
if( m_FileSize > 0 && m_pFile != NULL ){
result = 0;
}
}
}
return( result );
}

int CLoaderArchive::GetValue()
{
int result = -1;
int t_Value = 0;
int t_Flag = 0;
int t_State = 0;

for( ; m_NowVecNum < m_FileSize; m_NowVecNum++ ){
if( m_pFile[ m_NowVecNum ] >= '0' && m_pFile[ m_NowVecNum ] <= '9' ){
t_Value = ( t_Value * 10 ) + ( m_pFile[ m_NowVecNum ] - '0' );
t_State = 1;
}else if( t_State == 0 && m_pFile[ m_NowVecNum ] == '-' ){
t_Flag = 1;
t_State = 1;
}else if( t_State == 1 ){
if( t_Flag == 0 ){
result = t_Value;
}else{
result = -t_Value;
}
break;
}
}
//現在の添え字がファイルデータの要素数を超えた場合、オブジェクト生成モードに移行
if( m_NowVecNum >= m_FileSize ){
m_LoaderState = LOAD_OBJECT;
}
return( result );
}


んで、今回のアーカイブファイル読み込み作成クラスを作成したことにより、
テクスチャデータのcsvファイル記載形式が変わりました。

★今まで
SampleRogue4_3.jpg

★今回
SampleRogue4_2.jpg


2番目にLoadTypeが加わっただけです。
※LoadType … 読み込み方法を切り替えるフラグです。フラグは下記の2種類。
1:ファイルから読み込み、2:アーカイブファイル読み込み


プログラムの方は、下記の様に変更されています。

★今まで
if(画像分割するなら){
LoadDivGraph( //省略);
}else{
LoadGraph(//省略);
}

★今回
switch( t_ClassType ){
case 1:
if(画像分割するなら){
LoadDivGraph( //省略);
}else{
LoadGraph(//省略);
}
break;
case 2:
if(画像分割するなら){
CreateDivGraphFromMem( //省略);
}else{
CreateGraphFromMem(//省略);
}
break;
}



■入力管理システムの構築
まず入力判定をする場合のことを考えます。

例えば、「Enterキーを押したらゲーム開始」と決めたとします。
例:
if( Key[ KEY_INPUT_RETURN ] == 1 ){
//何らかの処理
}


ここで「やっぱりZキーでゲーム開始にしよう」
仕様変更が入ったとします。

この場合、当然ソースコードの書き換えが必要になり、
また、入力判定の書き換え忘れ等が発生する可能性があります。

なので、上記の可能性を考えると、
固定値を記載するのはマズイという結論に至りました。


そこで、下記の様な仕組みにしました。

<準備>
const int KEY_TYPE_MAX = 256;
char m_KeyFlag[ KEY_TYPE_MAX ];
int m_KeyCount[ KEY_TYPE_MAX ];
std::vector< int > m_KeyVec;

・GetHitKeyStateAll関数で、m_KeyFlagに押されているかどうかのフラグをセット
・KeyFlagが1であればm_KeyCountの値を加算、0ならm_KeyCountも0にする。
・初期化の際、m_KeyVecをKEY_TYPE_MAXの値だけ拡張しておく。
・初期化の際、m_KeyVecの0~255の添え字には、0~255を入れておく。
・キーが押されているかを判定する際、m_KeyCountの値を直接確認するのではなく、
 m_KeyVecの値を介して確認する。


赤字の部分が重要です。
図で見た方が分かり易いかもしれないので、画像を貼ってみます。

★今まで
RogueSample4_4.jpg


★今回
RogueSample4_5.jpg


要はm_KeyVecの値をm_KeyCountの添え字として使っている訳です。
これ、何のメリットがあるの?というと、
キーコンフィグ機能が実装できる
のです。

例えば、KEY_INPUT_Q = 16、 KEY_INPUT_P = 25 なのですが、

下記の様に、あらかじめKeyVecに値を代入しておくことにより、
「Qを押したら処理A実行」の部分を、「Pを押したら処理A実行」
動作を変える事が出来るのです。

//事前に代入しておく。
m_KeyVec[ 16 ] = 25;

int CheckKey( int t_KeyNum ){
int result = -1;

result = m_KeyCount[ m_KeyVec[ t_KeyNum ] ]

return( reuslt );
}

int main(void){
//代入されたので、「P」が押された際に処理A実行
if( CheckKey( KEY_INPUT_Q ) == 1 ){
//処理A
}
return( 0 );
}
※配列の範囲外チェックは省いています。


また、それだけではなく、
オリジナルのキーを作ることも出来ます。

例えば、m_KeyVecを要素数500に拡張します。
そして、m_KeyVec[499] = 25 と代入しておけば、

プログラム中の下記の記載がある部分は、「Pを押したら実行」という動作になります。
if( CheckKey( 499 ) == 1 ){

}


で、m_KeyVecに代入する値を外部ファイルに押し出してやれば、
コンパイルせずに、入力に反応するキーを
変える事が出来る
という寸法です。

今回は、
257 = 「M」
258 = 「N」
259 = 「O」
260 = 「P」 という事にしました。

実際のソースコードは下記の通り。
if( t_InputManager.CheckInput( 257 ) == 1 ){
//「M」キーを押した際に実行される。 (描画優先順位変更)
t_Manager.ChangePriority( 2, 2 );
}else if( t_InputManager.CheckInput( 258 ) == 1 ){
//「N」キーを押した際に実行される。 (描画状態変更)
t_Manager.ChangeDrawState( 0, 2 );
}else if( t_InputManager.CheckInput( 259 ) == 1 ){
//「O」キーを押した際に実行される。 (描画状態変更)
t_Manager.ChangeDrawState( 0, 1 );
}else if( t_InputManager.CheckInput( 260 ) == 1 ){
//「P」キーを押した際に実行される。 (テクスチャ変更)
t_Manager.ChangeTexture( 0, 0, 3 );
}


将来的には、入力番号257~260はゲームパッドの入力を受け付ける、
という風にしても良い
かもしれません。
その内、改良してみようと思います。

外部ファイルは下記の様になっているので、
KeyVecNumとKeyTypeを任意の値に書き換えれば動作が変わるはずです。

■InputData.csv
RogueSample4_6.jpg




★注意事項
今回からアーカイブファイル読み込みを実装したので、それに対する注意。
----------------------------------------------------------------------
【1】ファイルを個別に読み込む場合、プログラム中のInit.csvを読み込む部分の
   2番目の引数を1にしてください。
int t_Param[] = { 2, 1, 1, 100, 100 };
【2】アーカイブファイルから読み込む場合は、2番目の引数を2にして下さい。
int t_Param[] = { 2, 2, 1, 100, 100 };
【3】TextureData.csvには、ファイル読み込みタイプを指定するフラグがあります。
(2列目の"LoadType"の項目)
 こちらもファイル読み込みの場合は1、アーカイブからの場合は2を指定して下さい。

----------------------------------------------------------------------

なので、開発の際は下記の手順になると思われます。
----------------------------------------------------------------------------
【1】TextureData.csvの、"LoadType"の項目は1に指定。
【2】プログラム中のInit.csvを読み込む部分の、2番目の引数を1に指定。
【3】プログラム中に、MyArchive.arcを読み込む部分があるので、そこはコメントアウト。
【4】開発中は、各.png.jpgなどのファイルを個別に読み込んで実行

【5】TextureData.csvの"LoadType"の項目を2に指定。
【6】任意のフォルダに、アーカイブにするファイルとあーかいば.exeを入れる。
   あーかいば.exeを起動して、MyArchive.arcを作成
【7】プログラム中の、MyArchive.arcを読み込む部分のコメントアウトを外す
【8】プログラム中のInit.csvを読み込む部分の、2番目の引数を2に指定。

----------------------------------------------------------------------------

【ソースコード一式】http://rudora3.web.fc2.com/RogueSample4.zip

テーマ : ゲーム製作 関連 - ジャンル : ゲーム

ローグライクゲーム制作講座

※こちらは、ローグライクゲーム制作関連の記事をまとめたページです。

説明順序がゴチャゴチャになっている所があるので、
その内また纏めなおす予定です。

とりあえず、その6~その8はかなりゲームの基盤に値する部分の解説なので、
こちらから読むのをオススメしております。

ローグライクゲームを作ってみよう!  その1 ~ランダムダンジョン生成~
ローグライクゲームを作ってみよう!  その2 ~ランダムダンジョン生成~
ローグライクゲームを作ってみよう!  その3 ~ランダムダンジョン生成~
ローグライクゲームを作ってみよう!  その4 ~マップスクロールその他~
ローグライクゲームを作ってみよう!  その5 ~移動処理~
ローグライクゲームを作ってみよう!  その6 ~描画システム~
ローグライクゲームを作ってみよう!  その7 ~生成システム~
ローグライクゲームを作ってみよう!  その8 ~外部ファイル読み込みシステム~

<追記予定!>

テーマ : ゲーム製作 関連 - ジャンル : ゲーム

ローグライクゲームを作ってみよう!  その8 ~外部ファイル読み込み~

前回やった内容についてですが、
画像や描画オブジェクトの生成機構を作りました。

で、作ったのはいいのですが、
「このオブジェクトを生成してくれ」とFactoryクラスに依頼するたびに、
下記のコードを書かなければ
なりません。

{
std::vector< int > t_Vec;
std::string t_Str( "grade.jpg" );//grade.jpgは、256×256の画像とする。
int t_Param[] = { 0, 2, 2, 128, 128 };//画像配列番号、x分割、y分割、分割した際の横幅、分割した際の縦幅

CharToInt( t_Str, t_Vec );

for( int i = 0, count = sizeof( t_Param ) / sizeof( t_Param[ 0 ] ); i < count; i++ )
{
t_Vec.push_back( t_Param[ i ] );
}
IFactory::Create( FACTORY_TEXTURE, t_Vec );
}
--------------------------------------------------------
※豆知識。 先頭と最後に謎の{ }があると思いますが、
 これ、変数の有効範囲を決めてます。

 上記の例でいうと、t_Vec、t_Strといった変数は{}の中でのみ存在する事になります。
 for文やif文以外の何でもない所に{}を使って変数の有効範囲を指定できるのです。
--------------------------------------------------------

これは非常に面倒くさいですね。
(画像名などを修正するだけでコンパイルし直さないといけないですし。)

なので、外部ファイルから読み込むクラスを新たに創設し、
外部ファイルを書き換えて、ゲーム内部の動作に反映させるようにします。


イメージとしては下記の様になります。
※下記画像の矢印は、処理の流れを示したものであり、
 classの継承、集約などを示すもの(UMLのクラス図)ではありません。

【参考画像】  ※今まで
RogueSample3_1.jpg



【参考画像】  ※今回
RogueSample3_2.jpg


見ても分かりますが、
下記の様な流れを作ってしまうと、無限ループになります。
IFactory→CLoaderFactory→CLoaderManager→IFactory→繰り返し

なので、Test.csvというファイルに、
「Test.csvを読み込んで下さい」という指示を書く、

などの循環を生み出す記載はしないように注意しましょう。


※正常な流れは下記の通り。

■Test.csvに"Test.jpg"という画像を読み込んでくれという内容を記載した場合。
main→IFactory→CLoaderFactory→CLoaderManager→
IFactory→CTextureFactory→CTextureManager

■Test2.csvに"Test.csv"というファイルを読み込んでくれという内容を記載、
 Test.csvには、"Test.jpg"と"Test.bmp"を読み込んでくれという内容を記載した場合。

main→IFactory→CLoaderFactory→CLoaderManager→ //Test2.csvを読み込むLoaderが登録
IFactory→CLoaderFactory→CLoaderManager→ //Test.csvを読み込むLoaderが登録
IFactory→CTextureFactory→CTextureManager→ //Test.jpgが登録
IFactory→CTextureFactory→CTextureManage //Test.bmpが登録


外部ファイルについて。
外部ファイルを読み込めば良い、と言いましたが、
肝心の外部ファイルについて説明してなかったので、説明をします。
全部で3つです。


■Init.csv
RogueSample3_5.jpg

まず、Init.csv。
どの外部ファイルを読み込むのか?を指定しています。
分かりにくいパラメータだと思うのが、2番目のClassTypeですね。

実は外部ファイルを読み込むLoaderクラスを、派生させる事を想定しています。
現状未実装ですが、
1 … ファイルを開いて読み込む  (CLoaderFile)
2 … アーカイブファイルからデータを引っ張り出して読み込む  (CLoaderArchive)
にしようと思ってます。

CLoaderBaseを継承したCLoaderArchiveクラスの方は未実装なので、
現状、ClassType = 1で固定、と考えて良いです。


■TextureData.csv
RogueSample3_4.jpg

こちらのDrawerTypeについてですが、
こちらも派生の余地を残しております。

2Dではなく、3Dのモデルを描画したい場合、
2Dとは与えるパラメータが全く異なる事が予想される為、
(ライトの設定、背面カリングの設定、Z座標などなど。)
派生で対応しようと思ってます。

こちらも現状未実装なので、
DrawerType = 0で固定と考えてもらってOKです。


あと、VecNumは描画配列の添え字です。
↓こんな感じで、添え字を指定してピンポイントで消去したりする為に必要な情報です。
DeleteDrawer( 0 ); //描画配列の0番に登録されたオブジェクトを削除


■DrawerData.csv
RogueSample3_3.jpg

最後。
テクスチャ情報です。

画像を分割読み込みしない場合は、とりあえず全部-1を指定
分割する場合は、X軸、Y軸それぞれの分割数と、
分割した際に画像の縦幅と横幅のサイズがいくつになるのか
?を指定します。

上記では元の画像が256×256なので、
それぞれ2つずつに分割して、分割後のサイズは128になるよ、と記載しています。


最後に。
今回の付録(?)は下記の様になっています。

■Base
■Factory
┃┣CDrawerFactory.h
┃┣CDrawerFactory.cpp
┃┣CTextureFactory.h
┃┣CTextureFactory.cpp
┃┣CLoaderFactory.h
┃┣CLoaderFactory.cpp
┃┣IFactory.h
┃┗IFactory.cpp
■Manager
┃┣CDrawerManager.h
┃┣CDrawerManager.cpp
┃┣CLoaderManager.h
┃┣CLoaderManager.cpp
┃┣CTextureManager.h
┃┗CTextureManager.cpp
■Resource
┃┣CDrawer2D.h
┃┣CDrawer2D.cpp
┃┣CDrawerBase.h
┃┣CDrawerBase.cpp
┃┣CLoaderBase.h
┃┣CLoaderBase.cpp
┃┣CLoaderFile.h
┃┗CLoaderFile.cpp
■Support
 ┗CSingleton.h
main.cpp

■Resource
┣Init.csv
┣DrawerData.csv
┣TextureData.csv
┗grade.jpg


実行する際は、下記の手順になります。
【1】上記ソースコードがあるフォルダをコピーペースト
【2】リソースがあるフォルダの中身だけを、
   "●●●.vcxproj"とかいうファイルが存在するフォルダにコピーペースト


画像が表示されなかったりしたら、99%リソースの置き場所が間違ってる
と思われるので、フォルダの確認をお願いします。

【ソースコード一式】http://rudora3.web.fc2.com/RogueSample3.zip


【ここから余談】-----------------------------------------------------------
しかし、面白いですね。 こんだけcppやらhやら書きましたが、
ゲーム内部の制作には、ひとつも着手できてない
のです。
※しかも、ゲームの基盤は今回で完成ではありません。まだやる事があります。


知り合いから、
「3か月経ってもゲーム完成しないの?本当は挫折して遊んでるんじゃないの?」
とか言われて憤慨しそうになったことがあるのですが、

是非、上記のような作業が意外と時間がかかる
という事を分かって頂きたい。

※多分、キーボードでどんどん不可逆的に作業を進める、
といったイメージがあるのだろう。

実際には、"そもそものプログラムの構造を考える"時間や、
構造を考えてみたけど破綻したor改良法を見つけたから
作り直しor修正をしたりする手間もあるものである。
--------------------------------------------------------------------------


次回は、
Archiveファイルの読み込み→ゲーム内に反映と、
Moverクラスの実装
を行います。

※Moverクラス … 現状、単純にファイルを読み込んで表示だけをしてます。つまり、
「z」キーを押したら右に動く、といった動作が全く無いのです。
次は、これを実装します。

ちなみに、今作っている描画周りのシステムなどは、
パズルゲームなどにも転用します。

次回もお楽しみに!(`・ω・´)+

テーマ : ゲーム製作 関連 - ジャンル : ゲーム



上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。