その1 「WinMainのクラス化」

Windowsアプリを開発する上で、最初に出てくる面倒な奴「WinMain」

これを、アプリケーションを作るたびに書かなきゃいけない状況が億劫なので、
ライブラリ内に隠ぺいしてしまいます。


こちらのサイト様を参考にしました。
>> http://code4th.blogspot.jp/2011/06/winmain.html

とても感謝です!!


参考サイトのやり方から私なり作りましたコードは下記
ちなみに、今回のコードは全4ファイルから構成されています

Main.h
Main.cpp
この二つのファイルは、ライブラリ内に入るものではなく実際にライブラリを使用した際に
作成するものとなります

MNMainApp.h
MNMainApp.cpp
この二つのファイルが実際にWinMainを隠ぺいしているライブラリとなります。

補足:今回作っているのは「MNLib」と言うライブラリです。名前だけは一著前にもうつけていますbb


Main.h==================================================
class Main : public MNMainApp
{
public:
Main(void){}
~Main(void){}

void Init();
bool MainLoop();

};

========================================================

Main.cpp================================================
Main main_app;

void Main::Init()
{


bool Main::MainLoop()
{
return true;
}
========================================================

MNMainApp.h=============================================
class MNMainApp
{

public:
// WinMainを丸めこむ為、
static MNMainApp* my_instance;
MNMainApp(void);
virtual ~MNMainApp(void);
virtual bool MainLoop(){return false;}
int Boot();

void SetWindowSize(int w, int h);
void SetWindowName(LPCSTR window_name);
void SetAppName(LPCSTR app_name);

virtual void Init(){};

private:
int InitWindow();

private:
HWND hWnd_;

int window_w_;
int window_h_;
LPCSTR window_name_;
LPCSTR app_name_;
};
//====================================================

MNMainApp.cpp=========================================
HINSTANCE hInstance_;
HINSTANCE prev_instance_;
PSTR cmd_lin_;
int cmd_show_;


LRESULT CALLBACK WndProc(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
switch (msg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd , msg , wp , lp);
}


// WinMain
// windowsアプリが始まると最初にここへ入ります。
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE prev_instance, PSTR cmd_lin, int cmd_show )
{
hInstance_ = hInstance;
prev_instance_ = prev_instance;
cmd_lin_ = cmd_lin;
cmd_show_ = cmd_show;
return MNMainApp::my_instance->Boot();
}
MNMainApp* MNMainApp::my_instance = NULL;
MNMainApp::MNMainApp(void)
: window_w_(800)
, window_h_(600)
, window_name_("MNWindow")
, app_name_("MNApp")
{
my_instance = this;
}
MNMainApp::~MNMainApp(void){}

int MNMainApp::Boot()
{
MNMainApp();
Init();
InitWindow();

MSG msg;
do{
Sleep(1);
if( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) ){ DispatchMessage(&msg);}
else{
if(!MainLoop())
{
msg.message = WM_QUIT;
}
}
}while(msg.message != WM_QUIT);

return 0;
}

int MNMainApp::InitWindow()
{
WNDCLASS winc;

winc.style = CS_HREDRAW | CS_VREDRAW;
winc.lpfnWndProc = WndProc;
winc.cbClsExtra = winc.cbWndExtra = 0;
winc.hInstance = hInstance_;
winc.hIcon = LoadIcon(NULL , IDI_APPLICATION);
winc.hCursor = LoadCursor(NULL , IDC_ARROW);
winc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
winc.lpszMenuName = NULL;
winc.lpszClassName = window_name_;

if (!RegisterClass(&winc)) return 0;

hWnd_ = CreateWindow(
window_name_ , app_name_ ,
WS_OVERLAPPEDWINDOW ,
100 , 100 , window_w_ , window_h_ , NULL , NULL ,
hInstance_ , NULL
);

if (hWnd_ == NULL) return 0;

ShowWindow(hWnd_ , SW_SHOW);

return 0;
}


