カテゴリー「2.解剖記」の13件の記事

[解剖記]インデックス

01:これがサンプルのソースだっ
02:プログラムの構造
03:自機移動と弾の発射
04:敵の生成と移動アルゴリズム1
05:敵の生成と移動アルゴリズム2
06:敵の生成と移動アルゴリズム3
07:敵と自機弾の当り判定
08:敵弾の生成と移動
09:敵弾と自機の当り判定
10:その他の画面効果
11:全画面表示への改造
12:アナログジョイスティックへの対応

これらの元記事は、ファイルの更新日時を確認する限り、恐らく2000年10月16日頃書いたものです。現在の状況(既にHSP3.0がリリースされてい る)とは整合性が取れていない内容もあると思いますが、当時の記事のまま転載しております。間違っている記述があるかもしれませんが、あらかじめご了承下 さい。

解剖記の記述はHSP_version2.61を前提としております。現在でも以下のリンク先から入手可能です。
Get!HSP2.61

[解剖記]アナログジョイスティックへの対応

いまさら・・・という感じですが、ジョイスティックを購入したので、その対応をやってみました。でも、Hspの標準命令にはジョイスティックの入力に関する命令が無いため、拡張dllを使う事になります。Miaさんの開発された、hspjsis.dllをダウンロードし、hsp2.exeなどと同じフォルダに入れましょう。

詳しくはhspjsis.dll添付のreadmeやサンプルを参考にして頂きたいのですが、この拡張dllを使う場合はスクリプトの先頭を次のようにして下さい。


#uselib "hspjsis.dll"
#func jstick jstick $0
#func jsticka jsticka $0
#func jassign jassign $0
#func jdset jdset $0

これで準備ができました。

実は、デジタルジョイスティックへの対応は非常に簡単です。というのは、stick命令とjstick命令はほぼ同じ機能なので、


stick (変数)

となってる部分に追加して、


jstick : (変数)=stat

とやればOKです。より具体的にすると、次のような形にした方がいいでしょう。


stick pat,79
jstick 79 : if stat!0 : pat=stat
xv=(pat>>2&1)-(pat&1)
yv=(pat>>3&1)-(pat>>1&1)

非トリガータイプキーの指定の仕方も全く同じです。

問題はアナログジョイスティックへの対応です。システム変数statに代入される値がstick命令と全く違うため、対応させるためにはキー入力の判定を別の方法でやらなければなりません。その対応版は以下の通りです。色がついてる部分が変更箇所になります。


;■ 自機移動-ジョイスティック対応後-
*jikimove
if mf>0 :
jsticka : pat=stat
shottime--
axv=pat>>16&255 : xv=0
if axv< 96 : xv=-1
if axv>160 : xv=1
ayv=pat>>8&255 : yv=0
if ayv< 96 : yv=-1
if ayv>160 : yv=1
pat=pat<<4
if mf>0 : if (pat&64>0)&(shottime<0) : shot=1
if mf>0 : mx=xv*move+mx : my=yv*move+my
if mx<0 : mx=0
if mx>590 : mx=590
if my<0 : my=0
if my>430 : my=430
pos mx,my : gcopy 2,xv*50+50,0,50,50
return

赤い部分が運動方向の算出式です。
(jstickaで得られる値)>>16&255が左右方向を表します。この値は、127を中心として、それ以下(0まで)は左、それ以上(255まで)は右を表します。
(jstickaで得られる値)>>8&255が上下方向です。この値も同じく、127を中心として、それ以下(0まで)は上、それ以上(255まで)は左を表します
ちゃんとしたアナログジョイスティックというのは、大きく傾けると速く、小さく傾けると遅く移動する、というような判定もできるようです。要は、127を 中心として256段階の判定ができるというわけ。私の持ってるジョイスティックの場合、左なら0、右なら255と、とってもデジタルな値しか返してくれま せんでしたけども^^;
それゆえここでは、127を中心として64の範囲を「動かない(xvまたはyvが0)」、それ以外の場合をそれぞれ上下左右に割り振っています。

