神代クロ のすべての投稿

ゲーム開発13:キャラクタ管理クラス作成

キャラクタクラスCCharaを作ろうと思いましたが、その前にキャラクタ管理クラスCCharaManageを作成します。

キャラクタ管理クラスとは、CCharaをまとめて処理するクラスといった感じでしょうか。キャラクタの移動や描画はひとつひとつ別個に処理させず、この管理クラスに全部やってもらいます。

キャラクタの最大数MAX_CHARA分だけCCharaポインタを持ち、CreateChara()にて空いているCCharaポインタがあったら、そこに任意のキャラクタを生成します。キャラクタの種類kindを引数に持ち、ベタですがswitch~caseで生成キャラを選択します。ちなみに

Chara[i]= new CMyChara(i, x, y);
Chara[i]= new CEnemy01(i, x, y);

としているのは、CMyCharaCEnemy01がCCharaを継承したクラスだから可能にしている芸当です。キャラクタを増やす際、CEnemy02:public CCharaみたいに継承クラスを作って追加するだけで、中身が敵だろうとアイテムだろうと関係なく、汎用的に使いまわすことができます。

あとはMove()で移動、Draw()で描画を全キャラ分ループで回しています。キャラクタの寿命が来たらDeleteChara()で、これまた中身を気にせずに削除する事ができます。

次回は今度こそキャラクタクラスを作ります。

//-----------------------------------------------------------------------------
// 生成
CCharaManage::CCharaManage()
{
 int  i;

 // キャラクタ初期化(念のため)
 for (i=0; i<MAX_CHARA; i++) {
  Chara[i]=NULL;
 }
}

//-----------------------------------------------------------------------------
// 解放
CCharaManage::~CCharaManage()
{
 int  i;

 // キャラクタ解放
 for (i=0; i<MAX_CHARA; i++) {
  DeleteChara(i);
 }
}

//-----------------------------------------------------------------------------
// キャラクタ生成
CChara *CCharaManage::CreateChara(int kind, int x, int y, CChara *parent)
{
 int  i;

 // 空いているキャラクタがあるか検索
 for (i=0; i<MAX_CHARA; i++) {
  if (Chara[i]==NULL) {
   break;
  }
  i++;
 }
 if (i>=MAX_CHARA) {
  return NULL;
 }

 switch (kind) {
 case KIND_MYCHARA: // 自キャラ
  Chara[i]= new CMyChara(i, x, y);
  break;
 case KIND_ENEMY01: // 敵キャラ01
  Chara[i]= new CEnemy01(i, x, y);
  break;
 }

 // 自分の親を設定
 if (parent!=NULL) {
  Chara[i]->SetParent(parent);
 }

 return Chara[i];
}

//-----------------------------------------------------------------------------
// キャラクタ解放
void CCharaManage::DeleteChara(int no)
{
 if (Chara[no]!=NULL) {
  delete Chara[no];
  Chara[no]=NULL;
 }
}

//-----------------------------------------------------------------------------
// 移動
int CCharaManage::Move()
{
 int  i, ret;

 for (i=0; i<MAX_CHARA; i++) {
  if (Chara[i]!=NULL) {
   Chara[i]->Move();
  }
 }

 return ret;
}

//-----------------------------------------------------------------------------
// 描画
void CCharaManage::Draw()
{
 int  i;

 for (i=0; i<MAX_CHARA; i++) {
  if (Chara[i]!=NULL) {
   Chara[i]->Draw();
  }
 }
}

IE6からFirefox3に乗り換える

ブラウザはもう何年もInternet Explorer6(IE6)を使い続けてきましたが、Firefox3に乗り換えることにしました。

IE7が出ててIE8のベータ版?もあるけど、試しにIE7を入れて使った際、なんか使い辛く感じました。だからいっその事別のブラウザに乗り換えようと。まぁ試しに数ヶ月間、Firefoxを仕事パソコンで使ってて問題ないと判断したってのもあるんだけどね。

てゆうか、元々俺はNetscape Navigatorを使ってた人間だから、戻ったと言うべきかな。そういえばメーラーもずっとThunderbird使ってるし。

とりあえずアドオンはPopup ALT Attributes(画像のALT属性をIEみたいにポップアップ表示させる)とUser Agent Switcher(ブラウザを偽装する。携帯サイトを閲覧できるようになる)を入れました。

乗り換えたお陰で、ブラウジングの効率が上がりますね。

コタツを出す

21262d1c.jpg寒くなってきたので、コタツを出しました。まぁ出したっていうか、夏でもテーブル代わりに使ってるので、コタツ布団を買ってきて掛けたって事なんだけど。

