プチコン講座

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

【mkII専用】


「前編」に戻る

 BG画面が使えるプチコンにおいて、あえてGCOPYによるスクロールを行うのはやはり「お手軽」だからです。したがって、そのお手軽スクロールのメリットを生かすためにはステージデータを予め大量に用意するのではなくリアルタイム生成することにしましょう。
 ただし、その場合はRND関数による乱数を元に生成すると再現性がないという問題点があります。当たり判定が全くないただの背景ならば再現性が無くても問題ありませんが、ゲームの進行に関係ある場合はそうもいきません。

 再現性がないというのはそのステージと同じ構成のステージで再びプレイすることができないということです。乱数によるステージの生成は「プレイするごとに簡単になったり難しくなったりするという問題があるだけではなく場合によってはクリア不能なステージができてしまう可能性さえあります。
 この「クリア可能かどうか」ということをリアルタイムで判定することは結構難しいのです。生成されたデータを元にクリア可能かどうかを判定するのではなく最初から「確実にクリアできるステージ」を生成する方が簡単なのですが、その場合は言い換えれば「初見でも十分にクリアできるステージ」になってしまうために攻略性の乏しいステージになりやすくなります。

乱数によるステージ生成はクリア不能になる場合がある

1回のジャンプで5ブロック分の距離ジャンプできる横スクロールゲームの場合


@=自キャラ、■=ブロック、□=ブロックのない隙間

このように隙間が4ブロック以下になるようにステージを生成すれば確実にクリアできそうです。

     @12345  ←スクロール
□□■■■■□□□□■■□■■□□□■

     ↓ ↓ ↓ ↓

  自キャラがジャンプ中

     @


■■■■□□□□■■□■■□□□■■■

     ↓ ↓ ↓ ↓

ところが・・・

     @    AB
■□□□□■■□■■□□□■■■□□□
     012345
      012345


この場合は、AもしくはBの位置に着地するしかないためクリア不能になっているのです。

これを避けるためには、一端ステージを生成した後にA、もしくはBの位置がブロックになるように生成しなおせば良いですね。
これは「5ブロック前に足場があり、4ブロック前に隙間がある」というように判定を行えば簡単に実装が可能です。
しかし、これだけでは隙間の直前ギリギリでジャンプすれば常に足場に着地できるため足場の状態によって条件式を随時変える必要があります。

ただし、それでもジャンプの飛距離や画面に出てくるブロックの数やブロックの幅やスクロール速度によってはブロックが出てきた瞬間に判断しても間に合わなかったり、判断が難しい運任せのゲームになってしまう場合があります。
「運任せ」にしないため十分な長さの足場を確保した場合には簡単すぎたり、攻略性の乏しいステージになってしまいます。

このように様々な問題を抱えている乱数による自動生成ですが、これも再現性のある疑似乱数によってステージ生成をする場合は、下記のように意図的にクリア不能にして攻略性を高めるという方法もあるため「自動生成だから攻略性の低いステージしかできない」というものではありません。

 これが、再現性のある乱数であれば、何度もテストプレイを行ったり、生成されるステージの足場や穴の幅を元にしてクリア可能かどうかが判別可能だし、難易度や攻略性の面もチェックが可能になります。したがって、初見ではクリアが難しいけどステージ構成を覚えればクリアは可能というステージも生成可能になります。
 疑似乱数ルーチンについては詳しくは講座の番外編となる「プチコンで疑似乱数をつくろう」の方で書いているので参考にしてみてください。

 この疑似乱数ルーチンを使ってGCOPY使用の単純な横スクロールジャンプアクションゲーム(タイトルもそのまんま「JUMP ACTION GAME」)を作ってみました。

《 「JUMP ACTION GAME」プログラムリスト 》
@START
ACLS:CLEAR:PNLTYPE "KYA":COLOR 15
IF INKEY$()!=""THEN @START
@LOOP1
LOCATE 7,12:?"STAGE SELECT (1-9)"
K=VAL(INKEY$())
IF K<1THEN @LOOP1

@RETRY
ACLS:GCLS 6
GPAGE 1:GFILL 0,0,255,15,4
FOR I=0TO 7:GPUTCHR I*16+4,1,"BGF",ASC(MID$("GOAL IN!",I,1)),0,2:NEXT
SPSET 0,96,0,0,0,0
SPANIM 0,4,4:COLOR 1
GPAGE 0:GFILL 0,176,255,191,7
I=0:J=0:L=0:M=80:N=160:Q=0:V=174:S=0:X=K/9:W=0
BGMPLAY 14

@MAIN
GOSUB @RND:R=0OR X*100:I=I+1
IF W<=0 AND Q==0THEN W=R/2.2+K*4
IF W<=0 AND Q==1THEN W=12-K+R/20
C=11-Q*4:W=W-1:IF W<=0THEN Q=1-Q
GCOPY 0,1,70,255,175,0,70,0
GCOPY 0,2,176,255,191,0,176,0
GFILL 254,0,255,191,6
GFILL 254,V,255,191,11
GFILL 254,176,255,191,C
IF I>K*40+600THEN L=L+(L==0)*I:GCOPY 1,(I-L)*2,0,(I-L)*2+1,15,254,176,0
U=(R%5)-2:V=V+U*(U+V>70)*(U+V<174)
SPOFS 0,M,N
D=GSPOIT(M,N+16)
E=GSPOIT(M+8,N+16)
F=GSPOIT(M+16,N+16)
IF D==4THEN @END
A=B:B=BUTTON()
M=M+(8AND B)/4-(4AND B)/2
IF N==160 THEN IF D==7OR E==7OR F==7THEN G=0:J=0ELSE J=1
IF (J==0)*(A-B)*(16AND B)THEN J=1:G=10:BEEP 8
IF J==1THEN G=G-0.5
N=N-G:S=S+(0OR M/20)+1
LOCATE 0,0:?"STAGE "K
LOCATE 20,0:?"SCORE "S
IF N>250THEN @END
VSYNC 2
GOTO @MAIN

@END
LOCATE 14,9:?MID$("CLEARMISS",(D!=4)*5,5)
BGMPLAY 5+(D!=4)
LOCATE 9,15:?"[X] RETRY"
LOCATE 9,16:?"[Y] STAGE SELECT"
@LOOP
B=BUTTON()
IF B==64THEN @RETRY
IF B==128THEN @START
GOTO @LOOP
@RND
X=(X*479232+I%4095)%4096/4096
RETURN
 QRコード(ファイル名:OCHAJAG1)

 プレイするステージをキーボードから1〜9で選択して開始します。十字ボタンで左右に動作し、Aボタンでジャンプします。穴に落ちないようにゴールにたどり着けばクリアという内容になっています。

 このゲームでは再現性のある疑似乱数ルーチンを使うことで各ステージ内容は常に一定になっています。(0〜1の乱数を100倍することで0〜99の乱数として使用している)
 ステージ生成方法を簡単に解説していくと20行、21行によって変数Qが0の時は隙間の幅、変数Qが1の時は足場の幅を決定しています。当然のことながら足場と隙間は交互に出現するためQ=1-Qという式によって減算カウンタが0になるごとにQの値は0と1を交互にするだけです。
 足場と隙間の幅は計算式を見て分かると思いますがステージ1とステージ9では下記のようになっています。

