スポンサーサイト

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

ローグライクゲームを作ってみよう!  その7 ~生成システム~

まず、現状報告
--------------------------------------------
・アプリ開発
・3Dゲーム開発
・2D(日本語パズルのやつ)ゲーム開発
・ローグライクゲーム(解説用)開発

--------------------------------------------
上記を同時進行してます。
やる事が沢山あると、作業がなかなか進まないものですね。


とりあえず、ローグライクの解説を始めます。
今回やるのは生成システムです。

DXライブラリだと、画像を読み込ませたらint型のハンドルを取得すると思うのですが、
これ、関数ごとにバラバラに取得してたら管理がゴチャゴチャになります。

(DXライブラリの場合は、DxLib_End()関数で自動で解放処理をやってくれるので良いのですが、
全部DirectXとかで実装しようとすると、間違いなく開放忘れが出てくるだろう。)

なので、今回は
------------------------------------
・オブジェクトの生成
・生成したオブジェクトの管理

------------------------------------
に焦点を絞って解説していきたいと思います。


まず生成について。
-------------------------------------------------------------
【1】IFactoryクラスを定義。 (Factoryクラスのインターフェース)
【2】IFactoryクラスを継承した、Factoryクラスを定義

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

やるのは上記だけです。簡単ですね。

【1】IFactoryクラスを定義

#pragma once
#include< vector >

enum FactoryID {
FACTORY_TEXTURE,
FACTORY_DRAWER
};

class IFactory {
public:
IFactory();
virtual ~IFactory();
static int Create( FactoryID t_ID, std::vector< int >& t_ParamVec );
virtual int CreateObject( std::vector< int >& t_ParamVec ) = 0;
void ChangeIntToStr( std::vector< char >& t_CharVec, std::vector< int >& t_ParamVec, int t_OffSet, int t_StringNum );
private:

};

int IFactory::Create( FactoryID t_ID, std::vector< int >& t_ParamVec )
{
int result = -1;
CTextureFactory t_TextureFactory;
CDrawerFactory t_DrawerFactory;

switch( t_ID ){
case FACTORY_TEXTURE: result = t_TextureFactory.CreateObject( t_ParamVec );
break;
case FACTORY_DRAWER: result = t_DrawerFactory.CreateObject( t_ParamVec );
break;
default:break;
}

return( result );
}

ここでポイントは2つ。

■一つ目。
IFactoryの方では、CreateObject関数を純粋仮想関数にしています。

つまり、IFactoryクラスを継承してCTextureFactoryクラスを作った場合、
必ずCreateObject関数を定義して、この関数でテクスチャを生成
してね、
と言っている訳です。

■二つ目。
IFactoryクラスには、staticなCreate関数があります。
これは、"何か"のオブジェクトを作成したいときに使います。

"何か"というのが重要です。

つまり、IFactory.hさえインクルードすれば、
テクスチャだろうが描画オブジェクトだろうが生成できる

という事です。

#include"IFactory.h"

int WINAPI WinMain(//省略){
IFactory::Create( FACTORY_TEXTURE, t_ParamVec ); //テクスチャ作成
IFactory::Create( FACTORY_DRAWER, t_ParamVec2 ); //描画オブジェクト作成
}


もし、IFactoryというインターフェースが無ければ、
下記の様に各Factoryクラスのヘッダをインクルードしなければならず、
かなり面倒くさい事になります。

#include"CTextureFactory.h"
#include"CDrawerFactory.h"


int WINAPI WinMain(//省略){
CTextureFactory t_TextureFactory;
CDrawerFactory t_DrawerFactory;

t_TextureFactory.CreateTexture( t_ParamVec ); //テクスチャ作成
t_DrawerFactory.CreateDrawer( FACTORY_DRAWER, t_ParamVec2 ); //描画オブジェクト作成
}


更に、フォルダ構成を変えた場合は、
下記の様にファイルパスを全部書き換えなければならないですし、
【変更前】 #include"CDrawerFactory.h"
【変更後】 #include"../Factory/CDrawerFactory.h"


