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

ゲーム開発23:改めて方向性を考える

92df78b0.jpg3ヶ月ぶりのゲーム開発です。体験版が出来て燃え尽きてたけど、さすがにもう復活しないとね。

久しぶりなので、改めて方向性を考えてみます。

・画面解像度を変更する
何も考えずに昔と同じ640×480にしてたけど、今更ながら狭いよね。800×600にしようかと思ってます。

・当たり判定を作り直す
現在の矩形1つだけの当たり判定では、大型キャラや変わった形のキャラクタに対応できません。その辺に対応できるよう考え直してみます。

・キャラクタの共通処理を増やす
これはプログラムとしては当然の方向性だけどね。現在は当たり判定とスプライトくらいしか共通処理が無いので、物理演算等も共通化していきます。

・ストーリー性をもたせる
当初から考えてたことだけど、世界観やストーリーを入れ込んでみます。ちなみに使うかどうかわかりませんが、最近妖怪の本なんか読んでます。

他にはサウンドの追加やマップエディタの作成、キーコンフィグ処理、画面エフェクト、イベント、アイテム、セーブ・ロード等も必要ですね。

まだまだやる事は沢山あるな。

ゲーム開発22:一通り組んでみる

76c5835d.jpg期限が近いので、未完成ながら一通り動くように組んでみました。

前回までにキャラクタを動かして当たり判定なんかをやってましたが、平行してやっていた以下の処理を追加しました。

・タイトル表示
・メッセージ表示
・ヒットポイント制
・攻撃およびダメージの処理
・乗ると落ちる床
・得点
・ゲームオーバー

これで何とか体裁は整ったでしょうか。ただどっちにしても未完成なので、申し訳ありません。

上記の機能についてはおいおい解説していきます。

ゲーム開発21:キャラクタアニメーション

a793fad8.jpgキャラクタのアニメーションは結局シンプルなやり方で実装しました。

スプライトを6枚用意してspr_no[]という配列に格納します。spr_no[]内の0~2に右向きスプライト、3~5に左向きスプライトてな感じで3枚ずつ入れてます。

これを時間経過にあわせて差し替えていきます。時間経過はtimeという変数を用意して、これを加算していく事で実現します。ここではtimeが5の倍数になったら次のスプライトに差し替えます。スプライトの決定方法は(time/5)を3で割った余りになります。

最後にジャンプしてたり落下してる場合にアニメーションさせるのは変なので、重力変数gravityが0の場合のみ時間経過させるようにしてます。

これで歩くアニメだけは出来ました。

次回、というか並行して音楽の処理を触ってたんだけど、同じDirectX9.0cでも色々バージョンによる差異があるみたいで、資料が少なくちょっと苦労してます。

音楽後回しにして、ゲームとしてのギミックなんかを先にやろうかな。

//-----------------------------------------------------------------------------
// 移動
void CMyChara::Move()
{
 float work_x, work_y; // 移動量

 // 他の処理

 // スプライトアニメーション
 if (work_x!=0) {
  if (work_x<0) {
   // 左向き
   now_spr_no=spr_no[((time/5)%3)+3];
  }
  else {
   // 右向き
   now_spr_no=spr_no[((time/5)%3)];
  }
  if (gravity==0) {
   time++;
  }
 }
}

ゲーム開発20:アニメーション途中

238a2958.jpgゲームキャラクタのアニメーションやろうとしてますが、まだまとまってないので、とりあえず版。

理論は簡単で、キャラクタが動くたびにアニメーションパターン(スプライト)を差し替えていけば良いだけです。それを実現するためには、現在のフレーム値をキャラクタクラスCCharaに持たせて、フレーム値がある数値になった時に表示するスプライトを変更すればアニメーションしてるように見えます。

ただ諸々のデータをどうやって持たせようか考え中。しかも作ってて思ったけど、結構アニメーションパターン必要だね。描くのたいへん。

画像はとりあえずキャラクタに絵を貼り付けてみたもの。キャラクタの周りの白枠はちゃんと絵が描けたら透明にします。

ゲーム開発19:ゲームモードクラス作成

ゲームモードクラスCGameModeの作成です。