足場の幅隙間の幅
ステージ122〜32ドット8〜98ドット
ステージ96〜16ドット72〜162ドット

 これを見てのように足場の幅をステージごとに狭くして隙間の幅を広くすることによってステージが進むにつれて難易度が上がるように調整しています。ジャンプできるギリギリの幅だと素早いボタン操作が要求されるためその分だけ難しくなっています。足場が狭くなると当然ながらそれに乗るのは難しくなるのですが当たり判定の範囲があるのでむやみに狭くはできません。

 このゲームではジャンプ中に左右に動けるためジャンプの飛距離は可変で最小0ドット、最大160ドットとなっています。最大時の隙間は理論上のジャンプ幅を超えているのでクリア不能なステージが発生する場合もありますが、当たり判定の幅を考えれば理論上クリアが可能だし、再現性があるため実際にプレイしてもクリア可能であることが実証できています。
 背景の山らしきものはCAVEの洞窟の生成と同じアルゴリズムを使用しています。当然ながらこの山の形も各ステージで毎回同じものが生成されます。(CAVEはプチコンのRND関数を使用していたため毎回異なるものが生成されていた)
 背景は1ドット単位で足場は2ドット単位でスクロールさせているため2重スクロールになっています。スクロールは画面全体ではなく必要な分だけ(背景はY座標70〜175、足場はY座標176〜191)行うことで処理速度の向上を行っています。

 一定距離進むとゴールライン(青色のエリア)が現れますが、ここでは「GOAL IN!」の文字が表示されるようになっています。これについて少し解説しておきましょう。これは13行のGPUTCHR命令を使い下画面のグラフィック画面(GPAGE 1)に描いています。


コラム GPUTCHRで描画されたキャラをGCOPYする際の注意

 GPUTCHR命令を使えばコンソールキャラやスプライトやBG画面のキャラを1、2、4、8倍のいずれかのサイズでグラフィック面に描くことができます。例えば、下記のようにすればスプライト番号64の男の子のキャラをグラフィック面の座標(0,0)を起点(キャラの左上の座標)にして元の4倍のサイズで描くことができます。

 GPUTCHR 0,0,"SPU1",0,0,4
 GPUTCHR 32,0,"SPU1",1,0,4
 GPUTCHR 0,32,"SPU1",2,0,4
 GPUTCHR 32,32,"SPU1",3,0,4

 スプライト番号64は"SPU1"にあるというのはスプライトのキャラの一覧表を見ればすぐに分かるのですが、スプライト番号とキャラ番号は異なるため注意が必要になります。というのもスプライト番号は16x16ドット単位でSUP0からの通し番号となっているのに対してキャラ番号は8x8ドット単位でSPUの1つの領域の先頭を0番としてそれからの番号となっているからです。スプライト番号64のキャラはSPU1の先頭にあるキャラなのでキャラ番号は0〜3が割り振られています。(スプライト番号65のキャラはSPU1のキャラ番号4〜7が割り振られている)

 スプライトではなくコンソールのキャラは"BGF0"(バンク0の場合は「0」を省略可能なのでBGFのみでも良い)に入っているためGPUTCHR 0,0,"BGF",65,13,8とすれば座標(0,0)を起点とした場所に「A」(キャラコード65)がパレット13の色(赤)で8倍サイズで表示されるのです。

 ここで注意しておく必要があるのはGPUTCHR命令を使った場合にはそのパレットの色が16色分グラフィック面のパレットと置き換わるということです。上記では13番のパレットで表示したためGCOLOR 208〜223がコンソール、スプライト用のパレットの色に入れ替わってしまいます。つまり、GCLS 223で本来ならば緑色になるところが赤になるということです。(※パレット番号×16を起点とした16色が入れ替わる)
 第4回の講座で書いたようにカラーパレットは上画面と下画面で別々になっています。そのため下画面でGPUTCHR命令を使用した場合にはその画面のみパレットの変更が行われてしまい上下画面でパレットが変わってしまうのです。そのためGCOPYで異なるページ間でコピーした場合にはコピー元のページの色とコピー先のページのパレットの色が異なるためコピーされたものがコピー元とは異なる色で表示されてしまうという問題が発生してしまいます。

 これを回避するには下記の3つの方法があります。

(1)GPUTCHR命令では0番もしくは1番のパレットしか使用しない
(2)GPUTCHR命令を実行した直後にCOLINIT命令を実行する
(3)予め使用するグラフィックページすべてでGPUTCHR命令を実行しておく

(1)は0番、1番のパレットにおいてはコンソール、スプライト用のパレットとグラフィック用のパレットの色が共通であるためです。ただし、パレット0番、1番だけだとキャラデータを書き換えない限りコンソール文字は白と黒しか表示できません。

(2)COLINIT命令を実行すれば「コピー元の表示の色とコピー先の表示の色が異なる」という問題は回避できます。しかし、GPUTCHR命令で本来表示されるべき色と異なる色になってしまいます。この場合は、GCOLORの色が適用されるためスプライトキャラにおいては表示色のバランスが完全に崩れてしまう場合があります。(コピー元とコピー先の色が同じでもコピー元の色が崩れてしまえば本末転倒)

