2015年01月18日

プチコン3号 17日目 三角関数でスプライトを動かす&星拾いゲーム完成!

pc17-1.jpg

前回のプログラムに星を降らせる部分と星を拾う部分を追加して、ゲームは完成です。
星は、画面内に妖精を飛ばせて、その妖精から落とさせます。
プレイヤーが星を拾ったら、次の星を降らせるようにします。
弾む円は最初は数を少なくして、プレイヤーが星を取るたびに円を1つずつ増やしていきます。

また、ゲームの終了処理も少し追加します。
迷路探検ゲームのときはゴールすればそのまま終了にしていましたが、今回はプレイヤーがやられた後、円の弾む動きを続行し、画面外へすべての円が出て行くのを待ってゲーム終了にしてみました。

星の動きのアニメーションは、15日目で作成した「落ちた後2回弾んで止まる」という動きを使います。
また、星を落とす妖精はスプライトで表示します。

星の動きのアニメーションは、開始地点のY座標が50のときに画面下端まで落ちるように設計しています。
従って、妖精の動きは下図のように、画面上端から50ピクセルの位置で、左右に往復するようにします。
また、落とした星が画面外に出ないように、星のアニメーション開始のX座標は画面中央100ピクセルの範囲とします。

妖精の移動範囲はこれよりも広い幅300ピクセルの範囲としました。
これで、プレイヤーが星を取ってから、次の星が降ってくるまでのタイミングに変化が生じます。
pc17-2.jpg

妖精が左右に行ったり来たりする動きは、振り子の動きのように、端へ近づくほどスピードを落とすと雰囲気が出ます。
そのような動きをさせるために、今回は三角関数の一つであるサイン関数を使います。

サイン関数は角度をパラメータとする関数で、その値は下のグラフのように-1〜1の範囲を往復します。
これを妖精の動きのX座標に使えば、振り子が往復するような動きをさせることができます。
なお、横軸の単位は円周率πです。グラフの右端はθ=2ですが、これはθ=2πと読み替えてください。
θが0、π、2πのときsin(θ)=0、θがπ/2のときsin(θ)=1、θが3π/2のときsin(θ)=-1となります。
pc17-3.jpg


以上の処理を行うプログラムは以下のようになります。(今回はプログラム全体は大きいので、一部分だけ掲載します。)
赤字の箇所が妖精の処理、青字が星の処理です。
ACLS
_MAN=0:_FRY=1:_STAR=2
GPRIO 1
'PLAYER
MANX=200
SPSET _MAN,1128
SPCOL _MAN,12,16
SPANIM _MAN,"I",9,1128,9,1129,9,1130,9,1131,0
'FAIRY
FRYT=0
SPSET _FRY,900
SPHOME _FRY,8,8
SPSCALE _FRY,1.5,1.5
SPANIM _FRY,"I",15,900,15,901,0
'STAR
SPSET _STAR,226
SPCOL _STAR,16,16
SPANIM _STAR,"C",4,&HFFFFFFFF,4,&HFF808080,0


NOSTAR=TRUE
SCORE=0
BGMPLAY 39
N=1 'NUMBER OF BALLS-1
HITX=6:HITY=7 'PLAYER HIT TEST AREA
HITW=16-HITX*2:HITH=16-HITY
DIM PXL[HITW*HITH]
@LOOP
GSAVE MANX+HITX,224+HITY,HITW,HITH,PXL,1
HIT=0
FOR I=O TO HITW*HITH-1
HIT=HIT OR PXL[I]
NEXT
IF HIT!=0 THEN GOTO @EXIT

STICK OUT SX,SY
MANX=MANX+SX*4
MANX=MIN(384,MAX(0,MANX))
SPOFS _MAN,MANX,224,0

FRYT=FRYT+.02
FRYX=200+SIN(FRYT)*150
IF FRYT > 2*PI() THEN FRYT=0
SPOFS _FRY,FRYX,50,-1

IF NOSTAR && 150 < FRYX && FRYX < 250 THEN
NOSTAR=FALSE
SPOFS _STAR,FRYX,50,0
IF PI()/2 < FRYT && FRYT < 3*PI()/2 THEN
SPANIM _STAR,"XY+",LFALL,1
ELSE
SPANIM _STAR,"XY+",RFALL,1
ENDIF
ENDIF