ゴールドの部分がボタンの入力判定算出式です。
こうすれば、stick命令と同じ値が返されるようになります。具体的には、ボタン1がスペースキー(16)、ボタン2がEnterキー(32)、ボタン3がCtrlキー(64)、ボタン4がEscキー(128)です。


さて、問題はユーザーによって持ってるジョイスティックの種類が違うということです。デジタル入力の場合とアナログ入力の場合とで、入力判定の方法が異 なってくるため、同じルーチン内で処理するのは困難です。私の場合、コンフィグメニューでユーザーに「どちらを使うのか?」を選ばせるようにしてありま す。恐らく順々に入力判定を行えば、ユーザー側がデジタルアナログを意識することなく利用することが出来るようなスクリプトを組むことができると思います が、それは皆さんのほうで工夫してみてください。

参考までに、今までのキーボードからの入力のみの移動判定ルーチンを列記しておきます。


;■ 自機移動-ジョイスティック対応前-
*jikimove
if mf>0 : stick pat,79
shottime--
if mf>0 : if (pat&64>0)&(shottime<0) : shot=1
xv=(pat>>2&1)-(pat&1)
yv=(pat>>3&1)-(pat>>1&1)
if mf>0 : mx=xv*move+mx : my=yv*move+my
if mx<0 : mx=0
if mx>590 : mx=590
if my<0 : my=0
if my>430 : my=430
pos mx,my : gcopy 2,xv*50+50,0,50,50
return

[解剖記]全画面表示への改造

実は、全画面表示への改造は非常に簡単です。拍子抜けするくらい簡単です。スクリプトの先頭の当りをこのようにすればオッケーです。


;■ 初期設定
chgdisp 2 : mouse -1


;■ バッファへ画像の読みこみ
buffer 3,640,480,1 : picload "sample.bmp",1
bgscr 2,640,480,1  : palcopy 3 : gmode 2 : gsel 2,2

後はgcopyを行ってる部分を、バッファー2から3に直すだけ(gcopy 2,・・・となってるのを、gcopy 3,・・・にする)です。たったこれだけのことでなんだか市販ゲームみたいな雰囲気ですね(笑)

[解剖記]その他の画面効果

まず、倍率を表示する部分です。


;■ 倍率表示
*messageborn
dmy=my-ey.ent : if dmy<0 : dmy=-dmy
dstv=480-dmy/50
if highdst<dstv : highdst=dstv
dstlevel=level
dstcore=dstv*dstlevel
score=en.ent*100*dstcore+score
repeat imax
if iff.cnt>0 : continue
ix.cnt=ex.ent
iy.cnt=ey.ent
iv.cnt=dstv
iff.cnt=20
il.cnt=dstlevel
realscore=en.ent*100 ;・・・???
break
loop
return

倍率は、y軸座標の比較だけで行っています。そのため非常にシンプルです。自機に近いほど倍率が高いようにするための計算をし、あとはそれをスコアに加算するだけです。???の部分は何の処理をしてるのか私にもわからなくなってしまいました。realscoreという変数はここ以外には出てこないので、全く意味の無い行かもしれません^^;


続いてゲーム情報の表示をする部分です。ここにはなんのひねりもありません。画面上に倍率表示がしばらく残るような処理を行ってるだけです。まぁ特に解説する必要もないでしょう。


;■ ゲーム情報
*message
repeat imax
if iff.cnt<1 : continue
rnd icr,8 : icr=icr*32+31
rnd icg,8 : icg=icg*32+31
rnd icb,8 : icb=icb*32+31
color icr,icg,icb
font "MSゴシック",iv.cnt*2+8,1
pos ix.cnt,iy.cnt
mes "× "+iv.cnt
iy.cnt++
iff.cnt--
loop

