プチコン講座

第2回 スプライトとBG画面を使おう

【プチコン/mkII 両対応】

 第1回ではプチコンにおいてコンソールとグラフィックの使い方を私の自作ゲームを例に出して書いていきました。コンソールとグラフィックだけでもさまざまなジャンルのゲームを作ることができるのですが、スプライトやBG画面を使いこなせるようになればさらにさまざまなゲームを作ることができるようになります。
 スプライトには「キャラの移動が1ドット単位で簡単に行える」「透過処理によって重ね合わせも可能」「キャラの拡大、縮小、回転が可能」というメリットがあり、BG画面には縦横のスクロールが1ドット単位で簡単かつ高速に行えるというメリットがあります。コンソールやグラフィックで描かれたキャラを移動させるには表示の後には消去を行う必要があるのですが、スプライトの場合はその必要はないのです。
 それを使って第1回で書いた「プチコン50m走」を大幅に改良します。スプライトを使って自分のキャラを表示、BG画面を使って背景スクロールするような「プチコン100m走」を作ろうと思います。

 まずは、スプライトやBGというのがどんなものかについて書いていきます。
 スプライトやBG画面に使用するキャラは自分で描くことで自分のオリジナルのキャラを使ったゲームを作ることができます。そのためにはプチコン内蔵のキャラクタ作成ツール「CHRED」やBGスクリーン作成ツール「SCRED」などを使うといいでしょう。また、内蔵のツールよりさらに高性能なツールや使い勝手のよいツールもユーザーの手によって作られていますのでそれを積極的に活用していくのもいいかもしれません。ユーザーの作ったプチコンプログラムは公式サイトの他、プチコンまとめWiki、個人サイト、ブログやtwitter(ハッシュタグ#petitcom、mkIIのQRコード付きのものはハッシュタグ#ptcmqr)などを検索するといいでしょう。
 こういったツールを使いリソースファイルとしてセーブしておき、自分のゲームで使用する際にロードを行えば自作キャラを使ったゲームも自在に作れるというわけです。

 とはいえ、mkIIではQRコードによる読み込みに対応しているためリソースファイルもネット上で発表できるようになったものの初代用のはネットで不特定多数に向けてリソースファイルを公開することはできません。しかし、プチコンには幸いにして大量のプリセットデータが入っています。
http://smileboom.com/special/petitcom/manual/page_33.html
http://smileboom.com/special/petitcom/manual/page_34.html

 このプリセットデータを使う場合はキャクタデータは別途必要ないし、プチコンを所持している人が誰でも同じように使えるというメリットがあります。mkIIにおいては公開時のQRコードの枚数を減らせるというメリットがあります。
 では、実際にスプライトの使用してみましょう。
 何もせずに使えるコンソールと違ってスプライトを使用するためには事前の準備が必要です。
 まずは、VISIBLEの第5引数にてスプライトを表示する設定にしておく必要があります。それとSPPAGE 0によって「上画面でスプライトを使用する」という設定にしておく必要があります。とはいえ、これはデフォでは両方とも使えるような設定になっているため直前にスプライトを表示しない設定のプログラムを使用していない場合は省略が可能です。

 スプライトを使用する上で必要不可欠なのはSPSETによる管理するスプライトの選択、設定です。ここで自作キャラではなくプリセットのキャラを使う場合は上記のプリセットされている一覧から選べばいいだけなので何も難しいことはありません。設定できる引数が6つもあるため難しく感じるかもしれませんが基本的にはキャラを管理する番号(これはプログラム制作者が0から99の間の任意の数字を付けて管理していくため順番でなくてもいいけど0〜29までしか拡大、縮小、回転には対応していないという点に注意する必要がある)、キャラ一覧に記されている番号、パレット(要するに色の組み合わせ)を設定すれば問題なく使えるため残りの3つの引数は0にしておいても構いません。
 あとは、その管理設定したキャラをSPOFSを使って指定した場所に表示するだけです。男の子のキャラ(スプライト番号64)を十字ボタンを使って左右に動かす場合は下記のようになります

スプライト左右移動サンプル(1)
CLS
GCLS
CLEAR
SPSET 0,64,0,0,0,0
@LOOP
 SPOFS 0,X,80
 B=BUTTON()
 IF B==4 THEN X=X-1
 IF B==8 THEN X=X+1
 VSYNC 1
GOTO @LOOP

 左右に動くものの少々不自然ですね。足が動いてないためまるで滑って移動しているように見えるし左に移動中は後ろ向きに滑って移動いるように見えてしまいます。

改善すべきポイント
(1) 左に移動中はキャラを左向きにして右に移動中はキャラを右向きにする
(2) 足を動かしているように見せる

 (1)を行うためにはスプライトのキャラクタを変えるSPCHR命令を使います。これによって管理番号はそのままでキャラのみを変えることができます。左に移動する場合は同じキャラで左向きの72番のキャラにしましょう。
 (2)を行うためにはスプライトのアニメーションを行うSPANIM命令を使います。プリセットデータでは4パターンの繰り返しとなっているため第2引数は4にしてアニメーションの速度は第3引数で1フレーム(1/60秒)単位で設定できるので設定できるので適度なものにするといいでしょう。ここでは1パターンあたり1/6秒(=10フレーム)にしておきます。

スプライト左右移動サンプル(2)
CLS
GCLS
CLEAR
SPSET 0,64,0,0,0,0
SPANIM 0,4,10
@LOOP
 SPOFS 0,X,80
 B=BUTTON()
 IF B==4 THEN X=X-1:SPCHR 0,72,0,0,0,0
 IF B==8 THEN X=X+1:SPCHR 0,64,0,0,0,0
 VSYNC 1
GOTO @LOOP

 このサンプルでは左向きキャラと右向きキャラを別々に使用していますが、実はSPCHRの第4引数で1を指定すれば左右反転も可能になります。したがって、これを使えば右向きキャラだけで左向きのキャラも表示可能になるのですが、初代プチコンでは拡大、縮小、回転時の基準点(詳しくは後述)はキャラ左上だったのが左右反転機能を使うと基準点が右上に変わるという問題があるため反転しているか否かで逐次表示座標を修正する必要があります(mkIIではそれが改善されているため互換性がない)。「拡大、縮小、回転を行わない」もしくはmkII専用にするのならば全く問題ありませんが、初代とmkIIの両対応にする場合はSPSETとSPCHRの反転機能は使わない方が良いでしょう。
 また、基本的に引数が省略できないSPSETと比べてSPCHRは反転機能などを使わない場合は第3引数以降は省略も可能です。

 あとボタン入力に関してですが、現在(サンプル2)は左右ボタンをそれぞれ単独で押した場合のみ有効になるような判定をしています。この場合は左右に動かすということだけが目的なので問題ないのですが、ゲームで使用する場合には例えば「[A]ボタンを押しながら左に移動」とかいう場合や斜め移動の場合(右上に移動する場合には右と上の両方を同時に押す必要がある)にはこれでは判定できません。そこで必要になるのがANDです。
 ANDといえば第1回の講座ではIF文における複数の条件式の結合に使いましたが今度はビット演算で使います。
 [A]ボタンを押している場合はBUTTON関数の値は16になるのですがこれは2進数で表すと00000010000になります。この0と1が各ボタンと1対1で対応しています。

[START]
[R]
[L]
[Y]
[X]
[B]
[A]
10進数
1024
512
256
128
64
32
16
8
4
2
1
2進数
0
0
0
0
0
0
1
0
0
0
0
[SELECT]ボタンを押した場合は2048という値が取得できますが、RUNした時点で押しておかないと中断されてしまい取得できません。
 デバッグモードや裏技に使用することはできるかもしれません。(Tipsコーナーの[SELECT]ボタンの取得を参照)


 ここで[A]と[B]を両方を押した場合には2進数で表せば00000110000になることが分かるでしょう。10進数で表すと16+32で48になります。
 複数のボタンを同時に押している場合にBUTTON関数の値から特定のボタンを押しているかどうかがこのビット演算子ANDで分かるのです。例えば、変数BにBUTTON関数の値が入っている場合は[A]ボタンを押していれば「B AND 16」の値が16になります。要するに2進数で表した場合に各桁を比較して両方に1になっている桁が1になり、そうでない桁は0になるのです。

 (基本)
     0       0       1       1
   AND 0     AND 1     AND 0     AND 1
   -----     -----     -----     -----
     0       0       0       1

  0 AND 0 = 0  0 AND 1 = 0  1 AND 0 = 0  1 AND 1 = 1

 (基本)を各桁に適用すれば・・・

   00101010001 上[A][X][L]ボタンを同時押しをしている場合 (BUTTON関数の値は337)
 AND 00000010000 [A]ボタンの値(=16)
 ---------------
   00000010000 = 16 → Bの値が337の時 B AND 16 の値は 16 になる
 
   00101000001 上[X][L]ボタンを同時押しをしている場合 (BUTTON関数の値は321)
 AND 00000010000 [A]ボタンの値(=16)
 ---------------
   00000000000 = 0  → Bの値が321の時 B AND 16 の値は 0 になる

   00101010001 上[A][X][L]ボタンを同時押しをしている場合 (BUTTON関数の値は337)
 AND 00000110000 [A][B]ボタンの値(=16+32=48)
 ---------------
   00000010000 = 16 → Bの値が337の時 B AND 48 の値は 16 になる


 これでBUTTON関数の値がいかなる場合でも[A]ボタンを押していれば「B AND 16」の値が16になり、押していなければ0になることが分かるでしょう。ANDを使えばどのボタンが同時に押されていようとも全く関係なく判別可能というわけです。
 十字ボタンを押して8方向移動させる場合も十字ボタンの上下左右4つを個別に判断すれば可能になります。

スプライト8方向移動サンプル(1)
CLS
GCLS
CLEAR
X=128
Y=96
SPSET 0,64,0,0,0,0
SPANIM 0,4,10
@LOOP
 SPOFS 0,X,Y
 B=BUTTON()
 IF (B AND 4)==4 THEN X=X-1:SPCHR 0,72
 IF (B AND 8)==8 THEN X=X+1:SPCHR 0,64
 IF (B AND 1)==1 THEN Y=Y-1:SPCHR 0,76
 IF (B AND 2)==2 THEN Y=Y+1:SPCHR 0,68
 VSYNC 1
GOTO @LOOP

 ビット演算子ANDは演算優先順位が低いためIF (B AND 4)=4 THEN 〜のようにカッコでくくるのを忘れないようにしましょう。ただし、このサンプルのように押されているかどうか調べる対象となっているボタンが1つという場合には少しだけ簡略化ができます。(例えばIF (B AND 48)=16THEN 〜という場合は簡略化できない)

スプライト8方向移動サンプル(2)
CLS
GCLS
CLEAR
X=128
Y=96
SPSET 0,64,0,0,0,0
SPANIM 0,4,10
@LOOP
 SPOFS 0,X,Y
 B=BUTTON()
 IF B AND 4 THEN X=X-1:SPCHR 0,72
 IF B AND 8 THEN X=X+1:SPCHR 0,64
 IF B AND 1 THEN Y=Y-1:SPCHR 0,76
 IF B AND 2 THEN Y=Y+1:SPCHR 0,68
 VSYNC 1
GOTO @LOOP
青字は8方向移動サンプル(1)からの変更部分

 これで他のボタンを押しても上下左右のボタンの組み合わせで8方向移動ができるようになりました。

 このように8方向移動を行うのは簡単なのですが、斜めに押しても左右に動くので4方向移動のゲームを作ることはできません。もし、8方向ではなく4方向移動させる必要性がある場合は例えば左に移動する場合にはBの値を元に「左が押されている」かつ「上と下が押されていない」という判定を行う必要があります。
 複数の条件式で判定してもいいのですが、上記のANDによるビット演算をちゃんと理解していれば1つの条件式で簡単にできます。

スプライト4方向移動サンプル
CLS
GCLS
CLEAR
X=128
Y=96
SPSET 0,64,0,0,0,0
SPANIM 0,4,10
@LOOP
 SPOFS 0,X,Y
 B=BUTTON()
 C=B AND 15
 IF C==4 THEN X=X-1:SPCHR 0,72
 IF C==8 THEN X=X+1:SPCHR 0,64
 IF C==1 THEN Y=Y-1:SPCHR 0,76
 IF C==2 THEN Y=Y+1:SPCHR 0,68
 VSYNC 1
GOTO @LOOP
青字は8方向移動サンプルからの変更部分

 C=B AND 1515は上、下、左、右ボタンのBUTTON関数の値の合計値(1+2+4+8=15)に相当します。これによって他のボタンが押されているか否かに関係なく変数Cの値によって十字ボタンが押されているか否かということが判断可能になります。(十字ボタン以外のボタンの判定は変数Bの値を使用する)

 ただし、斜め方向のボタン入力は無効化されており入力判定がやや厳しめに感じる人もいるかもしれません。その場合は上下方向、左右方向のどちらか一方のみを8方向移動のようにANDを使って判定すれば斜め方向にボタンを押しても上下もしくは左右へと動作するようになり4方向移動を保ったまま入力は容易になります。(斜め方向にボタンを押している場合にボタンを押しているのにも関わらず反応しないということが無くなる)


 上記のサンプルプログラムをどれでも実行してみれば分かると思いますがスプライトはCLSやGCLSでは消去できません。そのためスプライトを消去するにはSPCLR命令を使います。(mkIIから加わったACLS命令を使えばコンソール、グラフィック、スプライト、BG画面がすべてまとめて消去可能)
 SPCLR 0のように引数によって番号指定すればその指定された管理番号のスプライトのみが消去できるのですが、引数を省略してSPCLRのみを記述した場合にはすべてのスプライトが消去可能になります。

 あとスプライト表示において注意しておかなくてはならないのは基準座標です。基本的にスプライトはキャラの左上が基準座標となっています。つまり、座標(0,0)に表示した場合にはその(0,0)は左上のドットになり、右側の座標は基準座標+スプライトも横のサイズ−1、下側の座標は基準座標+スプライトの縦のサイズ−1になります。これは拡大、縮小や回転の時にずれが生じる原因となっています。(mkIIから加わったSPHOME命令を使えば基準座標を自由に設定できるため拡大縮小や回転を行い場合にはそれを使えば非常に簡単になる)

 SPSCALEを使えば拡大、縮小もできますが、左上が基準座標となっているため拡大すればキャラが右下にずれたように見えるためそのずれの分だけ左上に補正するといいでしょう。通常サイズが16x16ドットのスプライトを基準に考えるとそれを2倍にした32x32ドットのスプライトは縦横16ドット分大きくなっているため表示座標を左上に8ドットずらす(X座標、Y座標ともに8小さくする)必要があります。(詳しくは第5回の講座参照)
 これはスプライトの中心座標で考えるとどのような拡大率でも対応できるため拡大、縮小を行うキャラの場合は常に中心座標で考えると良いでしょう。中心座標を基準にすればスプライトサイズの半分だけ座標を補正をすれば良いです。中心座標が座標(128,96)になるように管理番号0のスプライト(16x16ドット)を表示する場合はSPOFS 0,120,88とすれば良いのです。

 回転はSPANGLEで出来ますが、座標の補正は少々厄介です。拡大、縮小と同じく中心座標を基準に考えれば良いのですが、左上を基準に回転してしまうためスプライトの中心は円運動しているからです。そのためには回転移動の分を補正しないといけません。
 これには数学の一次変換を使います。16x16ドットのスプライトが原点(0,0)に存在しているとすれば中心座標は(8,8)になりますのでそこからA度回転移動をすると中心座標(V,W)は下記の式で求めることができます。(プチコンの三角関数ではラジアンしか使えないため「度」を「ラジアン」に変換する必要がある)

V=COS (PI()*A/180)*8-SIN (PI()*A/180)*8
W=SIN (PI()*A/180)*8+COS (PI()*A/180)*8
SIN (PI()*A/180)SIN (RAD(A))と同じ意味だけどRAD()は0度〜360度までしか扱えないことに注意する必要がある。

また一旦-45度回転させてそこからA+45度回転させた場合は最初に-45度回転させた時点中心座標は(0,8√2)になっているため下記の式でも求められます。

V=COS (PI()*(A+45)/180)*8*SQR(2)
W=SIN (PI()*(A+45)/180)*8*SQR(2)

 どちらの方法でも良いのでV、Wの補正値をマイナスしてやればどの座標に表示してもスプライトの中心を基準に回転が可能です。下記にサンプルを用意しました。

16x16ドットのスプライトを2倍拡大して中心座標を基準に回転させるサンプル
CLS
GCLS
X=128
Y=96
GLINE X,0,X,191,2
GLINE 0,Y,255,Y,2
SPSET 0,96,0,0,0,0
SPSCALE 0,200
@LOOP
 FOR I=0 TO 359
  V=COS (PI()*(I+45)/180)*16*SQR (2)
  W=SIN (PI()*(I+45)/180)*16*SQR (2)
  SPANGLE 0,I
  SPOFS 0,X-V,Y-V
  VSYNC 1
 NEXT
GOTO @LOOP

16x16ドットのスプライトを2倍拡大して中心座標を基準に回転させるサンプル【mkII専用】
CLS
GCLS
X=128
Y=96
GLINE X,0,X,191,2
GLINE 0,Y,255,Y,2
SPSET 0,96,0,0,0,0
SPSCALE 0,200
SPHOME 0,8,8
@LOOP
 FOR I=0 TO 359
  SPANGLE 0,I
  SPOFS 0,X,Y
  VSYNC 1
 NEXT
GOTO @LOOP

 mkIIではスプライトの表示基準点を設定できるSPHOME命令があることで初代と比べてスプライトの回転がかなり簡単にできるようになっていることが分かると思います。

 初代用、mkII用の両方において言えることは見てのようにスプライトを回転させた場合には32x32ドットからはみ出る範囲は欠けてしまうということです。これはどうやらプチコンの仕様(というかDSの仕様)みたいなのであきらめるしかありません。回転時に欠けるのが嫌ならば200%の拡大をせず140%程度に止めておけば問題ありません。(つまり回転時の最大縦横幅が元の2倍に収まるようなサイズに設定する)


 今度はBG画面について見ていくことにしましょう。
 BG画面もVISIBLE第3、もしくは第4引数(BG画面は手前であるBG0と奥であるBG1の2面使えるためどっちを使うかによって第3、第4引数のどちらを設定するかが変わる)で表示するように設定し、「上画面で使用する」ためにはBGPAGE 0と設定する必要がありますが、これらはデフォで使えるような設定になっているため特に問題はありません。  ただし、スプライトのように使うためにはさらなる準備が必要です。それは「BGPUTでBG画面にキャラを書き込む」「BGCLIPで表示領域の指定を行う」「BGOFSでBG画面の表示座標を設定する」というものです。
 BGPUTはFOR〜NEXTを使用してあらかじめベースとなるキャラで塗りつぶしておくと使いやすいでしょう(mkIIではBGFILL命令によって指定したキャラで簡単に塗りつぶすことができます)。表示する前にBGCLIPで画面中のどの領域を使用するのがキャラクタ単位の座標で選択します。そして、BGOFSではそのBG画面の座標を1ドット単位で位置指定できます。

 言葉で書けば簡単なのですが、これは実際に使ってみないとなかなか理解するのは難しいでしょう。

32x32キャラのBG画面を8方向に動かすサンプル1
CLS
GCLS
CLEAR
 FOR I=0 TO 31
  FOR J=0 TO 31
   BGPUT 0,I,J,I*32+J
  NEXT
 NEXT
BGCLIP 8,4,24,20
@LOOP
 BGOFS 0,X,Y
 LOCATE 0,0
 PRINT X,Y,
 B=BUTTON()
 IF B AND 4 THEN X=X-1
 IF B AND 8 THEN X=X+1
 IF B AND 1 THEN Y=Y-1
 IF B AND 2 THEN Y=Y+1
 VSYNC 1
GOTO @LOOP

 これはBG画面用のプリセットキャラ(0〜1023)を32x32のサイズに敷き詰めて画面中央部分(16x16キャラ)に表示して十字ボタンで8方向にスクロールできるというサンプルです。画面左上にはX,Y座標も表示しています。
 BG画面は縦横64キャラクタ分(512x512ドット)となっていてそれが上下左右に繋がっている仕様となっています。当然ながらそのうちの32キャラクタ分しか使用していないためある程度スクロールさせると空白が表示されます。

上記サンプルプログラムで使用しているBG画面
0・・・・・31

・ 使用

31
32・・・・・63

 未使用


32

・未使用

63


 未使用



 サンプルプログラムで、BGCLIP 8,4,24,20はコンソール画面における座標と同一であることが分かります。それはLOCATE 8,4:PRINT "A":LOCATE 24,20:PRINT "B"をループ中のどこかに挿入してみればすぐに分かります。表示しているBG画面の表示領域の左上に「A」、右下に「B」が表示されていますね。
 BGCLIPによって画面のどの領域に表示するのかということが分かったと思うのですが、ちょっと分かりづらいのがBGOFSの座標指定です。X,Y座標が(0,0)でちょうどBG画面の左上になりマイナスになったら空白部分が見えるはずなのに空白が見えるのはX座標はマイナス64、Y座標はマイナス32になっています。
 実はBGOFSの座標(0,0)の時は表示領域の左上ではなく画面全体の左上になっているのです。

32x32キャラのBG画面を8方向に動かすサンプル2
CLS
GCLS
CLEAR
 FOR I=0 TO 31
  FOR J=0 TO 31
   BGPUT 0,I,J,I*32+J
  NEXT
 NEXT
@LOOP
 BGCLIP 8,4,24,20
 BGOFS 0,X,Y
 VSYNC 5
 BGCLIP 0,0,31,23
 BGOFS 0,X,Y
 VSYNC 5
 LOCATE 0,0
 PRINT X,Y,
 B=BUTTON()
 IF B AND 4 THEN X=X-1
 IF B AND 8 THEN X=X+1
 IF B AND 1 THEN Y=Y-1
 IF B AND 2 THEN Y=Y+1
GOTO @LOOP
※目視できるように大きめのウエイトを入れているため動作は遅いです
 サンプル1からの変更部分が青字になっています。


これでBGCLIPで画面の一部に表示する場合も全体に表示する場合も重なっている部分を見れば同じになっていることが分かります。つまり、BGCLIPによって表示領域を画面の一部にする場合は画面左上からの座標分の差を取って考える必要があるということです。BG画面で左上から表示するようにBGCLIPで指定している場合(第1引数、第2引数が0の場合)は差がゼロになるためその座標通り表示されます。


 というわけで、スプライトとBG画面のスクロールを使った「プチコン100m走」を作ってみます。



 100m走の類はすでに第1回でコンソール画面における「プチコン50m走」を作ったので、この「プチコン100走」ではスプライトとBG画面の初歩的な利用しかしていないため上記の基本的な考えさえ理解していれば内容は容易に理解できると思います。
 このプログラムでBGPUT、BGCLIPとBGOFSの復習をしてみましょう。
 3行目のBGCLIPにて座標(0,15)〜(22,20)をBG画面を表示する領域に設定しています。そして、4〜8行でBGPUTでBG画面上にキャラを書き込んでいます。このゲームでは縦スクロールは無しで横スクロールのみなのでBGCLIPで指定したY座標15〜20の6行分だけBG画面に書き込みすれば問題ありません。したがって、BG画面ではY座標0〜5の6行分をこのゲームでは使っています。そして、横のサイズはBG画面で一度に設定可能な最大数である0〜63の64キャラクタ分書き込んでいます。つまり、BG画面上では(0,0)〜(63,5)を使用しているわけです。

 今度はそれをゲーム画面で利用することになります。BGPUTでBG画面に書き込んだデータはY座標で0〜5なのに対してBGCLIPで表示領域に指定しているのは15〜20となっているため15キャラクタ分(=120ドット)だけBG画面を下に移動させなくてはなりません。したがって、24行目でBGOFS 0,X,-120としているわけです。つまり、画面全体の左上の座標(0,0)がBG画面の表示領域の(X,-120)という座標に相当すると考えれば+120ではなく-120になっているのが分かるでしょう。あくまで表示領域を画面全体から一部へと狭めているだけだからです。
 この位置関係が分かりづらければBGCLIPの第1、第2引数を0,0にするか、BGPUTで書き込む際にBG画面の左上から使うのではなく画面の表示領域に合わせた位置にすれば良いです。プチコン100m走でいえば4行のFOR J=0 TO 5FOR J=15 TO 20にします。そうすれば24行のBGOFS 0,X,-120BGOFS 0,X,0という非常に分かりやすいものになります。
 このようにBG画面は書き込んだり、範囲指定するのはキャラ単位だけど実際に表示に使う場合はドット単位で指定するため注意が必要です。

 というわけでスプライトとBG画面を使用してスクロールさせているくらいであとはコンソールの「プチコン50m走」と大差ないのであえて解説するまでもないのですが、「プチコン50m走」に無かった要素として速度の概念(パラメータ)があります。これによりキャラの動き(というかスクロール)がスムーズになるのですが連射速度に比例したタイムではなくなるためバランス調整は難しくなります。
 「プチコン50m走」のように速度の概念が無ければ制作者が秒間8連射が可能で16連射ができる人が世界トップレベルの記録(9.6〜9.8秒)を狙えるようにバランス調整をする場合に自分のタイムがその2倍になるくらいにしておけば良いので簡単ですが、連射速度に比例したタイムが出ないならばこの速度の導入によって単純計算ができません。そういう場合は、その速度の計算式から期待値を求めることが必要になります。もっともその期待値計算が合っているかは実際に確かめる方法がないため十分なテストプレイをする必要があります。

 「プチコン50m走」はタイム加算量でバランスを取っていたのですが、今度は表示時間が実際の時間に近くなるようにしているためや速度加速度を変えることでバランス調整を行う必要があります。加速度がPの値でこれは16行目の(1-S)/3となっています。速度は17行目のS=S*0.98+Pで求めていますが、何もボタンを押していない場合に速度が0.98倍される上に加速度に上限設定があるためいくら連射速度が速くても一定レベルの速度で収束が行われます。これによって、上限速度や下限速度(速度が0未満)に達しているかを判定する必要が無くなります。

 実際の時間に近くするためには何らかの形で時間を計測しつつ時間調整のウエイトを入れる必要がありますがプチコンの場合はVSYNCによって簡単に実現できます。VSYNC 1ならば前回の実行から1フレーム(1/60秒)経ってない場合には1フレームになるようにウエイトが入ります。そのため、第1回でも書いたようにVSYNCを入れない場合のメインルーチンの実行速度が確実に60fpsを上回っている場合はVSYNC 1を入れることで60fps固定にすることが可能になります。

コラム 「VSYNC 2=30fps」なのか?

 VSYNCは初代プチコンのマニュアルによると「画面更新周期との同期(描画更新待ち)」となっており、引数は「直前のVSYNC呼び出しからの経過フレーム数」となっています。VSYNCを使えば画面のちらつきが無くなるのと同時にゲームのフレームレートを安定させることが可能になるのですが、期待しているようなフレームレートにはならない場合もあります。その期待しているフレームレートというのはVSYNC 1ならば60fpsVSYNC 2ならば30fpsVSYNC 3ならば20fpsになるというものです。
 メインルーチン1回分の処理が1フレーム(1/60秒)で完了するような高速なゲームの場合はその計算は通用しているのですが、1フレームで完了しない場合はこの計算が通用しません。
 VSYNC無しで45fpsのゲームの場合はVSYNC 1を入れた場合に前回からの経過時間が1/60秒以上すぎているため無視されるのかと思いきやそうはならず、30fps程度まで低下しているからです。そして、VSYNC2ならば前回から2フレーム(1/30秒)以上すぎているから30fpsが維持されるかと思えば今度は20fps程度まで低下しています。そして、VSYNC 3ならば15fps程度まで低下しています。
 VSYNC無しで30fpsのゲームの場合はVSYNC 1を入れたら30fpsのままで、VSYNC 2、VSYNC 3でもVSYNC無しで45fpsのゲームと動作速度は変わりませんでした。

 したがって、VSYNCでその指定した値通りのきっちりとした速度を得たいならば1フレーム(1/60秒)以内にで処理を完了させるようにするしかないと言えます。どうしても30fpsを維持したいというのであればMAINCNTLを使うしかありません。

 以上は初代プチコンにおけるVSYNCです。
 mkIIではVSYNCの仕様が変わりました。初代プチコンだと1回の処理に1.5フレームの時間がかかる場合にVSYNC 2としてしまえば、1〜2フレーム分のウエイトが加算されてしまうため上記のようなことが起きてしまいました。しかし、mkIIでは前回のVSYNCの終了時を起点にしているためVSYNC 2ならば0.5フレーム分のウエイトとなります。このためmkIIではVSYNC 2で30fpsが維持されます(処理落ちしている場合を除く)。VSYNC1指定して処理に1フレームを超える時間がかかっている処理落ち状態の時は初代はウエイトが加算されていましたが、mkIIのVSYNCはウエイトがかからずすぐ抜けるようになっています。
 このような動作の違いがあるためmkIIでは初代プチコンのVSYNCと全く同じ処理を行うWAIT命令が追加されました。初代プチコンにセーブしているプログラムをmkIIにコピーを行う際にはVSYNCは自動的にWAIT命令に置き換わるためユーザーは従来プログラムの互換性の心配をする必要はありません。(もっとも常時60fpsで動作しているならばmkIIにおいてもVSYNCとWAITに差異はないけど)

 メインルーチンを一定の速度で動作させたいときにはmkIIのVSYNCはかなり有用なのですが、単にウエイトが欲しいときにはWAIT命令の方が便利なのでmkIIユーザーは処理によって使い分けるのがベストだと思います。
 mkIIの新しいVSYNC命令によって30fpsや20fpsで固定のゲームも簡単に作れるようになったのですが、60fpsで動作していないプログラムにおいて注意すべき点は一部正常に動作しない入力命令があるということです。ボタン入力においてはBUTTON()関数はmkIIでは連射の設定も可能になっていますが、BUTTON(1)BUTTON(2)BUTTON(3)BTRIG()においては60fps時以外は入力の取りこぼしが出てしまいます。タッチパネルにおいてもシステムアイコン用のICONCHK()も同様に60fpsでないと取りこぼしが出てしまいます。(BUTTON関数やICONCHK関数やタッチパネル関係のシステム変数はVSYNC更新から次のVSYNC更新までの1フレームの間は同じ値を取ることに気を付ける必要がある)
 これらの命令を使う場合には何とかして60fpsでの動作(1フレームに1回読み出しを行う)を試みるか別の命令で代用するしかありません。

 60fps動作例 B=BTRIG():WAIT 6 → B=0:FOR I=1TO 6:B=B OR BTRIG():WAIT 1:NEXT
※このように1フレームに1回読み出しを行えば正常判定が可能
 別の命令で代用例 B=BTRIG() → C=BUTTON()B=C-(A AND C)A=C

 例えばレトロタイプなアクションゲームにおいて処理速度は十分に間に合っているけど時間稼ぎのために単純にVSYNC 8(7.5fps)と記述してしまうとBUTTON関数を使っていても入力の判定が8フレームに1回しかない(その判定がある1/60秒間以外に押したり離したりしたら正しい判定が行えない)ためボタン操作に対してキャラの動きが追従してくれない場合があります。その時は上記のように1フレームごとに入力を行いそれを元に入力判定を行えばスムーズな操作感覚が味わえるようになります。

 また、常時60fpsで動作しているプログラムでないならばVSYNCやWAITの置き方によっては表示のちらつきが発生してしまう場合があります。これはプチコンでは画面の表示更新を1/60秒ごとに行っていてVSYNCやWAITを使うことでそのタイミングに自動的に合わせてくれているため普段が気が付きませんが、30fpsなどの60fps以外で動作しているプログラムならばその処理の途中で画面表示更新のタイミングが訪れます。CLSやGCLSを使っているプログラムの場合は1/60秒ごとの表示更新のタイミングで画面に表示されていなければちらついているように見えるわけです。
 表示をちらつかさないためにはCLSやGCLSを行ってから次の表示更新までの間に表示を完了させる必要があります。そのため表示の処理が重いプログラムの場合はCLSやGCLSはなるべくVSYNCやWAITを実行してからすぐに実行して1/60秒をフル活用できるようにした方がベターです。(逆に言えばCLSやGCLSの直前にVSYNCやWAITを置けばよい)
 1/60秒では消去と表示をすべて完了させるのは無理という場合には全体消去は行わず、部分消去、部分表示を行うことで1/60秒に収めるという方法もあります。この場合であっても次の部分消去を行った段階で表示更新が行われたらちらついてしまうので途中にVSYNC 1を挟むことでちらつきが改善ができます。(私が作った「2D→3D レース」ではその方法が用いられている)
 グラフィック面に限定するならば表示をしていない裏画面に描画をして表示が終わったらGPAGEで表画面に切り替えるという方法もあります。(Tipsコーナーの「ダブルバッファリング」を参照)
 なお、ACLSは便利な命令ですが1回の実行につき0.966フレームかかる非常に動作が重い命令なので1フレームで表示を完了させるのがほぼ無理になってしまうためプログラムの最初以外では使わない方が良いでしょう。CLSならば0.116フレームなので表示のちらつきを無くすのは容易です。

mkII(ver.2.0)においては初回実行時にVSYNCが無視されるというバグがありました。
このバグはver.2.1の更新で解消されました。

 それを元にして考えると本来ならば18行のタイムはT/50ではなくT/60になるのが正しいのですが、そうしていないのには理由があります。
 それはT/50の場合は演算誤差がなく小数第2位までしか表示されない(T/50を表示するのではなくT=T+0.02としてTそのものの値を表示しても変わらないという意見がありそうですが、固定小数点で1/4096単位でしか扱えないプチコンの場合は0.02を正確に表現できないためです→詳しくは第5回講座のコラム参照)のに対してT/60にした場合にはプチコンの有効数字の最大となる小数第3位まで表示されるためです。
 無意味に小数第3位まで表示されるのが嫌ならば整数化する関数FLOORを利用してTの値を100倍して整数化した後に100で割る必要があります。そうするとプログラムが長くなってしまうため実際の時間との誤差が16%ありますが、ここでは厳密な時間を求めているわけではないためT/50としていますす。もっとも、今見てみたらプログラムを大幅に短縮できそうなのでその処理を入れられそうです。
 そのリスト短縮テクニックを駆使して1画面に収まる範囲内で見た目を限界まで強化した「プチコン100m走mkII」を作ってみました。これはBG画面ではなくグラフィック面をスクロールしているわけですが、この方法については第7回に書いています。キャラ単位ではないが配置が行われている背景をスクロールさせる場合にはBG画面ではなくグラフィック面の方が便利です。

 →第3回へ進む


RETURN

inserted by FC2 system