IF SPHITSP(_MAN,_STAR) THEN
N=MIN(NMAX-1,N+1) 'INCREASE BALLS
SPOFS _STAR,-100,-100 'HIDE THE STAR
NOSTAR=TRUE
BEEP 12
SCORE=SCORE+1
LOCATE 0,0
COLOR 7
PRINT "★"*SCORE
COLOR 15
ENDIF


最初に、プレイヤー・妖精・星のスプライト番号を変数に代入しています。
これはプログラムを読みやすくするためで、BASICには名前つき定数が無いので変数を使っています。
_MAN=0:_FRY=1:_STAR=2

妖精の位置を表す変数はFRYTです。
これは座標ではなく、サイン関数に渡すための角度です。
SPHOMEでスプライトの原点をスプライトの中心とし(通常は原点は左上)、スプライトのサイズを1.5倍にしています。

ループの中で変数FRYTを0.02ずつ大きくし、2πを超えたら0にリセットしています。
FRYT=FRYT+.02
FRYX=200+SIN(FRYT)*150
IF FRYT > 2*PI() THEN FRYT=0
X座標FRYXは、三角関数でX=200からプラスマイナス150ピクセルの範囲で変化させています。

星のスプライトは、SPCOLで当たり判定を有効にしています。プレイヤーのスプライトもSPCOLを追加しています。
そして、星が画面内に表示されている状態かどうかを表す変数NOSTARを用意します。
星の出現処理は、NOSTARがTRUEのときのみ行います。
星を出現させたらNOSTARをFALSE、プレイヤーが星を取ったらNOSTARをTRUEにします。

星の出現処理は、ループの中の
IF NOSTAR && 150 < FRYX && FRYX < 250 THEN
という条件が満たされたとき実行します。
出現地点は妖精の現在位置です。
また、アニメーションは「右に落ちる動作(RFALL)」「左に落ちる動作(LFALL)」の2種類を用意し、妖精の移動方向に合わせて選択しています。

プレイヤーが星を取ったかどうかは
SPHITSP(_MAN,_STAR)
で判定します。
衝突していれば円を増やし、次の星を出現可能にし、スコア表示を更新します。


円とプレイヤーが衝突するとラベル@EXITへジャンプします。
それ以降がゲーム終了処理です。
円が左右ではね返る処理を無くし、すべての円が画面から出てくるまで待ってからプログラムを終了します。
プログラムはこちらです。
@EXIT
SPCHR _MAN,1141
BEEP 13
REPEAT
DONE=TRUE

GCLS
FOR I=0 TO N
GCIRCLE X[I],Y[I],R[I],C[I]
GPAINT PAINTX(X[I],R[I]),Y[I],C[I],C[I]
V[I]=V[I]+G
IF Y[I]+V[I] > SCH-R[I] THEN
V[I]=V0[I]
Y[I]=SCH-R[I]
ENDIF
INC Y[I],V[I]
INC X[I],DX[I]
IF -R[I] < X[I] && X[I] < SCW+R[I] THEN
DONE=FALSE
ENDIF

NEXT
VSYNC 1
UNTIL DONE

DEF PAINTX(X,R)
IF X < 0 && (X+R) > 0 THEN RETURN 0
IF X > SCW && (X-R) < SCW THEN RETURN SCW
RETURN X
END


BGMSTOP
ACLS

REPEAT文の内側の処理は、メインのループの中での処理とほとんど同じですが、一部だけ違っています。

まず、円が画面から半分以上出た時のPAINTの処理です。
メインループでは、PAINTの開始点を円の中心にしています。
下図左のような状態のときはメインループと同様の処理で問題ありませんが、下図右のようになった場合、円の中心が画面の外にあるため、PAINTが実行されません。
pc17-4.jpg

このような場合のPAINT開始点を求める関数PAINTXを定義して使っています。
Y座標は円の中心のY座標ですので、必要なのはX座標だけです。
また、X座標は円が画面左から画面外へ出て行く場合はスクリーン左端(0)、画面右から画面外へ出て行く場合はスクリーン右端(399)となります。

次に、全ての円が画面外へ出たかどうかの判定です。
これはFOR〜NEXTループ内で
IF -R[I] < X[I] && X[I] < SCW+R[I] THEN
のところで判断しています。
この条件を満たす円は、スクリーンに残っていると判断されます。
FOR〜NEXTループに入る前に変数DONEにTRUEを代入し、上記の条件を満たす円があったときはDONEにFALSEを代入します。
すべての円の処理が終わったとき、DONEがTRUEであれば、全ての円が画面外に出たことになります。

