前回 はテキストを書くだけで燃え尽きてしまい、サンプルプログラムがありませんでした。そのせいか、ちょっと意見に (いつも以上に) 深みがない気がします。
今回は、これまで学んだこと、特に前回のスプライト関連の豊富な機能を生かして、サンプルとして、簡単なシューティングゲームを作ってみたいとおもいます。
今回のポイントは プチコンらしさ です。プチコンの仕様を眺めていると、スマイルブーム開発陣の「こんな風に使って欲しい、活用して欲しい」というコダワリといいますか、使ってみたくなるような 上質な誘い を感じるわけです。その声に従って作ってみたい、と。
以前に Unity のサンプルでも似たようなの作った ので、比べると構成や作り方が非常に異なっていることがわかると思います。
まずは自機を動かそう
シューティングゲームでまず必要なのは自機です。プチコンの標準スプライトには格好良い戦闘機がありますので、それを表示して、上下左右に操作できるようにしてみましょう。
あ、5行目は不要な、次の章で使うコードでした。すみませんが無視してください。
7~20 行まで、 REPEAT
から UNTIL
までがメインループです。いつものように、Bボタンを押すと終了するようになっています。
8 行目で左上にスコアを表示しています。まあ、今はスコアは 0 のまま変わらないのですが…
10~14 行が上下左右の操作に対応しているところです。STICK
命令で左の操作レバーの値を読み取り、BUTTON
命令で左のボタンの状態を参照しています。どちらでも動かせたほうがいいですよね?
15~16 行 で、自機が画面から出ないように制御しています。Unity だと Mathf.Clamp
という「値をある範囲内に留めておく」命令があるのですが、プチコンにはありません。でも MIN
と MAX
を組み合わせると同様の処理が記述できます。
で、最後の 17 行で自機を表示しています。SD という変数は、左右移動の際に機体を左右に傾けるために使用しています。標準のスプライト定義の中には。戦闘機が傾いた画像も用意されているので、左に移動するときは左に傾いた画像を、右に移動する際には右に傾いた画像を指定しているだけです。
これだけの短いプログラムで自機が画面を自由に動くので、プチコンの手軽さがあらためて実感できますね!
弾を発射する
さて、自機を動かしているだけではつまらないので、Aボタンで弾を撃てるように改良してみましょう。以下のように2連の弾を撃ちます。
以下が改良したプチコン4用のコードで、赤枠が追加した部分です。
まず 5 行目で、発射された弾に設定するアニメーションを定義しています。動きとしては単純で、アニメーションの開始時、つまり弾の発射された時の位置から、上に真っすぐ移動するだけ、です。
私の個人的な見解ですが、画面上でスプライトを動かす場合、それが状況に応じた動き(例:自機との位置関係で動作が変わる)である場合を除き、事前にセットした動きで対応できるならば、このアニメーション機能の利用を最優先に考えるべきだとおもいます。
10 行には ST
というカウンタ変数が追加されています。これは弾の発射間隔を制御するためのものです。ちなみに、プログラムを短くするため、初期値が 0 で良い場合、その変数の宣言を省くという悪しきBASICの伝統を利用しています。
さて 15~19 行が、今回追加したメイン部分となります。弾の発射条件としては、まずはAボタンが押されていること、そして前回の発射から20フレームが経過していること、としており、それを先ほどのカウンタ変数 ST
で確認しています。
弾の発射ロジックは 17, 18 行目にありますが、以下の流れです。同時に2発撃っています。
-
SPSET
で空きスプライトを見つけ、弾の画像を指定する -
SPOFS
で自機の上の適切な位置に弾を配置する -
SPANIM
で弾に 5 行で定義したアニメーションを設定する
なお青枠は弾の画像などを探す際に使用した簡易的なツールです。今回は無視して問題ありません。興味ある方は 25~26 行目のコメントを外して実行してみてください。
弾に設定したアニメーションをもう少し詳しく見ておく
スプライトに設定するアニメーション機能はゲームの肝となる部分ですので、繰り返しになりますが、もう少し詳しく見ておきましょう。定義は以下のようになっています。
ANIMDEF 0,"XY+.",-80,0,-240
最初の 0
はこのアニメーションの定義番号です。0~1023 の値が指定できますので、1024 種類のアニメを定義できることになりますね。
次の "XY+."
は、XY座標を変化させてアニメーションを実行することを指定しています。後に指定された +
はこの後で指定する座標が、現在のスプライトの位置からの相対座標であることを示します。これを外すと、画面の原点からの絶対座標として扱われます。
更に後に指定された .
がプチコンのユニークな機能で「アニメーション終了時に対象スプライトを削除する」働きをします。これすごく便利!撃たれた弾って、対象に当たらないと画面外に飛び去ってしまいますが、これに対する処理が不要になるのです。放置しておくと使用したスプライトを開放しませんから、しばらくすると空きスプライトが無くなりエラーになります。
アニメーションの実行が終わるころには、その対象スプライトで実現した自機の弾は、240 ドットぶん上に移動しており、必ず画面外に居ます。このスプライトが自動的に削除され、空のスプライトとして再利用されるわけです。
さて、残りの3つの引数はわかりやすいですね。アニメーションの実施時間(フレーム数)、X座標の移動、Y座標の移動、です。実施時間がマイナスになっていますが、この場合はアニメーションが補完され、スプライトの移動がスムーズになります。ワープ的な移動でない限り、基本マイナスで良いのではないでしょうか。
例えばこのフレーム時間を短くすると、アニメーションが早く実行されるようになります。例えば現在 -80
が指定されていますが、これを -40
に変更すると、半分の時間で同じアニメーションが適用されるため、つまり弾の飛行スピードが倍の速さになります。
また例えば Y座標の移動として -240
を指定していますが、これは現在の画面サイズと同じ、つまりは画面内のどこで発射しても、必ず画面上部に届くようになっています。例えばこれを -120
に変更すると、弾の到達距離が画面の縦幅の半分くらいに短縮され、ゲームの難易度が上がるでしょう。
このあたり、いろいろ変更して遊んでみてください。ナナメに飛ぶ弾、稲妻のようにギザギザに飛ぶ弾、などもアニメーション定義を変えるだけで実現できますよ。
ただし事前に定義したアニメーションなので、状況に応じた行動は苦手です。例えば「敵を追いかける追尾弾」などはアニメーション機能では実現が難しいでしょう。
アニメ機能活用のヒント
アニメ機能は便利ですが、制限もあります。ひとつのアニメーションに定義できるデータ配列のサイズは最大で32個、フレーム時間が必須なので、つまり値が1つであれば16個、"XY" のように値が2つであれば10個が限界です。
試してみて気がついたのですが、アニメーションの種類が異なれば、同時に指定して動作するようです。例えば移動を指定する "XY" と、回転を指定する "R" は併用できますので、移動して向きを変え、また移動、という動きは2つのアニメーションの組み合わせで実現できます。
また別な工夫の余地があります。例えば SPLINK
を使ってスプライトの連結ができますので、
- 透明なスプライトを用意
-
SPLINK
で上記の透明なスプライトを対象の親として指定 - 透明なスプライトにアニメーションを指定
- 対象スプライトには上記と組みわせる、もしくは引継ぐアニメーションを指定
とすればより複雑な動きが実現できそうな気がします。
また SPFUNC
ロジックに簡易的なアニメーション管理機能を実装しておき、SPVAR
あたりを参照しつつ、特定のタイミングでアニメーションを再生する、現在のアニメーションの実行が完了したら次のアニメーションを再生する、など実施させても面白そうです。
このあたりは私も実際に試したわけではないので、後日、時間を作って試してみたいと思います!
敵を登場させる
さて、自機だけ動かしていても寂しいので、敵機を実装してみましょう。
以下のように1種類だけですが敵戦闘機を追加し、火の玉のような敵弾を撃ってくるようにします。ただし当たり判定などはまだありません。
コードは以下の赤枠のように3ヶ所の追加があります。敵の数が多いわりには、追加したコードはあまり多くありません。
追加したコードを見ていきましょう。
まず、5~7行目にアニメーション定義が増えています。これは敵機の動きと、敵の撃つ弾の動きを定義したものです。慣れた方であれば、動きはすぐにわかるでしょう。「敵機は左右に揺れながら降りてきて、真っすぐ進む弾を撃つ」ということが。
そして敵機の生成をしているのが 24~30 行目です。さきほどの ST
と同様に EC
というカウンタ変数を用意して、60フレームごとに敵機が生成されることがわかります。ちなみに、簡易的な難易度調整として、スコアに応じてこの敵機の生成間隔は短くなっていくように指定してあります。
28 行目で敵機のスプライトが作成され、ランダムな位置に配置されたうえで、動きのアニメーションが指定されています。敵は逆向きなので、アトリビュート #A_REVV
を指定して、画像をひっくり返して利用しているのがポイントかな。
そして敵機の弾を撃つロジックが、24~30 行目にある CALL SPRITE
および SPFUNC
命令と、27~42 行目で定義されているユーザー定義命令 ENEMY1
です。中身は単純で、ランダムな間隔で敵弾を射出するだけです。
敵弾の実装については、自機の弾とほぼ同じ仕組みで、上下逆さなだけですので、説明は省きます。
弾を撃つロジックを ENEMY1
にまとめたうえで、スプライトに SPFUNC
で指定しておき、それらを一気に CALL SPRITE
で呼び出しています。スプライトの利用状況などの管理が不要ですぐ呼び出せますし、呼び出されるユーザー定義命令側も自身のスプライトだけに対処すれば良いので、全体的に疎結合になり、シンプルで改造しやすいコードになる気がします。
というわけで!
まだ当たり判定とスコア加算が実装できていないのに、かなりの量のテキストを書いてしまいました。疲れた…
今回は前編として、ここでいったん止めて、残りは 後編 でやりたいと思います。中途半端でスミマセンです。
それではまた!