ゆとりーなの日記

日記的な事を書いて行くと思はれる

早速対応

非想天則のver1.10パッチが出ましたね。なんか色々新しい要素が追加されたりバグ修正が行われたりと盛りだくさんの修正パッチらしいですが、我々世紀末勢にはそんなことどうでもいんです。一番気になるのは色んな値が保存されているアドレスの変動についてなんです。まあようするに世紀末非想天則の新パッチ対応ですね。取り敢えずver1.10を当てて今迄の世紀末パッチを当てると予想通り落ちます。残当です。とりあえずth123_aiやSWRSToysを落としてアドレス変動情報を調べます。結構変わってるとこもありました。この子たちを見て分かる範囲で変わってるところを直して、独自調査のアドレスを調べて直して新パッチ対応取り敢えず完了しました。数十分のやっつけ作業なのでどこか間違ってそうですがまあ自己満足のパッチで公開予定はないので取り敢えずはいいでしょう。yappy氏のパッチはどうするんでしょうね。

std::listとstd::tr1::shared_ptrのコンビは弾幕ゲーでは活躍できないのか特集

世紀末非想天則の話で終わりかと思いきや、まさかの特集第4弾です。タイトルの通りです。std::listとstd::tr1::shared_ptrのコンビは弾幕ゲーでは避けられる傾向がある気がするのでやってみました。個人的にはこのコンビで作る気満々なんですけどね。

std::listは遅いべ

よく言われるのがstd::listはnew濫発で遅いという話です。たしかにその通りですが、この手のコンテナはプレースメントnewとかを駆使して結構速いと思うんですよね。自作コンテナを作ってstd::listに勝つのは結構難しいんじゃないんですかね。あと標準なだけあって安全性はピカ一ですし。

std::shared_ptrは重いべ

std::shared_ptrは生ポインタに比べてコピーは遅いしサイズもでかい。その通りです。しかしサイズはせいぜい2倍、ということはコピーの遅さもポインタと比較して2倍くらいなのかしら。ポインタのサイズは小さいですし、コピーも早いので、それが2倍になったところで大して差は出ないでしょう。っと弾幕は数が多いのか・・・。要検証ですね。しかしdeleteの手間が省けるのは大きなメリットです。あとstd::listとのコンビでは殆どコピーすることはないでしょう。寧ろnewのオーバーヘッドの方が問題大なんじゃないんですかね。newしなきゃいけないのは生ポインタもstd::shared_ptrも同じです。っとshared_ptrは参照カウンタの領域をnewするからnewの数が2倍なのか・・・。矢張り要検証ですね。

それでも楽だべ

std::listとstd::tr1::shared_ptrのコンビで作るメリットは、とにかく楽なことです。自作するものなしですからね。というわけで、適当な弾幕サンプルを作ってfps60出ればもう黙って採用で良いと思います。自作コンテナと生ポインタの方が早いんだけどという話は聞きません。楽さと安全性の方が大事です。fps60出ればいいんです。

検証コード

早速検証コードいきましょう。いきなりですがめんどくさいです。この手の検証が避けられるのは、コード書く量が半端ないからじゃないんですかね。ウィンドウ作ったりDirect3D初期化したり、本題とは関係のないところで手間がかかりすぎるんです。まあそこは我慢して、今回は適当なライブラリを用意しました。手抜きの為グローバル変数濫発です。エラーチェックも適当です。最適化とか全く考えてません。まあこれでfps60出ればstd::listとstd::tr1::shared_ptrのコンビの強さがより際立つということで。最初はこのライブラリのコードも載せようかと思ったんですが、いくら手抜きと言っても長すぎるので、検証コード部だけ載っけることにしました。

