自機が弾を撃つ(2/2) |
3.自機の弾を射出する |
この処理と、「4.自機の弾を移動する」はメインループ内に入れます。
弾は、押しっぱなしで連射することを前提とします。一昔前は、オプションに「連射OFF/ON」という項目がありました。今のシューティングにはそういった項目さえなく、ボタンを押しっぱなしで連射するのが普通になっていると思います。
ここでは、まっすぐ上に進む弾を出すことにします。
弾を出すとは、こういう事です。
もしショットボタンが押されていたら、以下を行う。
1)弾の座標(shotx.n , shoty.n)を、自機のすぐ上に設定する。
2)弾の状態(shotf.n)を「存在する」にする。
3)該当する座標に、弾の絵を描く。
弾の絵をsamplecg.bmpに追加しました。ファイル中の座標、幅、高さは図の通りです。
弾のy座標は自機より弾の高さ分上にします。ですから、自機のy座標−16ドットになります。x座標は、自機のど真ん中にします。自機の幅は32ドット、弾の幅は4ドットです。すると、自機のx座標+14ドットが真ん中になります。なお、自機の座標も弾の座標も、キャラクタの左上隅の位置です。
「自機の移動」処理で、すでに「キー、ジョイパッドの入力」サブルーチンを呼んでいます。そしてその中で、ショットボタンが押されているかどうかも判別しています。自機の処理を行ってから、弾の処理を始めるまでの時間は、無視できるほどの短いものです。新たに呼びなおす必要はありません。
よって、n番目の弾を出す処理は以下のようになります。
if ckey=1 {
shotx.n=playerx+14
shoty.n=playery-16
shotf.n=1
ddpos shotx.n,shoty.n
ddsetrect 96,0,4,16
ddgcopy 1
}
さて、nはどのように決めたらいいでしょうか? 普通に考えて最初が0、次が1、次が2、最後が3ですね。ところが、2発目の弾が今敵に当たったとしたら、それは消さなければいけません。3発弾を出した時点で、2発目が消えた場合、その存在状態はこのようになります。
shotf.0 | shotf.1 | shotf.2 | shotf.3 |
1 | 0 | 1 | 0 |
このように、きれいに1が左から並ぶというわけではなく、歯抜け状態になります。4発目は配列変数のどの要素に割り当てればよいでしょうか? 1発目(shotf.0)と3発目(shotf.2)はまだ存在していますから、それに割り当てるわけにはいきません。順番通り、shotf.3でもいいのですが、空いている(shotf.nが0である)所だったらどこでもいいですね。そこで、左(shotf.0)から順に見ていき、最初に見つかった空いている要素に割り当てることにします。
n=-1
repeat 4
if shotf.cnt=0 {
n=cnt
break
}
loop
最初にnに−1を入れます。shotf.0から順に見ていき、値が0であるものを見つけたら、その時のcntをnに代入します。
breakは、ループから抜け出す命令です。例えば上の表の場合、cntが1の時にループから抜けます。repeat文のそれ以降の処理(cntが2の時、3の時の処理)は行われません。
4つとも値が1だった場合(4発とも存在している場合)、nは−1になります。この時は弾を出しません。すると、変数に値を設定する部分はこうなります。
if n!-1 {
shotx.n=playerx+14
shoty.n=playery-16
shotf.n=1
}
さて、メインループのこの処理を通るたびに弾を出すとどうなるでしょうか? 弾同士がくっついてしまいます。そこで、何回かに1回だけ出すことにします。例えば4回に1回とするならば、メインループ4回分ショットボタンを押し続けてやっと弾が出るということです。この処理を何回通ったかを数える変数をshotcntとします。shotcntを最初0にしておき(「2.自機の弾の変数に初期値を代入する」の所で0にしました)、ショットボタンが押されていたら1増やします。4になったら、弾を出すと同時にshotcntを0に戻します。4回に1回ですから、残りの3回の間に前の弾は進んでいて、十分な間隔があきます。
弾の変数を設定する部分はこうなります。
; 自機弾の射出
if ckey=1 {
shotcnt=shotcnt+1
if shotcnt=4 {
shotcnt=0
n=-1
repeat 4
if shotf.cnt=0 {
n=cnt
break
}
loop
if n!-1 {
shotx.n=playerx+14
shoty.n=playery-16
shotf.n=1
}
}
}
このように、何かの数を数えるものをカウンターといいます。システム変数cntもカウンターです。初期値が0で、1ずつ増えていくとは決まっていません。増えるのではなく、用途によっては減らす場合もあります。
弾の描画は、4発のうち存在するものだけを描きます。
; 自機弾を描画
repeat 4
if shotf.cnt=1 {
ddpos shotx.cnt,shoty.cnt
ddsetrect 96,0,4,16
ddgcopy 1
}
loop
なお、これはあくまでも本講座での例です。「自機から弾を発射する」という簡単な処理でも、人によってアルゴリズムも違えば、出来上がるプログラムも違います。
4.自機の弾を移動する |
4発のうち存在するものだけを上に移動します。ここでは1度に16ドット動かすことにします。一番上まで来たら、弾を消します。つまりy座標が0より小さくなったら、状態を「存在しない」に変えます。
; 自機弾の移動
repeat 4
if shotf.cnt=1 {
shoty.cnt=shoty.cnt-16
if shoty.cnt<0 { shotf.cnt=0 }
}
loop
以上をふまえて、自機の弾の処理をまとめます。
; プログラム起動時の初期設定 ; 自機弾の配列宣言 dim shotx,4 dim shoty,4 dim shotf,4 ; ステージスタート時の初期設定 repeat 4 shotf.cnt=0 loop shotcnt=0 ; メインループ ; 自機弾の移動 repeat 4 if shotf.cnt=1 { shoty.cnt=shoty.cnt-16 if shoty.cnt<0 { shotf.cnt=0 } } loop ; 自機弾の射出 if ckey=1 { shotcnt=shotcnt+1 if shotcnt=4 { shotcnt=0 n=-1 repeat 4 if shotf.cnt=0 { n=cnt break } loop if n!-1 { shotx.n=playerx+14 shoty.n=playery-16 shotf.n=1 } } } ; 自機弾を描画 repeat 4 if shotf.cnt=1 { ddpos shotx.cnt,shoty.cnt ddsetrect 96,0,4,16 ddgcopy 1 } loop
自機弾の射出より移動の処理を先にしています。どうしてかというと、射出を前に行うと、その後続いて移動の処理も行ってしまい、1回目に描かれる場所が、1つ移動した位置になってしまうからです。
実行画面
ひとくちメモ: |
・画像の重ね合わせ |
メインループの処理の並びは以下のようにしています。 1)自機の移動 2)自機弾の移動 3)自機弾の射出 4)自機を描画 5)自機弾を描画 先に各種変数をいじっておいて、後で描画します。描画を後ろにまとめています。なぜかというと、画像の重ね合わせを気にするからです。今はまだ関係ないのですが、この後背景や敵を描く処理を加えていきます。先に描いたものの方が下に表示されます。例えば、自機を描いた後に背景を描いてしまったら、自機が消えてしまいます。それくらいだったらすぐ分かると思いますが、アイテムと敵が重なったら、どっちを上にすべきかはちょっと迷いますね。描画処理を分離しておけば、後で変えたくなった時に、並べ替えるのは容易です。 |
・ddsetrenewaltimingとawait |
前にddsetrenewaltimingという命令を使いましたが、これは無理に使う必要はありません。使わない場合はddsetrenewaltiming 60を消し、await 0をawait 16に変えて下さい。(16である必要はありません。遅すぎる、あるいは速すぎると思ったら違う値にして下さい。)どうも、画面がわずかにちらついているような気がしてきたので、今回のプログラムでは使っていません。 awaitは今までただ待つだけの命令としか説明してきませんでしたが、より詳しく言うと、「awaitから次のawaitまでがちょうど指定したミリ秒数になるように時間調整する」命令なのです。すると、メインループ1回分の時間になります。 自機の移動→自機弾の移動→……→自機弾を描画→待ち時間 これが16ミリ秒になるようにするということです。(await 16の場合。) ddsetrenewaltimingと同様、この場合もやはり、パソコンの性能によって実行速度が違うということがなくなります。 |