font "MSゴシック",12,3
if highscore<score : highscore=score
color 0,255,255
pos 10,10 : mes "HIGHSCORE "+highscore

color 255,255,255
pos 10,30 : mes "SCORE "+score

color 0,255,0
pos 10,50
mes "HIGH DISTANCE "+highdst

color 255,255,0
pos 10,70
mes "LEVEL "+level

color 255,255,255
pos 10,460
mes "SHIELD "
if mf<1 : return
repeat mf
color 255,cnt*32,0
pos cnt*10+60,460
mes "■"
loop
return


続いて、ポーズの処理です。Escが押されたらこのルーチンへ飛び、再度押されるまでメインルーチンには戻らないようにしてあります。


;■ ポーズ処理
*pausegame
redraw 2
font "MSゴシック",40,3
rnd icr,8 : icr=icr*32+31
rnd icg,8 : icg=icg*32+31
rnd icb,8 : icb=icb*32+31
color icr,icg,icb
pos 250,200 : mes "PAUSE"
stick pat : if pat&128>0 : return
getkey returntop,121
 if returntop=1 : end ; 強制終了(F10)
await 30
redraw 1
goto *pausegame

例えば、ポーズ中も星は流れるようにする、といった処理を加えようと思ったら、もう一つメインルーチンを作るくらいのつもりでやらないとダメです。まぁサンプルではそこまで難しいことはやるつもりもなかったので、文字の色を変化させるだけに留めてあります。

一点だけ、メインルーチンにもある強制終了の処理で すが、ウィンドウモードで起動させてる限り特に必要はないのですが、全画面表示にしたり、DirectX対応にした場合などは必ず必要です。特に DirectX対応にした場合はCtrl+Alt+Delも効かなくなるので、無限ループになる部分には必ず入れるようにしましょう。

[解剖記]敵弾と自機の当り判定

続いて、敵弾と自機の当り判定です。基本は自機弾と敵機の当り判定と全く同じです。二重ループを使わない分、こっちの方が簡単かもしれません。ポイントは同じく、座標差(絶対値)を求め、その差によって判定するというところです。


;■ 敵弾移動 & 当り判定
*tekitamamove
repeat tmax
if tf.cnt<1 : continue
tx.cnt=tx.cnt+txv.cnt
ty.cnt=ty.cnt+tyv.cnt
if tx.cnt>640 : tf.cnt=0 : continue
if tx.cnt<-25 : tf.cnt=0 : continue
if ty.cnt>480 : tf.cnt=0 : continue
if ty.cnt<-25 : tf.cnt=0 : continue
pos tx.cnt,ty.cnt : gcopy 2,175,0,25,25
dx=(mx+25)-(tx.cnt+12) : if dx<0 : dx=-dx
dy=(my+25)-(ty.cnt+12) : if dy<0 : dy=-dy
if (dx>=0)&(dx<10)&(dy>=0)&(dy<10) {
repeat tmax : tf.cnt-- : loop
pos tx.cnt,ty.cnt
gcopy 2,175,25,25,25
bgcolor=128
snd 1
mf--
}
if (dx>=10)&(dx<40)&(dy>=10)&(dy<40) : score++
loop
return

これも計算式に無駄がありますね。自機弾と敵機の当り判定と全く同じです。絶対値を求めてるのだから、ゼロ以下になる事はありえませんね。こんな感じで充分です。

if (dx<10)&(dy<10)

自機と敵弾が当ったときの処理でちょっと変わってるのが、ダメージを受けたらその瞬間に敵弾が全て消えるという部分です。これによって、ゲーム難易度を落としています。非常に短い記述でこういうことが簡単にできるのです。例えば敵弾を全て消すフラッシュボムのようなものにも使えますね。

ダメージを受けたときの処理でもう一つのポイントが、画面を赤くフラッシュさせる処理です。これは、メインルーチンの部分で画面クリアをする色をダメージを受けたフレームだけ変更する事によって実現しています。メインルーチンのこの部分です。