このクラスはゲーム中の場面をまとめたものです。例えばゲーム本編のアクションパートをメインモード、ゲームクリア後のエンディングをエンディングモードといったようにまとめます。他にはゲームオーバーモードとかおまけモードとかね。

それらのモードは基底クラスであるゲームモードクラスを継承させて作ります。こうすればキャラクタクラスの時のように、クラスの種類に依存せず処理できます。

上位クラスであるシステムクラスCSystemにChangeGameMode()という関数を作って、こいつにゲームモードの切り替えをさせます。これで各ゲームモードは独立性が高まり、どこから作っても良くなります。ちなみに前回までキャラクタとかを作っていたのは、実はメインモードCGameModeMainの中だったのです。(語弊あるかな?メインモードからキャラクタ管理クラスが呼び出されているという意味です)

次回は、そろそろやらないとなぁという事で、キャラクタのアニメーションを実装します。

//-----------------------------------------------------------------------------
// ゲームモード変更
BOOL CSystem::ChangeGameMode(int mode_no)
{
 // 現在のゲームモードを解放
 if (GameMode!=NULL) {
  GameMode->Term();
  delete GameMode;
  GameMode=NULL;
 }

 // ゲームモード変更
 switch (mode_no) {
 case MAIN:
  GameMode= new CGameModeMain;
  break;
 case GAMEOVER:
  GameMode= new CGameModeGameOver;
  break;
 }

 // ちゃんと変更できた?
 if (GameMode==NULL) {
  MessageBox(NULL, "ゲームモードの変更に失敗しました", "CSystem::ChangeGameMode", MB_OK);
  return E_FAIL;
 }

 // 初期化
 GameMode->Init();

 return S_OK;
}

//-----------------------------------------------------------------------------
// ゲームモードクラス(継承させるだけなので、何も処理しない)
class CGameMode {
public:
 virtual void Init()=0; // 初期化
 virtual void Term()=0; // 解放
 virtual void Move()=0; // 移動
 virtual void Draw()=0; // 描画
};

ゲーム開発18:画面スクロール

画面スクロールです。これがあれば一気に世界が広がります。

でも実装は簡単です。表示する全てのオブジェクトの位置を、自キャラの位置を中心にして変換すれば良いだけです。

具体的には、オブジェクトの座標値から自キャラChara[0]の座標値を引くと自キャラからの相対座標が出るので、それを画面の中心(今回はX320,Y240)だけ移動させればおしまいです。

以下のプログラムでは、さらに画面の端ではスクロールしないようにしています。

スクロール範囲に固定値を入れてるので、後で修正が必要ですね。他にはスクロールして見えないところの描画のクリッピングや、当たり判定の省略なんかも考えないと。でも今時のPCならマシンパワーで押し切れるかなw

次回はやり忘れてたゲームモードクラスCGameModeの設計です。

//-----------------------------------------------------------------------------
// 座標をスクロール後に変換
void CCharaManage::Scroll(float before_x, float before_y, float *after_x, float *after_y)
{
 float my_x, my_y;

 // 自キャラの位置
 my_x=Chara[0]->mx;
 if (my_x<320) {
  my_x=320;
 }
 if (my_x>(6400-320)) {
  my_x=6400-320;
 }
 my_y=240;

 // 相対位置に変換
 *after_x=before_x-my_x+320;
 *after_y=before_y-my_y+240;
}

ゲーム開発17:キャラクタ情報をファイルから読み込む

今回はキャラクタの位置情報をファイルから読み込みます。別段難しい処理は行いません。

キャラクタ管理クラスCCharaManage
初期化関数Init()で処理します。まず引数にあるファイルを開いて、中に入っているデータを一行ずつ読み込んでいきます。データ形式は以下のような感じです。

0,3,5,-1

これは、キャラクタ種類、X座標、Y座標、親IDという順番です。座標はピクセルで入力すると面倒なので、1キャラクタ単位である48ピクセル毎に配置します。つまり上のデータ形式ではX=3、Y=5になってますが、実際はX=144、Y=240に配置されます。あと親IDが-1なのは親がいないという意味です。

読み込んだ行が空の場合、もしくは#が入ってる場合はコメント行と判断して、処理は行いません。これでデータファイル内にコメントが書き込めますね。

