2015年02月07日

プチコン3号 23日目 シューティングゲームの当たり判定とマスク値設定

前回までに、自機と自機の弾、敵機と敵の弾をそれぞれ生成・更新できるようになりました。
今回は当たり判定を追加します。

当たり判定はSPHITSP命令で行えます。
この命令は以前にも11日目に迷路ゲームで使用しました。
このときは、2つのスプライトが衝突しているか否かの判定でした。

今回は、SPHITSP命令のもう一つの機能である「衝突しているスプライトを検索する」機能を使います。
この機能を使う場合は、SPHITSPのパラメータにはスプライト管理番号を1つだけ渡します。
そのスプライトに衝突している別のスプライトがあれば、その管理番号が返り値として得られます。
衝突していなければ、-1が返ります。


これだけだと、処理する必要がない衝突まで検索されてしまいます。
たとえば自機と自機の弾の衝突は検出する意味がありません。

無用な衝突検出を避けるには、スプライトの衝突判定条件に「マスク値」を追加します。
マスク値はSPCOL命令のパラメータで、スプライトごとに設定することができます。

SPHITSPで衝突したスプライトを検索する際に、2つのスプライトのマスク値のANDを取り、0でないスプライトだけが衝突検出の対象になります。
具体的なマスクの設定の例を下図に示します。
pc23-1.jpg

画面に登場するスプライトと、当たり判定の要不要を表の形で表しました。
自機は敵機または敵の弾と当たり判定を行いますが、自機の弾との判定は不要です。
自機の弾は、敵機とのみ当たり判定を行います。

マスク値は、ボタン入力の判定と同じように2進数で考える必要があります。
上の図では、マスク値を2進数で書いています。
2進数の01は10進数の1、2進数の10は10進数では2です。

敵機のマスク値が11で、自機のマスク値が01、自機の弾のマスク値が10なら、

11 AND 01 = 01
11 AND 10 = 10

ですから、敵機は自機とも自機の弾とも当たり判定されることになります。

マスク値の具体的な決め方は、以下のように考えると良いでしょう。

(1)衝突判定の対象が1番多いオブジェクトを選ぶ。(複数ある場合は、その中からどれか1つ選ぶ)
(2)(1)のオブジェクトと衝突しないオブジェクトで、衝突判定の対象が1番多いオブジェクトを選ぶ。
(3)(1)と(2)に衝突しないオブジェクトで、・・・(以下同じ)

今回はオブジェクトが4種類しかありませんが、上記の方法でやってみると

(1)の候補は自機、または敵機。ここでは自機としましょう。
(2)の候補は自機の弾のみ。
(3)もうありません。

そして、上記で選び出したオブジェクトに、それぞれ独立なマスク値を与えます。
マスク値は32ビットですので、以下のように独立なマスクが32個あります。

0000 0000 0000 0000 0000 0000 0000 0001
0000 0000 0000 0000 0000 0000 0000 0010
         :
0100 0000 0000 0000 0000 0000 0000 0000
1000 0000 0000 0000 0000 0000 0000 0000

どのオブジェクトをどのマスク値にするか制約はありません。
ここでは、自機が末尾01、自機の弾が末尾10としましょう。

上記で選ばれなかったオブジェクトは、独立なマスク値を持ちません。
それらのオブジェクトのマスク値は、

「当たり判定が必要な全てのオブジェクトのマスク値のOR」

で求めます。

たとえば敵機は自機、自機の弾のいずれとも当たり判定が必要ですので、

 01 OR 10 = 11

がマスク値になります。
敵の弾は、自機とのみ当たり判定しますので、マスク値は01になります。

なお、(1)のところで自機ではなく敵機を選ぶことも可能です。
その場合は(2)で敵機の弾が選ばれ、この2つに独立なマスク値を与えることになります。


前回までのプログラムに衝突判定の処理を追加すると、以下のようになります。
まず独立なマスク値を定義します。
VAR _MASK_MY:_MASK_MY     = &H01
VAR _MASK_SHOT:_MASK_SHOT = &H02

