賞状+本到着

コンテストの賞状、革製しおり(参加賞)、入賞賞品の書籍「Arduinoでロボット工作をたのしもう!」が届きました。

Arduinoでロボット工作をたのしもう!

Arduinoでロボット工作をたのしもう!

入門編からどんどん難易度が上がっていく構成。
なるほど、Arduinoに興味があれば、どっかのスキルレベルに当たるなこれは。

入賞報告

宇宙海賊キャプテン・オニオンは「秀和システム賞」となりました。よく入賞できました。
他の受賞作と見比べると、本作は詰め込み以外に特筆すべき要素に乏しかったですね。
(おにたま氏も詰め込み具合に慣れてきちゃってるのか、コメント短いですのぅ。
まあ、そこだけでも評価頂ければ光栄なんですが。)


さて、今回の受賞作の内、自分が注目していたのは2つ。
一つ目は優秀ゲーム賞のオオツルギ。最優秀も狙えると思ってましたよ。
唸らされたのはソード攻撃の仕様。
シューティングでゴッドバードアタックや科学忍法火の鳥な感じの体当り攻撃を実装するとき、
どうやって自機をコントロールラインに戻すか、扱いに困るんですよね。
オオツルギでは、自機移動範囲が円のため、向い側への跳躍!で自然に解決できているのが凄い。
もちろんあんだけ「カッコイイ」演出というのも評価高いです。


もう一つ注目作だったのは、いつもハイセンスさが光る丸中さんのヌリツヴス。
結果はなんと2賞を同時受賞。
パネルを塗る一筆書き要素は去年の拙作Kacotte!にもあったので、かなり気になる作品でした。
パズル特化時の方向性とかアイデア、ルール拡張性など、深く掘り込む姿勢は見習うべきだなー。

終了判定

とりあえず、一次選考通過の報告。そして、ついでにロジック解説。


128パーセク到達後に敵を殲滅かつ母艦健在である場合、離脱成功としています。
クリア判定処理に関与するコードを抜き出してみると、以下のような感じ。

if(plmode){   プレイ中であれば
    repeat 99   艦艇オブジェクトを操作するループ
        gosub *verdup  オブジェクト管理テーブルからID=cntのオブジェクト情報を変数にdupする処理。
        if(eep){          オブジェクト存在時の処理
            flag>>f0      敵ならフラグをクリア
            if(eep>=1000){ オブジェクトが健在
                flag|=(cnt=0) 母艦(cnt=0)であればflagに1をORする。
            }
        }
    loop
}
:
進行度が128パーセク以上でflag>0であればゲーム終了判定を出す。

このループでは、すべての艦艇オブジェクトに関して状態値eepを判断し、それぞれの操作(移動、当り判定、爆発、敵生成)などを行います。(クリア判定に関係のない処理は省いています)
同時に、フラグ変数flagの状態を変化させ、母艦健在で敵殲滅の状態になっているかを求めています。
128パーセク到達後に通る箇所でflag>0か判断。
クリア判定が出ていれば、ゲーム終了シーケンス(終了メッセージ表示、ハイスコア更新)を行って、タイトルへ移行します。


まず、ループ前にflagを初期化していません。
敵が一体でも存在(爆発含む)していれば0になるし、flag>0が意味を持つのは128パーセク到達後の1度だけ(1度クリア判定すればタイトルに移行する)なので、省略しても問題がないのです。


実質の初期化は、母艦が健在のときだけ意味のある処理として flag|=(cnt=0) で行っています。母艦が健在なら1をOR。
母艦以外でも該当オブジェクトが健在であれば実行されますが、cnt=0の比較演算が0を返すのでflagは変化しません。
この初期化の前にflagが1だったとしても、先にも書いたように128パーセクに到達していなければ不問となるので不都合はありません。
そもそも、終盤は敵の出現頻度が高く、flagが1のままフレームを持ち越すことがないというのもあります。


そして、敵が存在(健在もしくは爆発)している場合にflagを落す処理は、flag>>f0 。
f0は敵味方の処理振分け用に用意されている変数で、値は味方→0、敵艦→1、敵ミサイル→2 です(*vardupにてcnt/33相当の値を代入)。
つまり、味方以外の処理でここを通ると、f0が0ではないためflagの左シフトが発生。flagは0か1なので、1ビットでも左シフトすれば0クリアされる仕掛けです。


ゲーム終了の処理は、ゲームクリア、ゲームオーバー兼用で、母艦が存在するかどうかでメッセージとハイスコア記録用文字列を変えるだけとしています。
これらの作り込みにより、100Bytes以内のコストでゲームクリア関連処理の追加を実現しています。

