« 2010年12月 | メイン | 2011年02月 »

2011年01月28日

AIMSでグラディウスっぽいSTGを作る(9)

sample4.jpg
今回はゲームオーバーの作り方です。
これはゲーム次第で全然違ってくるところですから、ひとつの例ということで。

最後の自機が死んだら、爆風が消えた後静止して
「GAME OVER」の文字を出すという仕様にします。

静止させる方法はAIMS本の5-3「複数シーンの並列動作と一時停止」が
とても参考になります。

setSceneFreeze()でシーンを停止させることができます。
これによってそのシーンに属するアクターやスレッドも止まる
(画面表示だけは行われる)ので、停止させるのは簡単です。

停止を再開させるためには並列動作するシーンが必要になるので
setSceneFreeze()の前にaddScene()で gameoverシーンを起動します。

gameoverシーンではcreateTextActor()で文字表示アクターを作成します。
ここで作成したアクターはsetSceneFreeze()の影響を受けずに動作するようです。
OnStepではボタンが押されるまで待ちます。

ボタンが押されたらフリーズを解除し、changeScene()でフリーズを解除したシーンを
タイトル画面のシーンに切り替えます。
そして自分自身をcloseScene()で終了させます。
ここではフリーズしているシーンを操作する必要があるので
グローバル変数などでシーンIDを保持しておかなければなりません。

function game_OnStep()
  if 自機が死んだ then
    setSceneFreeze(V.scene, true)
    addScene("gameover")
  end
end

function gameover_OnStart()
createTextActor(G.font, "GAME OVER", 320, 240,
LAYER_INDICATOR, 255, 255, 255, 255, ALIGN_CENTER)
end

function gameover_OnStep()
if getJoyPressCount(0, BUTTON_TRIG1) == 1 then
setSceneFreeze(V.scene, false)
changeScene(V.scene, "title")
closeScene()
end
end


2011年01月26日

AIMSでグラディウスっぽいSTGを作る(8)

そろそろ自機が死ぬようにしたいのですが
そのためにはシーンをちゃんと作らないといけません。

普通はゲームオーバーになったらタイトル画面に戻ります。
タイトル画面は独立したシーンとして作るのが普通です。
よってシーンを作る必要が出てくるわけです。

公式サンプルのSUiCA32ではサークルロゴ表示をひとつのシーンとして用意し
そこでデータの読み込みをしています。
読み込み自体はローダースレッド内で行い、メインスレッドはそれが終わるのを待ちます。

時間がかかるような大きなファイルの読み込みをメインスレッドで行うと
AIMSが応答なしになってしまうので、この形が推奨されています。

SUiCA32では"LOADING"の文字を表示するためにPXFというアクタークラスが使われていますが、
AIMS1.40にはcreateTextActor()で同等の機能を持ったアクターが使えるので
そっちを使うと楽です。

createTextActor()の引数を見ると、一つのアクターで表示できる文字列は
固定のように思えるかもしれません。
しかし、setActorString()という関数が用意されているのでこれを使えば
いつでも好きなだけ書き換えが可能です。

実際、スコアの表示にも使っていますし。こんな感じで。


function score_OnStep()
setActorString(string.format('1P %8d HI %8d', getScore(), getHiScore()))
end

そんなわけでテキストアクターを使うこと以外は
SUiCA32そのままでいいと思います。

2011年01月16日

AIMSでグラディウスっぽいSTGを作る(7)

sample3.jpg

適当に地形を作って、ミサイルがそれに沿って進むようになりました。

画像ではミサイルが地面から離れて見えますが
これはマップチップ番号が0以外の場合は全て当たり判定を持つようにしているためです。
チップのほとんどが透明であっても判定があるので。

ミサイルの挙動は、
1.落下中かつ、ひとつ下のチップに判定があるなら、水平移動に遷移
2.水平移動中かつ、ひとつ下のチップに判定がないなら、落下中に遷移
となっていて、これをミサイルのOnStep()に記述します。

ミサイルは段差を乗り越えたりはしないので
水平移動中に前方にある地形と接触したら消えなければいけませんが、
これはマップチップ側の当たり判定ロジックがやってくれます。


マップチップが動かない場合(スクロールを除く)は、ここまでで十分なはずです。
が、ミサイルが上を滑っていくような敵(グラIIのホッピングモアイとか)や
背景が動く場合(グラII最終面カニ前のせり上がる床など)には対応できません。

このへんも何とかしたいところです。

2011年01月13日

AIMSでグラディウスっぽいSTGを作る(6)

今回は公式で配布されている SUiCA32 を改造して、地形との当たり判定を実装します。

AIMSにはマップチップの描画機能が用意されているので表示はそれを使いますが、
当たり判定は完全に自前でやることになります。

editor.jpg
マップデータはAIMS付属のMapEditorを使って作成します。
チップのサイズは16*16、マップのサイズは64*64にして、
こんな感じに適当に木を生やしてみました。
(マップチップ画像はマップエディタPlatinumに同梱のサンプルを使っています)

fmf形式でマップデータを出力したら適当なディレクトリに
マップチップ画像と一緒に放り込んでおきます。