今回のプログラムはMiiverseで公開しています。
公開キーは【KKQEA1】です。
単純なゲームですが、BGMや効果音もつけて、それなりに遊べるゲームになったと思います。
posted by boochow at 23:07| Comment(0) | プチコン講座 | このブログの読者になる | 更新情報をチェックする

プチコン3号 16日目 グラフィックスの当たり判定

今回と次回で、14日目の「グラフィックスで描いた弾む円」と15日目の「弾むスプライト」を組み合わせたゲームを作ってみます。
プレイヤーが円を避けながら左右に走って、降ってくる星を集めるゲームです。
円にぶつかったらゲームオーバーです。
pc16-1.jpg

これには、グラフィックスで描いている円と、スプライトで動かしているプレーヤーキャラクタとの衝突判定が必要になりますので、今回はその部分を作りました。

スプライト同士の衝突はSPHITSP命令がありますが、グラフィックスでの衝突判定は、以下の2つの方法が考えられます。

(1)幾何学的判定
円同士の衝突の場合は、
2つの円の中心間の距離 < 2つの円の半径の和
となっていれば2つの円が重なっていることが言えますので、これで判定できます。
この場合は、プレイヤーキャラクタの衝突判定領域を円で近似する必要があります。
円と四角形の衝突も、計算が面倒そうですが、幾何学的な判定は可能です。

(2)画像による判定
これは実際に描かれたグラフィックスから、衝突判定したい領域の画像を切り出し、その画像に対象の画像が含まれているかどうかで判定する方法です。
直感的には判りやすいですが、実際には対象の画像かどうかは「色」で判断するしかありません。従ってグラフィックスに複雑な画像が描かれている場合は、誤判定が起こりやすくなります。
pc16-2.jpg

一般的には、画像による判定は、画面が複雑になればなるほど困難になりますし、ハードウェアに依存する度合いも高くなるので、可能なら幾何学的に判定するほうが良いでしょう。

しかし今回は、画像による判定を行いました。
グラフィックスには円の画像しか描かれていませんので、単純に領域内のピクセルが「黒か黒でないか」だけで衝突の有無を判定できるからです。

SmileBASICには、このような目的にGSPOIT命令やGSAVE命令が使えます。
GSPOIT命令は、指定した座標のピクセルの色を読み取ります。
GSAVE命令は、指定した矩形の領域の画像を配列にコピーします。
GSAVE命令では1ピクセルが配列の1要素に入りますので、たとえば16×16ピクセルの領域の画像は、サイズが256の配列にコピーされます。
画像を配列に変換することで、より複雑な処理をすることができます。

今回は、ある領域を指定して衝突判定をしますので、GSAVE命令を使います。
調べたい領域のピクセルを配列に変換し、次に配列の要素の中に0(黒のピクセル)でないものがあるかどうかを調べます。

それではプログラムです。
14日目のプログラムとかなり重複がありますので、追加・修正部分に全て色をつけました。
NMAX=9:G=0.1:SCW=400:SCH=240
DIM X[NMAX+1],Y[NMAX+1],R[NMAX+1]
DIM V[NMAX+1],V0[NMAX+1],DX[NMAX+1],C[NMAX+1]
FOR I=0 TO NMAX
R[I]=10+RND(40)
X[I]=R[I]+RND(SCW-2*R[I]-20)+10
V0[I]=-(4+RND(20)/10)
Y[I]=SCH-R[I]-16
V[I]=V0[I]
DX[I]=(RND(2)*2-1)*(RND(3)+1)
C[I]=RND(&HFFFFFF) OR &HFF1F1F1F
NEXT

ACLS
GPRIO 1

MANX=200
SPSET 0,1128
SPCOL 0,12,16
SPANIM 0,"I",9,1128,9,1129,9,1130,9,1131,0

N=5
HITX=6:HITY=7
HITW=16-HITX*2:HITH=16-HITY
DIM PXL[HITW*HITH]
@LOOP
GSAVE MANX+HITX,224+HITY,HITW,HITH,PXL,1
HIT=0
FOR I=O TO HITW*HITH-1
HIT=HIT OR PXL[I]
NEXT
IF HIT!=0 THEN GOTO @EXIT

STICK OUT SX,SY
MANX=MANX+SX*4
MANX=MIN(384,MAX(0,MANX))
SPOFS 0,MANX,224,0

