「ゲーム開発」カテゴリーアーカイブ

ゲーム開発36:坂道の当たり判定2

当たり判定を全面的に作り直しました。

これまでは「壁に当たったらそれ以上動かない」という処理をしていました。これは簡単に計算できるのですが、移動量が大きいと壁に近づけない、壁に埋まると動けなくなるという欠点がありました。それの対策のために色々と条件式を詰め込んだら、それなりに動くようになったものの、ソースがぐちゃぐちゃになったり。まぁ俺の組み方が悪いんだろうけど。

今回は「壁に埋まったら戻す」にしました。これならとにかく壁に体当りして、埋まってしまったらその分戻すというだけなので、ややこしい計算はいりません。これならラクチンだと思ったのですが…複数の物体にぶつかった際に、どこにどれだけ戻せばいいのかわからない。

これに関してはあまり深く考えずに、複数当たっていても1個ずつ処理していく事にしました。このやり方には欠点があって、処理する順番によって結果が変わる可能性があるんだけど、今のところおかしく見えないからいいか。

…といった前提があって、ようやく坂道の処理です。上記の方法では、当たった場合にどれだけ戻せば良いかを計算する訳ですが、坂道の場合、横から当たろうが上から当たろうが、当たったら必ず上方向に戻します。

下記CheckHitSlope()ではキャラクタの右下と左下の点で坂道と当たっているかを計算し、当たっていたら戻りの大きい方を実際に使う値として返しています。slopeを変えることで上り坂も下り坂も、急な坂もなだらかな坂もこの関数で計算出来ます。ちょっと効率が悪かったり、intとLONGが混在してたりするのは気にしないw

思った以上に当たり判定作り直すのに時間かかってしまったので、スケジュール的にやばくなってきました(汗)

//—————————————————————————–
// 当たり判定(坂)
// charaRect:キャラクタの矩形
// myRect:坂道の矩形
// slope:坂道の傾き
// *ret_x:X方向に戻す値
// *ret_y:Y方向に戻す値
// 戻り値:接地したか
//—————————————————————————–
int CField::CheckHitSlope(RECT charaRect, RECT myRect, float slope, int *ret_x, int *ret_y)
{
 int  ret=FALSE, work_y=0;
 LONG top=0, left=0, right=0;

 // 左下の高さと戻す量
 if (myRect.left<=charaRect.left && charaRect.lefttop) {
   left=top-charaRect.bottom;
  }
 }
 // 右下の高さと戻す量
 if (myRect.left<=charaRect.right && charaRect.righttop) {
   right=top-charaRect.bottom;
  }
 }
 // 戻す量の大きい方を使う
 if (left>right) {
  work_y=right;
 }
 else {
  work_y=left;
 }

 // 接地している?
 if (work_y<0) {
  ret=TRUE;
 }

 *ret_x=0;
 *ret_y=work_y;

 return ret;
}

ゲーム開発35:エフェクトの準備

久しぶりにゲームプログラミングです。

今回はエフェクトを導入すべくクラスを作成しました。エフェクト管理クラスCEffectManageエフェクトクラスCEffectです。とは言え、どちらも前に作ったキャラクタ系のクラスであるCCharaManageとCCharaから当たり判定とかを削除した程度のものです。

ホントはキャラクタクラスにそのままエフェクトも乗せちゃおうかと思ったけど、何となく分けた。

CEffectを継承して着地エフェクトクラスCLandを作成。地面に着地した時に砂ぼこりが出るような処理を入れてみたら、一応ちゃんと動きました。キャラクタクラスがCLandを呼び出して、表示が終わったらdeleteしてます。まぁとりあえずこれで大丈夫かな。

エフェクトは他に攻撃&ダメージは入れるとして、あと何が必要だろう。ちょっと他のゲームを見て研究してみるか。

プレイ動画を作成してみる

