Hatena::Groupfatalemployeetraining

資材部の懲りない面々・あれこれブログ

「間違った社員教育」製品版委託頒布中!

2011-06-09

レーダー索敵スクリプトについて

| 20:50

Sphere Engine研究所, 制作日誌003 スクリプトにて、レーダー照射範囲を円錐状と見立てて索敵可能エリアを限定するスクリプトが紹介されています。

素敵なことしてますねー。

うちは初心者向けミッションなのでこれを使わせてもらうのは難易度的にはアレなんですが、こういうスクリプトはかっこいいので好きです。

よくわからなかった人のために先方の解説を補足すると、自機から機体へのばしたベクトル\mathbf{a}(長さがR)、自機のレーダー照射範囲円錐の軸方向単位ベクトル\mathbf{e}(長さが1)とし、2つのベクトルの成す角を\thetaとすると、ベクトル内積の公式から、

   \mathbf{a}\cdot\mathbf{e} = |\mathbf{a}||\mathbf{e}|\cos \theta = R \cos \theta

   \cos \theta = \frac{\mathbf{a}\cdot\mathbf{e}}{R}

となります。レーダー照射範囲である円錐の半頂角を\varphiとすると、\cos \theta < \cos \varphiならば、 \theta >  \varphiですから、敵機は円錐の外にいることになります。

で、該当処理のincファイル読んだ感想をば。


real_rader_thread.incについて。

12行目~40行目のループ、ちょっとわかりづらいのでfor文を使って整理した方がよいように感じました。

また、27行目のsystem();があると、1機の索敵処理をするたびに1ステップ(1フレーム)時間が経過してしまいます。それが意図した動作ならかまわないのですが、そうでないならば(1ステップ中に全機の索敵処理を済ませるつもりならば)27行目のsystem();は不要のように感じました。

こんな感じでおなじ動作になると思いますが…

	while(TRUE){
		system();
		for(rader_target = 101; rader_target <= enemy_total; rader_target++){
			//敵の生死確認
			R_enemy_destroyed = get_status("ALL_LABEL", rader_target);
			//破壊済みならフラグを0に
			if (R_enemy_destroyed == 0) {
				enemy_flag[rader_target] = 0;
			}
			//フラグが0ではなくかつ6でもないならば
			if(enemy_flag[rader_target] != 0||enemy_flag[rader_target] != 6)
				system(); //←不要かも?(ブログ本文参照)
				bsccd_length = get_length(0, rader_target); // 自機と敵との距離を測定
				if (bsccd_length <= 100) { //600以内なら発見
					rader_total(1);	//最終処理へ
				}
				else {					
					rader_azimuth(); //発見しなければ次(円錐状索敵チェック)へ
				}
			}
		}
	}

radian.incについて。

肝のcos_azimuth関数についてですが、35行目、変数R(自機と敵機の間の距離)をint(整数)で宣言すると精度が落ちてしまうと思います。floatでいいんじゃないでしょうか。

	float R = get_length(label_A, label_B);

この関数はRの値が0に近づくと精度が極端に悪化し、0になると計算できなくなりますが、それについては先ほどのrader_loop()の処理内のif文で除外できてる(そういう場合は実行されない)ので問題ないですね。

ご託はいいから遊んでみれば…ということで

| 00:35

飛んでみました。

そしてなすすべもなく物量に押し負けるorz

索敵範囲に入らないとロックオンマーカーが出ないのはなかなかいい感じです。

マーカーでなくなっても(内部的に'OBJECT'扱いになっても)'ENEMY'状態のうちに視点ロックすると持続するのね。

これはおもしろいことを知った。

押し寄せる実際の編隊をすべてイベントシーンとして見せつつ、開始するとレーダーには見えない!とかになってくると「こええええええ!見えねえええええ!」とさらに恐怖をあおられて楽しくなりそうです。

あ…っと。もう一つ検算。

| 23:03

レーダーの円錐軸が機体軸に一致する場合、機体が真北・水平を向いている場合、

 \mathbf{e}=\left(\begin{array}0\\0\\1\end{array}\right)

となります。

機体のピッチ角が \psi _p、方位角が \psi _vの場合、さきほどの \mathbf{e}を、x軸(東方向)まわりに \psi _p回してから、y軸(鉛直軸まわり)に \psi _v回せばいいです。回転行列をかけると

 \mathbf{e}=\left(\begin{array}\cos \psi_v&0&\sin \psi_v\\0&1&0\\-\sin \psi_v&0&\cos \psi_v \end{array}\right)\left(\begin{array}1&0&0\\0&\cos \psi_p&\sin \psi_p\\0&-\sin \psi_p&\cos \psi_p \end{array}\right)\left(\begin{array}0\\0\\1\end{array}\right)