また、Factoryクラスの名前を変えたら、
その変更も反映させないと行けません。
【変更前】 #include"CDrawerFactory.h"
【変更後】 #include"CDrawerFactory2.h"


なので、絶対不変であるIFactoryインターフェースを定義しておき、

WinMainなどからは、Factoryクラスの内部仕様が変更されようが、
IFactory.hを読み込んでIFactory::Create関数を
呼び出せば、どんなオブジェクトであれ生成出来る
という風にしたのです。

※DxLib.hさえインクルードすれば、
 LoadDivGraph関数だろうがSetWindowSize関数だろうが使える、というのに近い。


次に、オブジェクトの管理。

オブジェクトの管理は、
・Factoryクラスが、対応するManagerにオブジェクトを登録
・Managerクラスはシングルトンパターンになっており、
 プログラムが終了するまで、オブジェクトを保持する。

・任意のタイミングで、オブジェクトを削除したい場合は、
 各Managerクラスの削除関数を呼ぶ。

という風になってます。

※Singletonパターン … 必ずオブジェクトが一つのみ生成される事を保証する。
             2つ以上は生成されない。


Singleton(シングルトン)についてですが、
自分は自作したテンプレートのSingletonクラスを使用しています。

細かい原理の説明はしませんが、
とりあえず、Singletonパターンにしたいクラスがある場合、
------------------------------------------------
【1】CSingleton.hをインクルード
【2】CSingleton<クラス名>を、publicで継承する
【3】CSingleton<クラス名>を、friendに指定する。

------------------------------------------------
とすればOKです。


例:CTestクラスをSingletonパターンにしたい場合。
#pragma once
#include"CSingleton.h"

class CText : public CSingleton< CTest > {
friend class CSingleton< CTest >;
public:
CTest();
~CTest();
int MyFunc();
};

ついでに、使用する場合。
CTest& t_Test = CTest::getInstance();
t_Test.MyFunc();

(getInstance関数で、インスタンスへの参照が返るので、それを受け取る。)


最後に。
今回のソースコードは下記になっています。
だんだん量が増えてきましたね。

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

main.cpp


あ、そうだ。
画像読み込みの補足説明をしておきます。

画像を読み込む場合、ファイル名をint型に変更してパラメータとして渡し、
Factoryクラス内で、また文字列に変換して画像を読み込んでます。


■変換の図
grade.jpg → 103,114,97,100,101,46,106,112,103 → grade.jpg

何でこのようにするかというと、
下記の様に、全部int型のvectorを引数にしてオブジェクトを生成したいからです。
IFactory::Create( FACTORY_TEXTURE, t_Vec );

また、全部int型にしておくと、外部ファイル化して、Loaderクラスを作った時に便利になります。
これについては、次回 解説します。


■サンプルのソースコードについて。
Enter … 描画順変更
「→」キー … 特定の描画オブジェクトを非表示に変更
「←」キー … 特定の描画オブジェクトを表示に変更


上記の動作を行う様にしています。
また、画像を回転、拡大縮小、半透明表示などに対応させています。

※不具合があったら適宜修正します。

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

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

ローグライクゲームを作ってみよう!  その6 ~描画システム~

今までソースコード一つのみの、サンプルを公開してきたわけですが、
やっぱり複数のソースコードに分割した方が良いと思ったので、

根本の描画システムの構築方法を解説していきます。
※あくまで自分の実装方法なので、参考程度に。


まず、力技で実装した場合、
下記の様なプログラムになっているかもしれません。
while(//省略 ){
MovePlayer();
MoveEnemy();
DrawPlayer();
DrawEnemy();
}
※whileの中が、ゲームループとする。

これは良くない構造です。

■理由
・敵が居なくても、とりあえずMoveEnemy関数 / DrawEnemy関数が呼ばれる
・主人公が居なくても、とりあえずMovePlayer関数 / DrawPlayer関数が呼ばれる
・描画順を制御できない


3番目が特に致命的ですね。