&Hは、「16進数」を表します。

次に、各オブジェクトについてSPCOL命令で衝突判定領域とマスク値の設定を追加します。
SPCOLのパラメータは

 管理番号, Xオフセット, Yオフセット, 幅, 高さ, スケール対応, マスク

です。
DEF MY_NEWSHIP
VAR SP
SP=SP_NEWOBJECT(_MY)
IF SP != _S_NULL THEN
SPCHR SP,3311'3299
SPOFS SP,_SCW>>1,200
SPCOL SP,-6,-6,12,12,TRUE,_MASK_MY
ENDIF
END
DEF NEW_SHOT X,Y,SPEED
VAR SP
SP=SP_NEWOBJECT(_SHOT)
IF SP != _S_NULL THEN
SPCHR SP,3353
SPOFS SP,X,Y
SPANIM SP,"XY+",-SPEED,0,-240,1
SPCOL SP,-1,-8,2,16,TRUE,_MASK_SHOT
ENDIF
END
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
SPCOL SP,-6,-8,12,16,TRUE,_MASK_MY OR _MASK_SHOT
ENDIF
END
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
SPCOL SP,-2,-2,4,4,TRUE,_MASK_MY
ENDIF
END

これで各スプライトの当たり判定が有効になりました。
当たり判定処理は、自機側で行うことも敵機側で行うこともできます。
どちらかと言えば、自機・自機の弾を中心に考えるほうがプログラムを書きやすいと思います。
以下の例では、自機および自機の弾のUPDATE処理内で行っています。

まず自機の弾の当たり判定処理です。
DEF UPDATE_SHOT SELF
VAR X,Y,HIT
SPOFS SELF OUT X,Y
IF Y < 0 THEN SP_FREE SELF:RETURN

HIT=SPHITSP(SELF)
IF HIT == -1 THEN RETURN
SP_FREE HIT
SP_FREE SELF
END

衝突する相手は敵機のみですので、衝突していたらその相手と弾自身を単に廃棄しています。
厳密には、他に「自機の弾同士の衝突」があり得ます。
連射速度が非常に速い場合などは、これを避けるために
IF SP_CLASS(HIT) == _SHOT THEN RETURN

という処理が必要になります。

次に自機の当たり判定です。
動作確認のために、とりあえず敵や敵弾に当たったらその都度、画面に「*」をプリントするようにしました。
実際にはこの部分に、自機がやられた場合の処理を入れます。
DEF UPDATE_MY SELF
VAR SX,SY,X,Y,B,HIT
STICK OUT SX,SY
SPOFS SELF OUT X,Y
X=MIN(_SCW-8,MAX(X+SX*8,8))
Y=MIN(_SCH-8,MAX(Y-SY*8,100))
SPOFS SELF,X,Y,1

B=BUTTON(1)
IF B AND 16 THEN
NEW_SHOT X,Y,60
ENDIF

HIT=SPHITSP(SELF)
IF HIT == -1 THEN RETURN
PRINT "*";

END

posted by boochow at 19:08| Comment(0) | プチコン講座 | このブログの読者になる | 更新情報をチェックする

2015年02月05日

プチコン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


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

ただ、ゲームとして成立させるためには、まだ当たり判定がありません。
当たり判定は次回に解説します。
posted by boochow at 01:17| Comment(0) | プチコン講座 | このブログの読者になる | 更新情報をチェックする

2015年02月03日

プチコン3号 21日目 SmileBASICでOOP(4)サンプル

pc21-1.jpg

前回までで、オブジェクト指向(風)プログラミングに必要な関数は一通りそろいましたので、使い方をサンプルでお見せします。
例題は

・スライドパッドで自機移動
・Aボタンで弾発射

という、作りかけのシューティングゲームみたいなものです。

プチコン3号本体の命令としては、3日目で書いたボタン入力、15日目で書いたスプライトのアニメーション(移動)を使います。
今回は特に説明はしませんので、上記の記事を参照してください。