;■ メインルーチン
*main
redraw 2
color bgcolor,0,0 : bgcolor=0
boxf 0,0,640,480
------------------------------------------------------------
goto *main

これによって、普段は(color 0,0,0)で画面更新を、ダメージを受けたときは(color 128,0,0)で画面更新を行うようになります。

もう一つ、画面効果を行わなかったので気づかない人も多いかと思うのですが、カスリボーナスの処理を 行っています。これも当り判定の応用で、一定範囲以下であれば得点を加算する、という処理だけをしています。例えば「Distance」の場合、カスった ら火花のようなものが飛ぶようになってるので、カスリボーナスの存在にも気づかれやすいです。が、このサンプルではあえてやらなかったので、実装してても 気づいてもらえなかったのではないでしょうか?プログラムの構造のところでも話をしましたが、プログラムの流れは

ユーザーからの入力その結果がどうなったかを判定画面に表示(音声の出力)

こういった形になっています。「入力」→「判定」部分は基本になってくるので、それほど違いを持たせることはできませんが、「表示(出力)」部分に関して は、力いっぱい凝ることもできますし、シンプルにしちゃうこともできます。あまり凝りすぎると処理速度の問題なんかも出てきてしまいますが、せっかく実装 している判定をユーザーに分かってもらえないというのも悲しいものです。サンプルの場合、わざと(面倒だったからじゃないです^^;)得点にだけ反映させ てます。が、こういう小さな部分に凝る事で、ゲーム自体の面白みが増す事もあるということを覚えておきましょう。

[解剖記]敵弾の生成と移動

さてお次は敵弾の生成です。ここのポイントは、如何にして自機を狙ってくる弾を撃たせるか?という部分です。Hspの命令に√を扱うものがあればそれほど問題にはならないし、sin・cos・tanを扱うものがあればもっと楽なんですが、残念ながら基本命令では四則演算しかできないため、ちょいとひねったことをやっています。


;■ 平方根の準備
dim calc,900
repeat 900
temp=cnt : calc.cnt=temp*temp
loop


;■ 弾生成
*tamaborn
if frame\(8-(level/4))>0 : return
dx=mx-ex.cnt : px=dx : if dx<0 : dx=0-dx
dy=my-ey.cnt : py=dy : if dy<0 : dy=0-dy
dst=(dx*dx)+(dy*dy)
edx=ex.cnt+12 : edy=ey.cnt+12
repeat 900
if dst<calc.cnt : dst=cnt : break
loop

repeat tmax
if tf.cnt>0 : continue
tf.cnt=1
tx.cnt=edx : ty.cnt=edy
rnd ds,8 : ds=level/5+3+ds
txv.cnt=px*ds/dst
tyv.cnt=py*ds/dst

break
loop
return

要は、最終的には弾の運動量(txv,tyv)が、ちゃんと自機の座標方向へ進むものであればいいわけです。その方向は(px,py)で決まってきます。 これは、自機座標から敵機座標を単純に引いたものです。例えば敵機が自機の右斜め前方にいる場合、弾の運動量はx方向はマイナス・y方向はプラスになりま す。

自機座標から敵機座標を引けば、どういう場合でもちゃーんと方向だけは算出できます。後はこれに弾の速さ(サンプルの場合ds)を掛けて、一定数で割ればいいだけです。問題は「割る数をどうやって出すか?」です。

例えば定数で割ると、自機と敵機が近いと遅い弾・自機と敵機が遠いと速い弾という妙な事になってしまいます。要は、自機と敵機の実距離で割ればいいのです。奮闘記の一番最後で二点間の距離の算出方法が解説してありますが、サンプルではそれを応用しています。