敵⇒主人公 という描画順から、主人公⇒敵という描画順に変更したい!
という場合、まともな対処方法がありません。


※下記の様な実装でごり押しする事も出来ますが、止めた方が良いです。
(中規模、大規模なゲームでは、間違いなくフラグ管理とかがゴチャゴチャになる。)
while(//省略 ){
if( flag == 0 ){
DrawPlayer();
DrawEnemy();
}else{
DrawEnemy();
DrawPlayer();
}
}



という訳で、描画をまずリスト形式にします。
STLにlistというのがあるので、それを使いましょう。
※STL … スタンダードテンプレートライブラリの略。

プログラム内で、唯一の描画リストを定義しておき、
それに登録された描画オブジェクトを描画
という風にします。

【参考画像】
drawsystem1.jpg


で、描画リストで重要になってくるのが、
下記の3つの処理を、具体的にどう実装するのか?です。
--------------------------------------------------
【1】描画オブジェクトの登録方法
【2】描画オブジェクトの削除方法
【3】描画オブジェクトの描画順変更方法

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

まず【1】登録。
リストに登録と言っても、
先頭に追加、末尾に追加、リストの途中に追加の3種類があります。

これは、描画オブジェクトに優先度の変数を設けて対応します。

例えばこんな感じ。
class Drawer {
public:
Drawer();
~Drawer();
int GetPriority();
private:
int m_Priority;
};

int AddDrawer( Drawer& t_Drawer )
{
int result = -1;

for( it = m_DrawerList.begin(); it != m_DrawerList.end(); it++ )
{
if( it->GetPriority() > t_Drawer.GetPriority() ){
m_DrawerList.insert( it, t_Drawer );
result = 0;
break;
}
}
//登録されなかったら末尾に追加
if( result == -1 ){
m_DrawerList.push_back( t_Drawer );
result = 0;
}

return( result );
}

※m_Priorityの値が小さいほど優先度が高い、とします。

何気に重要なのが赤字の部分です。
この処理が無いと、正常に描画オブジェクトが登録されません。

正常に登録されないのは下記の2パターンです。

【A】描画リストに、一つも描画オブジェクトが登録されていない場合。
【B】描画リストに登録されているものよりも、優先度が低いオブジェクトを登録する場合。


【A】について。
描画オブジェクトが登録されてなかったら、優先度の比較もクソも無いので、 
for文の処理自体がすっ飛ばされます。
なので、登録処理が行われません。

【B】について。
例えば、優先度1、2、3のオブジェクトが登録済みとします。
そこへ、優先度4のオブジェクトを登録しようとすると、

下記の様にif文の判定が順番に行われるのですが、
全部 成立しないので、結局 登録処理は行われずにfor文を抜けます。

1 > 4  //成立しない
2 > 4  //成立しない
3 > 4  //成立しない

まぁ、という訳で、
for文の方で登録が成立すればresultに0を代入しておき、
その後に、resultが-1のまま(登録が行われなかった)かどうかを判定して、
-1だったら、末尾に追加という処理をしておく必要があるのです。


次に【2】削除。

削除に関しては、これも
何らかの値を用意しておき、それを判別して削除する必要があります。


最初、下記の様に描画オブジェクトIDを用意して、
それを判別して削除してたのですが、


class Drawer {
public:
Drawer();
~Drawer();
int GetPriority();
int GetObjectID();
private:
int m_Priority;
int m_ObjectID;
};

//削除したい描画オブジェクトIDを渡す。
int DeleteDrawer( int t_ObjectID ) {
int result = -1;

for( it = m_DrawerList.begin(); it != m_DrawerList.end(); it++ )
{
//描画オブジェクトIDが一致すれば削除
if( it->GetObjectID() == t_ObjectID ){
m_DrawerList.erace( it );
result = 0;
break;
}
}
return( result );
}


描画リストにオブジェクトが200個登録されている場合、
最悪200回判定をしないといけない(効率が悪い)ので、
やり方を変えました。