hibakama-movie01自作ゲームをイベントに出す際、前々からPCを持ち込んでデモを流したいなぁと思ってました。という訳で練習を兼ねてデモムービーを作ってみた。

まず動画を取り込むためにアマレココというツールを使います。次に動画を編集するためにWindowsムービーメーカーを使いました。この時点で画質落ちた(汗) それをAviUtlで縮小してH.264形式に変換して完成。JW Player for Flashでページに貼り付けました。

ここで確認できます。
http://www.yagasuri.com/prog/

結構時間かかっちゃったけど、1回覚えれば次からはスムーズに作業できそうです。あと、もっと調べれば画質や効率が良いやり方があるんだろうけど、とりあえずはこれで。

しかしこうやって動画で見てみると、エフェクトが無いから地味だなぁ。今後のゲーム開発ではその辺を強化していきます。

ゲーム開発34:また方向性を考えてみる

冬コミから1月半。そろそろ先へ進むべくまた方向性を考えてみます。最低限のゲーム機能は既に出来てるので、見栄えとかアクションの作り込みをやるかな。

・ストーリーとキャラクタを作る
前にもこれ書いたけど、全然入れられてないなw 何となくの構想はあるので、それをちゃんと設定して入れ込んでいきます。ザコキャラの数も少なくて寂しいので、増やします。

・絵を描き直す
今ある絵は仮のものなので、ちゃんとした絵に置き換えていきます。個人的にはリアル系よりディフォルメ系の絵かな。アニメーションパターンも増やします。

・フィールド上にギミックを増やす
今は動く床くらいしか無いので、もっと増やしていきます。パズル的な要素まで入れ込みたいけど、まずは簡単なものからかな。また当たり判定作り直しになるかも…

・特殊効果(エフェクト)を入れる
攻撃した時のダメージ表示とか、フィールド上での副次的なアニメーションとかだけど、今は全然やってないんだよね。これをやらないと地味だからなぁ。

他にも必要なものがあると思うけど、とりあえずこの辺りから手をつけようかね。

完成目標8月!

ゲーム開発33:バグ修正

名称未設定 2コミケで公開したものには既知のバグがあったので修正しました。

坂道を実現するために、ちょっとした高さの障害物は乗り越えるようにしたんだけど、その判定が甘くて壁に引っ掛かってしまうというバグが発生してました。

コミケでは間に合わなかったけど、ようやく修正できました。こちらのゲーム開発ページからダウンロードして下さい。

まだ挙動の怪しい所があるけど、いちおう一通りの処理が完成したので、ようやくゲームとして作り込む作業に入れそうです。

ゲーム開発32:XAudio2でWaveを再生する2

続きです。今回はWaveファイルを読み込んで解析する処理です。

mmioというマルチメディア入出力関数があるので、それを使って解析すればOKです。

前回のCAudioBaseクラスと今回のCAudioWaveクラスの上にCAudioクラスという緩衝材を入れてますが、ほとんどCAudioBaseと同じなので省略します。

これでようやく音が鳴るようになりました。次はまた当たり判定かなぁ。いい加減当たり判定もFIXしたいところ。