弾の発生

通常弾、レーザー、ミサイルの生成処理について。
このゲームでは、登場するキャラクターをオブジェクト管理テーブル(2次元配列)で管理しています。
ID=0は母艦、1〜32は僚艦、33〜65は敵艦、66〜98は敵ミサイル、99不使用、100以上は攻撃オブジェクトに割り当てており、IDが99未満の「艦艇オブジェクト」については、共通のコードで弾の発生を処理しています。


フレーム毎に射撃インターバルカウンタが0かどうかチェックし、0になっていれば、以下のような流れで、攻撃オブジェクト(通常弾/レーザー)もしくはミサイルを生成します。(ミサイルは管理の都合上、艦艇扱い)。

1 ロックオン中の攻撃対象がない(ターゲットID=0)の場合は、対象範囲からランダムにIDを選択し、仮ロックオン。
  ロックオンしている攻撃対象の情報を取得。
2-A ミサイル艦の場合
  ターゲット(ミサイル)が不在(状態値=0)で、自身が特攻前なら、ミサイル発射処理を実施
   射撃インターバルを再設定(固定値)。
   該当のIDにミサイル用のパラメータを格納(ミサイル耐久値は、ゲーム進行により増加)。
   ロックオンは不要なので、攻撃対象をなしにする。
2-B ミサイル艦以外
  攻撃対象の座標を取得し、自身からの距離を計算
  「攻撃対象が健在、射程圏内、弾に空きがある」の全てについて成立するかチェック。
  成立する場合、攻撃オブジェクトの生成を実施
    射撃インターバルを再設定(カウンタ値は、ゲーム進行により減少)。
    弾の空きリストから1件取り出して攻撃オブジェクトとしての各種パラメータを格納。
  成立しない場合は、自身のロックオン中の攻撃対象をなしにする。

一度ターゲットIDが決まったら条件から外れるまで保持するので、射程圏内に入った攻撃対象をロックオンするようになります。
(ターゲットを撃破するか射程圏外になるまで撃ち続ける)


ランダムに選択するIDの範囲は、艦艇の種類ごとに以下のように割り振っています。

艦艇オブジェクトの種類 選択するIDの乱数範囲
プレイヤー母艦、プレイヤー僚艦(通常弾) 33〜65:敵艦
プレイヤー僚艦(レーザー) 66〜98:敵ミサイル
敵艦通常弾 0〜32:プレイヤー側全体
敵レーザー艦 0固定:常にプレイヤー母艦
敵ミサイル艦 66〜98:敵ミサイル(生成対象として)
敵ミサイル 99〜132(この範囲は常に状態値=0なので弾を撃たない)


母艦を示すID=0は、この処理ではロックオンなしの意味でもあります。
そのため、敵の通常弾攻撃は母艦に対しては消極的で、側に僚艦がいるとそちらをロックオンするようになります。
逆に敵レーザー艦では、攻撃対象をID=0固定とするので、常に母艦をロックオン(発射は距離次第)します。
また、ミサイル艦の場合は、求めたIDは新たに生成するミサイルのオブジェクトIDとして使用しますので、空きオブジェクトのIDを掴めないとそのフレームでの発射はなし、次フレームでの再試行に回されます。


通常弾とレーザーを生成する場合、弾の空きリストからオブジェクトIDを持ってくることによって、ほぼ切れ目なく、等間隔連射が可能になっています。
この弾の空きリストは、1次元配列と、リスト件数で管理しています。
フレーム毎にリスト件数をクリアし、通常弾とレーザーの描画や衝突判定など処理するループの中で、移動カウンタが0のオブジェクトについて空きリスト前方から順に追記。
(リスト件数が示す配列要素に該当IDを記録し、リスト件数を1加算)
次フレームの弾発生処理にて、リスト件数で示されている場所から順次遡って使用(リスト件数を減算しつつ、示されるIDを利用)します。

背景描画

本作の背景は、星を多重スクロール風(4層)で表示しています。
多重といっても描画は各層の重ねあわせではなく、それぞれの星を全くバラバラに縦移動させています。
このような描画をする場合、X座標、Y座標、速度、色などの情報を管理する必要があります。
しかし、各情報を個別の配列で管理したり、乱数値をそれぞれ得たりすると、コード量が膨れてしまいます。


最終的に以下のようなコード(本体はrepeat-loop部分)となりました。(このままスクリプトエディタに張り付けて実行可能です。)
なお、dim省略などの行儀のよくない作りをしており、ヘタに組み込むと弊害が出たりします。参考にする場合は十分注意してください。