~変えた点~
・描画リストに登録するオブジェクトを、参照数計測ポインタに変更
・描画リストに対応する、描画オブジェクト配列を定義
・描画オブジェクトを登録する際は、配列番号を指定
・描画オブジェクトを削除する際は、配列番号を指定
・描画する際に、描画フラグを調べて、フラグによって描画/削除を決定


文章だと分かりにくいですね。

例えば、優先度が4の描画オブジェクトを、
描画オブジェクト番号0番として登録する
とします。

すると下記の様に、描画リストと描画オブジェクト配列の
参照数計測ポインタが、メモリ上の同じ描画オブジェクトを指す
ようになります。

【参考画像】
drawsystem2.jpg

このようにしておけば、
削除する際は、描画オブジェクト配列を介して描画フラグを変更するだけで、
一発でオブジェクトの削除(描画終了フラグのセット)が出来ます。


//削除したい描画オブジェクト配列の添え字を渡す。
int DeleteObject( int t_VecNum )
{
int result = -1;

//添え字の値が正しいかチェック
if( t_VecNum >= 0 && ( int )m_DrawerVec.size() > t_VecNum ){
//ポインタがNULLではないことを確認
if( m_DrawerVec[ t_VecNum ] != NULL ){
//描画終了フラグをセット
m_DrawerVec[ t_VecNum ]->SetDrawFlag( DRAW_DELETE );
result = 0;
}
}
return( result );
}

void Draw()
{
for( it = m_DrawerList.begin(); it != m_DrawerList.end(); )
{
//削除フラグが設定されていれば削除
if( it->GetObjectFlag() == DRAW_DELETE ){
it = m_DrawerList.erace( it );
}else{
//そうでなければ描画
it->Draw();
++it;
}
}
}




最後に【3】描画順の入れ替え

描画順を入れ替える場合は、下記の手順を踏みます。
--------------------------------------------------
1)既にリストに登録されている該当オブジェクトを削除
2)再度、優先順位を比較しながら登録

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

とりあえず、先ほどの削除と同じように、
フラグを設定 → 描画時にフラグ判別を行い、処理実行
という風にします。

具体的にいうと、下記の様に描画順変更フラグ(DRAW_CHANGE)がセットされていれば、
とりあえず描画 ⇒
該当の優先順位の箇所に登録(ChangeDrawObject関数)⇒
リストから削除

とします。
void Draw()
{
for( it = m_DrawerList.begin(); it != m_DrawerList.end(); )
{
switch( ( *it )->GetObjectFlag() ){
case DRAW_DELETE: it = m_DrawerList.erace( it );
break;
case DRAW_CHANGE: ( *it )->Draw();
ChangeDrawObject( *it );
it = m_DrawerList.erace( it );
break;
default: ( *it )->Draw();
++it;
break;
}
}
}



ただ、この方法には欠点があります。
1)優先度を低くした場合、1フレームだけ、オブジェクトが2回描画される。
2)描画順変更フラグをセットして、実際に描画順が変更されるのは1フレーム後。


まず1)についてですが、
優先順位を1から3に変更したとします。
そして、下記の様に登録されている場合、オブジェクトAが2回描画されます。

オブジェクトA … 優先度1
オブジェクトB … 優先度2
オブジェクトC … 優先度4

A描画 → B描画 → A描画 → C描画


次に、2)について。
描画をする瞬間に優先順位を変更する仕様の為、
優先度変更フラグセット⇒
描画⇒
描画後、適切な優先度の位置へ変更

という風に、ラグが生じます。


という訳で、優先順位変更は描画時に行うものではない
という結論に至りました。


最終的に実装は、
・リストに登録されている参照数計測ポインタを指すポインタを定義
・削除する際、ポインタを通じて、参照数計測ポインタにNULLを突っ込む
・描画する際、描画フラグ判定の前に、オブジェクトがNULLでないか確認


これも図で見た方が分かり易いので、下記をご覧ください。

drawsystem3.jpg
描画リストの参照数計測ポインタを指すポインタを定義します。
(赤い点線。)