となります(数学の教科書と違うように見えるかもしれませんが、RSのxyz座標系にマッチするようにするとこうなる…はず)。

すると、

 \mathbf{e} = \left(\begin{array}\sin \psi_v \cos \psi_p\\\sin \psi_p\\\cos \psi_v \cos \psi_p\end{array}\right)

となりますね。(…手計算なのでちょっと心配ですが。)

radian.incの45行目を見ると、一見よさそうに見えます。

が、問題は、レーダー軸が機体軸に対し、相対ピッチ角 \phi _p相対方位角 \phi _vずれている場合についての処理です。

この場合、上記行列演算を行う前に、事前に相対角変化ぶんの回転行列をかけねばなりません。(単純に+すればいいわけではないです。)

つまり、まず

 \left(\begin{array}\cos \phi_v&0&\sin \phi_v\\0&1&0\\-\sin \phi_v&0&\cos \phi_v \end{array}\right)\left(\begin{array}1&0&0\\0&\cos \phi_p&\sin \phi_p\\0&-\sin \phi_p&\cos \phi_p \end{array}\right)

をかけてから

 \left(\begin{array}\cos \psi_v&0&\sin \psi_v\\0&1&0\\-\sin \psi_v&0&\cos \psi_v \end{array}\right)\left(\begin{array}1&0&0\\0&\cos \psi_p&\sin \psi_p\\0&-\sin \psi_p&\cos \psi_p \end{array}\right)

をかけます。

つまり

 \fs1 \mathbf{e}=\left(\begin{array}\cos \psi_v&0&\sin \psi_v\\0&1&0\\-\sin \psi_v&0&\cos \psi_v \end{array}\right)\left(\begin{array}1&0&0\\0&\cos \psi_p&\sin \psi_p\\0&-\sin \psi_p&\cos \psi_p \end{array}\right)\left(\begin{array}\cos \phi_v&0&\sin \phi_v\\0&1&0\\-\sin \phi_v&0&\cos \phi_v \end{array}\right)\left(\begin{array}1&0&0\\0&\cos \phi_p&\sin \phi_p\\0&-\sin \phi_p&\cos \phi_p \end{array}\right)\left(\begin{array}0\\0\\1\end{array}\right)

となります。

手計算の結果ですが、

 \mathbf{e} = \left(\begin{array}e_x\\e_y\\e_z\end{array}\right)

 \fs1 e_x = \cos \psi_v \sin \phi_v \cos \phi_p - \sin \psi_v \sin \psi_p \sin \phi_p + \sin \psi_v \cos \psi_p \cos \phi_v \cos \phi_p

 \fs1 e_y = \cos \psi_v \sin \phi_p + \sin \psi_p \cos \phi_v \cos \phi_p

 \fs1 e_z = -\sin \psi_v \sin \phi_v \cos \phi_p -\cos \psi_v \sin \psi_p \sin \phi_p +\cos \psi_v \cos \psi_p \cos \phi_v \cos \phi_p

となるようです。(ちょっと量が多いのであやしいですが…)確認しました。

radian.inc中では、

 e_x = \sin (\psi_v + \phi_v) \cos (\psi_v + \phi_v)

 e_y = \sin (\psi_p + \phi_p)

 e_z = \cos (\psi_v + \phi_v) \cos (\psi_v + \phi_v)

となっているようですが、三角関数の公式を使って整理展開してもこれらは一致しないように見えます。

単純に真後ろを向いている後方警戒レーダーの計算の場合でも、両者は結果が異なります。

(例えば e_yに値を入れた結果を見てみてください。)

一度検算してみていただいたほうがよいと思います。

スクリプトの改善に役立てば幸いです。

3次元の回転についての参考:座標変換(東北大・ロボット基礎工学)


追記…

| 23:47

作者様ご自身のご指摘があったのですが、これ、円錐軸を斜めに向ける時点で、機体のバンク角も考慮しないといけませんね。ということはもう一個行列をかける必要がありますね。