// main.cc
#include <algorithm>
#include <sstream>
#include <memory>
#include <functional>
#include <windows.h>
#include "lib.h"        // ウィンドウの初期化やDirect3Dのことを担当する関数群
#include "timer.h"      // fps測定用クラス
#include "objectlist.h" // オブジェクトのリストを管理する関数群
#include "alice.h"      // 自機はやっぱりアリスさん
#include "yousei.h"     // 敵はやっぱり妖精さん

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
  if (!initWindow()) { // ウィンドウ生成とDirect3D初期化
	return 0;
  }
  MSG               message;
  std::stringstream title;
  Timer             timer;
  int               count(0);
  // アリスさんと妖精さんを生成してリストにぶち込む
  pushBackObjectList(std::tr1::shared_ptr<Object>(new Alice()));
  pushBackObjectList(std::tr1::shared_ptr<Object>(new Yousei(320.f, 80.f)));
  for (;;) {
	if (PeekMessage(&message, NULL, 0, 0, PM_NOREMOVE)) {
	  BOOL result(GetMessage(&message, NULL, 0, 0));
	  if ((!result) || (!(~result))) {
		break;
	  }
	  TranslateMessage(&message);
	  DispatchMessage(&message);
	} else {
	  if (!(count % 50)) {
		timer.restart(); // 時間をリセット
	  }
	  if (beginDraw()) { // 要するにIDirect3DDevice::BeginScene()を呼んでる
	// いつか紹介したremove_ifとeraseでオブジェクト全部回していらなくなったのを消去までしてくれる構文
eraceObjectList(std::remove_if(beginObjectList(), endObjectList(), std::tr1::bind(&Object::main, std::tr1::placeholders::_1)), endObjectList());
		endDraw(); // 要するにIDirect3DDevice::EndScene()、IDirect3DDevice::Present(NULL, NULL, NULL, NULL)を呼んでる
	  }
	  if (!(count % 50)) {
		title.str("");
		title << "弾幕テスト FPS:" << 1000.0 / timer.elapsed(); // 時間をリセットしてからの経過時間
		setWindowTitle(title.str().c_str()); // 関数名のまんま
	  }
	  ++count;
    }
  }
  cleanUp(); // Direct3Dがらみのオブジェクト解放
  return static_cast<int>(message.wParam);
}

// std::listをexternするのを嫌ってこうなった
// objectlist.h
#pragma once

#include <list>
#include <memory>

class Object;

typedef std::tr1::shared_ptr<Object> ObjectPtr;
typedef std::list<ObjectPtr>         ObjectList;

void                 pushBackObjectList(const ObjectPtr &ptr);
void                 eraceObjectList(const ObjectList::iterator &begin, const ObjectList::iterator &end);
ObjectList::iterator beginObjectList();
ObjectList::iterator endObjectList();

// objectlist.cc
// std::listを隠蔽してるだけ
// しかも結局グローバル変数
#include "objectlist.h"

namespace {
ObjectList object_list_;
}

void pushBackObjectList(const ObjectPtr &ptr) {
  object_list_.push_back(ptr);
}

void eraceObjectList(const ObjectList::iterator &begin, const ObjectList::iterator &end) {
  object_list_.erase(begin, end);
}

ObjectList::iterator beginObjectList() {
  return object_list_.begin();
}

ObjectList::iterator endObjectList() {
  return object_list_.end();
}

// object.h
// ポリモーフィズムだったかを実現するための基底クラス
#pragma once

struct IDirect3DTexture9;

class Object {
public:
	          Object(float x, float y);
  virtual       ~Object() = 0;
  virtual bool  main() = 0; // 次フレームも生きてるときはfalseを返す
protected:
  float         x() const;
  float         y() const;
  void          moveTo(float x, float y); // オブジェクトを動かす
private:
  float         x_; // オブジェクトのx座標
  float         y_; // オブジェクトのy座標
};

// object.cc

#include "object.h"

Object::Object(float x, float y) : x_(x), y_(y) {
}

Object::~Object() {
}

float Object::x() const {
  return x_;
}

float Object::y() const {
  return y_;
}

void Object::moveTo(float x, float y) {
  x_ = x;
  y_ = y;
}