削除する際は、そのポインタを用いて、
描画リスト上の参照数計測ポインタにNULLを突っ込みます。

描画オブジェクト配列からの参照は残っているので、
メモリ上のオブジェクトは解放されない点がポイントです。
drawsystem4.jpg

NULL突っ込んだ後は、あとは普通に登録するだけです。
※登録する際の大雑把な流れは、本記事上部参照。

void Draw()
{
for( it = m_DrawerList.begin(); it != m_DrawerList.end(); )
{
//NULLならリストから削除
if( ( *it ) == NULL ){
it = m_DrawerList.erace( it );
}else{
//削除フラグが設定されていれば削除
if( ( *it )->GetObjectFlag() == DRAW_DELETE ){
it = m_DrawerList.erace( it );
}else{
//そうでなければ描画
( *it )->Draw();
++it;
}
}
}
}


現状は上記の様な構造になってます。
その内、また改善版を考えるかもしれませんが、
その際は、また記事にして纏めます。

最後に。

上記に記載したソースコードは、
大雑把な流れだけ示したものなので、動作しません。(変数定義や、main関数がない)

なので、動作するソースコードを以下に記載します。
下記のソースコードをベースに、ローグライクゲームの開発を進めてみる事にします。

【ソースコード】 http://rudora3.web.fc2.com/RogueSample1.zip
ソースコード一式をUPしました!(`・ω・´)

main.cpp
CSingleton.h
CDrawerBase.h
CDrawerBase.cpp
CDrawer2D.h
CDrawer2D.cpp
CDrawerManager.h
CDrawerManager.cpp


RogueSample1_1.jpg


※Enterキーで描画順変更、
「→」キーで描画オブジェクト削除 という動作になってます。
※拡大・縮小のフラグが用意してありますが、現状設定しても動作に影響しません。
※とりあえず"grade.jpg"という画像を読み込むようにしてます。
※ソースコードは、適宜修正、改良する可能性があります。

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

Flashの貼り付け方

FlashをブログやHPに貼り付ける場合は、embed要素を使用します。

■embed要素
Netscape Navigator 2.0というブラウザにて、独自に実装されていた機能。
しかし、後にFirefoxやInternet Explorerなどの他のブラウザも対応し
使用することが出来るようになっている。

■使用できる属性
embed要素の中には、下記の属性を指定して
貼り付け方を決めることが出来る。

・src属性 ・・・ 貼り付けるデータのURLを入れる。
・width属性 ・・・ 表示領域の横幅を決める。
・Height属性 ・・・ 表示領域の縦幅を決める。
・border属性 ・・・ 周囲に枠線を引くかを決める。 (ドット単位の数値)
・hidden属性 ・・・ 表示するか、非表示にするかを決める。
    (特に指定しない場合は"表示"扱いになるので、
    非表示にしたい時のみ明示的に記載すればOKかなと。hidden = true)

・pluginspage属性 ・・・ 必要なプラグインが存在しない場合に、
            プラグインの入手先を提示をすることが出来る。(URLを指定)
・type属性 ・・・ 貼り付けるデータのタイプを指定する。
※貼り付けるデータのタイプと、対応する文字列の一覧。
WindowsMediaPlayer  application/x-mplayer2
RealPlayer audio/x-pen-realaudio-plugin
SoundVQ audio/x-twinvq-plugin
Quicktime audio/quicktime
Shockwave application/x-director
AIFF audio/aiff
ASF video/x-ms-asf
ASX video/x-ms-asf
AU audio/basic
FLASH application/x-shockwave-flash
MIDI audio/midi
M3U audio/mpegurl
MP3 audio/x-mp3
MPEG audio/mpeg" ? type="video/mpeg
WAV audio/wav
WAX audio/x-ms-wax
WM video/x-ms-wm
WMA audio/x-ms-wma
WMD application/x-ms-wmd
WMV video/x-ms-wmv
WMX video/x-ms-wvx
WMZ application/x-ms-wmz

■【貼り付けの例】
縦幅:300
横幅:300
データタイプ:Flash
データのURL:http://blog-imgs-43.fc2.com/r/u/d/rudora7/TestFlex.swf
外枠:6ドットの太さ
プラグインの入手先:http://www.adobe.com/go/getflashplayer_jp

上記のように貼り付け方を指定したい場合は、下記の様に記載します。

<embed src="http://blog-imgs-43.fc2.com/r/u/d/rudora7/TestFlex.swf" type="application/x-shockwave-flash"width="300" height="300" border = "6" pluginspage="http://www.adobe.com/go/getflashplayer_jp"/>
↓実際に貼り付けてみたFlash。
(画像読み込んで表示するだけ)


【追記】
"FC2ホームページ"にてアップロードしたファイルは、
直接リンクしないとファイルにアクセス出来ない(上記のFlashが表示されない)ようなので、
iframe形式(インラインフレームけいしき)にしてみました。

そういえば自作曲を公開するときもこの問題が発生して、
(アクセス権限が無いとか言われて、視聴できない)
"このURLをコピペして、直接貼り付けて確認してください"という対応をしたんだった。

↓iframe版。

※iframeについては、また別途解説記事としてまとめてみます

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

【FlashBuilder4.7】Flash・アプリ製作Tips

※作りかけのページです
ここにTips集を記載していきます。

Flashの貼り付け方

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

ネット上のリソースから読み込む/書き込むプログラムを作ってみた ~その1~

今回、インターネット上のリソース(画像、テキストとか)から
書きこむ/読み込むプログラム
を作ってみました。
(URLを指定して、ファイルを取得)

【余談】
作った背景について。

自分は現在、外部ファイルによって、
ゲーム内の動作を完全に書き換えるシステムを作っています。
(例えるならば、古いが、"ディスクシステム"みたいなもの)

ですが、各PCのローカルに保存した場合
外部ファイル自体を書き換える術が無いので、結局意味が無いのです。
(ゲームのアップデート版を作りました、
     再度外部ファイルをダウンロードして置き換えてください、となる)

それだと面倒臭いので、下記の様に
ネット上のリソースの読み込みが出来たらいいのでは?と思ったのです。
-------------------------------------------------
【1】ゲーム(exeファイル)を起動
【2】起動した際、ネット上にアップロードされた外部ファイルを確認
【3】新しいものに置き換わっていれば、ダウンロード
【4】ゲーム内の動作をリアルタイムで書き換えられる

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


で、そんな関数があるのか、と思ったらありました。
InternetReadFile関数です。

※この関数にかかわる機能を使うためには、
下記の様にヘッダのインクルードと、リンクを指定する必要があります。
----------------------------------------
#include <windows.h>
#include <wininet.h>
#pragma comment( lib, "Wininet.lib" )

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

~やり方~
【1】InternetOpen関数で、インターネットのハンドル取得
【2】InternetOpenUrl関数で、指定したURLの接続を確立
【3】InternetReadFile関数で、インターネット上のデータ読み込み
【4】InternetCloseHandle関数で、ハンドルをクローズ



関数に適切な引数を渡して、実行するだけなので特に難しいことはありません。
とりあえず、ネット上の画像を読み込んでローカルに書き出し、
そして、ローカルに書き出した画像を読み込んで表示する
プログラムを作ってみました。
(記事の一番下)

【注意】
エラー処理を入れていないので、下記の様な状況の場合の動作は保証できません。
・超巨大なファイルを読み込もうとした
・読み込み中に、インターネットの接続が切れた

【実行画像】
SampleInternetRead.jpg
※凛々しい なるほど君が表示されたら成功です。

と言うわけで、今後 自作ゲームを公開する際、
一度公開したらアップデートの手間は取らせないようにします

引き続き"ゲームの根本システム作り"を行っていきます。
早くゲーム内部の実装に入りたいですね。
(早く作りたいからと言って、全部グローバル変数の可搬性無しのコードを書いても
   長期的に見て意味が無い行為
になるので、ここは我慢である。)

【ソースコード】Sample_InternetReadFile.txt

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



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