(3)「コピー元とコピー先で異なる色になるのを避けたい」というならば予めすべてのページのパレットを変えておくのが良いです。

 FOR I=0TO 3:GPAGE 0,I,0:GPUTCHR 0,0,"BGF",32,13,1:NEXT
 (これは13番パレットのみの変更だけどGPUTCHRをすべてのパレットで使用する場合は2〜15番も予めすべて変えておく)

 これで、全ページともに13番のパレット(GCOLOR 208〜COLOR 223)はすべてコンソール、スプライト用のパレットになるためコピー元とコピー先の表示色が異なるという問題は回避できます。当然のことながらパレットが変わるためグラフィック命令を使う際には本来のグラフィック用のパレットの色で表示はできません。
 そのパレット変更した16色はグラフィック用として使用しない場合はこれで問題ありませんが、そうでない場合は表示色が異なるという問題が発生してしまいます。(逆に言えばGCOLOR 33〜を使用しないのであれば、予めすべてのパレットを変更しておけばパレットの問題は完全に解決する)


 GPUTCHR命令は便利な命令なのですが、その処理の重さから速度の要求されるゲームでは一端別のグラフィック面に描画してそれをGCOPYして使う方が高速になる場合が多くSPU1のキャラ番号0のキャラを8倍表示した時にはGPUTCHRでそのまま表示するよりGCOPYの方が2倍高速になります。しかし、コンソール、スプライト用のパレットとグラフィック用のパレットが異なっているためにこのような問題があり、使用する際にはそれに関して知っておかないとゲームを作る際にも思わぬ誤動作を招いてしまうため注意する必要があります。


 このゲームでは予めグラフィック面に描かれた「GOAL IN」の文字となっているグラフィックをゴールラインを越えた時点でGCOPY命令を使って2ドット幅ずつゲーム画面にコピーして描画しています。上記のようにGPUTCHRをそのまま使うより一端GPUTCHRで描画したものをGCOPYする方が高速なのですが、この場合はすべての文字を一度にコピーするのではなく2ドット幅ずつ順次コピーしているためGPUTCHRをそのまま使うより圧倒的に高速になっています。
 今回は「GOAL IN」という文字でしたが、ここに適当なマップチップのキャラを描いておけばそれを表示することも可能になります。

 ジャンプアクションゲームは「ジャンプの操作をして気持ちいいか」という部分が非常に重要になってきます
 ジャンプ処理については第4回でも書きましたが初速と重力加速度で飛距離や最高到達点が変化します。初速は39行の変数Gの値、重力加速度は40行のG=G-0.50.5を変えるといいです。なお、このゲームにおいては地面とのY座標方向の判定は等号で行っているため演算誤差が出ないように重力加速度は2の累乗分の1しか使用できません。それ以外の値を使うと演算誤差が発生するため「地面の座標と同じ座標に達しない場合」を考慮しなくてはならないため「地面へのめり込み対策」を行う必要があります。
 デフォでは「初速が10、重力加速度が0.5」になっていますが、このゲームではX座標方向の速度が一定であるためこの部分を「20と1」、「5と0.25」、「2.5と0.125」のように両者の比率が同じ組み合わせで変えた場合にはジャンプの飛距離は変わらず、最高到達点のみ変化します。初速や重力加速度を変えることで操作感が変わるだけでなく空中に障害物がある場合にはゲーム性が変わってくるため自分が求めているものになるようにするといいです。(比率を変えた場合には飛距離が変わるため隙間の幅を調整の必要がある)
 キャラの当たり判定(落下判定)はキャラの前、中央、後ろで判定しておりどこか1カ所でも足場があればセーフであるためキャラの幅よりも狭い隙間はジャンプせずに通過できます。キャラ単位ではなくドット単位でステージを生成しているため狭い足場が連続して出るようなステージを用意すれば攻略性が増しそうな感じです。

 3行目にあるIF文はキーバッファをクリアしています。これを入れないとプレイ中に1〜9のキーを押してしまった場合にそれがキーバッファに残ってしまいステージ選択画面で選択をする前にスタートしてしまうという問題が発生します。

 このゲームの特徴はジャンプ中に左右に自由に動けるということです。というか、それくらいしか特徴はありません。
 しかし、これによって上記のように狭い隙間であってもクリアできないことがあったり、運任せになったりすることはなくジャンプ可能な距離を超える幅の隙間が開いてない限りは確実にクリアが可能になるため「クリア可能なステージ」を自動的に生成するのが非常に楽になります。これによって足場と隙間の距離を自由に設定できるためステージにバリエーションを持たせることができるようになります。
 ただし、実際に作ってプレイしてみると大きくジャンプ(右を押しながらジャンプ)して足場の上空に来たら後ろに下がる(左を押す)ことで簡単にクリアできるためワンパターンになってしまい攻略性に乏しい単調なゲームになりました。良くありがちなのですが、アイデア先行で作ってみたけど実際に作ってみたら面白くないというパターンに相当します。

 これを避けるためには空中に障害物を出すという方法がありますが、クリア可能かどうかのチェックを手動で行う必要があるし、障害物の量によっては第1回の講座のコラムで書いたように思考(攻略性)よりも反射神経が重視されるようなものになってしまう恐れがあり求めるゲームとは異なるものになってしまいます。

 そこで、「自由に移動やジャンプできる」というアイデアはあきらめてジャンプは[A]で小ジャンプ、[B]で大ジャンプとなり、ジャンプを使い分けることで隙間を飛び越えていくというよくありがちな操作にしてみました。(実はこのゲームは[A][B]ボタン同時に押すことで大ジャンプを越えるスーパージャンプが出せるけどスーパージャンプを使わないとクリアできない場面はない)
 名付けて「JUMP ACTION GAME2」です。(またしてもそのまんまのタイトル)

《 「JUMP ACTION GAME2」プログラムリスト 》
@START
ACLS:CLEAR:PNLTYPE "KYA":COLOR 15
IF INKEY$()!=""THEN @START
@LOOP1
LOCATE 7,12:?"STAGE SELECT (1-9)"
K=VAL(INKEY$())
IF K<1THEN @LOOP1

@RETRY
ACLS:GCLS 6
GPAGE 1:GFILL 0,0,255,15,4
FOR I=0TO 7:GPUTCHR I*16+4,1,"BGF",ASC(MID$("GOAL IN!",I,1)),0,2:NEXT
SPSET 0,96,0,0,0,0
SPANIM 0,4,4:COLOR 1
GPAGE 0:GFILL 0,176,255,191,7
I=0:J=0:L=0:M=80:N=160:Q=0:V=174:S=0:X=K/9:W=0
BGMPLAY 14

@MAIN
GOSUB @RND:R=0OR X*100:I=I+1
IF W<=0 AND Q==0THEN W=R/4+K
IF W<=0 AND Q==1THEN W=15-K+R/20
C=11-Q*4:W=W-1:IF W<=0THEN Q=1-Q
GCOPY 0,1,70,255,175,0,70,0
GCOPY 0,2,176,255,191,0,176,0
GFILL 254,0,255,191,6
GFILL 254,V,255,191,11
GFILL 254,176,255,191,C
IF I>K*40+600THEN L=L+(L==0)*I:GCOPY 1,(I-L)*2,0,(I-L)*2+1,15,254,176,0
U=(R%5)-2:V=V+U*(U+V>70)*(U+V<174)
SPOFS 0,M,N
D=GSPOIT(M,N+16)
E=GSPOIT(M+8,N+16)
F=GSPOIT(M+16,N+16)
IF D==4THEN @END
A=B:B=BUTTON()
IF N==160 THEN IF D==7OR E==7OR F==7THEN G=0:J=0ELSE J=1
IF (J==0)*(A-B)*(48AND B)THEN J=1:G=B/4:BEEP 8
IF J==1THEN G=G-0.5
N=N-G:S=S+5
LOCATE 0,0:?"STAGE "K
LOCATE 20,0:?"SCORE "S
IF N>250THEN @END
VSYNC 2
GOTO @MAIN