void MNMainApp::SetWindowSize(int w, int h)
{
window_w_ = w;
window_h_ = h;
}
void MNMainApp::SetWindowName(LPCSTR window_name)
{
window_name_ = window_name;
}
void MNMainApp::SetAppName(LPCSTR app_name)
{
app_name_ = app_name;
}
//====================================================


基本的な所は、先ほど乗せたサイト様に乗っているので、
実際私が作った所をメインに書いていきます


「int MNMainApp::Boot()」内に入った時に、Windowの生成を行い、
メインループを行います。
この時、ユーザーが諸々の初期化(WindowSizeや名前等)を行えないとまったくもって使えない子になります
そこで、Windowの生成の前に直接Init()を呼び出す事によって、初期化を行えるようにしました。

つまり、Main.cpp内の「void Main::Init()」に「SetWindowSize(1280, 720);」という風に記述するだけで、
Windowのサイズを変更する事が可能になります。


これからDirectXを使ってデバイスの使用をすると思いますが、これらの管理もこのクラスが行う事が出来るので
非常に使い勝手がいい物になるのではないでしょうか?



ただ、今のまんまだとコードが汚い気がするので書きなおそうかしら?



その1はこれにてお終いです。

果たしてサイト様の意図を汲んだコードになったのか不安が残りますが・・・。

ライブラリ開発をしよう!

すごく久しぶりに再会します。

自作ライブラリ開発にいよいよ本格的に乗り出そうと思います。
最大の理由は今まで自分でライブラリを作った事です。
誰かが作ったライブラリはいくつか持っているのですが、これを使ってゲーム作ったって
それは1から自分で組み上げたものではないので、気分が悪いです。

と言う事で本格的に開発に乗り出します!


プラットフォームはWindowsでDirectX9ベースで組み上げたいと思います。

最近忙しくて更新がなかなかできません。


さて、今回は#ifdefを使って、サンプルプログラムを作るのが楽しくなる方法を書いていきます。

サンプルプログラムは基本的な構文をテストしたり、実装のテストをするのに誰しも作ると思います。
そんなサンプルプログラムはいつでも見れる状態にしておくのがいいと思いませんか?
例えば、これってどんなんだっけ?とか、誰かに聞かれたりした時など瞬時に出せるのが望ましいですね。

そんな事をやる時、私がやっている方法は、#ifdefを使ってフラグを管理する事です。


サンプル
==================================================================
SampleFlags.h

//サンプルプログラムのフラグヘッダ

#define SAMPLE_IFDEF //ifdefのサンプル
//#define SAMPLE_FOR_LOOP //forループのサンプル
//#define SAMPLE_FOR_EACH //for eachのサンプル

===================================================================

==================================================================
Main.h
//mainのヘッダ
#include "SampleFlags.h"


==================================================================

==================================================================
Main.cpp
//main処理
#include "Main.h"

//以下コード
#ifdef SAMPLE_IFDEF
#include

int main()
{

//ここにサンプル処理

return 0;
}

#endif

#ifdef SAMPLE_FOR_LOOP

int main()
{
//ここにサンプル処理
return 0;
}
#endif

#ifdef SAMPLE_FOR_EACH

int main()
{
//ここにサンプル処理
return0;
}
#endif

==================================================================



さて、こんな感じですけど、やってる事は判りますか?
SampleFlags.hにサンプルに対してのdefine定義をしています。
これがサンプルの一覧の表示になり、コメントアウトを外したものを確認する事が出来ます。

コードをみれば判ると思いますが、一つのdefine定義に完成したコードの記述を行います。
だから、#includeやint main()等もしっかりとかきましょう。#include等は毎回かくのは面倒だ!
という意見もありそうですが、この処理に必要なincludeはこれだと言う事がどちらにしても必要になります。
最小限のinclude以外はいらない訳ですから、そこは面倒がらずにしっかりとやりましょう。