次はスクリプトに手を入れます。
ちょうどいい感じにレイヤー9が使われていないのでこれを使います。

#game_common.lua
LAYER_MAPCHIP = 9

マップチップの表示は専用のアクターで行います。
gameシーンの開始時に生成するので game_OnStart()内でcreateActor()を実行します。
このアクターは通常の画像表示をしないので透明のチップハンドル(G.common.clear)を渡しています。

#game.lua
A.mapchip = createActor(G.common.clear, 0, 0, LAYER_MAPCHIP, 'mapchip')

アクターの実体はとりあえずplayer.lua内に定義します。
本当は別ファイルにしたほうがいいんですけど。

#player.lua
function mapchip_OnStart()
G.mapchip = loadGraphic('gfx/empire-3.bmp', 0, 117, 117)
local t = loadFMF('gfx/test.fmf')
mapAllocate(LAYER_MAPCHIP, t.mapWidth, t.mapHeight, G.mapchip, 16, 16, 0, t.data[0])
mapSetWrapMode(LAYER_MAPCHIP, OUTSIDE_NONE, OUTSIDE_WRAP)
mapSetVisible(LAYER_MAPCHIP, true)
setLayerScroll(LAYER_MAPCHIP, 0, 0)
end

function mapchip_OnStep()
local x, y = getLayerScrollX(LAYER_MAPCHIP), getLayerScrollY(LAYER_MAPCHIP)
y = y - 1
setLayerScroll(LAYER_MAPCHIP, x, y)

for index, id in pairs(iLayerActors(LAYER_EBULLET)) do
ex, ey = mapGetWrappedPos(LAYER_MAPCHIP,
bit.arshift(getX(id), 4),
bit.arshift(getY(id) + y, 4))
if mapRead(LAYER_MAPCHIP, ex, ey) ~= 0 then
vanish(id)
end
end
end

function mapchip_OnVanish()
end

suica.jpg
これで、木がスクロールし、敵弾が木に当たると消滅するようになりました。
問題は色々ありますが。
最大の問題は当たり判定の大きさです。
敵弾の中心が当たり判定のあるチップ(16*16ドット)の内部に入った時に接触と見なすため
敵弾がもっと大きい判定を持っている場合に困ってしまいます。

また mapRead() ~= 0 と書いてあるとおり、チップ0番以外は全て当たり判定を持つと
見なすようになっているので、当たり判定を持たない背景が実現できません。

これらも追々解決しなければいけません。

2011年01月11日

AIMSでグラディウスっぽいSTGを作る(5)

敵キャラが出現して、それを破壊できるようになりました。
が、絵面はあまり変わらないのでSSはなし。

このあたりの処理はAIMSならではというところはあまりありません。

ショットやミサイルのOnStepでgetHitLayerを呼んで
当たっている敵の体力を減らしつつ自分はvanishで消滅。
敵のOnStepで体力が0になったらvanishを実行するだけです。
体力の値はVARスロットのどれかをそれ用に割り当てます。


敵のOnVanishでは cause==KILL_ORDERED だったら
爆発エフェクトのアクターを呼び、スコアをカウントアップするようにします。

これでかなりゲームらしさが出てきます。
自機はあいかわらず無敵のままなんですけどね。
自機が死ぬ処理を実装するにはシーンにも手を入れないといけないので
なるべく後回しにしておきたいところです。

2011年01月09日

今年は家の建て替えを予定

春になったら家の建て替えをする予定です。

なにしろ古い家ですからねぇ・・・。
私の部屋なんか、隣に音が筒抜けなので
常時ヘッドホン必須ですし、ジョイスティックも音がうるさいので使えません。

なので建て替えが終わったらXbox360とジョイスティック買って
格ゲーやシューティングをやりまくるんだ・・・!


近所の知り合いが最近タ○ホームで家を建てたばかりで、
「タ○ホームはいいよ」っていうのを真に受けて、家の親も乗り気だったんですけど
いやいや、そこはまずいだろと思って、とある口コミ掲示板のログを親に見せて
思いとどまらせました。

桧○の営業が熱心に足を運んできていて、たぶんここで決まるんじゃないかと思います。
評判もそんなに悪くなさそうですし。


早いところいらない本やCDを処分して荷物を減らしておかないとなぁ・・・。

2011年01月08日

AIMSでグラディウスっぽいSTGを作る(4)

sample2.jpg
ぱっと見では前回とあんまり変わりませんが、カプセルが出現するようになり
それを取ってグラディウス式のパワーアップが出来るようになりました。

さすがにパワーアップの制御ロジックはAIMSですることではないので
それなりにめんどうです。

カプセルを取る、つまりカプセルとの当たり判定はAIMSの出番です。
当たり判定矩形はチップ1枚ごとに設定する必要があるので、
アニメーションするアクターの場合は全てのチップに矩形を設定します。

当たり判定の実行は二つの関数が用意してあり、
isHitActor で一対一、getHitLayer でレイヤーに含まれる全てのアクターとの判定を行えます。
シューティングのような不特定多数のアクターが出現する場合は getHitLayer がいいでしょう。