@END
LOCATE 14,9:?MID$("CLEARMISS",(D!=4)*5,5)
BGMPLAY 5+(D!=4)
LOCATE 9,15:?"[X] RETRY"
LOCATE 9,16:?"[Y] STAGE SELECT"
@LOOP
B=BUTTON()
IF B==64THEN @RETRY
IF B==128THEN @START
GOTO @LOOP
@RND
X=(X*479232+I%4095)%4096/4096
RETURN
 QRコード(ファイル名:OCHAJAG2)

 これならばうまく大・小のジャンプを使い分けることができず初見でクリアできなくても同じステージで再度プレイ可能であるためステージ構成を覚えれば(理論上クリア不能なステージでない限り)クリアが可能になります。そのため再現性のある疑似乱数というのが生きてきます。
 もしも、大小2つのジャンプを用意せず、一定の距離しかジャンプできない場合はクリアできない場合が多く発生するだけではなくゲームそのものも単調なものになってしまうのでこれは非常に有用でしょう。

 操作方法の大幅変化によって当然ながら足場や隙間の幅の計算式も変える必要があります。(大ジャンプは80ドット、小ジャンプは40ドットの幅だけジャンプできる)

足場の幅隙間の幅
ステージ128〜38ドット2〜52ドット
ステージ912〜22ドット18〜68ドット

 最初のJUMP ACTION GAMEと同様に足場の幅をステージごとに狭くして隙間の幅を広くすることによってステージが進むにつれて難易度が上がるように調整しています。ただし、この「2」では、ゲームは大ジャンプと小ジャンプの使い分けがすべてなので隙間の幅が広くなったからといって難しくなるわけではありません。難しいのは大ジャンプと小ジャンプのどちらを使うのかという判断に迫られる場面です。したがって、小ジャンプ、大ジャンプどちらでも飛べる隙間や足場ではなく「大ジャンプだと隙間と足場を通り越して次の隙間に落ちる」もしくは「小ジャンプだとギリギリ届かない」というようなステージ構成になれば難易度が上がります。
 また、このゲームではキャラの幅より狭い隙間はジャンプせずに通過できるのですが、それを生かせるステージがありません。隙間に対して「ジャンプしない」「小ジャンプ」「大ジャンプ」の3つのどれにするのかという選択肢が発生するようなステージができれば良いのですが、まだまだその点においては不十分であり、ステージの生成計算式は見直す必要があるでしょう。
 とはいうものの現時点のゲーム内容だと「ジャンプしない」という選択をする必要性がないのでこれを生かすためにはステージ生成の計算式だけではなくゲームシステムを根本から変える必要がありそうです。このようなサイドビューの横スクロールゲームの場合はジャンプの飛距離だけだと戦略性の高いものは難しいため高さの概念を取り入れて上ったり下ったり空中の障害物を避けたりという要素を加える必要があるかもしれません。つまり現状ではステージデータは地面に隙間があるか否かという一次元的なものだったのをサイドビューの二次元的なものにするということです。(ただし、この場合はコースをランダムで生成してまともにクリアできるコースにするのは至難の業となる)

 このJUMP ACTION GAME2の完成度を高めても良いのですが、「移動やジャンプが自由にできる」というアイデアも捨てがたいのでもう一度最初のJUMP ACTION GAMEに戻って考えてみます。(後日、この「JUMP ACTION GAME2」のシステムを使いライフ消費型ジャンプを導入することで問題点を緩和した「ハカセジャンプ」も作ったのでそれも参考にしてみてください)



 自由にジャンプでき、なおかつ攻略性の高いゲームは本当にできないのでしょうか?
 それは乱数によるステージ生成をやめてパズル的要素を導入するなど各ステージにおいて攻略性の高いステージデータを予め用意しておけば可能です。ここでは再現性のある疑似乱数を生かすため別の方法で解決することにします。

 なぜ上記のJUMP ACTION GAMEが攻略性の低いゲームになってしまったかというと正解(クリアするためのルート)が1つ(常に隙間の手前ギリギリでジャンプすれば確実クリアできる)しかなくそのための操作方法も1通り(空中で足場の上に来たら左を押す)しかないためです。これをレースゲームに例えるならば、どのようなカーブであってもどのような速度であっても同じようにカーブが曲がれるというようなものです。最初からそれが分かっていればそれができるかどうかという反射神経ですべてが決まってしまいます。(PRINTのスクロールによる一般的なレースゲームやスキーゲームがそれに相当する)
 これがJUMP ACTION GAME2では大小2通りの選択肢が出来たと同時に「どこでジャンプしたら良いのか」という選択肢もあるため攻略性が高くなりました。

 つまり、自由に移動やジャンプができるようになってもジャンプの方法がいくつかあったり、ジャンプの開始や着地地点に選択の余地を用意すれば攻略性を高くすることが可能になるということです。この考えを元にして縦スクロールジャンプアクションゲーム「JUMPING ISLAND」を作ってみました。(操作方法等はリンク先参照)

