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

で座標を配列の末尾に追加しています。

プチコン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)まで描けました。

プチコン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は正負が逆になります。

今回はこれで終わりです。
角度での入力は、単純にパッドやタッチパネルの代わりに使うよりも、物理的な動きをシミュレートするときなどに使うと良さそうです。