strtok_s()でカンマ区切りの数値を取り出し、CreateChara()でキャラクタを生成します。

これでゲームステージ内の任意の場所に、キャラクタを配置できるようになりました。次回は画面スクロールかな。

//-----------------------------------------------------------------------------
// 初期化
void CCharaManage::Init(char *filename)
{
 int  ikind, ix, iy, iparent;
 char str[256], *nexttoken;
 FILE *fp;

 // キャラクタ情報読み込み
 if((fopen_s(&fp, filename, "r"))!=0) {
  sprintf_s(str, 256, "%s が見つかりません", filename);
  MessageBox(NULL, str, "CharaManage::Init", MB_OK);
  return;
 }
 while (fscanf_s(fp, "%s", &str, 256)!=EOF) {
  if (sizeof(str)!=0 && strstr(str, "#")==NULL) {
   // 分解する
   ikind=atoi(strtok_s(str, ",", &nexttoken));
   ix=atoi(strtok_s(NULL, ",", &nexttoken));
   iy=atoi(strtok_s(NULL, ",", &nexttoken));
   iparent=atoi(strtok_s(NULL, ",", &nexttoken));
   
   if (iparent==-1) {
    // 親がいない場合
    CreateChara(ikind, (ix*48), (iy*48), NULL);
   }
   else {
    // 親がいる場合
    CreateChara(ikind, (ix*48), (iy*48), Chara[iparent-1]);
   }
  }
 }
 fclose(fp);
}

ゲーム開発16:キャラクタ同士の当たり判定2

今回はキャラクタに当たり判定を実装してみます。

自キャラはアクションが多くてややこしいので、まずは単純な動きの敵キャラを作ります。敵01キャラクラスCEnemy01Move()内にて、前回作ったHit()を呼び出します。呼び出す際、移動後の座標を引数に入れ、当たっていたら移動させません。

ちなみにこの敵01はスーパーマリオで言うところのクリボーと同じ動きをします。つまり地面を歩いて、何かにぶつかったら方向転換し、地面が無ければそのまま落っこちる動作です。

重力を実現するために、常に下向きに移動させています。そしてdirectionというメンバ変数に、移動方向(LEFTとかRIGHTという列挙型定数)を保持させて、それの通りに歩かせます。これで上記の動きをしてくれます。

ちなみに作業中の自キャラクラスでは、もう少しリアリティを出すために擬似的ですが重力加速度を導入しています。

次回はキャラクタの位置情報をファイルから読み込む処理をやります。

//-----------------------------------------------------------------------------
// 移動
void CEnemy01::Move()
{
 int  move;
 RECT retRect;

 // 重力
 if (CharaManage->Hit(my_no, mx, my+4, &retRect)==-1) {
  my+=4;
 }

 // 移動方向
 move=-2;
 if (direction==RIGHT) {
  move=2;
 }
 if (CharaManage->Hit(my_no, mx+move, my, &retRect)==-1) {
  // 当たってなければ移動
  mx+=move;
 }
 else {
  // 当たってたら反転
  if (direction==RIGHT) {
   direction=LEFT;
  }
  else {
   direction=RIGHT;
  }
 }
}

ゲーム開発15:キャラクタ同士の当たり判定1

間髪入れずに当たり判定です。方法は色々ありますが、今回は矩形での当たり判定を行います。矩形とは四角形の事で、キャラクタを四角形として考えて、四角形同士が重なったかどうかで判定します。

判定はとりあえず存在するキャラクタ同士で総当たりさせます。キャラクタ管理クラスCCharaManageHit()という関数を作って、その中で行います。

まずは判定したいキャラクタからHit()が呼び出されます。内部では全キャラ分ループさせて、他のキャラと当たったかどうか判定します。キャラの矩形は、あらかじめキャラクタクラスCCharaに当たり判定用のRECT構造体を作っておき、そのRECTと座標mx,myから実際の座標系での矩形を生成します。重なるキャラが見つかったら、その番号と実座標の矩形を返します。

シンプルですね。ただこれだと複数のキャラとぶつかった場合、判定がおかしくなる可能性があります。あと高速で移動する小さなキャラの当たり判定も行えません。すり抜けちゃうから。