//-----------------------------------------------------------------------------
// 生成
CAudioWave::CAudioWave()
{
 m_pwfx=NULL;
 m_hmmio=NULL;
 m_pResourceBuffer=NULL;
 m_dwSize=0;
}
//-----------------------------------------------------------------------------
// 解放
CAudioWave::~CAudioWave()
{
 Close();

 SAFE_DELETE_ARRAY(m_pwfx);
}
//-----------------------------------------------------------------------------
// オープン
HRESULT CAudioWave::Open(LPSTR strFileName, WAVEFORMATEX* pwfx)
{
 HRESULT hr;

 if (strFileName==NULL) {
  return FALSE;
 }
 SAFE_DELETE_ARRAY(m_pwfx);

 // ファイルオープン
 m_hmmio=mmioOpen(strFileName, NULL, MMIO_ALLOCBUF | MMIO_READ);
 if (FAILED(hr=ReadMMIO())) {
  mmioClose(m_hmmio, 0);
  return FALSE;
 }

 if (FAILED(ResetFile())) {
  return FALSE;
 }

 // ファイルサイズ保持
 m_dwSize=m_ck.cksize;

 return TRUE;
}
//-----------------------------------------------------------------------------
// マルチメディア関数で読み込み
HRESULT CAudioWave::ReadMMIO()
{
 MMCKINFO  ckIn;
 PCMWAVEFORMAT   pcmWaveFormat;

 m_pwfx=NULL;

 if ((0!=mmioDescend(m_hmmio, &m_ckRiff, NULL, 0))) {
  return E_FAIL;
 }

 // Waveファイルかどうか
 if ((m_ckRiff.ckid!=FOURCC_RIFF) || (m_ckRiff.fccType!=mmioFOURCC('W', 'A', 'V', 'E'))) {
  return E_FAIL;
 }

 // FMTチャンク検索
 ckIn.ckid=mmioFOURCC('f', 'm', 't', ' ');
 if (0!=mmioDescend(m_hmmio, &ckIn, &m_ckRiff, MMIO_FINDCHUNK)) {
  return E_FAIL;
 }

 // pcmWaveFormat取得
 if (mmioRead(m_hmmio, (HPSTR)&pcmWaveFormat, sizeof(pcmWaveFormat))!=sizeof(pcmWaveFormat)) {
  return E_FAIL;
 }

 // 余分なBYTEがある?
 if (pcmWaveFormat.wf.wFormatTag==WAVE_FORMAT_PCM) {
  m_pwfx=(WAVEFORMATEX*)new CHAR[sizeof(WAVEFORMATEX)];
  if (NULL==m_pwfx) {
   return E_FAIL;
  }

  // waveformatexコピー
  memcpy(m_pwfx, &pcmWaveFormat, sizeof(pcmWaveFormat));
  m_pwfx->cbSize=0;
 }
 else {
  // 余分なBYTE読み込み
  WORD cbExtraBytes=0L;
  if (mmioRead(m_hmmio, (CHAR*)&cbExtraBytes, sizeof(WORD))!=sizeof(WORD)) {
   return E_FAIL;
  }

  m_pwfx=(WAVEFORMATEX*)new CHAR[sizeof(WAVEFORMATEX)+cbExtraBytes];
  if (NULL==m_pwfx) {
   return E_FAIL;
  }

  // waveformatexコピー
  memcpy(m_pwfx, &pcmWaveFormat, sizeof(pcmWaveFormat));
  m_pwfx->cbSize=cbExtraBytes;

  // 余分なBYTE読み込み
  if (mmioRead(m_hmmio, (CHAR*)(((BYTE*)&(m_pwfx->cbSize))+sizeof(WORD)), cbExtraBytes )!=cbExtraBytes) {
   SAFE_DELETE(m_pwfx);
   return E_FAIL;
  }
 }

 // FMTチャンクから出る
 if (0!=mmioAscend(m_hmmio, &ckIn, 0)) {
  SAFE_DELETE(m_pwfx);
  return E_FAIL;
 }

 return S_OK;
}
//-----------------------------------------------------------------------------
// サイズ取得
DWORD CAudioWave::GetSize()
{
 return m_dwSize;
}
//-----------------------------------------------------------------------------
// ファイルリセット
HRESULT CAudioWave::ResetFile()
{
 if (m_hmmio==NULL) {
  return CO_E_NOTINITIALIZED;
 }

 // データ検索
 if (-1==mmioSeek(m_hmmio, m_ckRiff.dwDataOffset+sizeof(FOURCC), SEEK_SET)) {
  return E_FAIL;
 }

 // データチャンク検索
 m_ck.ckid=mmioFOURCC('d', 'a', 't', 'a');
 if (0!=mmioDescend(m_hmmio, &m_ck, &m_ckRiff, MMIO_FINDCHUNK)) {
  return E_FAIL;
 }

 return S_OK;
}
//-----------------------------------------------------------------------------
// ファイルから読み込み
HRESULT CAudioWave::Read(BYTE* pBuffer, DWORD dwSizeToRead, DWORD* pdwSizeRead)
{
 MMIOINFO mmioinfoIn;

 if (m_hmmio==NULL) {
  return CO_E_NOTINITIALIZED;
 }
 if (pBuffer==NULL || pdwSizeRead==NULL) {
  return E_INVALIDARG;
 }

 *pdwSizeRead=0;

 if (0!=mmioGetInfo(m_hmmio, &mmioinfoIn, 0)) {
  return E_FAIL;
 }

 UINT cbDataIn=dwSizeToRead;
 if (cbDataIn>m_ck.cksize) {
  cbDataIn=m_ck.cksize;
 }

 m_ck.cksize-=cbDataIn;

 for (DWORD cT=0; cT<cbDataIn; cT++) {
  // BYTEコピー
  if (mmioinfoIn.pchNext==mmioinfoIn.pchEndRead) {
   if (0!=mmioAdvance(m_hmmio, &mmioinfoIn, MMIO_READ)) {
    return E_FAIL;
   }

   if (mmioinfoIn.pchNext==mmioinfoIn.pchEndRead) {
    return E_FAIL;
   }
  }

  // コピー
  *((BYTE*)pBuffer+cT)=*((BYTE*)mmioinfoIn.pchNext);
  mmioinfoIn.pchNext++;
 }

 if (0!=mmioSetInfo(m_hmmio, &mmioinfoIn, 0)) {
  return E_FAIL;
 }

 *pdwSizeRead=cbDataIn;

 return S_OK;
}
//-----------------------------------------------------------------------------
// ファイルを閉じる
HRESULT CAudioWave::Close()
{
 mmioClose(m_hmmio, 0);
 m_hmmio=NULL;
 SAFE_DELETE_ARRAY(m_pResourceBuffer);

 return S_OK;
}

