ゲームシナリオライターの眼(仮)

最近kenmoがお世話になっている(と言っても読んでいるだけだけど)、
ゲームシナリオ本の著者が一同に参加するイベントがあるそうなー。
http://d.hatena.ne.jp/AYS/20061123

日時
12/10(日)
場所
東京六本木の国際大学GLOCOM
開始時間
13:00〜14:00?

 
パネラーは、

とすさまじい感じに。
 
参加には、公式サイトでの予約が必要との事です。
http://www.glocom.ac.jp/project/rgn/
(今のところは、まだ予約できないみたいですがー)
 
 
面白そう&勉強になりそうなので、kenmoも行ってみようかと思います。

RectWinder HeartBeat

RectWinder HeartBeat

D使いのみかげさんがRectWinderを改造してくれた!
http://d.hatena.ne.jp/KouMikage/20061127
 
タイミングゲーから連打ゲーへ、、、
という超改造に吹きました。
 
 
システムとしては、

  1. 打ち返しボタンを押すと、敵弾が弱体化して(ゆっくりになって)、
  2. もう一回打ち返しを押すと、敵弾吸収。
  3. そして、吸収を続けるとゲージが溜まっていき、
  4. 開放ボタンで吸収した弾を吐き出す、

というシステムみたいです。
 
連射⇒チャージ⇒開放
という流れですね。
 
 
レベル0だと、ほどほどの連射でクリアできますが、
レベル50になると、超連射が必要になるのに爆笑しました。
 