自機アクターの onStep 内で getHitLayer を実行します。
当然カプセルの属するレイヤーは自機や自弾とは別にしておきます。
実行後、接触しているものがあればアクターハンドルの入ったテーブルが返るので

capsules = getHitLayer(LAYER_ITEM)
if #(capsules) > 0 then
for index, id in pair(capsules) do
vanish(id)
end
end

とすれば接触したカプセルを全て消滅させることが出来ます。

2011年01月07日

AIMSでグラディウスっぽいSTGを作る(3)

sample1.jpg
それらしく動くものを作ってみました。
敵は出ないので当たり判定ロジックは入っていませんし
パワーアップも最初からフル装備で固定です。

この程度なら簡単に作れてしまいます。

これを作っていてAIMSのアニメーションの仕様を理解できました。
AIMSのチップハンドル(cutGraphicなどで作成する)は
個別に「自分の表示フレーム数」「次に表示するチップハンドル」という情報を持っています。

ここに値を設定するには setGraphicAnime/setGraphicAnimeLoop/setGraphicAnimeSequence を使います。

アニメ情報を設定したチップハンドルをcreateActorに指定してアクターを生成すると
自動的にアニメーションが行われます。

setGraphicAnimeLoop({G.pat[1], G.pat[2], G.pat[3]}, 5, 1)
としてアニメ情報を設定し
createActor(G.pat[1], 0, 0, 1)
このようにアクターを生成すると、 1 > 2 > 3 > 1 の順にアニメーションがループします。

createActor(G.pat[2], 0, 0, 1)
であれば 2 > 3 > 1 > 2 の順にループします。

1 > 2 > 3 > 2 > 1 の順にループさせたい場合は
setGraphicAnimeLoop に渡すテーブルを {G.pat[1], G.pat[2], G.pat[3], G.pat[2]} にしたくなりますが
これではダメです。
2の次に表示するチップが「2 > 3」「2 > 1」の二種類存在することになってしまいます。
次に表示するチップは1種類しか指定できませんからどちらか一方しか有効になりません。

実際には「2 > 3」の設定が「2 > 1」で上書きされるので 1 > 2 > 1 というアニメになってしまいます。

ではどうするかというと、cloneGraphic を使って 2 のコピーを作って
それを4としてループを作成すればいいのです。

G.pat[4] = cloneGraphic(G.pat[2])
setGraphicAnimeLoop({G.pat[1], G.pat[2], G.pat[3], G.pat[4]}, 5, 1)

これで期待通りのアニメーションになります。

2011年01月06日

AIMSでグラディウスっぽいSTGを作る(2)

AIMSでは動くものは基本的に全てアクターです。それとは別にシーンというものもありますが。

そしてアクターには必ずひとつ以上の画像と結びつきます。
ここが少し特殊なところですね。
例えば編隊を組んだ敵を全滅させるとボーナス取得、とかいうのの実現には
編隊自体を表すアクターが必要になってきますが、
表示すべき画像が存在しないこのアクターについても画像が必要です。

そういったアクターのために1*1ドットの透明な画像をダミーとして
用意するのが良さそうです。


アクターを動かすには前述のとおり画像(グラフィックハンドル)を用意して
createActorで生成したあと、addMoverで大まかな動作を指定するだけという、簡単なのがいいですね。
画面外に出たら消滅というのも自動で行わせることができますし
画像を複数登録しておけばアニメーションも簡単です。

それだけでは済まない場合になって初めて、アクタークラスを用意することになります。
クラスといってもオブジェクト指向的なアレではなくて
(クラス名)_OnXXX というファンクションを三つ用意するだけ。

継承とかが使えないのはそれはそれで物足りないところもありますが
そのへんは割り切ってしまいましょう。
共通部分をファンクションにくくりだして呼び出せばいいだけです。
(Lua自体はクラスが実現可能な仕組みをもっていますがAIMSには不向きなのです)


ということで今回は終わり。

2011年01月03日

AIMSでグラディウスっぽいSTGを作る(1)

去年は色々あって創作活動をほとんど何もしていなかったのですが
今年はもう少しがんばろうと思います。
去年の問題は今なお継続中ですがそれを理由にしていると何もできなくなるので・・・。

と、いうことで去年末に知ったAIMSで色々やってみるつもりです。
AIMSというのはD.N.A.softwaresさんが開発したアクション系2Dゲーム開発に特化したゲームエンジンです。
ベースの言語はLuaなので、覚えやすくて高速だしコンパイルも不要です。
背景や地上敵が存在しないような縦STGならば驚くほど簡単に作れそうなところが魅力的ですね。

ネットで検索してみると、これを使ってゲームを制作しているという話はほとんど引っかからないし
サンプルも本家で公開しているものしか見つからないので、
同じようにAIMSでゲーム制作をしたい人のためにも情報を発信していけたらなぁと思います。

題材はタイトルにもあるように「グラディウスっぽいSTG」です。
グラディウスといったらオプションとかマップチップを使った背景とか
そういう要素が必須になってくるので、AIMSの使い方を覚えるのにも丁度いいかなぁ・・・。