おさらいになりますが、オブジェクト指向用の関数・命令として以下のものがあります。
詳細は以前の記事を参照してください。

・クラス作成
SP_NEWCLASS("クラス名称",インスタンスの最大数)
返り値:クラスオブジェクト(作成失敗時は_S_NULL)

・オブジェクト生成
SP_NEWOBJECT(クラスオブジェクト)
返り値:インスタンスオブジェクト(作成失敗時は_S_NULL)

・オブジェクト廃棄
SP_FREE インスタンスオブジェクト

・オブジェクトの状態チェック
SP_ISACTIVE(オブジェクト)
返り値:アクティブ(表示等更新要)ならTRUE、非アクティブならFALSE

・オブジェクトの表示等更新処理
UPDATE_クラス名称 オブジェクト
(SP_UPDATEから呼び出される)

今回のサンプルでは、「自機」「弾」の2つのクラスを作成します。

自機のインスタンスは、プログラム開始時に1つ生成します。
自機はスライドパッドで移動し、Aボタンで弾を発射します。

弾のインスタンスは、自機が生成します。
弾はアニメーションで勝手に画面外へ向かって飛んでいきます。
弾オブジェクトは、毎回座標をチェックし、画面外へ出ていたら自らを廃棄します。
pc21-2.jpg

メインプログラムは以下のようになります。
VAR _MY,_SHOT

_MY =SP_NEWCLASS("MY", 1)
_SHOT =SP_NEWCLASS("SHOT", 5)

VAR I

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

まず自機のクラス"MY"と弾のクラス"SHOT"を生成し、それぞれクラスオブジェクトを_MYと_CLASSに代入しています。
自機のインスタンスは最大1、弾のインスタンスは最大5つのリソースを確保しています。
(本来ここで_MYや_SHOTが_S_NULLになっていないかチェックが必要ですが、今回は省略します。)

MY_NEWSHIPは_MYのインスタンスを生成する命令ですが、後で説明します。

ループの中ではアクティブな全てのオブジェクトの更新を行っています。
この部分は作成した全てのクラスで有効です。
クラス固有の処理はUPDATE_クラス名のほうで記述します。

自機のクラス_MYに関連する処理は以下のようになります。
DEF MY_NEWSHIP
VAR SP
SP=SP_NEWOBJECT(_MY)
IF SP != _S_NULL THEN
SPCHR SP,3311
SPOFS SP,_SCW>>1,200
ENDIF
END

DEF UPDATE_MY SELF
VAR SX,SY,X,Y,B
STICK OUT SX,SY
SPOFS SELF OUT X,Y
X=MIN(_SCW-8,MAX(X+SX*8,8))
Y=MIN(_SCH-8,MAX(Y-SY*8,100))
SPOFS SELF,X,Y,1

B=BUTTON(1)
IF B AND 16 THEN
NEW_SHOT X,Y,60
ENDIF
END

MY_NEWSHIPで_MYの新しいインスタンスを生成しています。
オブジェクトは実際にはスプライト管理番号ですので、生成したオブジェクトをそのままスプライト管理番号として、キャラクタと座標を設定しています。
_SCWは掲載していない部分で定義しており、スクリーンの横方向ピクセル数-1(399)です。

メインプログラムのSP_UPDATEを介して、UPDATE_MYが呼び出されます。
UPDATE_MYは、クラス_MYのインスタンスメソッドの位置づけです。

インスタンスメソッドの引数SELFにはオブジェクト自身が入っています。
このオブジェクトがクラス_MYのインスタンスであることは保証されています。
オブジェクト指向言語では、SELFはパラメータではなく主体なので、SELF.SP_UPDATEというような書き方をしますが、BASICではパラメータにせざるを得ません。

UPDATE_MYの処理はスライドパッドによる自機の移動と、Aボタンを押したときの弾の発射です。
弾オブジェクトの生成はNEW_SHOTで行っています。
NEW_SHOTのパラメータは初期座標とY軸方向の移動速度の逆数(240ピクセル移動するのに要する時間を1/60秒単位で示した数。60なら1秒で240ピクセル移動)です。