《 「JUMPING ISLAND」プログラムリスト 》
MEM$="0"*45LOAD "MEM:OCHAJIHS"
FOR I=1TO 9H$(I)=MID$(MEM$,I*5-5,5)NEXT
@ST
ACLS:BGMPLAY 26PNLTYPE "KYA"W=0
@RT
GPAGE 0,2,0GFILL 0,2,255,5,6
GFILL 0,6,255,15,223
FOR I=0TO 255
GPSET I,723%(I%5+4),15
GPSET I,I%3*2+10,229
GPSET I,10-17%(I%4+1),234NEXT
GPAGE 0,0,0FOR I=0TO 19
GCOPY 2,0,6,255,15,0,I*10,0:NEXT
FOR I=W TO 1COLOR MAINCNTL%16
LOCATE 9,8?"JUMPING ISLAND":COLOR 1
?" "*135"STAGE SELECT (1-9)?"" "*141"SAVE[X] END[L]+[R]
B=BUTTON()IF B==768THEN ACLS:BGMSTOP:END
IF B==64THEN MEM$=""FOR J=1TO 9MEM$=MEM$+H$(J)NEXT:SAVE "MEM:OCHAJIHS"
K=VAL(INKEY$())WAIT 5I=K:Y=K
NEXT:PNLTYPE "OFF"CLS:TALK "@E7@S9イッ_ク'ゾオ--!"WAIT 60
SPSET 0,108,0,0,0,0
SPSET 1,200,1,0,0,0
SPHOME 0,8,8SPHOME 1,8,8
GPAGE 1,1,1COLOR 13
GLINE 0,96,255,96,2
GLINE 128,0,128,191,2
GPAGE 0,0,0SPANIM 0,4,8
X=K/11M=120N=90S=0T=0F=0H=0L=0W=0Z=0B=0E=0
BGMPLAY K+6FOR I=0TO 959
X=(X*479232+I)%4096/4096R=0OR X*240
IF!F*!(R%6)THEN H=30-K:U=(R%5+3)*(14-K)V=R
Q=(I>854)*9-!(I%4)*2H=H-1F=H>0
GCOPY 0,0,0,255,191,0,2,0
GCOPY 2,0,4+Q,255,5+Q,0,0,0
IF F*(I<855)THEN GCOPY 2,0,6+H%5*2,U,7+H%5*2,V,0,0
D=GSPOIT(M-8,N)+GSPOIT(M-8,N+9)+GSPOIT(M+8,N)+GSPOIT(M+8,N+9)
IF !L*(D<200)THEN BEEP 6M=999I=M
SPSCALE 0,100+L*0.8
SPSCALE 1,100-L/3E=999-E
SPOFS 1,M+E,N+4SPOFS 0,M,N-L
P=T:T=TCHST/45A=B:B=BUTTON()C=B%16B=16AND B
M=M+!C*T*(TCHX-128)+!T*((C-C%4+2)%6-2)
N=N+!C*T*(TCHY-96)+!T*((C%4*4+2)%6-2)
S=S+!L*(0OR (201-N)/10)S$=RIGHT$("0000"+STR$(S),5)
IF!L*((P-T)*!T+(A-B)*B)THEN G=10BEEP 8L=G
IF L THEN G=G-0.5L=L+G
LOCATE 0,0?"STAGE "K" HI "H$(K)" SC "S$
O=M>300VSYNC 2NEXT
TALK "@E9@S9"+MID$("ヤッタネ!ウワアア-",O*5,5)WAIT O*60BGMPLAY O+5
LOCATE 14,12?MID$("CLEARMISS!",O*5,5)
IF S$*!O>H$(K)THEN Z=1H$(K)=S$
COLOR 1WAIT 30?" "*227"PUSH [L] or [R] or [START]
FOR B=1TO 256B=BUTTON()WAIT 5COLOR MAINCNTL%16
IF!O*(K-Y)==8THEN LOCATE 8,5?"Congratulations !"" "*115"Thank you !
IF Z THEN LOCATE 12,16?"HI-SCORE!
NEXT:IF B>1024THEN @ST ELSE W=2K=K+!O*(K<9)GOTO @RT
 QRコード(ファイル名:OCHAJUMP)

 上から見下ろしの縦スクロールということで一次元的な横スクロールとは異なり足場が二次元に配置されています。これによって攻略のための選択肢が非常に増えているということです。どの島からどの島にジャンプするのかで難易度が変わるし得られるスコアも変わります。そのため「クリアするだけならば簡単にできる」という人であってもスコアアタックが非常に熱くなっています。

 このようなスクロールジャンプアクションゲームの場合に重要になるのは「スクロール処理」「(ジャンプ処理を含めた)自キャラの挙動」「当たり判定」「ステージ構成」(乱数によるステージの場合はステージ生成)でしょう。

 さて、実際にリストを見ていくことにします。
 1画面プログラム並のリスト短縮をしているため分かりにくい部分もありますが、それにおいては順を追って解説していきます。

 まずはメインとなるステージデータ生成とスクロール処理についてです。
 BG画面ではなくGCOPYのスクロールを行う理由は何度も書いているように処理が簡略化されるため短いリストで済むというメリットが挙げられます。そして、ドット単位でステージを構成することが可能になるというのもグラフィック面を使うメリットになるでしょう。
 ドット単位でステージデータを構成するとなるとデータ量は飛躍的に大きくなるため疑似乱数によってデータを削減しています。とはいえ、この疑似乱数は再現性があり、1677万通りのステージを生成可能であるためその中からベストなものを選択すれば疑似乱数だから攻略性のないステージになるとは限らないし上記のJUMP ACTION GAMEとは異なり上下左右に自由に移動できるため攻略性は高くなっています。(どこに着地すれば良いのかが初見で分かるということが無いため)

 このゲームでは0〜239の240通りの値を返す疑似乱数を元にステージデータを生成しています。ただし、乱数によって単純にステージ生成をした場合には「CAVE」の洞窟のように「いかにも乱数によって生成したステージ」という感じのものになるため意図的にパターン化してあります。例えるならば、レースゲームにおいて乱数で単純に左右のカーブを用意すれば小刻みに変化するカーブにしかならないけど予めいくつかの曲率のカーブを用意してそれを乱数で選ぶようにすれば乱数っぽさが軽減されたコースが生成されるということです。

 後述の当たり判定の所で書いているように狭い間隔(8ドット以下)の隙間はジャンプしなくてもそのまま渡れるというのがこのゲームの特徴になっています。上記のJUMP ACTION GAMEでもそれは可能でしたが、ステージデータが一次元的な物でありそのアイデアは十分に生かされていませんでした。JUMP ACTION GAMEでは、1回の乱数で隙間の幅を決めていたために狭い隙間も広い隙間も同程度の確率になっていましたが、このJUMPING ISLANDではメインルーチン1回ごとに乱数を用いて6分の1の確率で次の島が来るようにしています。そのため高確率(約1/2の確率)でジャンプせずに渡れる隙間(島同士のY座標の間隔が8ドット以下)が生成できます。
 そのためJUMP ACTION GAMEとは異なり、ステージ9でさえもジャンプせずに渡れる狭い隙間ができます。このゲームではジャンプ回数を減らすことがスコアアップに直結するためハイスコア競争において「ジャンプせずに渡れる狭い隙間」が生かせるようになりました。
 そして、ジャンプせずに渡れる狭い隙間の生成確率アップしているのと同時に「島は二次元的に配置される」ためにX座標がずれることで広い隙間も自動的に生成できるようになりました。足場が一次元的に配置されるJUMP ACTION GAMEにおいて、JUMPING ISLANDと同じく狭い隙間の確率アップと広い隙間の確保の両立を行うためには「狭い隙間発生フラグ」を別途用意しない限り難しいでしょう。

 ステージが進むごとに難易度を上げるためステージごとに島の縦幅を狭くして(横幅は乱数で変わってきますが縦幅は同一ステージでは一定となっている)、横幅が狭くなるようになっています。島が小さくなれば単純にそこに着地するのが難しくなるというだけではなく島同士のX座標方向の距離の期待値が大きくなり、X座標がずれることでY座標方向の距離の期待値も大きくなります。これは、要するにステージが進むにつれて島同士の距離が離れやすくなるということです。(ただし、この場合においても上記のように狭い隙間は高確率で発生するようになっている)
 そのためクリアできるかどうかはX座標、Y座標方向の両方を考慮する必要があります。このJUMPING ISLANDでは生成時にはクリア可能かどうかの判断をプログラム内では行っていません。これはリスト短縮のためというのもありますが、島が二次元的に配置されるためクリアまでは複数のルートが用意できるのでクリアできないルートを意図的に入れるためです。そういうルートを自動的に入れるのは難しいため生成されたステージを複数試して乱数の初期値や島の生成計算式を変えながら調整しています。(クリア不能なルートと思っていた部分がプレイを重ねることでぎりぎりクリアことが分かりハイスコアルートに一転したこともあるけど)