GCLS
FOR I=0 TO N
GCIRCLE X[I],Y[I],R[I],C[I]
GPAINT X[I],Y[I],C[I],C[I]
V[I]=V[I]+G
IF Y[I]+V[I] > SCH-R[I] THEN
V[I]=V0[I]
Y[I]=SCH-R[I]
ENDIF
INC Y[I],V[I]
INC X[I],DX[I]
IF X[I] > SCW-R[I] || X[I] < 1+R[I] THEN
DX[I]=-DX[I]
ENDIF
NEXT
VSYNC 1
GOTO @LOOP
@EXIT

ちょっと長いですが、今回のメインは青い部分です。

赤字の部分は変数名の変更や細かい修正です。
たとえば、円の中心座標は、スタート時にいきなりスプライトと重なってしまわないように、16ピクセル上に動かしています。
「GPRIO 1」はグラフィックス画面のZ軸の指定です。スクリーン(Z=0)のすぐ後にしています。
また、円が弾む際の画面下端との接触処理もより厳密にしています。

紫の部分は、プレイヤーのキャラクタを表示するスプライトの初期設定と、スティックでの操作です。
ここは3日目に説明しましたので省略します。
スプライトがグラフィックス画面のすぐ手前に表示されるよう、SPOFSでZ値を0に指定しています。

グラフィックスの当たり判定関連の部分は、青字にしています。
ループの外側は初期設定です。
HITX=6:HITY=7
HITW=16-HITX*2:HITH=16-HITY
それぞれの変数の意味は下図の通りです。
スプライトの16×16ピクセルの領域のうち、青枠で囲った部分だけを当たり判定の対象にします。
pc16-4.jpg

青枠の部分が狭いほど、当たり判定が甘く(当りにくく)なります。
画面下端付近では円の落下速度が速いため、やや甘い当たり判定にしてあります。

その次のDIM文で宣言している配列PXLを、画像を読み込むために使います。
実際の読み込みはループの開始直後、
GSAVE MANX+HITX,224+HITY,HITW,HITH,PXL,1
の部分です。
パラメータは、矩形領域の左上のXY座標、矩形領域の幅、高さ、読込先の配列、色変換指定です。
色変換指定は今回のプログラムでは、1(変換あり)でも0(変換なし)でも動作には違いはありません。

配列に読み込んだデータはFOR〜NEXTループで全部(といっても4×9=36個ですが)0かどうかチェックします。
今回は、ループでデータ全てのORを求め、その結果が0かどうかをループの後で判定しています。
(OR演算はビット演算で、少なくとも一方が1なら結果は1になります。0 OR 0=0、1 OR 0 = 0 OR 1 = 1 OR 1 = 1です。)

円と衝突したと判定した場合はプログラム末尾へジャンプしていますので、こうなります。
pc16-5.jpg


これで当たり判定ができたので、次回は星を降らせる処理や効果音などを追加してゲームを完成させたいと思います。
posted by boochow at 00:14| Comment(0) | プチコン講座 | このブログの読者になる | 更新情報をチェックする

2015年01月17日

プチコン3号 15日目 スプライトを決まった経路で移動させる

SmileBASICでは、ゲーム作成に便利なスプライト用命令をいろいろ持っていますが、その中でも特徴的なのが9日目の記事でも使ったSPANIM命令です。
SPANIM命令では「座標変更」を指定することもでき、これを使うと、あらかじめ決めたコースの上をスプライトを移動させていくことができます。

今回は前回の「落下する動き」「弾む動き」をスプライトにさせるプログラムを作ってみました。
前回は円が画面の中で弾む動きをさせるためにY座標をプログラムで計算させていましたが、今回は
(1)落下の座標計算をより簡略化し、
(2)計算結果を配列に記録し、SPANIM命令で再生する
ようにしてみます。

落下する動きの計算を簡単にすると、以下のようなダイレクトモードでも動かせる程度の短いプログラムになります。
これを入力してみると、スプライトが画面上端から画面下端まで落ちる動きをします。
pc15-1.jpg

SPSET 0,226
Y=0:FOR I=1 TO 9:FOR J=1 TO 5:Y=Y+I:SPOFS 0,200,Y:VSYNC 1:NEXT:NEXT
ここで行っている処理は、スプライトを下方向へ
「最初は1ピクセルずつ、5回動かす」「次は2ピクセルずつ、5回動かす」…「9ピクセルずつ、5回動かす」
というものです。これだけでも落下するような表現になります。

前回、落下する動作とは
「移動速度が時間経過に従って直線的に増えていく」
ような動作であると説明しました。
そして、ループの中で
V[I]=V[I]+G
INC Y[I],V[I]
という計算により、VをGずつ増やし、そのVをYに加算することで落下動作を計算していました。
pc14-5.jpg