RSEの場合はバンク(z軸まわり)→ピッチ(x軸まわり)→方位(y軸まわり)の順に回せばいいはずなので…機体のバンク角 \psi_bとするなら

 \fs1 \mathbf{e}=\left(\begin{array}\cos \psi_v&0&\sin \psi_v\\0&1&0\\-\sin \psi_v&0&\cos \psi_v \end{array}\right)\left(\begin{array}1&0&0\\0&\cos \psi_p&\sin \psi_p\\0&-\sin \psi_p&\cos \psi_p \end{array}\right)\left(\begin{array}\cos \psi_b&-\sin \psi_b&0\\\sin \psi_b&\cos \psi_b&0\\0&0&1\end{array}\right)\left(\begin{array}\cos \phi_v&0&\sin \phi_v\\0&1&0\\-\sin \phi_v&0&\cos \phi_v \end{array}\right)\left(\begin{array}1&0&0\\0&\cos \phi_p&\sin \phi_p\\0&-\sin \phi_p&\cos \phi_p \end{array}\right)\left(\begin{array}0\\0\\1\end{array}\right)

になります。


計算してみます。

 \mathbf{e} = \left(\begin{array}e_x\\e_y\\e_z\end{array}\right)

 \fs1\begin{array}{lcl}e_x&=&\cos \phi _p (\sin \phi _v (\cos \psi _b \cos \psi _v-\sin \psi _b \sin \psi _p \sin \psi _v)+\cos \phi _v \cos \psi _p \sin \psi _v)\\&&+\sin \phi _p (\sin \psi _b (-\cos \psi _v)-\cos \psi _b \sin \psi _p \sin \psi _v)\end{array}

 \fs1 e_y = \cos \phi _p (\sin \phi _v \sin \psi _b \cos \psi _p+\cos \phi _v \sin \psi _p)+\sin \phi _p \cos \psi _b \cos \psi _p

 \fs1\begin{array}{lcl}e_z&=&\cos \phi _p (\sin \phi _v (\sin \psi _b \sin \psi _p (-\cos \psi _v)-\cos \psi _b \sin \psi _v)+\cos \phi _v \cos \psi _p \cos \psi _v)\\&&+\sin \phi _p (\sin \psi _b \sin \psi _v-\cos \psi _b \sin \psi _p \cos \psi _v)\end{array}

これはさすがに、回転行列を1個かける計算を関数化して5個かけていったほうがすっきりしていいかもしれませんね…

計算やっかいですが、この修正ができれば、ルックダウン性能とかすらも表現できるすごい機能になるので…、期待してます。がんばってくださいっ!

FROSTFROST2011/06/09 22:56貴重な意見ありがとうございます。float Rはその通りですね。今のところ精度は気にならないですが、他の人にとっては気になるかもしれませんし。
あと、1機索敵するごとに1フレームかかるのは仕様です。敵がたくさんいたらレーダーも処理が遅れそうだと思ったので、そうしました。100機も登場させるようなミッションを作らなければ大丈夫ですし、それに数フレームで何十機も処理したら、ゲーム処理も大きくなりそうでしたので。

FROSTFROST2011/06/10 00:12こんな面倒な計算をわざわざすみません。 二次元空間の回転行列は理解できるのですが…

2011-06-07

リングーっ

| 01:35

次の面は夜間飛行面なのですが。最初の作業として。

とりあえずST4をそのままコピーして環境IDのみ晴天・夜に変えて、通過点指定用リングを仮に作って置いて見ました。

ゲーム内容はST4とはもちろん違う予定ですが、あくまでリングのテストってことで。

f:id:yumeno:20110608013403p:image:w320

f:id:yumeno:20110608013404p:image:w320

f:id:yumeno:20110608013405p:image:w320

f:id:yumeno:20110608013406p:image:w320

f:id:yumeno:20110608013407p:image:w320

まあまあ遠くからでも見える?かな…もうちょっと大きくするともっと遠くからでも見えるかも。

HUDへのTGT表示や、リングの色・半透明の設定、通過したら消えるようにするスクリプト制御…は今後の必要作業だなあ。

2011-06-06

ST02演出強化!

| 01:35

ST02クリア時のイベントですが、演出を強化しました。試作品のころとは別の方向に。

ネタバレが嫌な人は、以下の動画と解説は見ずに、まずはダウンロードして遊んでみてください。










実はミサイルバンカー+ジェネレータだったり。

後ろの黄色いモノは、始めはFLAK拡大して済ませようとしましたがいまいちなので速攻でデッチアップしました。

…これをやるためにデカールを貼ろうとしたんですね。

以上、くだらない小ネタでございましたっっと。

2011-06-04

テクスチャを.bmp->.ddsに変換する

| 20:50

本パッケージの特徴のひとつともいえる顔グラフィックアイコンですが、

  • COLORS betaでキャラクターを生成する
  • COLORS betaから「シェル作成」機能で全表情の立ち絵を生成する
  • Image Magick(Windows用)を使って顔部分のみくりぬき+並べて接合したbmp形式画像(800x400)を量産
  • スプライトとしてユニット登録