ちなみに、何故Main.hをわざわざ作ったかと言うと、ファイルを跨いで処理を行う場面のテストに対応する為です。
この時もしっかりと、#ifdefでくくって、どの処理の内容なのか明示的にする必要がありますね。

これが続くと自分のサンプル集が出来ていき、なぜかサンプルを作るのが楽しくなってきます♪


こうしてこんなページを作ってしまうわけですよ……。。


あくまでも自分なりの方法なので、もっといい方法が有ったり、自分には合わないって人は控えて下さい。


では終わり。

さて、仕事の続きでもやるかー

データを固める


まず初めに、何の為にゲーム制作を行うのか。
これを問いたいと思います。


単純ですが、私的には誰かに遊んで貰いたいから。これに尽きると思うんですよ。
そして、自分が作ったゲームを楽しんでもらえたら最高じゃないですか。

さて、何でこんな話をしたかと言いますと、ゲームを遊んでくれるプレイヤーにとって
より楽しんでもらうためのUI(ユーザーインターフェイス)を意識してゲームを作っているのか?
という事を書きたかったからです。
UIとは広い範囲ですが、プレイヤーにとって一番のUIはロード時間の軽減だと私は思います。
早くゲームをやりたいのに、ロードが頻繁に長時間行われるとそれだけで萎えちゃいますよね。

ではロード時間を速くするにはどうすればいいのか?
色々な方法があると思います。

その方法の一つとしてデータを固めると言った方法をご紹介していこうと思います。


まず準備として、データを固めるツールの作成を行います。
固めると言ってもzipやgzでの圧縮ではないですよ?
あくまでも、複数のファイルを一つにまとめる事が目的です。

今回使うのは『C#』です。

何故C#かと言うと、単に便利だからです。
余計な事考えずにデータとかの操作が行えるので……別にc++でもよかったんですが、
ツールでc++を使う利点とかそんなにないし。。。


固めるツールの仕様としては
・exeの置いてあるディレクトリと同じところに有るフォルダーまたはファイルを全て固める。
・固めたファイルの情報をまとめたテキストを作る
・テキストには、ファイル名,始まりのバイト,終わりのバイトをかきこむ事

このくらいでいいのでは?

以下サンプル、判り易さ重視です。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections;


namespace dat_making
{
class Program
{
//エラー部
enum ErrorMessage
{
eSUCCESS,
eACCESS_FAILED,
};

//ディレクトリ内のファイルをリストへ登録します
static ErrorMessage FileCheck(string sz, ArrayList fileList)
{
//指定したディレクトリのファイルを全て保存
string files = Directory.GetFiles(sz);

//ディレクトリにフォルダーが有った場合
string dirs = Directory.GetDirectories(sz, "*", SearchOption.AllDirectories);
foreach (string s in dirs)
{
//再起呼び出しでフォルダー内も同様の処理
FileCheck(s, fileList);
}

int cnt = fileList.Count;
foreach (string s in files)
{
//.exe(このexe等)は含まない
if (Path.GetExtension(s) != ".exe")
{
//リストへ登録
fileList.Add(s);
cnt++;
}
}
if (cnt == 0)
{
//ファイルがないのでエラー
return ErrorMessage.eACCESS_FAILED;
}
//リスト登録成功
return ErrorMessage.eSUCCESS;

}

//メイン処理
static void Main(string args)
{
string myPath = Directory.GetCurrentDirectory(); //現在のカレントパスを取得
ArrayList fileList = new ArrayList();
ErrorMessage myError;

Console.WriteLine(myPath);

//カレント内のファイルを全て探し出し、パスを取得する
myError = FileCheck(myPath, fileList);

//エラー処理
if (myError == ErrorMessage.eACCESS_FAILED)
{
Console.WriteLine("結合ファイルが有りません");
Console.WriteLine("続行するには何かキーを押してください...");
Console.ReadKey();
return;
}

Console.WriteLine("ファイル数:" + fileList.Count);

byte oneFile;
UInt64 allByte = 0;
foreach (string s in fileList)
{
//ファイル名を全てフルパスで書出し。
Console.WriteLine(s);
//ファイル全てのバイト数を調べる
oneFile = File.ReadAllBytes(s);
allByte += (ulong)oneFile.Length;

}
Console.WriteLine("全てのバイト数:" + allByte);
byte[] allBuf = new byte[allByte];
int count = 0;

//個々のファイルのバイト数を記録
ArrayList map = new ArrayList();
foreach (string s in fileList)
{
oneFile = File.ReadAllBytes(s);
Array.Copy(allBuf, count, oneFile, 0, oneFile.Length);
map.Add(count + oneFile.Length);
}

File.WriteAllBytes*1;
UInt64 iii = 0;
for (int i = 0; i < fileList.Count; ++i )
{
//sz = ファイル名, ファイルの開始位置, ファイルサイズ
string sz = fileList[i] + "," + iii + ","+ map[i];
sw.WriteLine(sz);
//ファイルの開始位置のために
iii += Convert.ToUInt64(map[i]);
}
sw.Close();

Console.WriteLine("全て成功");
Console.WriteLine("続行するには何かキーを押してください...");
Console.ReadKey();
return;

}
}
}

