プチコン講座

第7回 グラフィック面(GRP)をスクロールさせよう (前編)

【mkII専用】


 プチコンにおいてはスクロールを行う場合にはBG画面を使うのが一般的です。しかし、今回はグラフィック面を使ったスクロールについて書いていくことにします。

 グラフィック面を使った場合には表示の自由度の大きさがメリットになります。それは第6回の疑似3Dスクロールのレースゲームを見てのようにこういった処理はグラフィック面だからこそできることであり、BG画面では実現が困難です。

 第6回では疑似3Dのスクロール表示を行うために1ラインごと毎フレーム表示していました。実は道路のようなカーブの表現を行わない単なる疑似スクロールならば毎フレーム表示をする必要はないのです。
 まず、下記のサンプルリスト1を入力してみてください。

《 サンプルリスト1 》
ACLS:CLEAR:Z=1
@LOOP
A=B:B=B+Z:Z=Z*1.04:C=(C+1)%8
GFILL 0,86-A,255,86-B,C+16
GFILL 0,106+A,255,106+B,C+16
IF A<118THEN @LOOP
 QRコード(ファイル名:OCHA7S01)

 このリスト1を実行すると上下対照に手前になるに従って徐々に太くなる縞模様が描かれたと思います。この処理は第6回の疑似3Dレースゲームで使われたものであるためここでは省略します。
 では、下記のリスト2を実行してみてください。

《 サンプルリスト2 》
ACLS:CLEAR:Z=1
@LOOP
A=B:B=B+Z:Z=Z*1.04:C=(C+1)%8

GFILL 0,86-A,255,86-B,C+16
GFILL 0,106+A,255,106+B,C+16
IF A<118THEN @LOOP

@LOOP2
D=D+1
COLSET "GRP",D%8+16,"008800"
COLSET "GRP",(D-1)%8+16,"00FF00"
VSYNC 1
GOTO @LOOP2
 QRコード(ファイル名:OCHA7S02)

 リスト1とは異なる色でしかも再表示していないにも関わらずスクロールしているように見えます。
 これはCOLSET命令によってパレット切り替えを行うことで実現しています。
 グラフィック面(GRP)は256色のパレットを持っていますが、各色においてRGBの値を0〜255(16進数で00〜FF)に設定可能なのです。とはいえ、DSの液晶が最大26万色の表示しかできないためRGB256階調のフルカラー(1677万色)をすべて表現はできないので細かい色調整は無意味になってしまいますが、それでも自由に色を変更できるというのは覚えておくと良いでしょう。

 数値変数R、G、BにRGB各色の値が入っている場合にこれを16進数6桁(RGB各色2桁)で表現するためにはHEX$を用いると良いです。

 COLSET ("GRP",15),HEX$(R,2)+HEX$(G,2)+HEX$(B,2)

 とすることで、GCOLOR 15(白)を変数R、G、Bで指定した色にすることが可能になります。
 15番の色を元に戻す時はCOLINIT "GRP",15としてください。パレット変更した色をすべて元に戻したい場合はCOLINIT "GRP"、もしくはCOLINITとしてください。また、ACLSを実行時もパレット変更された色は元に戻ります。

 リスト2は実行直後のわずかの間、元の色が表示されているのが気になる人もいるでしょう。これは@LOOP2の中ですべてのラインのパレットが書き変わるまでループを行う必要があるためです。気になるようであれば@LOOP2の前に下記の処理を入れておくとよいでしょう。

 FOR I=0TO 7:COLSET "GRP",I+16,"00FF00"

 問題はこのスクロールに合わせてキャラを表示する方法ですが、これは第5回を思い出してもらえたら簡単です。一定速度で移動している物体は一定比率でサイズを変更させてそれに応じたX座標、Y座標に表示するだけです。

《 サンプルリスト3 》
ACLS:CLEAR:Z=1
SPSET 0,100,0,0,0,0:SPHOME 0,8,8
@LOOP
A=B:B=B+Z:Z=Z*1.04:C=(C+1)%8