でも欲張ったら先に進めなくなるので、このまま行きます。

次回はこのHit()を使って、実際に当たり判定を行います。

//---------------------------------------------------------
// 当たり判定
int CCharaManage::Hit(int chara_no, float x, float y, RECT *retRect)
{
 int  i, ret;
 RECT myRect, yourRect;

 // 自分の矩形
 myRect=Chara[chara_no]->GetHitArea();
 myRect.top+=(LONG)y;
 myRect.bottom+=(LONG)y;
 myRect.left+=(LONG)x;
 myRect.right+=(LONG)x;

 ret=-1;
 for (i=0; i<MAX_CHARA; i++) {
  if (i!=chara_no && Chara[i]!=NULL) {
   // 相手の矩形
   yourRect=Chara[i]->GetHitArea();
   if (yourRect.top!=-1) {
    yourRect.top+=(LONG)Chara[i]->my;
    yourRect.bottom+=(LONG)Chara[i]->my;
    yourRect.left+=(LONG)Chara[i]->mx;
    yourRect.right+=(LONG)Chara[i]->mx;

    if (myRect.bottom>yourRect.top && myRect.top<yourRect.bottom 
     && myRect.left<yourRect.right && myRect.right>yourRect.left) {
      // 当たった
      ret=i;
      *retRect=yourRect;
    }
   }
  }
 }
 return ret;
}

ゲーム開発14:キャラクタクラス作成

0307a462.jpg「みずほ銀の著作権投資、ゲーム開発にシフト」らしい>挨拶
---

キャラクタクラスCCharaを作るため試行錯誤してたのですが、あれやこれやと色んなモノを詰め込もうとして、訳わからん状態となってしまいました。

これじゃいつまで経っても完成しないという事で、複雑な処理は排除、外部ファイルとして持たせるデータもソースにベタ打ちさせて、とにかく動くもの優先で作ることにしました。

とりあえず以下の値だけ持たせました。

座標mx,my,mz
キャラクタ番号my_no
スプライト番号spr_no
キャラクタ管理クラスへのポインタCCharaManage*

ソースですが、前回のキャラクタ管理クラスCCharaManageからCCharaを継承した自キャラクラスCMyCharaを呼び出します。呼び出し時に即値でスプライトを生成して、spr_noに格納します。今はあまり意味無いけど、CCharaManageへのポインタもCommonInit()の中で格納しています。

Move()ではCInputクラスからキー入力を受け取り、座標mx,myに移動量を加算します。これで移動できます。

最後にDraw()からCommonDraw()を呼び出してスプライト表示させます。今は無駄に見えますが、キャラクタ毎に描画方法は変わりますので、こう書いてます。

一応これで任意の場所にキャラクタを表示し、キーボード等で移動させる事が出来ます。欲張らずに、最初からこうやって組んどけばよかったよ。

次回は当たり判定を行います。

//---------------------------------------------------------
// 初期化
CMyChara::CMyChara(int no, int x, int y, CCharaManage *manage)
{
 RECT workRect;

 my_no=no;
 CommonInit(x, y, 1, manage);

 // スプライト作成
 SetRect(&workRect, 0, 0, 48, 96);
 spr_no[0]=System->Graphics->CreateSprite("data\test.jpg", workRect);
}

//---------------------------------------------------------
// 移動
void CMyChara::Move()
{
 float work_x, work_y;
 KEYCODE key;

 // キーコード取得
 System->Input->PressKey(&key);

 work_x=(float)key.x*6;
 work_y=(float)key.y*6;

 mx+=work_x;
 my+=work_y;
}

//---------------------------------------------------------
// 描画
void CMyChara::Draw()
{
 CommonDraw();
}

//---------------------------------------------------------
// 共通描画
void CChara::CommonDraw()
{
 System->Graphics->DrawSprite(spr_no[0], mx, my, mz, D3DXCOLOR(1,1,1,1), 0, 1, 1);
}

//---------------------------------------------------------
// 共通初期化
void CChara::CommonInit(int x, int y, int type, CCharaManage *manage)
{
 CharaManage=manage;

 mx=(float)x;
 my=(float)y;
 mz=1.0f;
}