弾のクラス_SHOTに関するコードを以下に示します。
DEF NEW_SHOT X,Y,SPEED
VAR SP
SP=SP_NEWOBJECT(_SHOT)
IF SP != _S_NULL THEN
SPCHR SP,3353
SPOFS SP,X,Y
SPANIM SP,"XY+",-SPEED,0,-240,1
ENDIF
END

DEF UPDATE_SHOT SELF
VAR X,Y
SPOFS SELF OUT X,Y
IF Y < 0 THEN SP_FREE SELF
END

NEW_SHOTで弾のクラス_SHOTの新しいインスタンスを生成しています。
そして、座標とアニメーションの設定を行っています。

メインプログラムのSP_UPDATEを介して、UPDATE_SHOTが呼び出されます。
UPDATE_MYはクラス_SHOTのインスタンスメソッドです。

弾オブジェクトはSPANIMで移動させていますので、移動処理は不要です。
現在の座標を取得して、Yが負(画面上端に到達)ならばオブジェクトを廃棄しています。

クラス_SHOTの生成時に、インスタンス数を最大5に指定していますので、弾は5発まで連射可能です。
5発とも画面内に存在している間は、NEW_SHOTからSP_NEWOBJECTを呼び出したときに_SP_NULLが返りますので、新しい弾オブジェクトを生成できません。

「OOP風」プログラムのサンプルは以上です。
「BASICで無理やりOOPをやろうとしている」という雰囲気は感じていただけたでしょうか。
posted by boochow at 00:13| Comment(0) | プチコン講座 | このブログの読者になる | 更新情報をチェックする

2015年02月01日

プチコン3号 20日目 SmileBASICでOOP(3)コンストラクタ・デストラクタ

18日目で関数SP_NEWCLASSを作成し、クラスを生成すると同時にインスタンスのリソースも確保しました。
今回は、インスタンスの管理(生成と廃棄)を扱います。

インスタンスの個数が状況に応じて増えたり減ったりする場合、確保したリソースを上限として、インスタンスを増やしたり減らしたりする処理が必要です。
たとえばシューティングゲームにおいて、自機は通常画面内に1機ですが、敵は新しく登場したりプレイヤーの攻撃によって消滅したりするため、数が変動します。
新しく敵を登場させるには、リソースに空きがあるかどうか確認が必要ですし、敵が消滅したらそのリソースは返却しなければなりません。

オブジェクト指向言語ではインスタンスを生成する処理をコンストラクタ、廃棄する処理をデストラクタと呼びます。
今回は、このコンストラクタとデストラクタを作ります。
といっても、実質的にはオブジェクト=リソース=スプライトですので、

・未使用のスプライトと使用中のリスプライトを、スプライト変数1番の値で区別する。
・新しくインスタンスを生成するときは、未使用のスプライトから割り当てる。
・インスタンスを廃棄する時は、割り当てていたスプライトを未使用状態に戻す。

という処理をするだけです。

スプライト変数0番はクラスに割り当てましたので、スプライト変数の利用状態は下図のようになります。
pc20-1.jpg

1ビットの情報を表現するのに、残り7つしかない変数を一つ使ってしまうのはちょっともったいない気もしますので、スプライト変数1番は、広く「オブジェクトの状態」を表す変数とします。
そして、「未使用」はその状態の1つで値は0とし、状態が0以外の値なら使用中と見なすことにします。

そのほか、スプライトは廃棄時にSPHIDEで不可視にし、生成時にSPSHOWで可視にします。
ただしクラスオブジェクトのスプライトはクラス生成時にSPHIDEします。

コンストラクタ・デストラクタの実装は以下のようになっています。
VAR _S_UNUSED:_S_UNUSED = 0
VAR _S_ACTIVE:_S_ACTIVE = 1


