今日は保険屋と出版社に打ち合わせに行きました。まぁ取り立てて収穫は無し。
その後、打ち合わせに同行した人と飲みに行きました。飲みながらビジネスの話をすると、色んなアイデアが出てきて面白い。ま、所詮飲みの席なので実現化する事はないけどねw
そんな普通な一日。
今日は保険屋と出版社に打ち合わせに行きました。まぁ取り立てて収穫は無し。
その後、打ち合わせに同行した人と飲みに行きました。飲みながらビジネスの話をすると、色んなアイデアが出てきて面白い。ま、所詮飲みの席なので実現化する事はないけどねw
そんな普通な一日。
今日は地方から後輩が出てくるので、ジョニーと共に焼肉に行く事にしました。
秋葉原のゲーセンにいるというので行ってみると、なんか見知った面子が他にも…3人の予定が5人になりました。で、5人で焼肉屋に行ったら案の定席が無いと言われたので、普通に居酒屋に入ったよ。
来週同じ面子で忘年会の予定なんだけどね。まあこんなんも良し。飲み放題で久しぶりにがっつり飲めました。
ゲームモードクラス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; // 描画 };
画面スクロールです。これがあれば一気に世界が広がります。
でも実装は簡単です。表示する全てのオブジェクトの位置を、自キャラの位置を中心にして変換すれば良いだけです。
具体的には、オブジェクトの座標値から自キャラ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; }
今回はキャラクタの位置情報をファイルから読み込みます。別段難しい処理は行いません。
キャラクタ管理クラス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); }
今回はキャラクタに当たり判定を実装してみます。
自キャラはアクションが多くてややこしいので、まずは単純な動きの敵キャラを作ります。敵01キャラクラスCEnemy01のMove()内にて、前回作った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; } } }
夏からずっとレンタル自習室を借りて、そこで仕事してますが、知人の紹介でレンタルオフィスも借りてみました。
レンタル自習室は静かで良いのですが、反面、携帯電話のバイブ音すら鳴らすの禁止だったり、勉強に集中するためか室内は暗く、各机にある蛍光灯を点けて作業するため、何となく滅入る気がしていました。あとみんな勉強してるので、パソコンのキーを叩くのにも気を使ったり。
対してレンタルオフィスは、椅子とかショボいけど、事務所仕様なので携帯電話OK、室内は明るく、食べ物の持ち込みもOKでした。ただ雑談してる人がいたり、利用時間が短かったり、値段が自習室より少し高かったりします。
どっちが良いかな。来週半ばまでに決めないといけないんだけど、迷うな。
今日は知人に誘われてビジネスのセミナーに行ってきました。最近この手のイベントに行ってなかったしね。
ネットに関するものだったんだけど、ぶっちゃけいまいちでした。まぁ値段が安かったから、それ相応なのか。あと紙の資料は一切無し。その代わり数万円する高額商材のチラシは何枚も渡されたよ。
知人は「安いセミナーで人を集めて高い商材やセミナーに誘導するのでは」と言ってました。確かにそういうのってあるなぁ。「セミナービジネスで稼ぐ!」みたいな情報がネットに出回ってるから、こういうセミナーがあるのも仕方ないか。
その後は知人と酒を飲んで帰りました。鍋が美味しい季節だね。
間髪入れずに当たり判定です。方法は色々ありますが、今回は矩形での当たり判定を行います。矩形とは四角形の事で、キャラクタを四角形として考えて、四角形同士が重なったかどうかで判定します。
判定はとりあえず存在するキャラクタ同士で総当たりさせます。キャラクタ管理クラスCCharaManageにHit()という関数を作って、その中で行います。
まずは判定したいキャラクタから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; }
「みずほ銀の著作権投資、ゲーム開発にシフト」らしい>挨拶
---
キャラクタクラス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; }