何が何だかわかんねって方はとりあえずC#のコンソール作ってコードをコピペで動かしてみて下さい。

先ほど書きましたが、exeと同じディレクトリにフォルダもしくはファイルは全て固められます。
つまり、余計なファイルやフォルダ等を入れておいたらそれらも固められるので注意が必要です。



では、実際に固めたデータのアクセスと、個々のデータのアクセスで差が出るのか検証してみます。
次はC++を使いますね。

#include
#include
#include

int main()
{
using namespace std;

clock_t now;
clock_t out;

char sz[255];
FILE* fp;
now = clock();

for(int cnt = 0 ; cnt < 100 ; ++cnt)
{
for(int i = 0 ; i < 10 ; ++i)
{
for(int j = 0 ; j < 10 ; ++j)
{
sprintf(sz, "file\\HOGEHOGE%d\\test%d.txt", i, j);
fopen_s(&fp, sz, "rb");
fclose(fp);
}

}
}
out = clock();



cout << "個別ファイルオープン(ミリ秒)" << endl;
cout << now << endl;
cout << out << endl;
cout << out - now << endl;



now = clock();
for(int cnt = 0 ; cnt < 100 ; ++cnt)
{

fopen_s(&fp, "file\\srcAll.dat", "rb");
fclose(fp);
}
out = clock();


cout << "固めたデータ(ミリ秒)" << endl;
cout << now << endl;
cout << out << endl;
cout << out - now << endl;


return 0;
}


出力:
個別ファイルオープン(ミリ秒)
1
790
789
固めたデータ(ミリ秒)
802
809
7


最初ファイル100個でやってもクロックだと結果が判り難かったので、同じ処理を100回繰り返すようにしました。
およそ100倍の差が出ていますね。まぁ実際はこんなに差は出ませんが、アクセスする数が減ればそれだけ
効果を生む事が出来る訳です。

今回のテストでは、ファイル名がめんどくさかったのでsprintfなんてものを使ってしまいました、sprintfだけの計算で
およそ38フレームだったので、やっぱり今回の実験では100倍のアクセス時の差があるって言っても間違えではないかも…。


しかし、実際データとして使うために一つの固めたファイル自体をわけなければいけないので、実際やってみると
100倍の差も出ないと思います。


ちなみに、この固めたデータを使う場合ファイルオープンは常にしっぱなしになります。ファイルを開くのだけでこれほど時間がかかるんですから当然です。
しかし、思いもよらないところでゲームが落ちたりすると、ファイルが開きっぱなしとかになってしまうので十分注意して下さい。


さて、長編ものの終了となります。

どうもお疲れ様でした。