という手順で作成してきました。

しかし、この方法には欠点があり、RSE3.5側ではbmpjpgなどの形式のテクスチャを使うと、「黒」色部分を透明指定として扱うようになっています。この仕様のおかげで、アルファチャンネルのことなど気にしなくても簡単にビーム光などを作ることができるのですが、今回は逆に悪い方に作用してしまいました。

すなわち、黒髪や暗色系の髪のキャラが多いのです。そのため、透明指定しているつもりのないところで色抜けが生じてしまいました。

そこで、SEC2011出展時~SEC2011+a版までは、髪の色をすこし明るい色に変えて、色抜けが出ないようにしたのです…が。実は「まったく君の会社ではいったいどんな社員教育を(略)」のキャラクター達は、すべてもともとは仲間うちで遊んでいるTRPGのキャラクター達で…すべてキャラクターを設定したプレイヤーの方が存在します。グラフィックも(実は台詞の言い回しも)彼らの監修を受けているので…この選択は苦渋でした。

しかし、SEC2011で「dds形式に変換すれば、この制約から逃れられるよ!」という情報をいただきました。しかも、変換は「Paint.NETというフリーウェアでできるよ!」とのこと。もっともSEC2011での文脈は透過キャノピーなどを作るための話でしたが…

早速Paint.NETを落としてきて修正作業に入りました。

しかし、始めはうまくいきませんでした。…なぜか。それは、dds形式の制約によるものでした。

  • dds形式では、ピクセル数が2の乗数(2,4,6,8,...,256,512,1024,...)でなければならない。
  • 長方形で大丈夫という情報もあるが、正方形にしておくのが無難という情報も。

とりあえず1024x1024にキャンバスサイズを拡大し、800x400の画像を左上に寄せて貼り付けることで解決しました。現在、これをSEC2011+a2としてリリースしています。

(…1024x512でもいけそうですね。これはそのうち試してみます。)

ST2ラストの演出について

| 20:50

試作版のころと、ST2ラストの演出を一部変更しました。諸般の事情によるものです。

ただ、現状の演出はインパクトに欠くのも事実なので、さらなる修正を考えています。

とりあえず手元での準備とメンバー内での確認は終わったので…

…次のバージョンリリース時には再修正される予定です。

うまい方法を見つけた!と思ったんだけど…

| 05:18

ddsで抜ける部分がアルファ指定されてる画像を作って、それをテクスチャにして光扱いでユニット作るとすけすけのユニットが作れるので…これを使えば、既存のモデルの上に、透明なモデルをかぶせてレタリングできない?と考えたわけです。(なんでそんなことをやろうとしたかは後日明らかになるでしょう)

f:id:yumeno:20110605051153p:image:w320

この画像、「172F」という文字が、この方法でスクリプト上で後付けされています。

こんな感じ。

UNIT_ID412番が、172Fという文字だけの透明テクスチャを貼ったR-21のモデル(LIGHT扱い, EFFECTでGHOST)です。

catapult(LABEL_PLAYER, 412, LABEL_STAMP1, FALSE);
set_team_id(LABEL_STAMP1, 'OBJECT');
set_parent(LABEL_STAMP1, LABEL_PLAYER);
set_scale(LABEL_STAMP1, 1.0, 1.0, 1.0, TRUE);
move(LABEL_STAMP1, 0.0, 0.002, 0.0, TRUE);

白い字ならなかなかいい感じです。

が、光扱いなので、…

f:id:yumeno:20110605051154p:image:w320

影に入っても文字だけ輝いてしまいます。

また、絵柄をつけてみたりすると…

f:id:yumeno:20110605051155p:image:w320

f:id:yumeno:20110605051527p:image:w320

見事にすけすけですね。駄目でした。

うーむ、ぬか喜びさせてすいません。orz

そりゃそんな簡単にできてるならみんな苦労してないか。

まあ用途を限れば、役に立つかもしれないので…一応まとめておきます。

2010-12-02

顔グラフィック+アニメ追加

3面

なんとか、顔グラフィック+目パチ口パクまでできました。