先ほど示したプログラムは、「5回ごと」に「移動幅が1ピクセル」増えますので、「G=1/5ピクセル」としたのと同じことになります。

この「移動幅」と「回数」を変化させれば、「落下速度・落下する距離」が異なるいろいろな落下を表現できます。

上で示したプログラムでは、移動幅1ピクセル〜9ピクセル、同じピクセル数での移動回数5回です。
従ってトータルでの移動距離は
(1+2+3+4+5+6+7+8+9)×5 = 225

となります。
これは実は「画面の上から下まで落ちる動き」をさせる場合の移動距離
画面縦240ピクセル−スプライト縦16ピクセル=224ピクセル

にほぼ等しくなるようしたのです。

移動回数を5回ではなく4回にすると、より早く落下する動作になります。
この場合は「G=1/4ピクセル」としたことになります。
4回ずつの移動で、例えば移動幅を1ピクセル〜10ピクセルに変化させた場合、移動距離は
(1+2+3+4+5+6+7+8+9+10)×4 = 220

となります。

下図に、移動幅と移動回数から移動距離を求める早見図を載せておきます。
pc15-2.jpg


移動幅をマイナスから始めると、スプライトはいったん投げ上げられてから戻ってくる、すなわちジャンプするような動きになります。
例えば下のプログラムのように、-9ピクセル〜9ピクセルとすると、画面いっぱいの大ジャンプになります。
移動幅の初期値と終了値をもっと小さくして、例えば-5ピクセル〜5ピクセルとすると、より小さなジャンプになります。
SPSET 0,226
Y=224:FOR I=-9 TO 9:FOR J=1 TO 5:Y=Y+I:SPOFS 0,200,Y:VSYNC 1:NEXT:NEXT


それでは、ループの中でSPOFS命令で毎回座標を指定するのではなく、先に一連の座標を配列に記録してからSPANIM命令で再生してスプライトをアニメーションさせてみます。
この方法のメリットは、座標をあらかじめ計算済みなので、動かすたびに計算し直す必要がないことです。

SPANIMの座標変更機能は、
(1)座標の列をパラメータで直接指定する
(2)座標の列を配列で渡す
(3)座標の列を格納したDATA命令の直前のラベルを指定する
という3つの方法がありますが、今回は(2)を使います。

使い方は
SPANIM 管理番号,"XY",配列,ループ
です。
または
SPANIM 管理番号,"XY+",配列,ループ
です。
"XY"で指定した場合は、スプライトは指定された座標へ移動しますが、"XY+"で指定した場合は、スプライトは「SPANIM命令が実行された時点の座標+指定された座標」へ移動します。
つまり、SPOFSで移動の出発点を指定すると、そこから出発してスプライトを動かすことができます。
配列は1次元配列で、
フレーム数,X座標,Y座標
の3つを連続して格納します。
なお現在のプチコン3号では、配列の中にこの3つを33組以上格納するとエラーになるようです。

さらに、SPANIMには「補間機能」があり、これを使うと少ないパラメータで長時間の動きを表現できます。

補間機能は、1フレームごとに「移動距離/フレーム数」だけ移動させることができる機能です。
たとえば、今回の落下の動きは「最初は1ピクセルずつ、5回動かす」で始まりますが、これは
「5ピクセルを5フレームで動かす」という指定の仕方ができます。

下図を見てください。左側が1フレームごとの動作を指定した場合、右側が補間機能を指定した場合です。
補間機能を使う場合は、フレーム数を負の値で指定します。
左側は時間軸に沿って、1フレームごとにY座標を1ずつ5回増やしています。
右側では、グレーになっている部分の指定は不要で、フレーム数が「-5」Y座標が「5」という指定をしています。
これで左側と同じ指定をしたことになります。
このあとの5フレームは2ピクセルずつ動いて、Y座標が15まで増えますので、この次の指定はフレーム数が「-5」Y座標が「15」という指定をすればよいのです。
pc15-3.jpg


今回は「落下運動の座標を配列に入れて、SPANIMに渡し、スプライトに落下運動をさせる」プログラムを作りました。
リストはこちらです。
pc15-4.jpg
DIM A[0]
SP_FALL 0,0,1,-4,9,-5,A OUT X,Y
SP_FALL X,Y,1,-4,4,-5,A OUT X,Y
SP_FALL X,Y,1,-2,2,-5,A OUT X,Y
PUSH A,-5:PUSH A,X:PUSH A,Y
SPSET 0,226