*1:myPath + "\\srcAll.dat"), allBuf); //datファイルを読むための情報 StreamWriter sw = new StreamWriter((myPath + "\\srcAll.txt"

ちょっとマニアック その2『mallocのアライメント』

データを動的に扱うためにmallocというものを使う人も
結構多いのではないのでしょうか?

しかしそのmallocの特性上気を使わないといけない場面が有ります。


そもそもmallocでchar型のデータを確保した時って
実際ヒープにはどのくらいの領域を取られるか知っていますか?

正解は8バイトです。

char型は1バイトだろ!!!
っていう人が出てくるかと思いますが、そこがmalllocの特性上に関わるお話

mallocはどのデータ型にも対応するため戻り値がvoid*なのはご存じですよね?
故に32ビットマシンのdoubleにも対応させなくてはいけません。そのため、
取得される領域は8バイトからとなるわけです。

windowsではさほど問題がない(?)のかもしれませんが、重要なのは
メモリを跨いで取得を行わないという事です。←ちょっと言葉が悪いかも
通常メモリとCPUは4バイトのデータバスで転送が行われます。

例をあげて判り易くご説明しましょう。

1.2000番地から4バイト確保
2.2004番地から2バイト確保
3.2006番地から4バイト確保

この時1のデータ転送はデータバス1回、2も1回
しかし3の場合2回の転送が行われる事になります。
データの転送は大変時間がかかるもの、
プログラマならば余計な処理を少しでも減らしたいものですよね??


その問題を解決するためにアライメントというのは大変重要なものだと
言う事がわかると思います。


実際にアライメントしてからのメモリ確保の方法は
_aligned_mallocというものを使えば簡単に出来ます。
しかし今回はそれを使わずmallocだけを使ってアライメントさせます。
何故_alined_mallocを使わないかと言うと、確保した領域が明示的ではないからです。
実際確保する量は内部で勝手にやってしまうため、
100バイトを128バイトでアライメントして確保してと言っても、実際いくら確保されたか
は知ることが出来ないのです。

ではmallocを使ってのサンプル

#include
#include
#include
int main()
{
static const int ALIGNE_SIZE = 64;

char* str;

str = (char*)malloc((100 + ALIGNE_SIZE -1) / ALIGNE_SIZE * ALIGNE_SIZE);
int size = _msize(str);

std::cout << "malloc時のサイズ:" << size << std::endl;


free(str);
}
出力:
malloc時のサイズ:128

今回は100バイト確保して、64バイトでアライメントをしています。
ちなみにアライメント後に確保された領域のサイズを知るには
_msize(確保したポインタ) これで確保されたサイズを知ることが出来ます。
直接sizeof(str)とかやっても4バイトしか出てきませんよ?

ここで注意!
メモリ確保は最初に言いましたが、8バイト単位で行われます。なので
10バイトとかでアライメントしても無意味です。


こんな感じで終わります

ちょっとマニアック その1『物理メモリの使用量を調べる』

さて、ゲーム制作で必要なのかどうかわからない部門として

手始めに物理メモリの使用量の調べ方を書いていきたいと思います。


//現在の物理メモリの状況を表示してみる
#include
#include
#include

int main()
{
using namespace std;

MEMORYSTATUSEX ms = {sizeof(MEMORYSTATUSEX)};
GlobalMemoryStatusEx(&ms);

//空きメモリ
DWORDLONG dwFree = ms.ullAvailPhys;
//使用メモリ
DWORDLONG dwUsed = ms.ullTotalPhys - ms.ullAvailPhys;
//搭載メモリ
DWORDLONG dwSize = ms.ullTotalPhys;


cout << "搭載メモリ:" << dwSize << endl;
cout << "空きメモリ:" << dwFree << endl;
cout << "使用メモリ:" << dwUsed << endl;

return 0;
}


まあこんなかんじです。
特に説明とかはいらないですよね?
とりあえずWin32APIのひとつです。


おっわりー