試作品(ダウンロード

よろしかったらお試しください。現時点で出来てる3面まで遊べます。(要 RaideresSphere 3rd / RaidersSphere 3rd 3D-Dive Edition)

あと、最初の最初に作ったひな型的ミッションをリメイクしたものも用意しました。RSE3.5でのミッション製作の参考になれば幸いです。(こちらには顔グラフィックライブラリは含みません)

(2011/6/3追記)「まったく君の会社ではいったいどんな社員教育を(略)」にて4面まで遊べるバージョンを公開しています。よろしかったらお試しください!

ひな型(ダウンロード)

スレッドの起動には、ミッション開始からの通算総起動数に制限がある

まず、RSEにおけるスレッドの基礎知識。

おそらくスレッドの同時起動数にも制約があるとは思うんですが、今回目パチ口パク作成でハマッたのは通算総起動数、つまりミッション開始から何回create_thread()を実行したか(途中で終了するしないに関わらず)、です。

当初、目パチ口パクは、ある表情絵の表示が始まったら、目パチ用スレッド口パクスレッドを起動し、別の表情・別のキャラ絵に切り替わったり、絵自体が消えたりしたらスレッドを終了する形で記述していました。毎回かならず終了しているので同時起動数は抑えられますし、アニメーションしないときには終了していますから、計算処理的にも負担が抑えられ、コード記述もシンプルになります。

しかし、すこし長めにプレイすると、エラーが発生。

関数コールの段数が深いかも、とか関数名が長いかも、とエラーメッセ-ジにあるので、多少調節してみましたが改善せず。

自己回帰呼び出しとかが発生していないかもチェックしてみましたが、それもありません。

もしやと思い、単純なテスト専用ミッション(目パチ・口パクもはずしたシンプルなもの)を用意し、2つテストを別々に行いました。

  1. ボタンを押すたびに自己再帰関数再帰回数制限付き)を呼び出して実行テストする。毎試行が終了するごとに再帰回数制限を多くしていく。
  2. ボタンを押すたびにスレッドを作成する。ただしスレッドは1フレーム以内に計算が終了(本編に影響しない引き算1回のみ)しスレッド自体もすぐに終了するもの。

1番は相当な数、再帰の回数を増やしてもエラーが出なかった(150回くらい?結局エラーを見ていないのでもっと大丈夫かも)のに対し、2番は74回でエラー発生。また、2番のテストで、スレッドからさらに別の関数を呼ぶなどするとエラー発生までの回数は少なくなります。

この試行から、

  • ミッション中で行えるcreate_thread()の回数には制限がある。起動したスレッドを終了するかしないかには全く関係がない。起動されるスレッドの構成(内部で別の関数をコールするなど)によって上限がより厳しくなる。

ことがわかりました。

これは正直なところ、(疑似だけども)マルチスレッドを謳う言語としては、リソースリークと言える深刻な問題仕様と思います。

(だって、現状の仕様と用意されてる関数だけ見てると、thread_idはintだからintの変数の上限値による同時起動数制限は受けそうだけど…終了しさえすれば大丈夫、って見えるじゃないですか。)

とはいえ、もともと過去のRSEは並列実行できるスレッド数に仕様上、上限があるものでしたから、内部動作的に上限がある前提で作られている部分が残っていてもおかしくはないとも思いますし、終了したスレッドについて逐一動的に痕跡を消すのがめんどくさい内部仕様になってるのかな、とも想像はできます。(並列実行する関数群をまとめて配列に積みっぱなしで、頭から最後までぐるりとひとループして疑似並列実行、exit_thread()されたやつは読み飛ばすようにフラグつけるだけ、とかなんですかね。双方向リストじゃないのかなー。)

とりあえず目に見えない上限があることを前提にしたコードに修正しました。(目パチを管理するスレッド口パクを管理するスレッドを用意し、グローバル変数で用意したフラグで目パチや口パクの動作開始を制御する。フラグが増えること、目パチ・口パク動作開始してからの時間測定を別途カウンタ変数を用意し管理する必要があること、などコードはかなり複雑になります。実は目パチ・口パク以外にも、メッセージイベントスレッドの自作ライブラリスレッド起動回数制限を考慮しない仕様だったので動作を変更。)

先日のget_pos()の子ユニットに対する挙動およびそれと関連すると思われる航空機ユニットの子ユニット化不可問題と併せて、本件もいずれ仕様が改善されることを期待します。

2011/6/4 追記:

SEC2011でこの点を話したところ、スレッドの起動回数上限問題は、電プロさんによると、想定外動作であるとのことです。いずれ修正されるはず。修正されたら、自前でスケジューリングしないで済むので楽になります。期待してます!

一方、航空機ユニットの子ユニット化不可問題は、公式掲示板でさしあたり無理という回答をいただきました。4thでこのあたりの内部仕様も組み直しで変わる可能性が高いので、4thになってどうなるかに期待ですね。