さすがに、レベル50はkenmoの連打では追いつかない、、。
やけになって、
「震えるぞハート!! 燃え尽きるほどヒート!!刻むぜ血液のビート!! 」
とか叫びながら連打してました。
でもムリ。
連射パッドでクリアしました(←コラ
 
 
いやー、まさかこんな形で改造されるとは思いもよらなかったので、
kenmo的には大満足でしたー。はい。

D言語でPlatinum

そろそろステージデータを使ったゲーム作りをしたいと思って作ってみた。
 
マップエディタはいつものように、「Platinum」
http://www.hyperdevice.net/
 

import std.file;

/**
 * 2次元マップ<br>
 * ハッシュテーブルによる実装
 */
class Layer2D
{
public:
	/**
	 * データテーブル
	 */
	int[int] data;
	/**
	 * 幅
	 */
	int width;
	/**
	 * 高さ
	 */
	int height;
public:
	/**
	 * コンストラクタ
	 * @param width  幅
	 * @param height 高さ
	 * @param data   データ
	 */
	this(int width, int height, char[] data)
	{
		this.width  = width;
		this.height = height;
		foreach(i, d; data)
		{
			if(d != 0)
			{
				this.data[i] = cast(int)d;
			}
		}
	}
	/**
	 * 座標を指定して値を取得
	 * @param x, y 座標
	 */
	int get(int x, int y)
	{
		if(x < 0 || width <= x || y < 0 || height <= y) return -1; // 場合によっては例外吐いてもいいかも
		if(y*width+x in data)
		{
			return data[y*width+x];
		}
		return 0;
	}
	/**
	 * デバッグ出力
	 */
	void dump()
	{
		for(int j = 0; j < height; j++)
		{
			for(int i = 0; i < width; i++)
			{
				printf("%d", get(i, j));
			}
			printf("\n");
		}
	}
}

/**
 * FMF読み込みクラス
 */
class FMFLoader
{
public:
	/**
	 * 識別文字「FMF_」
	 */
	char[] identifier;
	/**
	 * ヘッダを除いたデータサイズ
	 */
	int datasize;
	/**
	 * マップの幅
	 */
	int width;
	/**
	 * マップの高さ
	 */
	int height;
	/**
	 * チップの幅
	 */
	int chipwidth;
	/**
	 * チップの高さ
	 */
	int chipheight;
	/**
	 * レイヤー数
	 */
	int layercount;
	/**
	 * データのbit数(8/16)
	 */
	int bitcount;
	/**
	 * レイヤー配列
	 */
	Layer2D[] layerArray;
private:
	char[] _file;
	int    _ptr;
public:
	/**
	 * コンストラクタ<br>
	 * TODO: マップデータは8bitのみ
	 * @param filepath FMFファイル
	 * @throw ファイルフォーマット例外
	 */
	this(char[] filepath)
	{
		_file = cast(char[])read(filepath);
		_ptr  = 0;
		identifier = _file.dup[0..4]; // ファイル識別子  [4byte]
		if(identifier != "FMF_")
		{
			throw new Error("Invalid FMF format (" ~ filepath ~ ")");
		}
		_ptr += 4;
		datasize   = readLong(); // ヘッダを除いたサイズ [4byte]
		width      = readLong(); // マップの幅           [4byte]
		height     = readLong(); // マップの高さ         [4byte]
		chipwidth  = readByte(); // チップの幅           [1byte]
		chipheight = readByte(); // チップの高さ         [1byte]
		layercount = readByte(); // レイヤー数           [1byte]
		bitcount   = readByte(); // データのbit数(8/16)  [1byte]
		layerArray = new Layer2D[layercount];
		for(int i = 0; i < layercount; i++)
		{
			Layer2D layer = new Layer2D(width, height, _file[_ptr.._ptr+width*height]);
			layerArray[i] = layer;
			_ptr += width*height;
		}
	}
	/**
	 * 指定レイヤーを取得する
	 * @param id レイヤーID
	 * @return 2次元マップ
	 */
	Layer2D getLayer(int id)
	{
		if(id < 0 || id >= layercount) return null;
		return layerArray[id];
	}
	/**
	 * デバッグ出力
	 */
	void dump()
	{
		printf("identifier='" ~ identifier ~ "' datasize=%d (w,h)=(%d,%d) chip(x,y)=(%d,%d) layer=%d bit=%d\n",
			datasize, width, height, chipwidth, chipheight, layercount, bitcount);
		foreach(i, layer; layerArray)
		{
			printf("Layer-%d\n", i);
			layer.dump();
		}
	}

private:
	/**
	 * 1byte読み込み
	 */
	int readByte()
	{
		if(_ptr - 1 >= cast(int)_file.length) return -1;
		int result = _file[_ptr];
		_ptr += 1;
		return result;
	}
	/**
	 * 4byte読み込み
	 */
	int readLong()
	{
		if(_ptr - 4 >= cast(int)_file.length) return -1;
		int result = _file[_ptr] + (_file[_ptr+1] << 8) + (_file[_ptr+2] << 16) + (_file[_ptr+3] << 24);;
		_ptr += 4;
		return result;
	}
}



void main()
{
	FMFLoader fmf = new FMFLoader("map.fmf");
	fmf.dump();
}

まー、やっていることは、
http://d.hatena.ne.jp/kenmo/20060509
とだいたい同じ。
 
あと、Layer2Dがハッシュテーブルになっているのは、
例えば、シューティングで敵出現情報をマップに埋め込む場合、
多くの部分が「0(存在しない)」になるため、メモリの節約となるからです。
……まー、そんなに気にする必要はないかもしれませんが。
 
注意点は、8bitデータのみ対応、です。
 
 

おまけ情報

あと、Platinumのちょっとしたテクニックはこちら。
http://d.hatena.ne.jp/kenmo/20060522

もち丸パンツあとがき

あとがき、というか言い訳っぽいのでも書いときましょう。
なんかの役に立つかもしれないので。
 

コンセプト

コンセプトは、「正月」。
まー、そういうお題なんだから当たり前ですが。
 
元ネタは、、、HIZさんが指摘していますが、
MSXHAL研が出していた名作「ぶた丸パンツ」。
 
ただ、実際にプレイしたことはないので、設定だけいただきました。
 
「つかみ投げ」というゲームシステムは、
メガドラの名作ガンスターヒーローズから。
 
ガンスターで爆弾を投げてくる兵士がいるんですよね。
で、これを投げ返してぶつけるのが面白かったので、
その要素に特化したゲーム性になっております。
 
 

ウェイト

今回もウェイト(画面更新がピタっと止まる)をガンガン入れています。
これにより、
おもちを投げるときの重みや、
おもちをぶつけたときの重みが表現できたのではないかと思います。
 
あと、最初は着地の硬直を入れていたのですが、
不自由でしかないので、やめました。
 
まー、当たり前の話なのですが、ウェイトは、

  • 操作感に重みを出す

というメリットがある反面、

  • ゲーム性に支障をきたす場合がある

というデメリットを再確認しました。
 

レベルデザイン

おもちの種類

最初はおもちの種類に、

  • 床に落ちると爆発するおもち
  • 時間で爆発するおもち

などを用意しようと思ったのですが、
唯一の攻撃手段にマイナス要素を持たせるのはどうかと思い、やめました。
 

ライフ制

最初はライフを3つ持たせました。
ですが、そうするとプレイヤーは常に残りライフの表示を見る必要があります。
これがUI的につらいかな、と思って、1シールド制にしました。
こうすることで、プレイヤーにバリアが張り付いているので、
直感的になったのでないかと思います。
 

敵の攻撃方法

プレイスタイルとして、敵の下に移動してもちを投げる、
というのが安定なので、一部の敵で下に攻撃するようにしました。
 

早回し

今回初めて早回しを入れました。
おかげで、だいぶテンポがよくなった気がします。
当たり前ですが、早回しを入れないと、
敵がいない時間ができてヒマになってしまうんですよね。

もち丸パンツ

もち丸パンツ

ヘルディスク2収録予定の、「正月」をテーマにしたおもちつかみゲー。
http://www5f.biglobe.ne.jp/~kenmo/dest/d/omoti.lzh
○操作方法

  • Zキー/1ボタン:ジャンプ
  • Xキー/2ボタン:もちをつかむ
  • ESCキー:アプリの終了
  • F9キー:ウィンドウモード・フルスクリーン切り替え
  • F10キー:マウスカーソルの表示・非表示
  • F12キー:スクリーンショットの保存

(※ジョイパッド推奨)
 
……おもちが出てくる以外は、正月関係ありませんが、、(´Д`;
 
○修正履歴
・ジャンプが使えない、という指摘を受けて、
 ジャンプ力アップ/ジャンプボタン押しっぱなしでホバーできるようにしてみた。
・空中でつかむとジャンプ可能回数が1回増える、という妙な仕様を止めて、
 最初から3段ジャンプできるようにした
・プレイヤーの重力加速度を少しだけ減らした
 
○登場人物
・もち丸
 にわとりです。
・パンツ
 もちの神様。もちをばら撒きます。

RECT WINDER NTバージョンアップが

RectWinder NT version up

家に帰ってからやるつもりだったんですが、他ごとをしていてプレイできませんでした。
 
 
……と思っていたら、早速バージョンアップが!?
http://d.hatena.ne.jp/o_mega/20061120/1164041384
 
 
あ、これ、やばい。
kenmoの理想の形に近いですね。
「レベル選択」がついただけなのですが、全然別物になっています。
 
 
例えば、最大のレベル50だと、「弾避け」「打ち返し」「アイテム確保」「ボム弾消し」、、
それぞれの状態・戦略がめまぐるしく変わっていきます。
 
なんというか、、「周期」の早回しとでもいうのでしょうかー?
かなりスピード感が増しています。
 
逆にレベルが低いと、それらの状態遷移のスピードがゆっくりなので、
じっくりプレイできます。
 
 
そもそもシューティングというより、アクションパズルに近いノリのゲームなので、
「数値型のレベル選択」というアプローチは非常に正しいですね。
 
例えるなら、テトリスのレベル選択みたいに、
プレイヤーが自分のレベルに合わせてプレイする、、という感じで。
 
 
で、勝手にまとめ。
○数値型レベルの長所

  • 数値型は、(プレイヤーが)レベルの微調整がしやすい
  • プレイヤーがどれだけ成長できたかを確認しやすい

 
これは「EASY」「NORMAL」「HARD」などというEnum型にはない長所だなー、、
と思いました。
基準が分かりにくい、プログラム側の調整が難しい、という欠点はありますがー。

RectWinder NT

RectWinder NT

おめがさん仕様のRectWinderが完成したようです。
http://d.hatena.ne.jp/o_mega/20061119
 
さすがに、初プレイでクリアは難しいけど、落ち着いてプレイすればクリア出来るようになっています。
いい感じにゆるめの難易度に。
 
会社なので、まだあまりやりこめていないけど、、なんとなく印象。

  • クリアは簡単。得点稼ぎがメイン……っぽい。
  • アイテム取得時・打ち返しヒット時のウェイトが短くなったので、スピード感が増した、ような。
  • 打ち返しヒット時のウェイトが短くなったことにより、打ち返しで敵のHPをゴリゴリ削るような「感覚的面白さ」が出てきた。
  • 敵弾のアニメ・敵・アイテムの動きが、非常におめがさんらしく、コミカルに。(高速離脱艦はワロタwww
  • 弾幕は基本的には同じだけど、いい感じに改良されている。
  • ショットがなくなったので、打ち返しに集中できて、よりタイミングゲーっぽく。(やらせたいことが明確になった)

 
なんとなく、ファンシーな印象ですね。

あと、打ち返しのチャージ時間も正しく修正されてました。

/**
 * 打ち返しに必要な時間(240/120fps=2sec)
 */
static const int BOMB_COUNTER_WAIT = 120;	// 240;

……あれ、コメントがそのままだ(´Д`;
そこを一番直して欲しかった!!*1

*1:ホントは60FPS