@LOOP
SPOFS 0,160,50
SPANIM 0,"XY+",A,1
SPANIM 0,"C",4,&HFFFFFFFF,4,&HFF808080,0
WHILE SPCHK(0) AND 1 != 0:WAIT 1:WEND
GOTO @LOOP

DEF SP_FALL X,Y,DX,VMIN,VMAX,VTIME,ANIM OUT NEWX,NEWY
K=ABS(VTIME)
FOR I=VMIN TO VMAX
PUSH ANIM,VTIME
PUSH ANIM,X
PUSH ANIM,Y
X=X+DX*K
Y=Y+I*K
NEXT
NEWX=X
NEWY=Y
END

このプログラムを動かすと、下図のように星のスプライトが上から落ちて2回弾んで止まるという動作を繰り返します。
pc15-5.jpg

プログラムの中にループがありますが、このループはアニメーションを繰り返し再生させているだけです。
落下の動きそのものは
SPOFS 0,160,50
SPANIM 0,"XY+",A,1
だけで実現しています。

まずSPOFSで移動の開始ポイントを指定しています。
次にSPANIMでスプライトの移動開始です。配列Aに座標のデータが入っています。
ループ指定は1ですので、1回移動したら終了です。

その次の
SPANIM 0,"C",4,&HFFFFFFFF,4,&HFF808080,0
は、スプライトの色を4フレームごとに明るくしたり暗くしたりして、チカチカ瞬くように見せています。

その次の
WHILE SPCHK(0) AND 1 != 0:WAIT 1:WEND
は、スプライトの移動が終わるまでループして待つ処理です。
今回は詳しく説明しませんが、SPCHKはSPANIMで指定した動作の状態を調べる命令です。
パラメータはスプライトの管理番号で、結果はSPANIMで指定できる様々な動作について「1(実行中)」「0(完了)」がビットパターンで得られます。
「AND 1」で座標変更動作の状態を調べています。

SPANIMに渡す座標の計算は、15行目からの
DEF SP_FALL X,Y,DX,VMIN,VMAX,VTIME,ANIM OUT NEWX,NEWY
の中で行っています。
各パラメータの意味は、

・X,Y:座標の初期値
・DX:X軸方向の移動量
・VMIN、VMAX:Y軸方向の移動量の最小値と最大値
・VTIME:同じ移動量で移動するフレーム数
・ANIM:座標を格納する配列
・NEWX、NEWY:最後の座標(ANIMの中には格納しない)

となっています。
一番最初に挙げた例「最初は1ピクセルずつ、5回動かす」「次は2ピクセルずつ、5回動かす」…「9ピクセルずつ、5回動かす」の場合は、
・VMIN=1
・VMAX=9
・VTIME=5
に相当します。

計算結果はPUSHで配列の末尾に追加しています。
PUSH命令は12日目にスタックの説明のところで解説しましたが、今回は単に配列の最後にデータを追加するために使っているだけです。

最後の座標をANIMに入れずにNEWX、NEWYで返しているのは、SP_FALLを繰り返し呼んだ場合に、動きをスムーズにつなげるためです。
SP_FALLを呼び出している2行目から4行目までは、
SP_FALL 0,0,1,-4,9,-5,A OUT X,Y
SP_FALL X,Y,1,-4,4,-5,A OUT X,Y
SP_FALL X,Y,1,-2,2,-5,A OUT X,Y
となっていますが、1つ目の呼び出しが最初の落下動作、2つ目と3つ目の呼び出しが弾む動作です。
直前の呼び出しの最後の座標X,Yを、直後の呼び出しの際に座標の初期値として渡しています。
そして、最後に
PUSH A,-5:PUSH A,X:PUSH A,Y
で座標を配列の末尾に追加しています。
posted by boochow at 13:28| Comment(0) | プチコン講座 | このブログの読者になる | 更新情報をチェックする

2015年01月12日

プチコン3号 14日目 グラフィックスとアニメーション

プチコン3号にはスプライトやBG以外に、画面に直接絵を描く命令もあります。
描ける図形は
・矩形(線幅1ピクセル、塗りつぶし無し)
・矩形(塗りつぶし)
・直線(線幅1ピクセル)
・真円(線幅1ピクセル、塗りつぶし無し)
・点(1ピクセル)
の5種類で、さらに
・領域塗りつぶし(領域の境界の色指定可)
も可能です。
多角形や楕円、太い線などは描けません。
命令は、ダイレクトモードから簡単に試すことができます。
pc14-1.jpg