ちなみに今まで使ってたコタツ布団は安物だったので、中の綿が千切れまくり、表面も破れてダメになってたので、新しく写真のやつを買いました。

これは掛け布団の四隅が柔らかい別の生地になっていて、あまり広がらないというものです。狭い部屋だと結構場所食うからね、こういうアイデア商品は良いね。

これで冬が越せます。

ゲーム開発12:キャラクタクラスの設計

キャラクタクラスキャラクタクラスCCharaを作るため、あれこれ試行錯誤してました。

キャラクタを構成するのは初期化・移動・描画・解放の処理と、スプライトや位置情報等のデータです。

ゲーム内にはキャラクタがたくさん出てくるので、このキャラクタクラスは生成・解放を頻繁に繰り返し、そのため無駄が出てきます。なので再利用できるデータは再利用して無駄を省きたい。その辺でデータの持ち方を色々と考えてましたよ。

画像の表はとりあえず現時点でのクラス案です。定型部分をパーツクラスCCharaPartsとして分けてみました。

これで大丈夫かな? とりあえず次回から実装に移ります。

ノートパソコン買いました2

この前買ったdynabookで、視野角が狭いと書きましたが、勘違いでしたよ。

画面が明るいと目が疲れるので、暗くして使うのですが、暗くすると画面端が暗くなりすぎて見づらい。この調節はグラフィックのプロパティで行っていたのですが、実は東芝省電力のプロパティから調節するとイイ感じの明るさになるのでした。

バッテリー駆動で使ってたら、バッテリーの減少と共に画面の明るさが変わったので気付いたよ。

画面の明るさも調節できたし、これで仕事用マシンとして外でガンガン使っていけます。寒くなってきたら、NASとこのdynabookで、コタツに居ながら仕事できるねw

ノートパソコン買いました

eab2b2d7.jpg
最近よくライブドアブログが落ちる…>挨拶
---

ノートパソコンを購入しました。今回は東芝です。dynabook SS RX1という、今年の1月に出たものです。今まで使っていたレッツノートCF-W2は良いものなんだけど、如何せん古いので性能的に厳しくなってきてたからね。

購入時に考えたのは、毎日持ち運ぶから軽いもの、どうせなら今より画面解像度が高いもの、やっぱりWindowsXP搭載、あとは性能が少々といったところです。

dynabook SS RX1 TE120E/2W PPR1TE2EPZRUK
Core2Duo 1.2GHz (超低電圧版)
メモリ1Gbyte
HDD80Gbyte
液晶12.1インチ(1280×800)
DVDスーパーマルチドライブ
WindowsXP Pro
重量1.1kg

ホントはEeePCみたいなネットブックを買おうと思ってたんだけど、いつまで待ってもインテルとゲイツの縛りが緩和されず、全然性能が上がらなかったから諦めました。今使ってるPCより性能が下じゃ買う意味ないしね。

ちなみに価格表見ると37万円って書いてあるけど、新品なのにその1/3くらいで買えたよ。

しかしこのdynabookの液晶は視野角狭すぎる。普通に見てても画面の下の方が暗くなってるし。値引率が高いだけあるのか。でもまぁ、慣れるでしょう。(東芝省電力のプロパティで解決しました)

写真の左側が今回買ったdynabookで、右側がレッツノート、後ろにちょっと見えてるのがNEC LaVie Gです。

統一感ないなぁw

ゲーム開発11:方向性を決める

前回までに、基底・グラフィック・入力のクラスが完成しました。今回はどんなゲームにするかを決めます。

まずは基本システムを考えてみました。

・横スクロールアクションゲーム
・敵を倒したり、障害物を乗り越えていく
・ステージクリア型でボスを倒したらクリア
・自キャラは左右移動、ジャンプ、近接および遠距離攻撃

ありがちなシステムですね。そしてやってみたい事を列挙します。

・ある条件でパワーアップさせる
・フィールドに高低差を付ける
・スピード感を出す
・自キャラの他にサブキャラを出す
・敵の頭に乗れるようにする
・演出に凝ってみる

どこまで出来るかわかりませんが、とりあえずこんな方向性で行ってみます。あと、このままだと地味なので、他と差別化するアイデアを引き続き考えてみますか。

次回はキャラクタークラスを作ります。いちばん難しく、かつ何度も作り直すことになるであろうクラスです。

ニコニコ動画のマイリストRSS取り込み

いつからなのかは知らないけど、ニコニコ動画RSSに対応していました。

なので今までIFRAMEで表示させていたマイリストをRSSで取得し、それっぽく並べてみます。RSSはニコニコ動画のURLの後ろに「?rss=2.0」みたいな引数を付ければ取れます。