// 自機(アリスさん)
#pragma once

#include "object.h"

class Alice : public Object {
public:
	               Alice();
	               ~Alice();
  bool               main();
  void               move();
  void               fire();
private:
  IDirect3DTexture9 *center_;
  IDirect3DTexture9 *right_;
  IDirect3DTexture9 *left_;
  IDirect3DTexture9 *posture_;
  int                shot_count_;
};

// alice.cc
#include <algorithm>
#include <windows.h>
#include <d3dx9math.h>
#include "alice.h"
#include "lib.h"
#include "objectlist.h"
#include "shot.h"

#undef max
#undef min

Alice::Alice() : Object(320.f, 320.f), center_(createTexture("alice_center.png")), right_(createTexture("alice_right.png")), left_(createTexture("alice_left.png")), posture_(center_), shot_count_() {
}

Alice::~Alice() {
}

bool Alice::main() {
  move();
  fire();
  draw(x(), y(), 32.f, 64.f, 0.f, posture_); // 見たまんまの引数を渡すと描画してくれる関数
  return false;
}

void Alice::move() {
  float x(x()), y(y());
  float move_x(0.f), move_y(0.f);
  posture_ = center_;
  if (GetKeyState(VK_LEFT) < 0) {
	move_x = -4.f;
	posture_ = left_;
  }
  if (GetKeyState(VK_RIGHT) < 0) {
	move_x = 4.f;
	posture_ = right_;
  }
  if (GetKeyState(VK_UP) < 0) {
	move_y = -4.f;
  }
  if (GetKeyState(VK_DOWN) < 0) {
	move_y = 4.f;
  }
  if ((move_x) && (move_y)) {
	move_x /= sqrtf(2.f);
	move_y /= sqrtf(2.f);
  }
  x = std::min(640.f, std::max(0.f, x += move_x));
  y = std::min(480.f, std::max(0.f, y += move_y));
  moveTo(x, y);
}

void Alice::fire() {
  if (!(shot_count_ % 4)) {
	if (GetKeyState('Z') < 0) {
	  pushBackObjectList(std::tr1::shared_ptr<Object>(new Shot(x() - 32.f, y(), -3.f, -19.f, D3DXToRadian(-8.f))));
	  pushBackObjectList(std::tr1::shared_ptr<Object>(new Shot(x() - 24.f, y() - 8.f, 0.f, -20.f, 0.f)));
	  pushBackObjectList(std::tr1::shared_ptr<Object>(new Shot(x() - 8.f, y() - 24.f, 0.f, -20.f, 0.f)));
	  pushBackObjectList(std::tr1::shared_ptr<Object>(new Shot(x() + 8.f, y() - 24.f, 0.f, -20.f, 0.f)));
	  pushBackObjectList(std::tr1::shared_ptr<Object>(new Shot(x() + 24.f, y() - 8.f, 0.f, -20.f, 0.f)));
	  pushBackObjectList(std::tr1::shared_ptr<Object>(new Shot(x() + 32.f, y() , 3.f, -19.f, D3DXToRadian(8.f))));
	}
  }
  ++shot_count_;
}

// 敵(妖精さん)
// yousei.h
#pragma once

#include "object.h"

class Yousei : public Object {
public:
	               Yousei(float x, float y);
	               ~Yousei();
  bool               main();
  void               shot();
private:
  IDirect3DTexture9 *yousei_;
  int                shot_count_;
};

// yousei.cc
#include <d3dx9math.h>
#include "yousei.h"
#include "lib.h"
#include "objectlist.h"
#include "barrage.h"

Yousei::Yousei(float x, float y) : Object(x, y), yousei_(createTexture("yousei.png")), shot_count_(0) {
}

Yousei::~Yousei() {
}

bool Yousei::main() {
  shot();
  draw(x(), y(), 32.f, 32.f, 0.f, yousei_);
  return false;
}