もっとシンプルにやるのであれば、xの差(dx)とyの差(dy)で大きいほうで割るという手もあります。Miaさんのページで はその方法が解説してあり、「Aeolianation」でもこの方法で敵弾の処理を行っています。処理速度と手間を考えると、この方法のほうが楽です が、斜めに飛んでくる弾が若干速くなるという欠点があります。いずれにしても、基本命令だけでやろうと思うと結構無理があります。拡張dllを使い、斜め の実距離を計算で出す(hepext.dllを使う)か、ソフト的にやらせる(hspdx.dll = DirectXの拡張dllを使う)方が全然ラクです^^;

そういえば、この「二点間の距離を求める」解説をした際に、もっと簡単なやり方があるよ~と教えてもらったことがありました。これを使ってやるという方法 もありますので、紹介させていただきます。実は私も最初はサンプルを作るときにこれを使おうと思ったのですが、うまくいかないので自分なりに開発したやり 方にしてしまったという経緯があります^^;


bits=10 ; 2のbits乗-1までの整数を平方根として求める。
x=573* ; xに√を求めたい値を代入

b=$400 ;1<<bits;10でないときはこれを使う

repeat bits
b=b>>1 : aa=ans|b : if aa*<=x : ans=aa
loop

mes "√"+x+"="+ans
stop

どなたか、これを使ってもっと簡単に狙い弾を撃たせるスクリプトを開発してくださいまし。私にはできませんでした。お願い~。

[解剖記]敵と自機弾の当り判定

さて、問題の当り判定です。これがしっかりしていないと、ゲームとして成立しませんね。

ここでのミソは2重ループを 使ってることです。つまり、敵一機一機について、弾一個づつ順番に当り判定をしているわけです。このサンプルでは敵が最大50(emax=50)で、自機 弾が最大3(wmax=3)なので、このルーチンでは合計50x3=150回の当り判定ループを行っていることになります。そして、その2重ループを行う 上で一番大切な処理が「 ent=cnt 」の部分です。


;■ 敵移動
*enemymove
repeat emax
et.cnt++
if ef.cnt<1 : continue
ent=cnt
repeat wmax
if wf.cnt<1 : continue
ddx=(ex.ent+25)-(wx.cnt+12)
if ddx<0 : ddx=-ddx
ddy=(ey.ent+25)-(wy.cnt+25)
if ddy<0 : ddy=-ddy
if (ddx>=0)&(ddx<20)&(ddy>=0)&(ddy<40) {
ef.ent-- : wf.cnt=0
if ef.ent>0 {
pos wx.cnt,wy.cnt
gcopy 2,175,25,25,25
snd 4
score++
}
else {
pos ex.
ent,ey.ent
gcopy 2,200,0,50,50
snd 2
gosub *messageborn
}
}
loop
if en.cnt=1 : gosub *enemy1
if en.cnt=2 : gosub *enemy2
if en.cnt=3 : gosub *enemy3
loop
return

普通に1重のループであれば、「 cnt 」というシステム変数を使えば「何番目の要素番号」というのは簡単に処理できます(奮闘記10.配列変数を参照)。ところが、2重のループになった場合、内側のループではそのシステム変数が内側だけのものになってしまいます。つまり、外側のループが何回目の ループ(要素番号何番か?)なのか?を記憶しておく変数を用意しなければ、内側のループでそれを使う事ができないのです。つまり、内側のループではent が敵、cntが自機弾の番号を意味しているわけです。

さて、問題の当り判定。これは座標の差(絶対値)によって判定します。その際敵機と自機弾の大きさの違いを考慮に入れて、計算式の中で調整を行っています。要は、中心座標の差(絶対値)を求めているわけです。敵機の大きさは50x50なので、中心座標は(ex+25、ey+25)になります。自機弾の大きさは25x50なので、だいたいの中心座標は(wx+12、wy+25)です。この差を求め、それが一定数以下ならばあたりという判定を行っているわけです。

ここで、お気づきの方もいるかと思いますが、この計算式には無駄があります。座標の差を絶対値で求めているので(ddx>=0)や(ddy>=0)という条件は不要です。無駄が無いようにするならばこの部分の計算式はこうなります。