クリア不能なルートを意図的に入れる

1回のジャンプで5ブロック分の距離ジャンプできる横スクロールゲームの場合


@=自キャラ、■=ブロック、□=ブロックのない隙間

上記では乱数によるステージ生成をした場合にはジャンプ可能な幅の隙間であってもクリア不能になる場合があるということを書きました。
では、次の場合はどうでしょうか?

@                    ゴール
■■■■□□■□■■□■■□■□□■■□■■■■

     ↓ ↓ ↓ ↓

   @12345
■■■■□□■□■■□■■□■□□■■□■■■■

     ↓ ↓ ↓ ↓

         @12345
■■■■□□■□■■□■■□■□□■■□■■■■

     ↓ ↓ ↓ ↓

              @12345
■■■■□□■□■■□■■□■□□■■□■■■■

     ↓ ↓ ↓ ↓
                   lll
■■■■□□■□■■□■■□■□□■■@■■■■
                   ウワァー
ということで、クリア不能に見えます。
しかし、これは最初のジャンプ位置が悪かったのです。
Aの位置からではなくBの位置からジャンプしてみましょう。

 @12345
■■■■□□■□■■□■■□■□□■■□■■■■
 B A

     ↓ ↓ ↓ ↓

      @12345
■■■■□□■□■■□■■□■□□■■□■■■■

     ↓ ↓ ↓ ↓

            @12345
■■■■□□■□■■□■■□■□□■■□■■■■

     ↓ ↓ ↓ ↓

                 @12345
■■■■□□■□■■□■■□■□□■■□■■■■

     ↓ ↓ ↓ ↓

                       @
■■■■□□■□■■□■■□■□□■■□■■■■

見事クリアできました。
Aの位置ではなくBの位置からジャンプすることがこのステージのクリアには不可欠だったというわけです。

しかし、再現性のない乱数によるステージ生成ではやり直しができないためこういったステージはクリア不能になってしまいます。
最初から3回ジャンプした先まで見通せる場合にはクリア不能にはなりませんが、ステージが表示されて瞬間的に数回ジャンプした先の座標をすべて把握して「AではなくBの位置からジャンプしなくてはならない」と判断するのは極めて困難であり、スクロール速度によってはクリアできるかは運任せといえるでしょう。

これが再現性のある疑似乱数での生成ならば何度かプレイしていくうちに「AではなくBでジャンプしなくてはならない」ということが分かるかもしれません。こういう「一見クリア不能に見えるルート」を意図的に入れるということはゲームの攻略性アップに繋がると思います。

ただし、これを乱数によって自動生成するのは簡単ではありません。

 ステージごとの島の横幅や縦幅の計算式は何度もテストプレイしてベストだと思うものを選択しました。ステージ1においてはジャンプせずに渡れる島が多いことや前後移動をせず左右移動+ジャンプのみでクリア可能な簡単さを備えておりチュートリアル的な要素が高くなっていると思います。簡単ではあるけど360度自由に動けるゲームシステムによってどの足場に飛び移るのがベストかという選択が可能になるのと同時にハイスコア競争に高い攻略性が出てくるようになっています。

 このゲームでは上記のようにパターン化したステージを生成しており、それをマップチップを使って描画しています。このマップチップはGFILLで描画された矩形上に一定パターンでGPSETでドットを表示することで生成しているためIF文を使えば簡単に処理できるのですが、ここではリスト短縮のため剰余を使ってパターン生成しています。例えば海の波の形は9行目を見てのようにGPSET I,723%(I%5+4),15で生成できます。この723%(I%5+4)はIの値が大きくなるごとに3、3、3、2、3の値を繰り返します。これで、波の跳ねた部分を別に描画する場合にIF文1つとGPSET1つが省略可能になります。
 残り2つのGPSETも地面のパターン描画用にリスト短縮のために使っています。
 このマップチップはページ2に描画しており、メインルーチン内でそれをGCOPYしながらリアルタイムでステージ生成をしています。たくさんのマップチップを用意すればそれだけ見た目のバリエーションは豊富になるのですが、リスト短縮を行うため必要最小限のもののみ用意しています。
 タイトル画面の背景をスタート時のステージデータを兼ねているためそれでリスト短縮も行われています。

 縦スクロール処理は基本的には上記の「JUMP ACTION GAME」と何ら変わりません。ページ0の画面をY座標を2ずらしたものをコピーすることで2ドット単位の縦スクロールが行われています。そして、開いた2ドット幅のライン上に予め用意しているマップチップを予め計算されたパターン通りにGCOPYで貼り付けていけばスクロールルーチンの完成です。2ドット単位のスクロールなのでマップチップは2の倍数ドットで構成されています。こうすることでマップチップを貼り付ける際の処理が簡単になります。
 もしも、マップチップのサイズとスクロール量が異なるゲームの場合(もしくはスクロール量が可変式の場合)はゲーム画面のサイズを実画面より小さくしておけばマップチップを貼り付ける際の処理が簡単にできます。例えば、上下左右にスクロールが可能なゲームにおいてスクロール量が2ドットでマップチップのサイズが8x8ドットならばマップチップのサイズ分だけ上下左右の表示サイズを縮小して240x176ドットで表示を行うとよいです。その場合はBG画面もしくはコンソールでマスクをして余分な部分を見えないようにする必要があるでしょう。
 もっとも、8x8ドットのマップチップを使うならばGRPよりBGの方が遙かに楽になりますが。

 続いて、キャラの挙動について書いていきます。このゲームではタッチパネルとボタン操作のどちらでも動かすことができますが、まずは分かりやすいボタン操作の方から書いていきます。

 ボタン操作の場合は8方向に移動できるのですが、これは"第2回で書いたようにBUTTON関数の値をビット演算ANDを使って上下左右各方向において別々にIF文で判定すれば簡単にできます。しかし、ここでは私が考えた(恐らく最も短いであろう)8方向移動ルーチンを採用しています。(リストの41〜42行)