GFILL 0,86-A,255,86-B,C+16
GFILL 0,106+A,255,106+B,C+16
IF A<118THEN @LOOP

FOR I=0TO 7
COLSET "GRP",I+16,"00FF00"
NEXT

A=0:Z=1
@LOOP2
C=C+1
A=B:B=B+Z
X=128+Z*D:Y=106+B
SPOFS 0,X,Y
SPSCALE 0,Z*30
Z=Z*1.04:IF Z>6THEN Z=1:B=0:D=RND(64)-32
COLSET "GRP",C%8+16,"008800"
COLSET "GRP",(C-1)%8+16,"00FF00"
VSYNC 1
GOTO @LOOP2
 QRコード(ファイル名:OCHA7S03)

 リスト3を実行すればキャラがスクロールに合わせてちゃんと動作していることが分かるでしょう。
 今回は単なる横縞の表示でしたが、これは放射状の表示にしたり、市松模様にしたりと様々に応用が可能になります。またこのパレット切り替えを使用すれば簡単なアニメーションを行ったりフェードイン、フェードアウトなどの表現も可能になります。この辺はまた別の機会に書いてみたいと思います。

 縞模様、放射状、市松模様などの一定パターンをスクロールしているように見せるだけならばこれで十分なのですが、今回は画面上に描かれている背景を縦や横にスクロールする方法をメインに書いていきます。


 BG画面においてはBG画面が実画面より大きいことを利用してその表示位置をBGOFS命令で指定することでスクロールが行えました。しかし、グラフィック面は実画面と同じであるためこういうわけにはいきません。しかし、mkIIから新たに加わったGCOPY命令を使えば可能になります。

《 注意 》
mkII(ver.2.0)のGCOPYにはバグがあり、異なるページ間であっても同じ座標にコピーできないため指定座標によっては正常に動作しない場合があります。
GCOPY命令を問題なく使うためには、ver.2.1にバージョンアップしてください。


 GCOPY命令を使えばグラフィック面に描画された指定範囲のものを別の場所にコピーできますが、まずはGCOPYの基本的な使い方を見ていくことにしましょう。
 例えばGCOPYを使ってモグラたたきゲームを作る場合には次のようにできます。

《 「モグラたたき」プログラムリスト 》
@START
ACLS:PNLTYPE "OFF":CLEAR
GCIRCLE 20,22,20,7
GPAINT 20,20,7
GFILL 0,20,40,80,7
GCIRCLE 20,20,4,14
GPAINT 20,20,14
FOR I=0TO 1
GCIRCLE I*20+10,10,3,14
GLINE I*30+5,15,I*14+13,18,14
GLINE I*30+5,20,I*14+13,20,14
GLINE I*30+5,25,I*14+13,22,14
NEXT
GPAGE 1:GCLS 11:BGMPLAY 14
FOR A=0TO 4:GOSUB @MCLR:NEXT

@MAIN
IF M==0THEN A=RND (5):SP=S/9+1:M=1
IF MO>70THEN SP=-SP
MO=MO+SP
IF MO<=0THEN @END
GOSUB @MOLE
PNLSTR 0,0,"SCORE "+STR$(S),11
TX=TCHX:TY=TCHY
Z=GSPOIT(TX,TY)
IF TCHTIME==1 AND (Z==7OR Z==14)THEN GOSUB @HIT
VSYNC 1
GOTO @MAIN

@HIT
BEEP 11:M=0:MO=0:S=S+1:GOSUB @MCLR
RETURN

@MOLE
IF SP<0THEN GOSUB @MCLR
GCOPY 0,0,0,40,MO,A*50+5,150-MO,0:RETURN

@MCLR
GFILL A*50+5,70,A*50+45,150,11
GFILL A*50+4,149,A*50+46,151,0
RETURN