巫女さんのマイリストは頻繁に更新しないので、キャッシュとして自サーバ保存しておきます。で、取り出して表示させたのが以下ページです。

巫女さん関連のニコニコ動画

画像やタイトルをクリックすると詳細ページに飛びます。詳細ページではニコニコ動画の外部プレイヤーを使ってページ内で表示させようと思ったのですが、どうも許可されたドメインからしか再生できないみたいです。仕方ないので、IFRAMEにしました。

結局IFRAMEか! あんま意味ないなぁ。外部プレイヤーが使えるようになるか、せめてRSSが検索結果に対応してくれればいいのに。

気が向いたらYouTubeからも取り込んでみよう。

ゲーム開発10:スプライト処理修正

以前作ったスプライト処理ですが、間違いを見つけました。今のままだとZ方向の移動が出来ず、奥行きが実現できません。

そんな訳でまず、CGraphicsBase::InitD3D()に以下のパラメータを追加します。これは奥行きを使うための宣言です。

d3dpp.EnableAutoDepthStencil=TRUE;
d3dpp.AutoDepthStencilFormat=D3DFMT_D16;

D3Device->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);

次にCGraphicsBase::DrawSpriteBase()の中の計算とかを変更します。これでZ方向にも移動できるようになりました。

なぜ2Dなのに奥行きが必要かというと、背景の手前にキャラクタを表示し、キャラクタの手前に文字や設定画面を表示させたいからです。前のままでも、描画の順番を奥から順に並べれば同じ事はできます。でも、描画命令はあっちこっちのクラスから投げられてくるので、管理がややこしくなりそうです。そのため、今のうちに厳密に設定させるという訳です。

//---------------------------------------------------------
// スプライト描画
void CGraphicsBase::DrawSpriteBase(int tex_no, RECT drawRect, float x, float y, float z, DWORD color, float angle, float scale_x, float scale_y)
{
 D3DXMATRIX mat, mat1, mat2, mat3;
 float  center_x, center_y;
 D3DXVECTOR3 Trans, Center;

 // マトリクス初期化
 D3DXMatrixIdentity(&mat1);
 D3DXMatrixIdentity(&mat2);
 D3DXMatrixIdentity(&mat3);

 // マトリクスで拡大縮小回転移動
 D3DXMatrixScaling(&mat1, scale_x, scale_y, 1);
 D3DXMatrixRotationZ(&mat2, D3DXToRadian(angle));
 D3DXMatrixTranslation(&mat3, x, y, 0);
 mat=mat1*mat2*mat3;
 D3Sprite->SetTransform(&mat);

 // 移動
 Trans=D3DXVECTOR3(0, 0, z);

 // 画像の中心
 center_x=(float)(drawRect.right-drawRect.left)/2;
 center_y=(float)(drawRect.bottom-drawRect.top)/2;
 Center=D3DXVECTOR3(center_x, center_y, 0);

 // 描画
 D3Sprite->Draw(Texture[tex_no]->Tex, &drawRect, &Center, &Trans, color);
}

ゲーム開発9:入力処理2

90353511.jpg入力処理のCInputクラスです。

前回CInputBaseクラスにて、キーボードとゲームパッドから情報を取得する設定を行いました。今回は取得した情報をゲームで使える値に変換します。

ゲームで使う値は移動のxとy、そして決定キーa、キャンセルキーb、選択キーcとします。アクションゲームだったら、決定キーが攻撃になったりキャンセルキーがジャンプになったりします。ともかくそれをKEYCODE構造体としてまとめておきます。

ボタンが押された際、キーボードからは配列が、ゲームパッドからは構造体が返されます。押されたボタンを確認するには、dinput.hに書かれた定義を参照して、ビットが立っているかどうかで判断します。

ところでゲームパッドの十字キーは、アナログで最大0~65535まで値を取ったりするらしいので、閾値を10にして、ちょっとでも押されたら反応するようにしました。ホントはゲームパッドが返した情報を元に、ちゃんと決めないといけないんだけどね。持ってるゲームパッドは0か1000かの2択だったし。

押されたボタンがわかったら、それをKEYCODE構造体に格納して返せばOKです。ちなみにPressKey()では過去に押されたボタンの履歴を参考に、同時押しを抑制しています。

これでスプライトを動かす事が出来るようになりました。音楽以外は一通り基底部分が完成しましたので、次回はゲームの仕様を考えますか。

//---------------------------------------------------------
typedef struct {
 int x; // X軸
 int y; // Y軸
 int a; // 決定
 int b; // キャンセル
 int c; // 選択
} KEYCODE;

#define KEYDOWN(name, key) (name[key] & 0x80)