剰余を使った場合
普通に論理式を使った場合
X座標方向の移動量(C-C%4+2)%6-2((C AND 8)==8)*2-((C AND 4)==4)*2
Y座標方向の移動量(C%4*4+2)%6-2((C AND 1)==1)*2-((C AND 2)==2)*2
 ※変数CにはBUTTON()%16の値が入っている

 これで縦横2ドット単位の移動が可能になります。これはすでにTipsコーナーの剰余を使ったリスト短縮の方法で書いていることなので詳しくはそちらを見てください。

 このゲームはボタン操作よりもタッチパネルの操作でのプレイを前提にしてバランス調整を行っています。タッチパネルだと360度自由な速度で動かすことが可能なのに対してボタン操作だと移動方向は8方向までだし、移動量は固定(2ドット)となっているので微妙な動作ができないためスコア競争では不利だし、斜め30度ジャンプとかできないので一部のステージでは難易度が激増してしまいます。
 ただし、このゲーム自体の難易度はそこまで高くしていないのでボタン操作でも全ステージクリアできるため問題ありません。

 タッチパネルで上下左右に動かす場合はタッチする座標の中心点を決めてそれから上下移動量が2になるように設定すればいいだけなので実はボタン操作よりも簡単にプログラムを作ることができます。中心点のY座標が96ならば96からタッチされたY座標を引いてそれを48で割ればいいからです。つまり、(96-TCHY)/48がY座標の移動量になるということです。
 しかし、このゲームでは48ではなく45で割っています。というのも、タッチパネルで素早く操作した場合にはギリギリ端を押すというのは結構難しいからです。このゲームではブレーキ操作(スクロールの速度に合わせてキャラ移動をさせることでキャラをマップ上の同一位置に止めておく)を頻繁に行うため2ドット後ろへの移動の頻度が極めて高いためです。これが2ドット分に満たない移動量であれば崖っぷちで停止しているつもりなのに海の上に真っ逆さまになってしまうため45で割ることによって6ドット分の余裕を設けています。

 続いてジャンプ処理について書いていきます。ジャンプをする場合には上記のJUMP ACTION GAMEのようにジャンプフラグを用意してジャンプ中にジャンプ操作をしても無効になるようにするのが一般的でしょう。しかし、このゲームの場合は地面はすべて平坦(Z座標0)であるためZ座標が0の時のみジャンプを可能にしています。それでは海上に落下した場合でもZ座標が0ならばジャンプ可能になってしまうのですが、ジャンプ判定より足場判定を先に行っているためその問題はありません。

 このゲームではジャンプの高さを表現するためにSPSCALEを用いてキャラの大きさを変えているのですが、この表示位置と拡大率の関係は第5回にも書いているので省略します。ただ、横スクロールゲームとは異なりこのゲームの場合はキャラがジャンプしてしまうと落下位置が分からなくなってしまいます。そのため影を表示していてそれがキャラの落下場所を示すため問題なくプレイできます。この影はキャラ本来の座標からマイナス4の座標に表示しています。これは影の縦幅を8ドットして、その中央がちょうとキャラの足下にするためです。
 キャラの影は、オリジナルのキャラ定義をして使用する必要がありそうですが、スプライト番号200番のグリーンスライムをグレーで表示したら影で使ってそれほど違和感がなさそうだったので少しでもリスト短縮をするためキャラ定義はしていません。もしも、「影に違和感がある」という人は初期設定に下記のリストを追加して影のキャラ(グレーの楕円形)を定義するといいでしょう。このキャラ番号ならば他の部分の変更は一切不要です。

 CHRSET "SPU3",32,"0"*64
 CHRSET "SPU3",33,"0"*64
 CHRSET "SPU3",34,"000DDDDD00DDDDDD0DDDDDDDDDDDDDDDDDDDDDDD0DDDDDDD00DDDDDD000DDDDD"
 CHRSET "SPU3",35,"DDDDD000DDDDDD00DDDDDDD0DDDDDDDDDDDDDDDDDDDDDDD0DDDDDD00DDDDD000"


 問題は影を普通にスプライトで表示すると影がすごく目立ってしまうため一般的なゲームのように半透明で影を表示したいところです。残念ながらプチコンでは半透明処理を行うことはできませんが、1フレームごとに影の表示と非表示を繰り返せば擬似的に半透明処理が可能になります。こういった手法はかつて8ビット機でよく使用されていました。
 この1フレームごとに影を表示するためには影のキャラと空白キャラをSPANIMを使って交互に表示する(具体的にはSPANIM 1,2,2とする)のが最も簡単でしょう。そのためには影のスプライト番号の次に空白キャラを作成する必要があります。上記の定義した影データを流用するならばSPU3のキャラ番号36〜39に"0"*64を定義するとよいです。
 ただ、少しでもリスト短縮をするために影のキャラ1フレームごとに画面内と画面外の表示に切り替えることで空白キャラをわざわざ定義しなくても済むようになりました。これはリストの39行にあるようにE=999-Eという処理によってフレームごとに本来表示すべき座標と999ずらした座標になるようにして実現しています。別に999でなくてもいいのですが、255とかだと下手をすると消すつもりの影が画面内の隅の方に残ってしまう恐れがあるためそれが100%あり得ない999という値にしています。
 これくらいならキャラ定義をしても良かったのですが、当初は1画面プログラムとして作っていたため少しでもリスト短縮を行いたかったのでこのような形をとっています。(1画面プログラムでありながら全9面の攻略性の高いステージを用意し、ステージ1はチュートリアル的な要素を高め、表示においても拡大によるジャンプ表現や影の擬似半透明処理まで行っています)

 次は当たり判定です。このゲームの場合は判定すべきなのは足下が地面か否かということだけです。BG画面ではBGREAD関数によって指定座標のキャラを分かりそれを元に判定が可能になるのですが、グラフィック面ではGSPOIT関数を使用するしかありません。もちろん配列変数にマップチップのデータを入れてそれを元に判定を行うことは可能なのですが、お手軽さが無くなるだけではなくドット単位でマップチップを置くことができるためデータ量が非常に多くなってしまうという問題があります。
 色によって当たり判定を行う場合には当たり判定の必要性があるマップチップには他所で使用していない色を使う必要があります。このゲームでは足場のみ当たり判定を用意しているため足場は他所で使用していないCOLOR 200番台の色を使って描画しています。

 さて、問題は色で判別が可能になってもキャラのどの部分を判定させるかという問題があります。このゲームでは、キャラの下半身となる矩形部分の四隅において当たり判定を行っています。それならば真ん中だけ足場に乗っている場合は当たり判定で足場がないことになってしまうのですが、このゲームにおいては島の縦幅はすべてキャラの縦幅よりも長く島の横幅は最小となるステージ9でもキャラの横幅ー1であるため四隅のいずれかは確実に足場に乗っているため問題はありません。
 もしも島の横幅がキャラの半分まで狭くなる場合にはキャラの中央部分の当たり判定が必要になるため「少しでもキャラが地面に乗っていればセーフ」というのを実現するためには最低でも合計6カ所での判定が必要になってきます。1ドット単位のサイズの足場に対応するためには当たり判定のある矩形に隣接したすべてのドットにおいて調べる必要がありますが、ゲーム内で不要な処理をわざわざやる必要はありません。とはいえ、ゲームの仕様が定まっておらずあらゆる状況に対応する場合には思わぬ判定漏れがないためには想定される判定をすべて行う必要もあるためこれはすでに仕様が定まっているゲームのみに言えることだと思います。

 また、このゲームでは9x16ドットの矩形で判断しているため島と島に8ドットの隙間があってもジャンプしなくても落下せずに通り抜けることが可能になっています。このようにマップチップをドット単位で配置可能であるというGCOPYによるスクロールのメリットを生かしたゲームシステムになっています。8ドット、もしくはそれ以下の隙間を通過するだけならば9x16ではなく8x16の矩形で判断しても良いのですが、9x16とすることで上手く操作すれば普通には通過できない10ドット幅の隙間も通過が可能になっているためより攻略性が増しています。

 上記のようにこのゲームでは唯一の足場となる地面にはGCOLOR 200番台の色が使用されているために4カ所の当たり判定を行う場合それぞれのGSPOITの値が変数A〜Dに入っている場合には下記のように4つの条件式での判断が必要になります。

  IF A>200 OR B>200 OR C>200 OR D>200THEN 〜

 これで、キャラの当たり判定に使用しているA、B、C、Dの4カ所のうちいずれかが足場に乗っているかどうかが判断できます。
 しかし、他所で使っている色の値は15(白)が最高であるため4つの条件式で判定をすることなく1つの条件式で判定が可能なのです。

  IF A+B+C+D>200 THEN 〜

 A+B+C+Dの値が200を超えるのは4カ所の当たり判定A、B、C、Dのいずれかが足場にあるときです。これはこのゲームの場合は地面以外に足場がないので地面かそれ以外かを判別さえできればいいためです。これが特殊な障害物が用意されているようなマップだとこのようにはいきません。

 あと このゲームにはハイスコアセーブ機能が付いており1行目、2行目はハイスコアデータの読み込み部分です。詳しくはTipsコーナーのセーブ機能を付ける方法を参考にしてみてください。

 拙作「JUMPING ISLAND」のリストについて解説してきましたがいかがでしたでしょうか。
 元々はGCOPYによるスクロールのテストから発展したゲームですが、疑似乱数によるステージ自動生成をしつつ自キャラを自由に移動やジャンプができるゲームを作ろうということで出来た横スクロールジャンプアクションゲーム「JUMP ACTION GAME」が思った以上につまらないものになってしまいました。「固定画面で自由なジャンプができるゲーム」「ランダムステージ生成による横スクロールで2種類のジャンプを使い分けるゲーム」(JUMP ACTION GAME2のようなもの)はポケコンで作った経験はあったのでどんなゲームになるのか分かりましたが、面白いアイデアをミックスすればさらに面白くなるという単純なものではありません
 1つのゲームに様々なアイデアを盛り込む場合にはそれらが打ち消し合って逆につまらなくなってしまう場合があるので「思ったより面白くない」と感じたらそのアイデアの中でどれが重要なのかを考えて取捨選択したり、面白さを無くしている理由は何かという点について考えたりする必要があります。理由もなく面白くないゲームというものは存在しません。

 JUMPING ISLANDは「ランダム生成によるステージスクロール」「自由にキャラ移動やジャンプできる」という要素を持ったゲームとしてはそこそこ攻略性の高いものになったと思います。とはいえ、足場が島に変わっただけであって、やっていることは最初に作ったJUMP ACTION GAMEと変わらないのです。「作ってみたら今ひとつだった」というアイデアもちょっと視点を変えて作り直してみれば全く別物になるという1つの例になるでしょう。

 さて、長々とGCOPYのスクロールについて書いてきましたが、スクロールさせるならばBG画面の方が簡単という人もいるかもしれません。しかし、それは慣れの影響もあるでしょう。
 GCOPYによるグラフィック面のスクロールは2ドットずらしてコピーするだけで2ドットのスクロールができるため初心者ならばBG画面のスクロールより直感的で分かりやすいのではないかと思います。1画面で作られているプチコン100m走mkIIもあらかじめ下記のようなデータを生成しておけばそれをサンプルリスト4のように左右を繋ぎ合わせることで簡単にスクロールが可能になります。

