RECT WINDERプレイ動画

これが最終系なのか……!?
 
おめがさんとRemovey.sさんが超絶プレイをやってくれました。

体当たりボス撃破1


゚д゚)ポカーン
本当にやってしまわれた……。
 
ただ、相打ちだとゲームオーバー扱いになってしまうようです。
そうだったのか……。
 

体当たりボス撃破2


゚д゚)・∵. ポワーン
ば、化け物か……。
いや、もう、動きがニュータイプ並みの回避です。
(特に第6形態のところ)
 
今度はシールドがある状態で体当たり。
 
しかし、問題発生。

見れば分かりますが、無限バグです(゚∀゚) 時間が3600を超えてもゲームは終了しません。

なんてこった。
というか、公然とバグを晒すなんてヒドイ〜 。・゚・(*ノД`*)・゚・。
公開処刑プレイです。
 

0点ボス撃破

隣でそれを観察していたRemovey.s君。
体当たり破壊時に点が入っていないことに気づいたらしく、
「これはゼロ点ボス撃破プレイが出来るんじゃね!?」
と狂気乱舞。

「1000点撃破」以上の難易度を持つプレイを実現してしまいました。
 
……感無量です。
今度、何かおごらせてください。
 
 

教訓

教訓めいたこと。
    * デバックは徹底的にする(抜け穴をつぶす。より確実な条件を作るのも手。今回であれば、時間3600で強制的にゲームオーバー/クリア等)
    * 共通の処理は共通にする(バグの温床を減らす)

今回のバグは、

  • ボスがゲームの状態遷移をハンドリングしていた

ということが原因っぽいですね。
 
ボスなどの敵キャラは、シーンにぶら下がっているものであり、
ボスからシーンの状態を変えるのは設計的に問題ありですね。
例えば、シーンが常にボスの状態をチェックして、
ボスの状態が変化したら状態遷移を行う、
という方法を取るべきなのかもしれません。
 
ソースコードを見ると、721行目あたりで、

	g_gamemain.state = g_gamemain.STATE_GAMECLEAR;

というように、グローバルでシーンにアクセスできるようにしていたためです。
 
ただ、敵キャラから、シーンに「通知」するという手法もアリで、
その場合は、

共通の処理は共通にする(バグの温床を減らす)

ということも重要ですね。
 

    * 何らかのプレイングでゲーム結果に差が生まれる場合、それはゲームシステムとして有効な深みをもたらす。(倍率バグは本来の仕様にはないが、結果として高/低スコアプレイにおいて深みをもたらした。これは体当たりのゼロ点も同様。)

それまでに取った行動により、結果に変化を加えることが、
ゲームに波をもたらし、プレイヤーに感情の揺らぎを与えることができる。
 
うーん、これを意識してできるようになると、かなりの確率で面白いゲームが作れそうですね。
 
 
あと、プレイ動画を見てて気づいたこと。

アイテムをうまくキャッチできてないことが結構ありました。
 
なので、アイテムなどのプラスアイテムは当たり判定を大きくした方が良いかな、と思いました。
(逆に敵弾などのマイナスは当たり判定を小さくする)

RECT WINDERの感想が来た!

おめがさんから感想の感想の返答が来ました。
http://d.hatena.ne.jp/o_mega/20061110/1163151586
 
まとめると、

  1. アドリブ要素が大切。このアドリブ要素とは「自由度」とか「ゆるさ」とか。例えば困ったら連打でなんとかなる、とか。
  2. まずはプレイヤーの教育が必要。単純な要素を教えていき、徐々にハードルを高くする。例えばマリオなら、最初に要求するのは「ジャンプ」だけ。次に「ジャンプ」⇒「踏みつけ」を要求するなど徐々に組み合わせを増やす、など。
  3. 教育は前提知識により省略可能。例えば、これは○○タイプのゲームだよ。といえば、すぐに大まかなルールを理解できる。(ただしプレイヤーがゲーマーであるときのみ)
  4. パターンゲーであるならば、未来予測を入れる。対処動作は未来予測から逆算して可能であるよう、調整する。
  5. ゲームオーバーには理由が必要。理由とは「なぜゲームオーバーになったか」が分かる。そして「こうすれば次は対処できそうだな」思わせる。

 
こんな感じでしょうか。
 
 
ただ、これらのことは頭では分かっているんですけど、kenmoはどうも実行できません。
 
なんでだ。
 
 
そこで、OZ-オズ-の公式ページの開発日記をのぞいてみたら、答えらしきものを見つけました。

「OZ」では、プレイヤーが倒れてもゲームオーバーにならないようにしてみました。
プレイヤーは力尽きても、共に戦う仲間が立っている限り、何度でも立ち上がる事が出来ます。
仲間達も、あなたが立ってさえいれば、何度倒れても立ち上がってくれます。 

 と、しぶといプレイヤーサイドですが、敵からすると厄介でした。
3人がかりでタコ殴りにしてくるわ、倒しても倒しても立ち上がってくるわ。
もー、ドカーンとでかい爆発でも起こして、ボカーンと全員いっぺんに殺したろかという衝動に何度駆られた事か。
しかし、所詮、敵とは倒されるために存在するもの。
ドカーンと爆発を起こして、ボカーンと敵をいっぺんに倒すのはあなたの方です。

これですね。
 

敵とは倒されるために存在するもの

ここだ。
 
 
敵は倒されるもの!
 
 
こういうサービス精神が抜けていたような気がします。
 
ゲームはサービス業なんですよね。
 
 
プレイヤーに、すりきれるほどサービスするのがゲームですね。
 
 
例えば、ビューティフルジョーだと、敵の攻撃をかわすと、くるくる回ってピヨります。
その隙を狙って、強力なパンチを叩き込む。
 
こういった、あからさますぎるっていうぐらいがちょうどよい気がします。
 
おめがさんの言葉を借りるなら、

いくつか感じるのはkenmoさんのゲームは「誘導」が足りないと思う。
ほんのりと解法を匂わせるとかそういう…、センス?テクニック?
多少露骨で良いので、プレーヤーが見つけて
「はは〜ん、見つけたぞ!そういうことか!」
とニヤニヤできるようにしておく。
プレーヤーが自分で見つけた発見は、プレイのモチベーションに繋がる。
露骨さをコントロールできれば、発見に難易度がつけれるのでより良い。

これですねー。
 
 
ということで、サービス精神かな、と。

RECT WINDER自己分析

自機の状態による分析

自機の状態には大きく分けて、以下の4つの状態があります。

  1. シールドあり・打ち返しゲージあり・2回分
  2. シールドあり・打ち返しゲージあり・1回分
  3. シールドあり・打ち返しゲージなし
  4. シールドなし

 

1の状態について

もっとも優位な状態。
打ち返しをして敵を殲滅し、アイテムを取得しショットを最強にすることが目的。
 

2の状態について

やや優位な状態。
時間の経過により1にするか、打ち返しをしてアイテムを回収し、現状維持を狙う。
ただし、打ち返しを失敗すると3に転落する。
 

3の状態について

やや不利な状態。
時間の経過により2にする。
グリッドがあるため、有利な状態に見えるが、実は逃げ回るしかない。
 

4の状態について

かなり不利な状態。
逃げ回る以外の戦略が存在しない。
 
いずれの状態も中型機の自爆によりアイテムを回収し、
1または2にもっていくことができる。
 
しかし、被弾すると1,2,3すべてが4の状態になり、全てを失う。
 
 

まとめ

このシステムから分析すると、ややトランプの「大富豪」に近い気もします。
「大富豪(1,2)」はますます繁栄、「貧民(3,4)」は自爆待ちジリ貧。
やれることの差が大きい。
 
しかも、その状態遷移のスピードが速い、速い、速い。
転落の差が激しければ、上昇も速く、下降もドン底。
なんというか周期の早回し。
 
 
とすると、
そこらへんのギャップ・スピード感が受けたのかな、とか思ったり思わなかったり。
 
問題は、3の状態がなんとなく有利に見えるけど、不利な状態であるという違和感、かも。

OZ -オズ-購入

「アルミラ!」
「少年!」
 
前々から気になっていた、連携空中コンボタイミングゲー、「OZ -オズ-」を買いました。

OZ -オズ-(コナミザベスト)

OZ -オズ-(コナミザベスト)

 
 
きっかけは、「GAMESIDE 8月号」の紹介記事の開発者インタビュー。
確か、こんなんだった気がする

最近のアクションゲームは、
・シナリオ重視
・ボタン連打
・アイテム依存
のゲームが多いですよね。
 
でも、やっぱり、
「アクションゲームは、タイミングと修行の日々でしょ!」
と思っていて、
このゲームはそういったコンセプトの元に作りました。
 
なので、今となっては珍しいプレイヤー成長型のゲームです。
 
懐かしい、ちょっと不思議なゲーム性を感じてもらえればと思います。

 
この、

「アクションゲームは、タイミングと修行の日々でしょ!」

という言葉に影響されて、
最近、kenmoは「タイミングゲー」ということを強く意識しております。
(「RECT WINDER」なんかもタイミングゲーを強く意識しています)
 
 
まあ、そんなこんなで、やってみた感想としては、
仲間のとの連携が異様な感覚を生み出しますね。
連携の流れとしては、

  1. 連続で殴り続けていると、敵がピヨる。
  2. そこで、吹っ飛ばし攻撃を入れて、敵を仲間のところに飛ばす。
  3. そうすると、仲間がキャッチ(CPUが勝手にやってくれる)して空中コンボを入れる。
  4. しばらく見ていると、仲間が敵に吹っ飛ばし攻撃をいれて、パスしてくる。
  5. それをタイミングよくコンボするなり仲間に吹っ飛ばす。
  6. 3〜5の動作を繰り返すとテンションゲージが上がって必殺技を出す

要は敵を地面に落とさないようにパスしつづけるだけ、なんですよね。
バレーボールみたいに。
 
3Dゆえに距離感が掴みにくく、2ボタン+レバーの同時押しを要求されるのが、ややつらいですが、
10分ほどトレーニングモードで遊べば、慣れて面白いように連携がつながりますね。
 
なんというか、ストイックにタイミングを極めていく、レトロな感覚のアクションゲーです。
 
 
http://d.hatena.ne.jp/futsu-9/20060910/p1

職場の人の前の仕事がこれの製作だったらしい。
Amazonの評価を見ればわかるけど、かなり高評価。
その人曰く「アクションゲームとは何であるかと一から考え直して再構築した労作。
だが続編が作れないぐらい売れなかった」そうだ。残念。

今の時代、こういう、プレイヤーにストイックな負担を強いるゲームは流行らないみたいですね。。。
悲しいなー。

RECT WINDER REDuce

おめがさんが調整バージョンを作ってくれた!
 
http://d.hatena.ne.jp/o_mega/20061109/1163072782
 
修正点は微少。
しかし、その差は大きい!

変更点は
・自機のあたり判定を 16x16dot から 2x2dot へ縮小
・例の倍率バグの解消
・fpsが60で安定しないバグの解消
だけです。どうでしょうか?

「2x2dot」??
それって小さすぎない?!
 
と思ったのですが、プレイしてみると、
「あ、これぐらいがちょうどいいねー」
と自分の感覚がズレているのを確認しました。
むー。
 
それでも、やっぱり、難しいのですが、
だいぶ遊びやすくなっていると思います。
 
当たり判定の大きさなのですが、
実は、描画と当たり判定で別々のサイズを持つのが面倒だったという、
しょーもない理由だったりします。
 
まあ、いくらやっつけ仕事でも、当たり判定の調整は頑張ってやるべきですね。
 
 
あと、修正点のほとんどが、トークンの座標の取り方を「左上」から「中心」に置き換えているところですね。
kenmoは当たり判定に矩形使うの久々だったので、手抜きして左上座標にしてしまいました。
面倒な置き換え作業をさせてすみませんでした。
 
あと、FPSの固定もありがとうございますー。
バグ回避もー。
 
 

RECT WINDERプレイ動画2

何が、そこまでおめがさんを動かすのか……!?
 
おめがさんがやってくれた!!
もー、笑いが止まりませんです。
 

1000点クリア動画


ボス(1000点)だけ倒すモード。
いや、そんなこと、思いつかないってよー。
 
第4形態のリング弾で、最後のタイミングが狙い目だったとは盲点でした。
無駄がなく美しいですね。
あと、バグ技を回避するため、何回もトライしているということが泣けます、、。
 

0点クリア(自爆待ち)動画


実は「ショット・打ち返し」なしのバージョンをこっそり実装していたのですが、
ゲームのコンセプトにそぐわないということで消してました。
 
そのバージョンで、TIME3500まで行けたのでなんとなくできそうな気はしてました。
が、まさかそれをやってしまうとは思いませんでした。
 
なんとなく死んだ我が子が生き返ったような感じです。
 
ポイントは、発狂の第一形態〜第二形態で、アイテムをナイスキャッチしているところですね〜。
 
あと、避け方もキレイだしー。
うっとり。
 
 

…あと遣り残したこと「ボスに体当たりをキメてクリア」ぐらいかなぁ。

や、やめれ〜〜。
いや、理論上は可能*1だけれども、失敗したら死亡確定*2だし。
 

*1:体当たりは10ダメージ

*2:ダメージ後の無敵タイマーなし

フレームスキップ

どうやら、おめがさんが「RECT WINDER」に手を入れようとされている模様。
http://d.hatena.ne.jp/o_mega/20061109/1163033243
しかし、ソースコードを読んだところ、フレームスキップ処理を入れてないのにガッカリしたようです。
 
適当ですみません。
 
 
その点、ABAさんだと、ウェイトを可変にしたりフレームスキップしていたりと、しっかり計算してますね。
(mainloop.dからコード抜粋)

public void loop() {
	done = false;          // ゲームループ終了フラグ
	long prvTickCount = 0; // 1つ前の時間
	int i;
	long nowTick;          // 現在の時間
	int frame;             // 更新フレーム数
	// ゲームループ
	while (!done) {
		nowTick = SDL_GetTicks(); // 現在の時間を取得
		frame = cast(int) (nowTick-prvTickCount) / interval; // interval=16 経過時間を更新間隔で割って、更新回数を求める
		if (frame <= 0) {
			frame = 1; // 1回だけ動かす
			SDL_Delay(prvTickCount+interval-nowTick); // 余裕が出来たので止めてやる
			if (accframe) {
				prvTickCount = SDL_GetTicks(); // 早送り
			} else {
				prvTickCount += interval;
			}
		} else if (frame > maxSkipFrame) {
			// フレームスキップ数の最大値を超えた(処理落ち)
			frame = maxSkipFrame;
			prvTickCount = nowTick;
		} else {
			prvTickCount += frame * interval;
		}
		// フレームスキップ
		for (i = 0; i < frame; i++) {
			gameManager.move();
		}
	}
}

 
 
まー、お詫びにD言語Tipsでも。
 

Q1.Access Violationで落ちる

たいていヌルポです。または.objを消してからビルドしなおすとうまくいく場合があります。
 

Q2.これ何で型の指定がないのにうまくいくの?

foreach(i; [1,2,3])
{
	printf("%d\n", i);
}

foreachでは「型推論」というのが働いてくれるので、型の指定を省略することが出来ます。
 

Q3.この「inout」って何?

foreach(inout token; getToken())
{
	token = new Token;
}

foreachはデフォルトではコピーしてしまうので代入ができません。
「inout」をつけると参照になりますので、代入ができます。
 

Q4.連想配列が使いたいんですけど……

このようにすると使うことができます。

SomethingObject* [char[]] somethingObjectTable;

 

Q5.数値を文字列に変換したい!

std.string.toString()を使います。

import std.string;
・
・
・
char[] str = std.string.toString(50) ~ "/" ~ std.string.toString(100);
 

Q6.これ何?

static TokenManager!(Item) parent;

TokenManager(T)
というテンプレートクラスのテンプレートを「Item」クラスで使用する、という宣言です。
 

Q7.this()って?

クラスのコンストラクタです。
 

Q8.cast(int)posXって?

変数「posX」をint型にキャストしています。
 

Q9.「nan」とかいう怪しい値が出てきたんですけど、、。

「float/double/real」型の初期値は「nan」で、これを加算代入したりすると「nan」になってしまいます。
必ず「0」などで初期化したほうがよいです。
なお、bool型は「false」、int型は「0」で初期化されます。