プチコン3号 22日目 SmileBASICでOOP(5)クラスオブジェクトの使い方

pc22-1.jpg

前回のサンプルで、シューティングゲームにおける自機、および自機の弾の処理をOOP風に記述してみました。
今回は同じシューティングゲームでの敵機と敵の弾の処理をOOP風に記述してみます。

敵機の動作は、

・画面上端にランダムに出現
・真下へ向かって移動する
・Y座標がプレイヤー機以上になったら、プレイヤー機へ近づく方向への水平移動に切り替える

とします。
(この動作が初めてゲームに登場したのは1984年のテーカン「STAR FORCE」だったと思います。)

また、敵弾は敵機が下へ向かって移動している間だけ、プレイヤー機に向けてランダムに発射することにします。

pc22-2.jpg

以上を実現するために、「敵オブジェクト」「敵弾オブジェクト」の2つのクラスを新たに追加します。
メインプログラムのクラス生成を行う部分は以下のようになります。
赤が前回からの追加部分です。

VAR _MY,_SHOT,_ENEMY,_ESHOT

_MY     =SP_NEWCLASS("MY",    1)
_SHOT   =SP_NEWCLASS("SHOT",  5)
_ENEMY  =SP_NEWCLASS("ENEMY", 20)
_ESHOT  =SP_NEWCLASS("ESHOT", 20)

VAR I

MY_NEWSHIP
WHILE TRUE
FOR I=0 TO __MAXOBJ-1
IF SP_ISACTIVE(I) THEN SP_UPDATE I
NEXT
VSYNC 1
WEND

敵機と敵弾は、それぞれ最大20個にしました。

次に、敵機の生成です。

敵機の生成は、自機の生成と同様にメインループの中で行うこともできます。
しかし、できるだけ関連するコードを一箇所に集めたほうがプログラムが見やすいので、敵機の生成はクラスオブジェクトで行うことにします。

敵機のクラスを生成した直後は、敵機のインスタンスは未生成状態で、クラスオブジェクト(_ENEMY)のみが存在しています。
クラスオブジェクトはアクティブな状態なので、SP_UPDATEからクラスオブジェクトのメソッドUPDATE_ENEMY_Cがメインループの中で呼び出されます。
敵機の生成処理は、このUPDATE_ENEMY_Cの中で行います。

なお「UPDATE_クラス名_C」は、クラスオブジェクトのメソッドで、インスタンスオブジェクトのメソッド「UPDATE_クラス名」とは異なります。
(詳しくは19日目の記事を参照してください。)
1つのクラスには、クラスオブジェクトは1つしかありませんので、UPDATE_クラス名_Cはループの中で必ず1度だけ実行されます。

UPDATE_ENEMY_Cの実装は以下のようになります。
(以降、OOP関連の命令を赤で、それ以外のユーザ定義命令を青で示します。)

DEF UPDATE_ENEMY_C SELF
VAR SP
IF MAINCNT MOD 20 != 0 THEN RETURN
SP=SP_NEWOBJECT(SELF)
IF SP != _S_NULL THEN
SPCHR SP,3251
SPOFS SP,50+RND(_SCW-100),-8
SPANIM SP,"XY+",-180,0,256,1
SPANIM SP,"I+",5,0,5,1,5,2,0
ENDIF
END

MAINCNTは経過時間を1/60秒単位で示すシステム変数です。
UPDATE_ENEMY_Cは20/60秒すなわち1/3秒に1回、敵機オブジェクトを生成し、移動アニメーションとキャラクタアニメーションを設定します。
移動アニメーションは、180ticks(3秒)で256ピクセルだけ画面下方へ移動するようになっています。

さて、敵オブジェクトが生成されると、今度は敵オブジェクトのメソッドUPDATE_ENEMYがメインループからSP_UPDATE経由で毎回呼び出されます。

敵機の状態は、「下方向へ移動」と、「水平方向へ移動」の2つがあります。
これらを、オブジェクトの状態を表すために使っているスプライト変数1番の値の違いで区別することにします。

オブジェクト生成直後は変数値は_S_ACTIVEですので、これを下方向へ移動中の状態と考えます。
水平方向へ移動中は、「変数値が_S_ACTIVE+1」とします。
(もう少し状態が多い場合は、状態一つ一つにそれぞれ個別の変数名を定義したほうが良いでしょう。)

敵機のY座標がプレイヤー機のY座標以上になると、移動をプレイヤー機方向への水平移動に切り替えます。
また、下方向へ移動中の場合は、確率的に敵弾を発射します。

以上の処理を行うUPDATE_ENEMYの実装は以下のようになります。

GET_MY_POSは自機のXY座標を返す手続きです。
自機のオブジェクトはクラス_MYの最初のインスタンスである__I1ST[_MY]になります。
(本当は、このアクセサは_MYのクラスメソッドか、せめて_ENEMYのクラスメソッドとして定義すべきですね。)
自機オブジェクトがアクティブでないときは、(-100,-100)を返します。

DEF GET_MY_POS OUT X,Y
VAR SP
SP=__I1ST[_MY]
IF SP_ISACTIVE(SP) THEN
SPOFS SP OUT X,Y
ELSE
X=-100:Y=-100
ENDIF
END

DEF UPDATE_ENEMY SELF
VAR X,Y,MX,MY,A
SPOFS SELF OUT X,Y
IF Y > _SCH THEN SP_FREE SELF:RETURN
IF X < 0 || X > _SCW THEN SP_FREE SELF:RETURN
IF SP_GETSTATE(SELF) == _S_ACTIVE THEN
GET_MY_POS OUT MX,MY
IF Y >= MY THEN
SPANIM SELF,"XY+",-150,SGN(MX-X)*400,0,1
SP_SETSTATE SELF,_S_ACTIVE+1
ELSE
IF RND(100)==0 THEN
A=ATAN((MX-X)/(MY-Y))
NEW_ESHOT X,Y,SIN(A)*500,COS(A)*500,150
ENDIF
ENDIF
ENDIF
END

Y座標が画面下端に達したか、あるいはX座標が画面右端または左端に達したら、SP_FREEでオブジェクトを廃棄します。

Y座標がプレイヤー機のY座標より小さい場合は、1/100の確率で敵弾を生成します。
(小さい確率に思えるかもしれませんが、毎秒60回×敵機の数だけ実行されますので、平均して毎秒数発は生成される計算になります。)

敵弾の移動方向は、自身から見たプレイヤー機の方向です。
方向は座標を元に三角関数ATANで求め、さらにSINとCOSでその方向のX成分とY成分を求めています。

pc22-3.jpg

敵弾のインスタンス生成は関数NEW_ESHOTで行います。
この処理は前回の自機の弾発射と同じです。
敵弾のUPDATE処理も、画面外へ出たらオブジェクトを廃棄するだけです。
実装は以下の通りです。

DEF NEW_ESHOT X,Y,DESTX,DESTY,TICK
VAR SP
SP=SP_NEWOBJECT(_ESHOT)
IF SP != _S_NULL THEN
SPCHR SP,3372
SPOFS SP,X,Y
SPANIM SP,"XY+",-TICK,DESTX,DESTY,1
SPANIM SP,"I+",5,0,5,1,5,2,5,3,0
ENDIF
END

DEF UPDATE_ESHOT SELF
VAR X,Y
SPOFS SELF OUT X,Y
IF Y < 0 || X < 0 THEN SP_FREE SELF
IF Y > _SCH || X > _SCW THEN SP_FREE SELF
END

以上で、敵機と敵弾を作ることができました。

ただ、ゲームとして成立させるためには、まだ当たり判定がありません。
当たり判定は次回に解説します。

コメント