プチコン100m走mkIIのデータ

 このデータそのものはグラフィック命令とGPUTCHR命令を使っているだけなので誰でも簡単に生成することが可能です。(ただし、上記のプチコン100m走mkIIでは普通に作れば12行かかる処理を8行に短縮しているためかなり変則的な処理を行っている)

 そして、今回作ったようにマップチップがキャラ単位で配置されないようなゲームを作る場合にはBG画面ではかなり難しくなると思います。確かに、予めドット単位でずらしたキャラを定義しておいてそれを上手く組み合わせればBG画面においても擬似的にドット単位でマップチップを置くことは可能ですが、そうなると必要データ数は莫大に大きくなるだけではなく当たり判定の処理も煩雑になってしまうという問題が発生してしまいます。

 例えばJUMPING ISLANDをBG画面を使って同等のものを作るためには最低でもキャラを136個定義する必要があり1つのキャラデータを64バイトとするとキャラデータだけでJUMPING ISLANDのリスト全体の約5倍のサイズになります。さらにその136種類のマップキャラと自キャラの4カ所との当たり判定を行う必要があるため当たり判定を1行にするのは不可能になります。
 BG画面では擬似的にキャラをドット単位で配置できても当たり判定はキャラ単位でしか行うことができないため表示よりもむしろ当たり判定の方が厄介になるでしょう。BGREADを使用するよりもステージデータを配列変数に入れてそれを元に当たり判定を行うか、余っているグラフィック面にBGキャラをGPUTCHRでコピーしてそれを元にGSPOITによって当たり判定を行う方が楽になるかもしれません。
 こう考えるとJUMPING ISLANDはGCOPYによるスクロールだからこそ簡単に実現できたゲームといえるかもしれません。(JUMPING ISLANDは慣れたら簡単にクリアできるため後日ハカセジャンプで導入したライフ消費式ジャンプシステムを使った「JUMPING ISLAND MANIX」も作ってみた)

 ただし、GCOPYで画面全体の過半数の部分を描画する場合にはそれだけで1フレーム分くらいの処理時間が必要になってきます。マップチップがキャラ単位の配置で問題ない場合はBG画面の方が桁違いに高速スクロールが可能だし、多方向のスクロールはGCOPYだと煩雑になるのでそういう場合もBG画面の方が有利になります。
 したがって、BG画面とGCOPYのどちらが良いかはケースバイケースになると思いますが、今回書いたようなグラフィック面をスクロールさせる方法もあるということは知っておくと便利なことも多いでしょう。

 →第8回へ進む


RETURN

inserted by FC2 system