if (ddx<20)&(ddy<40)

またしても半日で作った弊害が表面化してしまいました。まぁあんまり問題にはならないですが、皆さんがこういう処理を行うときは、こういった無駄な事はしないようにしましょう^^;

ちなみに、この数値を大きくしたり小さくしたりする事によって、当り判定の範囲を大きくしたり小さくしたりする事ができます。ゲームの難易度に大きくかかわってくる部分ですね。

後は、当りだった場合にどう処理をするかだけです。このサンプルでは敵の固さという概念がありますのでそれを減らし(ef.ent--)、自機弾を消し (wf.cnt=0)ます。この自機弾を消す処理を行わないと、貫通するレーザーのできあがりです。また、ef.entから減らす値を武器によって変えれ ば、威力の違いという概念も導入できます。「Aeolianation」ではLimitedモードだけしか出てきませんが、自動標準レーザーと誘導弾の威 力を著しく落とすために、この概念を導入しています。

後は、敵がまだ破壊できてない場合(ヒットした効果音と画像の表示)と、破壊した場合(爆発音と爆風の表示・得点のカウントと倍率表示の処理)の処理を行 うだけです。このあたりの処理をどうするかはアイデア次第です。例えば爆風も、もっと飛び散るような派手なものにするんだったらそれなりの処理を行わなけ ればなりませんし、例えば連続ヒットコンボのような概念を導入するのであれば、やはりもっと処理は複雑になってきます。ただ、基本は座標の差を求め、それ が一定数以下の場合は当り、という部分です。

[解剖記]敵の生成と移動アルゴリズム3

続いて敵3のアルゴリズムです。基本は今までの敵と全く同じです。問題はアイデアということになってきますね。「Aeolianation」で一番気に 入ってる敵は、3面で出てくる目の前に貼りつく奴だったりします。それ自体に攻撃力は無いものの、まるで盾のように自機の攻撃を防いでしまうという滅私奉 公ぶりが何とも言えませんね^^


■ 敵3
*enemy3
pos ex.cnt,ey.cnt : gcopy 2,350,0,50,50
dx=mx-ex.cnt
if et.cnt<2 {
if dx<0 {
exv.cnt=-3
}
else {
exv.cnt=3
}
}
if ey.cnt>200 : eyv.cnt--
if et.cnt>20 : if ey.cnt<50 : eyv.cnt++
if ey.cnt>200 : gosub *tamaborn
if et.cnt>20 : if ey.cnt<50 : gosub *tamaborn
ex.cnt=ex.cnt+exv.cnt
ey.cnt=ey.cnt+eyv.cnt
if ex.cnt<-50 : ef.cnt=0
if ex.cnt>640 : ef.cnt=0
if ey.cnt<-50 : ef.cnt=0
if ey.cnt>480 : ef.cnt=0
return

左右の移動方向は敵2と同じように、生成直後に決まります。後は、上下にサインカーブを描いて飛んでいくだけです。カーブの頂点付近で弾を乱射するのも敵 2と同じです。敵2の場合はUカーブを描いてそのまま画面の上に消えてしまいますが、こいつの場合いつまでも画面に残ってるため、撃ちもらすとやっかいな ことになるのです。実は「Aeolianation」の1面のボスとほぼ同じ動きだったりします(画像も同じだけど)。「Aeolianation」の1 面のボスの場合、画面の左右の端にきたら、左右の運動量を反転させ、あたかも画面端でバウンドするような動きになっています。

ここまで3種類の敵のアルゴリズムを解説しましたが、いかがでしょう?生成されてからの時間であったり、自機との距離であったり、実際の座標であったりと、いろんな条件を使って動きを制御しているのがわかると思います。それほど難しくはないですよね?問題はアイデアです。

[解剖記]敵の生成と移動アルゴリズム2