@END
BGMPLAY 6
PNLSTR 12,9,"GAME OVER",13
PNLSTR 12,15,"RETRY=[A]",11
PNLSTR 12,16,"END =[X]",11
@LOOP
B=BUTTON()
IF B==16THEN @START
IF B==64THEN ACLS:END
GOTO @LOOP
 QRコード(ファイル名:OCHAMOLE)

 これはGLINEやGCIRCLEを使って上画面に描いたモグラを下画面にコピーすることでモグラの表示を行っています。
 あらかじめいろいろなキャラを画面上に表示しておけばそれを使ってキャラを表示をすることが可能になります。上画面に余分なものが表示されては困るという場合は、mkIIではグラフィック画面が4ページ分使えるのでGPAGE命令を使って表示ページと描画ページを異なるものに設定すると良いでしょう。
 この手法はVBなどWindowsのプログラミングに慣れている人だと「Bitbiltのような使い方」と言えば簡単に分かるかもしれません。
 スプライトと違って同じグラフィック面に表示されているため表示と消去を繰り返す必要があるのですが、キャラ定義が不要なだけではなく画面サイズ(256x192)以下のキャラは自由に表示できることや自由な範囲を表示できるというのがメリットになります。(このモグラも40x80ドットで構成されている)

 このモグラは穴から出たり入ったりしているのですが、穴から出ている量を変えたパターンをすべて用意しているのではなくGCOPYの選択範囲を変えることで実現しています。
 ちなみにサブルーチン@MOLEがモグラの表示ルーチン、サブルーチン@MCLRがモグラの消去ルーチンとなっているのですが、もしこのモグラが40x80というサイズではなくほぼ画面サイズの大きなキャラだったらどうなるでしょう。「それでは、ゲームにならない」というのはごもっとも意見ですが、画面全体をちょっとずつ出したり引っ込めたりできますよね。これをモグラではなく背景に置き換えて考えれば「スクロールしている」という状態になるわけです。

 1ドット右から左へスクロールさせる場合には画面上(1,0)から(255,191)の矩形部分(つまり、X座標が0となる最も左のラインを除く画面全体)を座標(0,0)に表示します。これをGOPYで記述すると GCOPY 0,1,0,255,191,0,0,1 となります。(これはGPAGE 0 の場合ですが、描画ページと転送元ページが同じならば最初のGPAGEの指定は省略可能)
 ただし、この場合はX座標255のラインは何も描かれません。正確には前のフレームのラインがそのまま残ってしまいます。そのためこの部分にちゃんと繋がるものを表示しなくてはなりません。BG画面でその幅を超えるスクロールさせる場合には画面外でBGPUTを行うことで無限にスクロールが可能でしたが、表示領域に余裕のないグラフィック面のスクロールの場合はその都度スクロール方向の新規描画を行う必要があります。

 これを使った簡単な横スクロールゲーム「CAVE」を作ったのでスクロールの参考にしてみてください。

「CAVE」プログラムリスト
 QRコード(ファイル名:OCHACAVE)

 「1画面」ならぬ「2分の1画面」に収めたためリスト短縮によって分かりにくい部分もあるかもしれません。とはいえ、基本的な使用方法であるためそれほど難しい部分はないでしょう。
 この「CAVE」はAボタン1つで上下に動作させるいわゆる「酔っぱらい系ゲーム」となっています。GCOPYによって画面全体を他の場所にコピーする場合は、いくら処理速度の速いプチコンであってもそれだけで約1.6フレームの処理時間がかかってしまいます。これでも、2ドット幅のGFILLを128回行えば約5.2フレームの処理時間がかかるため3倍以上速くなっており、グラフィック面をスクロールさせようと思えばGCOPYが最も高速となります。
 しかし、1ドット単位ではスクロールが遅く感じたので2ドット単位のスクロールにしています。その処理速度の問題でVSYNC 1では常時処理落ち起こしてしまうのでVSYNC 2(30fps)を使用しています。

 「CAVE」では、徐々に狭くなっていく洞窟を乱数を元にして生成しているのですが、これと同じことをBG画面で行うとキャラ単位(8ドット単位)でない限りはかなり難しいものになります。
 グラフィック面によるスクロールはGCOPYによって1行の記述でスクロールが可能になるというお手軽さがあるだけではなく、ドット単位でのキャラ表示が可能になるというメリットがあります。今回はGCOPYによるスクロールをもう少し掘り下げて書いていきます。


 「CAVE」の場合は単なるランダムなGFILLでスクロールで右端部分の新規部分を描画できましたが、そうではなくもうすこし凝った画面をスクロールさせてみましょう。
 例えば、下記のような背景をスクロールさせることを考えてみます。