これらの命令の実行速度は結構速いので、スクリーンを消しては絵を描き、を繰り返すと、描いた絵を動かすことができます。
そこで、ボールを床に落とすと弾むような感じで、円がスクリーンの中を弾んで動くアニメーションを作ってみました。
pc14-2.jpg

11個のボールがポンポン弾みながら画面の中を動きます。

プログラムリストはこちらです。
pc14-3.jpg

N=10:G=0.2:SCW=400:SCH=240
DIM X[N+1],Y[N+1],R[N+1]
DIM V[N+1],V0[N+1],DX[N+1],C[N+1]

FOR I=0 TO N
R[I]=10+RND(40)
X[I]=R[I]+RND(SCW-2*R[I])
V0[I]=-(5+RND(30)/10)
Y[I]=SCH-R[I]
V[I]=V0[I]
DX[I]=(RND(2)*2-1)*(RND(3)+1)
C[I]=RND(&HFFFFFF) OR &HFF404040
NEXT
CLS
@LOOP
GCLS
IF BUTTON(1) AND 16 THEN END
FOR I=0 TO N
GCIRCLE X[I],Y[I],R[I],C[I]
GPAINT X[I],Y[I],C[I],C[I]

V[I]=V[I]+G
IF Y[I] > SCH-R[I] THEN V[I]=V0[I]

INC Y[I],V[I]
INC X[I],DX[I]
IF X[I] > SCW-R[I] || X[I] < 1+R[I] THEN
DX[I]=-DX[I]
ENDIF
NEXT
VSYNC 1
GOTO @LOOP

今回のプログラムは、比較的簡単な内容です。
最初に定数の宣言です。
N=10:G=0.2:SCW=400:SCH=240

Nは円の個数−1、Gは加速度、SCWはスクリーンの横方向のピクセル数(Width)SCHはスクリーンの縦方向のピクセル数(Height)です。
次に、円を表すために必要なパラメータを、円の個数分、配列で宣言しています。
各変数の意味は、下図を見てください。

初期化時に、各円のY座標は画面下部に接する位置(床に接触した状態)とし、VYの初期値V0を上方向にランダムに与えています。
このV0が、床から跳ね返って上に向かう瞬間の初速になります。

X軸方向の移動量は、-1, 1のいずれか(RND(2)*2-1)をランダムに決め、さらにそれを1〜3倍(RND(3)+1)しています。
pc14-4.jpg

ループの中では、まずGCLSで画面全体を消去します。
次にボタンの状態をチェックして、Aボタンが押されていればプログラム終了です。

そのあと、各ボールの処理をするループになります。
まずGCIRCLEで円を描き、GPAINTでその中を塗りつぶします。
境界色をGCIRCLEの色指定と同じ色にしていますので、他の描画済みの円があっても上書きします。

次にY座標の計算です。
まず、Y方向の速度VYに重力を表す加速度Gを加算します。
落下速度が時間につれて大きくなっていきますので、円の動きは下方向に向かって加速していきます。
この様子は下図を見てください。
pc14-5.jpg
そのあと、現在のY座標をチェックし、円の下端がスクリーンの下端を超えていたら、VYを初期値であるV0にリセットします。
V0は画面上方向に向かう初速ですので、これで円が床から上に跳ね上がります。

この部分ですが、物理法則から考えると
VY=-VY

としても良さそうに思えます。
しかし、今回は速度を整数ではなく浮動小数で表しているため、演算の際の計算誤差の問題があって、跳ね返るたびにだんだんVYの絶対値が小さくなっていってしまいます。

その後はX座標の計算ですが、こちらは座標が整数値ですので、画面の左右端でDXの符号を反転させるだけです。

今回のプログラムですが、Nの値をいろいろ変えてみると、18個(N=17)までは画面がちらつかずに描画することができました。
塗りつぶした円を描く命令が無いので、GCIRCLEとGPAINTを組み合わせて描きましたが、それでもかなり速いですね。

なお、プログラムの中身は大きく変えずに命令の実行順序を変えれば、さらにちらつきを少なくすることができます。
ループの中のFOR〜NEXT文で、円1つずつ、描画と座標の計算をしていますが、ここを2つのFOR〜NEXTループに分解して、まず全部の円を描画し、その後で全部の円の座標の計算をするように変更すると、21個(N=20)まで描けました。
posted by boochow at 23:43| Comment(0) | プチコン講座 | このブログの読者になる | 更新情報をチェックする

2015年01月11日