void Yousei::shot() {
  if (!(shot_count_ % 10) && (shot_count_ < 150)) {
	for (int i = 0; i < 20; ++i) {
	  pushBackObjectList(std::tr1::shared_ptr<Object>(new Barrage(x() + cosf(D3DX_PI / 2.f + D3DX_PI / 150.f * (shot_count_ % 150)) * 100.f, y() + sinf(D3DX_PI / 2.f + D3DX_PI / 150.f * (shot_count_ % 150)) * 100.f, D3DX_PI / 20.f * i)));
	  pushBackObjectList(std::tr1::shared_ptr<Object>(new Barrage(x() + cosf(D3DX_PI / 2.f - D3DX_PI / 150.f * (shot_count_ % 150)) * 100.f, y() + sinf(D3DX_PI / 2.f - D3DX_PI / 150.f * (shot_count_ % 150)) * 100.f, -D3DX_PI / 20.f * i)));
    }
  }
  ++shot_count_;
}

// 自機が撃つショット
// shot.h
#pragma once

#include "object.h"

class Shot : public Object {
public:
	               Shot(float x, float y, float speed_x, float speed_y, float angle);
	               ~Shot();
  bool               main();
private:
  IDirect3DTexture9 *shot_;
  float              speed_x_;
  float              speed_y_;
  float              angle_;
};

// shot.cc
#include <windows.h>
#include "shot.h"
#include "lib.h"

Shot::Shot(float x, float y, float speed_x, float speed_y, float angle) : Object(x, y), shot_(createTexture("shot.png")), speed_x_(speed_x), speed_y_(speed_y), angle_(angle) {
}

Shot::~Shot() {
}

bool Shot::main() {
  moveTo(x() + speed_x_, y() + speed_y_);
  draw(x(), y(), 8.f, 32.f, angle_, shot_);
  if ((x() < -8.f) || (x() > 648.f) || (y() < -32.f) || (y() > 512.f)) {
	return true;
  }
  return false;
}

// 敵が撃つ弾幕
// barrage.h
#pragma once

#include "object.h"

class Barrage : public Object {
public:
	               Barrage(float x, float y, float angle);
	               ~Barrage();
  bool               main();
private:
  IDirect3DTexture9 *barrage_;
  float              angle_;
};

// barrage.cc
#include <math.h>
#include "barrage.h"
#include "lib.h"

Barrage::Barrage(float x, float y, float angle) : Object(x, y), barrage_(createTexture("barrage.png")), angle_(angle) {
}

Barrage::~Barrage() {
}

bool Barrage::main() {
  moveTo(x() + 1.2f * sinf(angle_), y() + 1.2f * cosf(angle_));
  draw(x(), y(), 32.f, 32.f, angle_, barrage_);
  if ((x() < -16.f) || (x() > 656.f) || (y() < -16.f) || (y() > 596.f)) {
    return true;
  }
  return false;
}

いやはや長いですね。これに検証用ライブラリも載せようと思ったら2倍くらいになりますよ。ふと思い立ってもテストコードを書きたくなくなるわけです。
で、このコードを動かしてみて得られた結論。
std::listとstd::tr1::shared_ptrのコンビで弾幕ゲーを作る件に関しては、
微妙
という結論になりました。まあ或る程度まではfps60余裕なのですが、頭おかしくなるほど弾幕を出すとfpsが30位に落ちますね。因みに上のコードではfps60出ます。動かすとどんな弾幕になるのかというと、

こんな感じです。ウィンドウタイトルにfpsを表示しているので、fps60出ていることが確認できます。このくらいの弾幕であれば余裕という指針になりますかね。唯、このコードは当たり判定を付けていないのでその計算量も考慮に入れる必要があるかもしれません。あくまで描画限界としての指針ということで。因みに使ったPCはデスクトップでCore2Duoですね。
あと、画像については東方Project上海アリス幻楽団さん)の二次創作である東方夢終劇(danmaqさん)内の画像を使用させてもらいました。