ゲーム開発31:XAudio2でWaveを再生する1

前にXACT3Waveを再生する処理を書きましたが、いちいちWaveファイルを固めるのはメンドくさい。

てな訳で、改めてXAudio2でWaveを再生することにしました。まぁサンプルにそのものずばりな物があるので(XAudio2BasicSound)、それを元にゲームで使える様な物に作り替えます。

基本的な処理の流れは、COMを初期化してXAudio2を使えるようにする。マスターボイスを作ってその中にWaveから取り出したソースボイスを入れる。再生。といった感じです。

今回はXAudio2の部分CAudioBaseクラスだけ記述しました。Waveファイルからデータを取り出す処理は次回。

//--------------------------------------------------------------------------------------
// 生成
CAudioBase::CAudioBase()
{
 for (int i=0; i<MAX_SOUND; i++) {
  pSourceVoice[i]=NULL;
  pXAudioBuffer[i]=NULL;
  pbWaveData[i]=NULL;
 }

 pXAudio2=NULL;
 pMasteringVoice=NULL;
}
//-----------------------------------------------------------------------------
// 解放
CAudioBase::~CAudioBase()
{
 Release();

 // マスターボイス解放
 if (pMasteringVoice!=NULL) {
  pMasteringVoice->DestroyVoice();
  pMasteringVoice=NULL;
 }

 // XAudio解放
 SAFE_RELEASE(pXAudio2);

 // COM解放
 CoUninitialize();
}
//--------------------------------------------------------------------------------------
// 初期化
BOOL CAudioBase::Init()
{
 // COM初期化
 CoInitializeEx(NULL, COINIT_MULTITHREADED);

 // デバッグモードの場合
 UINT32 flags=0;
#ifdef _DEBUG
 flags|=XAUDIO2_DEBUG_ENGINE;
#endif

 // XAudio2生成
 if (FAILED(XAudio2Create(&pXAudio2, flags))) {
  CoUninitialize();
  return E_FAIL;
 }

 // マスターボイス生成
 if (FAILED(pXAudio2->CreateMasteringVoice(&pMasteringVoice))) {
  SAFE_RELEASE(pXAudio2);
  CoUninitialize();
  return E_FAIL;
 }

 return S_OK;
}
//-----------------------------------------------------------------------------
// 解放
void CAudioBase::Release()
{
 // ソースボイス解放
 for (int i=0; i<MAX_SOUND; i++) {
  ReleaseSound(i);
 }
}
//-----------------------------------------------------------------------------
// サウンド解放
void CAudioBase::ReleaseSound(int no)
{
 if (pbWaveData[no]!=NULL) {
  SAFE_DELETE(pbWaveData[no]);
 }
 if (pXAudioBuffer[no]!=NULL) {
  SAFE_DELETE(pXAudioBuffer[no]);
 }
 if (pSourceVoice[no]!=NULL) {
  pSourceVoice[no]->DestroyVoice();
  pSourceVoice[no]=NULL;
 }
}
//--------------------------------------------------------------------------------------
// ファイル読み込み
int CAudioBase::LoadSound(LPSTR szFilename, int loop_flag)
{
 int  i;

 // 空いているWaveがあるか検索
 for (i=0; i<MAX_SOUND; i++) {
  if (pSourceVoice[i]==NULL) {
   break;
  }
 }
 if (i>=MAX_SOUND) {
  return -1;
 }

 // Waveファイル読み込み
 CAudioWave wav;
 if (FAILED(wav.Open(szFilename, NULL))) {
  return -1;
 }

 // Waveファイルのフォーマット取得
 WAVEFORMATEX* pwfx=wav.GetFormat();

 // サイズ計算
 DWORD cbWaveSize=wav.GetSize();

 // メモリにデータを乗せる
 pbWaveData[i]=new BYTE[cbWaveSize];
 if (FAILED(wav.Read(pbWaveData[i], cbWaveSize, &cbWaveSize))) {
  SAFE_DELETE_ARRAY(pbWaveData[i]);
  return -1;
 }

 // ソースボイス生成
 if (FAILED(pXAudio2->CreateSourceVoice(&pSourceVoice[i], pwfx))) {
  SAFE_DELETE_ARRAY(pbWaveData[i]);
  return -1;
 }

 if (loop_flag!=0) {
  loop_flag=XAUDIO2_LOOP_INFINITE;
 }

 // オーディオバッファ設定
 pXAudioBuffer[i]=new XAUDIO2_BUFFER;
 pXAudioBuffer[i]->Flags=XAUDIO2_END_OF_STREAM;
 pXAudioBuffer[i]->AudioBytes=cbWaveSize;
 pXAudioBuffer[i]->pAudioData=pbWaveData[i];
 pXAudioBuffer[i]->PlayBegin=0;
 pXAudioBuffer[i]->PlayLength=0;
 pXAudioBuffer[i]->LoopBegin=0;
 pXAudioBuffer[i]->LoopLength=0;
 pXAudioBuffer[i]->LoopCount=loop_flag;
 if (FAILED(pSourceVoice[i]->SubmitSourceBuffer(pXAudioBuffer[i]))) {
  pSourceVoice[i]->DestroyVoice();
  SAFE_DELETE_ARRAY(pbWaveData[i]);
  return -1;
 }

 return i;
}
//--------------------------------------------------------------------------------------
// サウンド再生
void CAudioBase::PlaySound(int no)
{
 if (no==-1 || pSourceVoice[no]==NULL) {
  return;
 }
 StopSound(no);
 pSourceVoice[no]->FlushSourceBuffers();
 pSourceVoice[no]->SubmitSourceBuffer(pXAudioBuffer[no]);
 HRESULT hr=pSourceVoice[no]->Start(0);
}
//--------------------------------------------------------------------------------------
// サウンド停止
void CAudioBase::StopSound(int no)
{
 if (no==-1 || pSourceVoice[no]==NULL) {
  return;
 }

 pSourceVoice[no]->Stop(0);
}