//---------------------------------------------------------
// キーボード入力取得
void CInput::GetKeyboard(KEYCODE *keycode)
{
 char buffer[256];
 HRESULT hr;

 // キーボードステータス取得
 hr=ProcessKeyboard(buffer);
 if FAILED(hr) {
  return;
 }

 // 6
 if (KEYDOWN(buffer, DIK_NUMPAD6)) {
  keycode->x=1;
 }
 // 4
 else if(KEYDOWN(buffer, DIK_NUMPAD4)) {
  keycode->x=-1;
 }

 // 8
 if (KEYDOWN(buffer, DIK_NUMPAD8)) {
  keycode->y=-1;
 }
 // 2
 else if(KEYDOWN(buffer, DIK_NUMPAD2)) {
  keycode->y=1;
 }

 // 右
 if (KEYDOWN(buffer, DIK_RIGHT)) {
  keycode->x=1;
 }
 // 左
 else if(KEYDOWN(buffer, DIK_LEFT)) {
  keycode->x=-1;
 }

 // 上
 if (KEYDOWN(buffer, DIK_UP)) {
  keycode->y=-1;
 }
 // 下
 else if (KEYDOWN(buffer, DIK_DOWN)) {
  keycode->y=1;
 }

 // Z
 keycode->a=0;
 if (KEYDOWN(buffer, DIK_Z)) {
  keycode->a=1;
 }

 // X
 keycode->b=0;
 if (KEYDOWN(buffer, DIK_X)) {
  keycode->b=1;
 }

 // C
 keycode->c=0;
 if (KEYDOWN(buffer, DIK_C)) {
  keycode->c=1;
 }

 // エンター
 if (KEYDOWN(buffer, DIK_RETURN) || KEYDOWN(buffer, DIK_NUMPADENTER)) {
  keycode->a=1;
 }

 // スペース
 if (KEYDOWN(buffer, DIK_SPACE)) {
  keycode->b=1;
 }
}

//---------------------------------------------------------
// ジョイスティック入力取得
void CInput::GetJoystick(KEYCODE *keycode)
{
 DIJOYSTATE js;
 HRESULT  hr;

 // ジョイスティックステータス取得
 ZeroMemory(&js, sizeof(DIJOYSTATE));
 hr=ProcessJoystick(&js);
 if FAILED(hr) {
  return;
 }

 // X軸
 if (js.lX>10) {
  keycode->x=1;
 }
 if (js.lX<-10) {
  keycode->x=-1;
 }

 // Y軸
 if (js.lY>10) {
  keycode->y=1;
 }
 if (js.lY<-10) {
  keycode->y=-1;
 }

 // 1ボタン
 if (KEYDOWN(js.rgbButtons, 0)) {
  keycode->a=1;
 }

 // 2ボタン
 if (KEYDOWN(js.rgbButtons, 1)) {
  keycode->b=1;
 }

 // 3ボタン
 if (KEYDOWN(js.rgbButtons, 2)) {
  keycode->c=1;
 }
}

//---------------------------------------------------------
// キー入力取得
void CInput::PressKey(KEYCODE *keycode)
{
 static int old_xy=0;  // 前回の移動キー
 KEYCODE  di_key, di_joy;
 int   new_x, new_y;

 ZeroMemory(keycode, sizeof(KEYCODE));

 // キーボード&ジョイスティック取得
 ZeroMemory(&di_key, sizeof(KEYCODE));
 GetKeyboard(&di_key);
 ZeroMemory(&di_joy, sizeof(KEYCODE));
 GetJoystick(&di_joy);

 // X軸
 new_x=di_key.x;
 if (di_joy.x!=0) {
  new_x=di_joy.x;
 }

 // Y軸
 new_y=di_key.y;
 if (di_joy.y!=0) {
  new_y=di_joy.y;
 }

 // 前回の軸を見て移動軸を決定
 if (new_x!=0 && new_y!=0) {
  if (old_xy==0) {
   keycode->y+=new_y;
  }
  else {
   keycode->x+=new_x;
  }
 }
 else {
  // 片方だけ移動
  keycode->x+=new_x;
  keycode->y+=new_y;
  if (new_y==0) {
   old_xy=0;
  }
  else {
   old_xy=1;
  }
 }

 // 決定ボタン
 keycode->a=di_key.a;
 if (di_joy.a!=0) {
  keycode->a=di_joy.a;
 }

 // キャンセルボタン
 keycode->b=di_key.b;
 if (di_joy.b!=0) {
  keycode->b=di_joy.b;
 }

 // 選択ボタン
 keycode->c=di_key.c;
 if (di_joy.c!=0) {
  keycode->c=di_joy.c;
 }
}