背景グラフィック

 これをBG画面で行うならば一端すべてのパターンをキャラ定義する必要があるためとても現実的とは言えませんがGCOPYであれば範囲指定をして表示座標を変えるだけで済むため何も難しいことはありません。

 この背景グラフィックは、乱数によって山や木を生成しているのですが、左右がシームレスに繋がるようになっています。このようなシームレスで繋がる背景の場合は2ドット分左にスクロールさせてスクロールさせる前の左端2ドット分のラインを右端に付け加えれば良いです。
 ただし、シームレスで繋がるループ背景の場合はもっと分かりやすい方法があります。

《 サンプルリスト4 》
ACLS:CLEAR
GPAGE 1:GCLS 6
A=0:B=110

@LOOP
C=A:D=B
A=A+RND(20)+20
B=B+RND(40)-20
IF B>140THEN B=140
IF B<20THEN B=20
IF A>=240THEN A=255:B=110
GLINE A,B,C,D,157
IF A<255THEN @LOOP
GFILL 0,155,255,191,7
GPAINT 0,131,157
IF A<255THEN @LOOP

@LOOP2
A=RND(10)+8
B=RND(10)+10
E=E+A+RND(15):IF E+A>240THEN @LP2END
GLINE E-A,160-B,E,160-B*3,10
GLINE E,160-B*3,E+A,160-B,10
GLINE E+A,160-B,E-A,160-B,10
GPAINT E,160-B*3+5,10,10
GFILL E-3,160-B,E+3,160,23
GOTO @LOOP2
@LP2END

GPAGE 0

@LOOP3
FOR I=0TO 254STEP 2
GCOPY 1,I,0,255,191,0,0,0
GCOPY 1,0,0,I-1,191,256-I,0,0
VSYNC 2
NEXT
GOTO @LOOP3
 QRコード(ファイル名:OCHA7S04)

 CAVEのように1回ごとのスクロール量を見てそれによってスクロールさせているのではなくトータルのスクロール量を見てそれでスクロールさせています。背景がトータルで100ドット分だけ左にスクロールしているならば背景の左から100ドット分のところで縦に切ってそれを左右逆にして貼り合わしています。

 一見するとCAVEのようにスクロールさせたあとに開いた隙間を埋めるという方法よりも難しそうに見えますが、こうすることで左右どちらにもスクロールが可能になるだけではなくスクロール量の変更も自由に行うことが可能になります。
 実は、CAVEの方式はスクロール方向と量が固定、かつ、スクロール量が整数ドットの場合のみ簡単になるのです。スクロール量が0.5ドットなどの小数の場合には「0.5ドット開いた隙間を埋める」ということはできないため1ドット単位スクロールするのをチェックするルーチンが別途必要になるからです。リスト4のように左右に分解して貼り合わせる方法ではそれが不要になる分だけ簡単に記述ができます。
 もっとも、リスト4では2ドット単位のスクロールなのでCAVEの方式でスクロールさせても何ら問題はありません。なぜCAVEの方式をとらなかったのかというとそれは下記のように多重スクロールや8方向スクロールへ発展させるためです。

 リスト4は画面全体を2ドットずつスクロールさせていきましたが、近景と遠景などのように複数の背景を別々に用意してそれぞれのスクロール量を変えた状態で重ねてGCOPYすれば多重スクロールが可能になります。

《 サンプルリスト5 》
ACLS:CLEAR
A=0:B=110

GPAGE 0,1,0:GCLS 6
@LOOP
C=A:D=B
A=A+RND(20)+20
B=B+RND(40)-20
IF B>140THEN B=140
IF B<20THEN B=20
IF A>=240THEN A=255:B=110
GLINE A,B,C,D,157
IF A<255THEN @LOOP
GLINE 0,154,255,154,157
GPAINT 0,131,157
IF A<255THEN @LOOP