*main
    redraw:await 15:redraw 0:color :boxf
	repeat 207			; cntはx座標成分と兼用
		dup fs,sppg(cnt)	; sppgはdimしていないが、初回描画で207個分に拡張される
		if(fs){}else{fs=rnd(32767)}	; 描画色(0-31)、速度(0-3)、Y位置(32倍計算)を1度に乱数取得
		tmp=((fs&3)+1)*32:hsvcolor (fs&31)*6,100,tmp+127:pset cnt*3,fs/32
		fs+tmp:fs\(16400+cnt) ; Y位置を動かして、下まで行ったら変化を加えて上から繰り返し。
	loop
	goto *main


詳細を見ていきましょう。

repeat 207

207は星の数。半端ですが、後のpsetで3倍した値をX座標の範囲にあてています。
右側ゲージに掛からないよう、X座標を621までに抑えたので、この値。

dup fs,sppg(cnt)

整数型配列sppgによって各星の位置や属性を管理しています。アクセスのコストを下げるためcntで示される要素をfsにdupします。
どこにもdimがありませんが、初回で207個分のループによって配列が自動拡張されることを利用して、配列宣言を省略しています。

if(fs){}else{fs=rnd(32767)}

fsの値が0である場合、乱数値で初期化します。初期化したいのはY位置、色指定、スクロール速度。
関数は高コストなので、rnd()は一回。
Y座標の初期値(32倍計算)、描画色指定(0-31)、速度指定(0-3)のまとまりとして乱数を得てfsに代入します。
最初はどの配列要素も0ですから、ランダムなY座標に星が置かれます。
(厳密には配列拡張が行われた際に反故にされる範囲がありますが、初回描画だけなので無視)

tmp=((fs&3)+1)*32

fsの下2ビット(0-3の4種)に1足して32倍したものを速度値(32,64,96,128)としています。
速度であると同時に、明度の制御にも使っています。

hsvcolor (fs&31)*6,100,tmp+127

hsvcolorによる色指定。
fsの下5ビット(0-31)を6倍して色相値として指定、彩度は100固定、明度は速く動く星ほど大きくします。

pset cnt*3,fs/32

X座標は上にも書いたようにcntの3倍、Y座標はfsを32で割った値。

fs+tmp

Y方向の移動計算。速度値を加算するだけです。

fs\(16400+cnt)

星が画面下に到達した場合に、画面上部に持ってくるための計算。480*32より適当に大きな値でfsを割った余りを代入しています。
速度や描画色も変わってきます。(隣の星と変化の差をつけるためにcntも利用)
このとき余りが丁度0になって、次の描画で乱数値の取得対象になることが稀にあります。
その場合、途中の高さから星が出現するわけですが、ただの背景描画ですから気にする必要はありません。


repeat−loop部分、AXにして220Bytesになります。

パーセク

蛇足事項。
このゲーム、ステージ進行の単位をパーセクにしたため、ワープ時亜空間での戦闘、という裏設定を入れています。
パーセクとは「年周視差が1秒(角度)になる距離」っていう定義で、だいたい3.26光年。ものすごい距離。
128パーセク生き延びるということは、417.3光年の逃避行です。
通常宇宙空間の前提で考えると荒唐無稽な移動距離なので、亜空間なる御都合空間にいるという設定。
パーセクを採用したのは、なにより単位記号が「pc」の2文字であるのと、語感を重視。
光年はlyで1とlが紛らわしいため却下。天文単位(au)は某キャリア名と被ったので除外。
あと、某アニメの宇宙キロとかも捨てがたかったけど、記号がわからなかった。
(キロだけだと、キロメートルかキロマイルかまったく別のキロナントカなのかも不明だし…)

V1.07

バージョンをV1.07に上げました。多分これが最終バージョン。反映待ち。
クリア(離脱成功)時のHSP-TVへのハイスコア登録で、*マークをつけるようにしました。


128pc.に到達しても、敵を殲滅する前に母艦が撃破されるとクリアにはならないため、
クリア時と失敗の両方ともハイスコア登録が128pc.の表記になって区別不能です。
というわけでV1.07では、失敗時「 128pc.」、クリア時「*128pc.」を登録することで対処。
スコア登録時の文字列先頭は、通常空白なので、クリア時のみPOKEで'*'を書き込むことで実現。
この処理のために必要な16Bytesを捻出するため、僚艦ドラッグ時の僚艦ステータス表示を抑止して、
僚艦追加メニューと僚艦ステータス/強化メニューの表示に使用するPOS命令を1本化しました。
ついでに、GameOver時・クリア時のメッセージ文字色が僚艦耐久値ゲージと被っていたので、
個別指定で赤に変更。