プチコン3号 13日目 本体を傾けてキャラクタを動かす

3DSは移動や傾きを検出するモーションセンサがついています。今回はこれを使って、9日目のプログラムを傾きでコントロールするように変更してみました。

3DSには加速度センサが搭載されていて、平行移動(前後上下左右)の「加速度」、および回転(ピッチ、ロール、ヨー)の「速度」を計測できます。
SmileBASICではその値を読み出すことができるほか、回転については、時間軸に沿って速度を積算することで、トータルの回転角も得られるようになっています。
今回は、このトータルの回転角を使って、下画面が水平のときはキャラクタは静止し、画面を傾けたときは傾いた方向へ移動するようなプログラムを作ってみました。

座標がX、Y、Zの3つの軸で表されるのと同様、角度も以下の3つの値で表されます。

(1)X軸の周りでの回転(ピッチ)
(2)Y軸の周りでの回転(ロール)
(3)Z軸の周りでの回転(ヨー)

いずれも、原点から座標軸の正の方向を見て左回りで角度を測ります。
pc13-1.jpg

SmileBASICで値を読み出すには、
GYROA OUT P,R,Y
とします。
P,R,Yにそれぞれピッチ、ロール、ヨーの値が入ります。

このとき、角度の値は「度」ではなく「ラジアン」という単位で与えられます。
ラジアンで計測すると、180度がπ(パイ)ラジアンになります。
πは円周率のπ、すなわち3.14159265…です。
逆方向へ傾けた場合は負の値になりますので、角度の値の範囲は-πからπまでです。

なおGYROAコマンドを使うには、あらかじめ
XON MOTION
でモーションセンサをオンにしておかなければなりません。

また、先に書いたようにGYROAで読み出すトータルの回転角は、実際には回転の速度を積算したものです。
そのため、長期間使っていると誤差が出てきます。
水平位置に戻しても、水平ではなく角度がついているように認識される、ということが起こります。
これをリセットするには
GYROSYNC
命令を使います。
この命令を実行すると、その時点で3つの角度が全てゼロにリセットされます。

それではプログラムリストです。
実行すると下画面にキャラクタが表示され、画面を傾けるとトコトコと歩きます。
表示関連は、9日目の記事で説明しましたので今回は説明は省略します。
pc13-2.jpg

XSCREEN 2,255,2
DISPLAY 1
GX=184:GY=104:GC=2904
C=GC+4:C_OLD=GC
SPSET 0,C
SPSCALE 0,2,2
XON MOTION
GYROSYNC
@LOOP
SPOFS 0,GX,GY
IF C != C_OLD THEN
SPANIM 0,"I",15,C+1,15,C+2,15,C+3,15,C,0
C_OLD = C
ENDIF
IF BUTTON(1) AND 16 THEN GYROSYNC
GYROA OUT P,R,Y
DX=0:DY=0
IF ABS(P) > 0.05 THEN DY=SGN(-P)
IF ABS(R) > 0.05 THEN DX=SGN(R)
GX=MAX(MIN(GX+DX,384),0)
GY=MAX(MIN(GY+DY,224),0)
C=(3-(DY>=0)*(DX+2))*4+GC
IF DY != 0 || DX != 0 THEN
SPSTART 255
ELSE
SPSTOP 255
ENDIF
VSYNC 1
GOTO @LOOP

初期設定として
XON MOTION
GYROSYNC
を実行しています。
そのあとループの中で
IF BUTTON(1) AND 16 THEN GYROSYNC

としていますので、Aボタンを押せばいつでも角度の誤差をリセットできます。

次にGYROAで角度を読み出していますが、そのあと
IF ABS(P) > 0.05
というように値の絶対値をチェックしています。
これは、水平からある程度傾いたときに初めて「傾いた」と認識させるためです。
0.05ラジアン以上傾いたときのみ、DXとDYに値を設定しています。
ちなみに0.05ラジアンは、180*(0.05/3.14)=2.87度くらいです。

また、ピッチについては、上の図からも判るとおり、画面を手前から向こうへ傾けたときに正の値になりますが、このときキャラクタを画面上に向かって(Y座標が小さくなる方向へ)歩かせるので、ピッチの値とDYは正負が逆になります。

今回はこれで終わりです。
角度での入力は、単純にパッドやタッチパネルの代わりに使うよりも、物理的な動きをシミュレートするときなどに使うと良さそうです。
posted by boochow at 01:47| Comment(0) | プチコン講座 | このブログの読者になる | 更新情報をチェックする
人気記事