GPAGE 0,2,0:GCLS
GFILL 0,155,255,191,7

@LOOP2
A=RND(10)+8
B=RND(10)+10
E=E+A+RND(15):IF E+A>240THEN @LP2END
GLINE E-A,160-B,E,160-B*3,10
GLINE E,160-B*3,E+A,160-B,10
GLINE E+A,160-B,E-A,160-B,10
GPAINT E,160-B*3+5,10,10
GFILL E-3,160-B,E+3,160,23
GOTO @LOOP2
@LP2END

@LOOP3
FOR I=0TO 510STEP 2
K=I/2:L=I%256
P=3-P:GPAGE 0,P,3-P
GCOPY 1,K,0,255,154,0,0,1
GCOPY 1,0,0,K-1,154,256-K,0,1
GCOPY 2,L,100,255,191,0,100,0
GCOPY 2,0,100,L-1,191,256-L,100,0
VSYNC 1
NEXT
GOTO @LOOP3
 QRコード(ファイル名:OCHA7S05)

 このサンプルプログラムではページ1に遠景、ページ2に近景を描画しています。遠景は近景の2分の1の速度でスクロールさせることで2重スクロールを表現しています。もっとも、その場合はGCOPYを大量に行う必要があるため速度面での問題が発生して画面のちらつきが大きくなるためページ0とページ3に交互に描画することでちらつきを抑えています。
 この場合の注意点としては最も下(最も遠景)の背景はGCOPYの最後のパラメータは「1」(透明色を含まない場合は「0」でもよい)にして、上から重なっている部分は「0」にするということです。こうすることで近景と遠景が重なっている部分でも正しく描画されます。

 グラフィック面をスクロールする場合はスクロールする範囲を狭めればいくらでも可能になるため速度や実用性を考慮しない場合は100重スクロールでも200重スクロールでもできます。ただ、縦幅が数ドットの背景を横スクロールさせても意味はあまりないと思うので、5〜6重スクロールくらいが実用限界(15fps以上)になるのではないかと思います。

 左右片方、もしくは上下片方のみのスクロールの場合はGCOPYによるスクロールは簡単なのですが、4方向や8方向スクロールのゲームを作る場合にはその表示方法に合わせた新規部分描画ルーチンが必要になってきます。ただし、予め用意されたシームレスで繋がる背景をスクロールするだけならば4分割でGCOPYを行えば8方向スクロールが可能になります。
 リスト4の背景グラフィックを十字ボタンで8方向スクロールさせる場合には下記のようにできます。(スクロールには慣性がついているため十字ボタンは軽く押してください)

《 サンプルリスト6 》
ACLS:GPAGE 0,1,0
CLEAR:GCLS 6
A=0:B=110

@LOOP
C=A:D=B
A=A+RND(20)+20
B=B+RND(40)-20
IF B>140THEN B=140
IF B<20THEN B=20
IF A>=240THEN A=255:B=110
GLINE A,B,C,D,157
IF A<255THEN @LOOP
GFILL 0,155,255,191,7
GPAINT 0,131,157
IF A<255THEN @LOOP

@LOOP2
A=RND(10)+8
B=RND(10)+10
E=E+A+RND(15):IF E+A>240THEN @LP2END
GLINE E-A,160-B,E,160-B*3,10
GLINE E,160-B*3,E+A,160-B,10
GLINE E+A,160-B,E-A,160-B,10
GPAINT E,160-B*3+5,10,10
GFILL E-3,160-B,E+3,160,23
GOTO @LOOP2
@LP2END