ゲーム開発30:坂道との当たり判定

坂道との当たり判定だいぶ間があいたけど、いくつかの処理を追加しました。

マップチップをシンプルに
ドット絵をあれこれいじくり回してたけど、結局シンプルな形にしてしまいました。地味だけど仕方ない。そのうちマップチップを増やして、奥行きを表現したいです。

縦スクロール対応
今までは横スクロールしかしませんでしたが、マップエディタ導入を機にマップサイズを可変にし、縦にもスクロール出来るようにしました。横と違い、staticな変数を持って、ゆっくりスクロールするようにしてます。縦が激しいと酔う。

坂道に対応
坂道を上り降り出来るようになりました。方法は線分の交差判定を使ってます。キャラクタの足元を横一直線の線分とし、坂道の線分との重なりで当たり判定を取っています。この方法なら複雑な地形も簡単に作れるなぁ。実装はネット上にあったコードを参考に組んでます。

処理落ち(汗)
大した事してないはずなのに処理落ち(フレームスキップ)が起きてました。ちなみに50fpsだと大丈夫なのに、60fpsだと処理落ち。もしかしたら単にフレームレートの計算ミスの可能性もあるけど、何にしても原因を特定しなきゃなぁ。

引き続き坂道の処理を作ります。実はまだちょっと挙動がおかしい。