続いて「敵2」のアルゴリズムです。敵を何種類も用意しようと思ったら、このようにその数だけサブルーチンを用意しなければなりません。この敵の動きがゲーム性を決める要素の一つでもあるので、いろいろと頭をひねりましょう。意外にこういう作業が楽しかったりします。


;■ 敵2
*enemy2
pos ex.cnt,ey.cnt : gcopy 2,300,0,50,50
dx=mx-ex.cnt
if et.cnt<2 {
if dx<0 {
exv.cnt=-6
}
else {
exv.cnt=6
}
}
if et.cnt>25 : eyv.cnt-- : if eyv.cnt<-10 : eyv.cnt=-10
if ey.cnt>200 : gosub *tamaborn
ex.cnt=ex.cnt+exv.cnt
ey.cnt=ey.cnt+eyv.cnt
if ex.cnt<-50 : ef.cnt=0
if ex.cnt>640 : ef.cnt=0
if ey.cnt<-50 : ef.cnt=0
if ey.cnt>480 : ef.cnt=0
return;

まず出現してからすぐのタイミングで、左右どっちへ動くか決めます。この場合、自機の方向へ動くようにし、その方向は死ぬ(消える)まで変わりません。

続いて一定時間経過後、y軸の移動方向を変え、画面の上のほうに進むようにします。V型カーブの動きにしようと思ったら、ここでeyv.cntの値を一気に変えればいいのです。この敵はU型カーブの動きをするように、eyv.cntの値を徐々に変化させています。

弾を撃つタイミングは、y軸座標によって決めています。だいたいカーブの頂点当りで乱射するようになっています。

もっと複雑な動きをさせたい場合、et.cnt(生成からのフレーム数)が非常に意味を持ってきます。これをうまく使えば、一定時間経過後自爆して弾をば らまく「機雷」とか、一定距離を移動した後静止して弾を撃ち再度動く、といった動きも可能になってきます。「Aeolianation」ではボスも含めれ ば30種類以上の敵を用意してあります。変数の名前は違いますが、基本は同じなので参考にして下さい。

「Aeolianation」のソースを 見る上での注意としては、大きさの違う敵の場合座標の調整を行っていることです。というのは、自機の座標(mx,my)にしても敵機の座標(ex,ey) にしてもそこを左上として画像を表示するために、大きさの異なるもの同士だと本来当り判定を行いたい位置とはずれが出てくるのです。

このことについては、敵と自機弾の当り判定の中で詳しく説明をします。

[解剖記]敵の生成と移動アルゴリズム1

敵の移動や硬さなどを制御する要素には、

  1. 画面に表示できる最大数
  2. x軸座標
  3. y軸座標
  4. x軸の移動量
  5. y軸の移動量
  6. 画面上に表示されているかどうか

最低限この6つが必要となってきます。このうち「画面上に表示されているかどうか」の要素を使って、「敵の固さ」という概念も導入しています。つまり、自機弾が何発か当らなければ倒せない、という敵を用意する事もできるわけです。

また、このサンプルではこれらの要素のほかに、

  • 敵の種類
  • 出現からのフレーム数

といった要素も用意してあります。ゲームをやってみると分かりますが、敵は全部で三種類あり、三種類とも動きや固さ・弾の発射タイミングなどが異なります。これを処理するためにはこういった要素も不可欠でした。


emax=50 ;敵の最大数

;■ 配列定義
dim ex,emax ;敵x軸座標
dim ey,emax ;敵y軸座標
dim ef,emax ;敵固さ・生死
dim en,emax ;敵種類
dim exv,emax ;敵x方向運動量
dim eyv,emax ;敵y方向運動量
dim et,emax ;敵出現からの時間

;■ メインルーチン
*main
--------------------------
--
 if frame\(25-level)=0 : gosub *enemyborn ;敵生成
gosub *enemymove ;敵移動
-------------------------
--
frame++
level=frame/500+1
if level>20 : level=20
goto *main