@LOOP3
P=3-P:GPAGE 0,P,3-P
B=BUTTON()
V=V-(4AND B)/8+(8AND B)/16
W=W-(1AND B)/2+(2AND B)/4
X=(X+V+256)%256
Y=(Y+W+192)%192
GCOPY 1,0,0,X,Y,256-X,192-Y,0
GCOPY 1,X,0,255,Y,0,192-Y,0
GCOPY 1,0,Y,X,191,256-X,0,0
GCOPY 1,X,Y,255,191,0,0,0
VSYNC 1
GOTO @LOOP3
 QRコード(ファイル名:OCHA7S06)

 この背景は左右方向はシームレスですが、上下方向においてはシームレスでないため上下方向のスクロールはおかしな感じになっていますが、それでもスムーズに8方向スクロールできていることは確認できると思います。ちなみにリスト中のX=(X+V+256)%256というのはXの値が0〜255の範囲外に出ないようにするためのものです。(256をプラスすることで負の数になることを防いでいる)
 後述のようにGCOPYでは画面外の座標を指定すると誤動作を招く場合があるのでこのようなことをしています。

 これを簡単に説明すると背景上の座標(X,Y)が画面上の原点の座標(0,0)にくるようにするためには背景を下記のように、4分割する必要があります。
 
0・ ・ ・ ・ X

領域A

Y
・ ・ ・ ・ 255

 領域B




領域C

191
(X,Y)

  領域D



分割されたA/B/C/Dの4領域をD/C/B/Aの順にコピーすれば良いです。
具体的に書くと次のようになります。

 領域A → 画面上(256-X,192-Y)を起点とする座標にコピー
 領域B → 画面上(0,192-Y)を起点とする座標にコピー
 領域C → 画面上(256-X,0)を起点とする座標にコピー
 領域D → 画面上(0,0)を起点とする座標にコピー

 座標X、Yがともに整数の場合はこれで良いのですが、小数の場合は表示座標が1ドット分だけずれてしまいます。したがって、厳密な表示座標を要求する場合はXの値を事前に整数化しておくと良いでしょう。これは、GCOPY限らずプチコンにおけるすべての座標指定において小数部分は無視されるためX座標が10との場合と10.1の場合は同じになるのですが、256-XにおいてはX=10の時は246なのに対して、X=10.1の時は245.9となり245と見なされてしまうからX=10のときとX=10.1の時では表示が1ドット分だけずれているわけです。(このサンプルプログラムではそこまで厳密な表示は要求していないため整数化処理は行っていない)
 また、プログラム内でX、Yが整数の値をとることがない場合は座標を事前に1ドット分ずらしておけばわざわざ整数化しなくても常に正しい表示が可能になります。

 言葉で書くと難しそうですが、これは1枚の絵を描いてはさみで4つにばらしたものを並べ替えるのと同じであるため感覚的には非常に分かりやすいのではないかと思います。

 このようにシームレスな背景をスクロールする場合は8方向でも簡単に実現できるのですが、そうではない場合は8方向にスクロールさせる場合は8方向分の新規描画ルーチンが必要になってきます。そのためその処理を簡略化するためにも8方向スクロールを行う場合にはゲーム中で使用する画面はプチコンの画面サイズ(256x192)より小さくするといいでしょう。縦横の最大スクロール量よりも表示画面サイズを小さくする(最大8ドットのスクロールならば240x176の表示サイズにする)ことによって、斜め方向の新規描画が不要になり8方向スクロールでも4方向分の処理で済むようになります。

 GCOPYによるスクロールで注意しなくてはならないのは画面全体がグラフィック面は範囲外では何も描画が行われないためGCOPYを使う時には座標(-2,0)を指定するなどの画面の範囲外の値を指定する時には誤動作を招いてしまうことがあるということです。もしも、GCOPYを使った作ったゲームにおいて表示に誤動作が発生した場合にはこの転送元のグラフィック面において画面範囲外の座標を指定をしているのを疑ってみるといいかもしれません。GCOPYを正常に行うためにはコピーの転送元のX座標は0〜255、Y座標は0〜191でなくてはなりません。
 それともう1つの注意点は上記のように座標指定は小数部分が無視されるため小数による座標指定をした場合には表示座標が1ドットずれることがあるということです。それを避けるためには座標の整数化が必要になることがあります。

「後編」に行く


RETURN

inserted by FC2 system