//-----------------------------------------------------------------------------
// 交差判定
BOOL CField::CrossLine(POINT p1, POINT p2, POINT q1, POINT q2)
{
 float A, B, C, D;
 BOOL ret=TRUE;

 A=(float)(p2.y-p1.y);
 B=(float)(p1.x-p2.x);
 C=-(A*p1.x+B*p1.y);
 D=(A*q1.x+B*q1.y+C)*(A*q2.x+B*q2.y+C);

 if (D>0) {
  ret=FALSE;
 }
 else {
  A=(float)(q2.y-q1.y);
  B=(float)(q1.x-q2.x);
  C=-(A*q1.x+B*q1.y);
  D=(A*p1.x+B*p1.y+C)*(A*p2.x+B*p2.y+C);

  if (D>0) {
   ret=FALSE;
  }
 }
 return ret;
}

ゲーム開発29:マップ表示

名称未設定 1前回読み込んだマップデータを表示させました。

スプライト表示用のモジュールは既にあるので、並べて表示するだけです。とりあえずマップデータのレイヤー0を背景、レイヤー1をキャラクタ配置として、各所の初期化部分を書き換えました。

マップデータの読み込みは以下のようになります。

MapData= new CMapData;
MapData->File("stage01map.fmf");

そしてマップ表示はこんな感じ。map_chip=255は透明なので表示しません。

for (i=0; i<MapData->GetHeight(); i++) {
 for (j=0; j<MapData->GetWidth(); j++) {
  map_chip=MapData->Get(0, j, i);
  if (map_chip!=255) {
   g->DrawSprite(spr_no[map_chip], (j*48), (i*48), 0.9f, 0, 0);
  }
 }
}

ただ、これだと表示されない場所まで描画するので、クリッピングする必要があります。あと画面スクロールにも対応させないとね。

上記の処理で表示する事が出来ました。んーやっぱりブロックのグラフィックが不自然な気がするなぁ…

表示はしたものの、当たり判定が入っていないので、今はゲーム開始直後に下に落ちてゲームオーバーになりますw

そんな訳で、次回は大ざっぱな当たり判定を入れてみます。

ゲーム開発28:マップデータ読み込み