DEF SP_NEWOBJECT(CLASS)
VAR I
IF __I1ST[CLASS] != _S_NULL THEN
FOR I=__I1ST[CLASS] TO __ILAST[CLASS]
IF SP_GETSTATE(I)==_S_UNUSED THEN
SP_SETSTATE I, _S_ACTIVE
SPSHOW I
RETURN I
ENDIF
NEXT
ENDIF
RETURN _S_NULL
END

DEF SP_FREE SELF
SPHIDE SELF
SP_SETSTATE SELF,_S_UNUSED
END

_S_UNUSEDはリソースが未使用である状態を示す定数です。
_S_ACTIVEは、状態が_S_UNUSEDではないことを表すために便宜上定義した定数です。

SP_NEWOBJECT(CLASS)は、CLASSの新しいインスタンスを生成して返します。
リソース不足で生成できなかった場合には、_S_NULLを返します。
呼び出し側では、返り値が_S_NULLかどうかを必ずチェックしなければなりません。

SP_FREEは、オブジェクトを廃棄します。引数は廃棄するオブジェクト自身で、返り値はありません。

また、オブジェクトの状態に関して以下のようなユーティリティ関数を作成しました。
DEF SP_SETSTATE SELF,STATE
_SPVAR SELF,1,STATE
END

DEF SP_GETSTATE(SELF)
RETURN _SPVAR_(SELF,1)
END

DEF SP_ISACTIVE(SELF)
RETURN (SP_GETSTATE(SELF) != _S_UNUSED)
END

DEF SP_COUNT_INSTANCE(CLASS,STATE)
VAR I,C
C=0
IF __I1ST[CLASS] != _S_NULL THEN
FOR I=__I1ST[CLASS] TO __ILAST[CLASS]
IF SP_GETSTATE(I)==STATE THEN INC C
NEXT
ENDIF
RETURN C
END

SP_GETSTATEとSP_SETSTATEはオブジェクトの状態を取得・設定します。
SP_ISACTIVEはオブジェクトが未使用ならFALSE、使用中ならTRUEを返します。
SP_COUNT_INSTANCE(CLASS,STATE)は、CLASSのインスタンスで状態がSTATEであるものの個数を返します。
SP_COUNT_INSTANCE(CLASS,_S_UNUSED)で、あと何個インスタンスを作成可能かが分かります。

スプライト変数1番にアプリケーション本体からアクセスするのではなく、これらの関数を介してアクセスすることで、オブジェクトの実装を将来変更した場合の影響を小さくします。
例えば、現在クラスは512個しか作成できないのですから、スプライト変数0番の上位ビットに状態を表す数を設定することで、スプライト変数1番を使わないようにすることができます。
これらの関数を使っていれば、上のような変更をした場合でも、これらの関数の実装を変更するだけで対応できます。

このほか、関数SP_NEWCLASSにクラス作成時点でのオブジェクトの状態を設定するためのコードを追加しました。
赤が追加部分です。
クラスオブジェクトは使用中、インスタンスオブジェクトは全て未使用に設定します。
DEF SP_NEWCLASS(CNAME,N_INSTANCES)
VAR CLASS,OBJ
CLASS=__MAXOBJ
OBJ=SP_SETCLASS(CLASS,CLASS)
SP_SETSTATE CLASS, _S_ACTIVE
SPHIDE CLASS

INC __MAXOBJ
FOR I=1 TO N_INSTANCES
OBJ=SP_SETCLASS(__MAXOBJ,CLASS)
SP_SETSTATE OBJ, _S_UNUSED
SPHIDE OBJ

INC __MAXOBJ
NEXT
__CNAME$[CLASS]=CNAME
IF N_INSTANCES > 0 THEN
__I1ST[CLASS]=CLASS+1
__ILAST[CLASS]=CLASS+N_INSTANCES
ELSE
__I1ST[CLASS]=_S_NULL
__ILAST[CLASS]=_S_NULL
ENDIF
RETURN CLASS
END

posted by boochow at 11:51| Comment(3) | プチコン講座 | このブログの読者になる | 更新情報をチェックする
人気記事