;■ 敵生成
*enemyborn
rnd temp,7
if temp=0 : return ;出現しない
repeat emax
if ef.cnt>0 : continue
;↑未使用の要素番号を調べる
if temp=1 : en.cnt=1 ;敵1
if temp=2 : en.cnt=1 ;敵1
if temp=3 : en.cnt=1 ;敵1
if temp=4 : en.cnt=2 ;敵2
if temp=5 : en.cnt=2 ;敵2
if temp=6 : en.cnt=3 ;敵3

ef.cnt=en.cnt ;固さ
rnd ex.cnt,640 ;初期出現位置x軸
ey.cnt=-50 ;初期出現位置y軸
exv.cnt=0 ;初期移動量x軸
eyv.cnt=10 ;初期移動量y軸
et.cnt=0
;↑出現からのフレーム数リセット
break
loop
return

まず、メインルーチンでどういったタイミングで敵を生成するかの 制御を行っています。このサンプルではレベルが上がっていくと生成頻度が高くなるような形になっています。これを、決まったタイミングでいつも同じように 出現するようにしようと思うと、全く違った形の処理(「Aeolianation」ではそういう処理をおこなっています)をしなければなりません。

続いて「敵生成」のルーチンで、どの敵を出現させるのかを 決め、その固さや初期出現位置などを決めます。ここでは乱数(変数tempに代入した値)によってそれを決めていますが、弱い敵1を多く出現させ、強い敵 3はあまり出現させないようにしています。見たままですが、(敵1:敵2:敵3)=(3:2:1)という出現頻度になっています。


;■ 敵移動
*enemymove
repeat emax
et.cnt++
if ef.cnt<1 : continue
----------------------

if en.cnt=1 : gosub *enemy1
if en.cnt=2 : gosub *enemy2
if en.cnt=3 : gosub *enemy3
loop
return


;■ 敵1
*enemy1
pos ex.cnt,ey.cnt : gcopy 2,250,0,50,50
dx=mx-ex.cnt
dy=my-ey.cnt

if dy<350 {
if dx<0 {
exv.cnt=exv.cnt-1
if exv.cnt<-25 : exv.cnt=-25
}
else {
exv.cnt=exv.cnt+1
if exv.cnt>25 : exv.cnt=25
}
}
if dx<0 : dx=-dx
if dx<50 : gosub *tamaborn
eyv.cnt=4
ex.cnt=ex.cnt+exv.cnt
ey.cnt=ey.cnt+eyv.cnt

if ex.cnt<-50 : ef.cnt=0
if ex.cnt>640 : ef.cnt=0
if ey.cnt<-50 : ef.cnt=0
if ey.cnt>480 : ef.cnt=0

return

ここでは敵の動きと弾発射のタイミングを決めています。まず、自機との距離(x軸の差とy軸の差)を求め、y軸方向の距離が350未満の時には、自機に接近する方向にx軸の移動速度を変化させています。この移動速度を一気に変えると恐ろしい動きをすることになってしまうので、だんだん変化させ、一定以上の速度にはならないようにしています。

続いて、自機とのx軸方向の距離(差を求めてから絶対値を出す)を調べ、自機との距離が50未満の時には弾生成ルーチンへ飛ぶようになっています。

・・・そしてここでもまたやっちゃってるんですが、先に表示してから移動をさせてしまっています。これをやってしまうと本来の位置と画像の表示位置が微妙にずれるので、当り判定をした時に「画像は当ってないのに当った」ということが起こってしまうので、移動をしてから表示するようにしましょう。悪い例です^^;

最後に、画面外へ消えたときは消滅させる処 理を行っています。画面外から戻ってくるようにするには、ここの値を変えたり処理しないようにすればいいのです。ただ、最終的には消滅させるようにしなけ れば、画面外に消えた敵がいつまでも残り、emaxで決めた値を超えるとそれ以上敵が出てこないようになってしまうので気をつけましょう。