今回はマップデータを読み込む処理を、CMapDataクラスとして作成しました。

Platinumというマップエディタでマップを作ってますが、このツールから書き出す形式にFMFというのがあって、これがベタデータでわかり易かったので、とりあえずこれをそのまま取り込みます。

サンプルプログラムがあったのでそれを参考に、というか多少手を入れた程度のものを作ってみました。

まずFMFのデータ形式ですが、ヘッダ部は以下のようになっています。

typedef struct FMFHeader
{
 DWORD Identifier; // ファイル識別子 'FMF_'
 DWORD Size;    // ヘッダを除いたデータサイズ
 DWORD Width;    // マップの横幅
 DWORD Height;   // マップの高さ
 BYTE ChipWidth;  // マップチップ1つの幅(未使用)
 BYTE ChipHeight;  // マップチップ1つの高さ(未使用)
 BYTE LayerCount;  // レイヤーの数
 BYTE BitCount;   // レイヤデータのビットカウント(未使用)
}FMFHEADER;

これの後ろに実データがレイヤー毎に区切り無く入ってます。なのでヘッダ内のSizeでメモリを確保し、WidthHeightから1レイヤーのサイズを計算して、任意の場所のデータを読み書きすれば良い感じです。もうサンプルのまんまだけどねw

しかしこのレイヤーという概念は便利で良いですね。1つのデータファイルにマップデータのみならず、キャラクタの配置イベント発生場所といった物を持たせられます。しかも同一のデータ形式で。

今まではキャラクタの位置情報は別形式で持ってたけど、FMFに一緒に載せてしまおうか。

次回は読み込んだマップデータを表示させます。

//-----------------------------------------------------------------------------
// ファイル読み込み
BOOL CMapData::File(char *filename)
{
 HANDLE FP;
 DWORD ReadBytes;

 // ファイルオープン
 FP=CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
 if (FP==INVALID_HANDLE_VALUE) {
  return FALSE;
 }

 // ヘッダ情報取得
 if (!ReadFile(FP, &fmfHeader, sizeof(FMFHEADER), &ReadBytes, NULL) || ReadBytes!=sizeof(FMFHEADER)) {
  CloseHandle(FP);
  return FALSE;
 }

 // 必要なメモリ確保
 LayerAddr= new BYTE[fmfHeader.Size];
 if (LayerAddr==NULL) {
  CloseHandle(FP);
  return FALSE;
 }

 // レイヤーデータ取得
 if (!ReadFile(FP, LayerAddr, fmfHeader.Size, &ReadBytes, NULL) || ReadBytes!=fmfHeader.Size) {
  CloseHandle(FP);
  return FALSE;
 }

 CloseHandle(FP);

 return TRUE;
}

//-----------------------------------------------------------------------------
// マップ情報取得
int CMapData::Get(BYTE layer_no, BYTE x, BYTE y)
{
 int  chip_no;
 BYTE *Layer;

 Layer=LayerAddr+fmfHeader.Width*fmfHeader.Height*layer_no;
 chip_no=*(Layer+y*fmfHeader.Width+x);

 return chip_no;
}

//-----------------------------------------------------------------------------
// マップ情報上書き
void CMapData::Write(BYTE layer_no, BYTE x, BYTE y, BYTE chip_no)
{
 BYTE *Layer;

 // 範囲チェック
 if (layer_no>=fmfHeader.LayerCount || x>=fmfHeader.Width || y>=fmfHeader.Height) {
  return;
 }

 Layer=LayerAddr+fmfHeader.Width*fmfHeader.Height*layer_no;
 *(Layer+y*fmfHeader.Width+x)=chip_no;
}

//-----------------------------------------------------------------------------
// マップの横幅取得
int CMapData::GetWidth()
{
 return fmfHeader.Width;
}

//-----------------------------------------------------------------------------
// マップの縦幅取得
int CMapData::GetHeight()
{
 return fmfHeader.Height;
}