【mkII専用】
乱数によるステージ生成はクリア不能になる場合がある 1回のジャンプで5ブロック分の距離ジャンプできる横スクロールゲームの場合 @=自キャラ、■=ブロック、□=ブロックのない隙間 このように隙間が4ブロック以下になるようにステージを生成すれば確実にクリアできそうです。 @12345 ←スクロール □□■■■■□□□□■■□■■□□□■ ↓ ↓ ↓ ↓ 自キャラがジャンプ中 @ ■■■■□□□□■■□■■□□□■■■ ↓ ↓ ↓ ↓ ところが・・・ @ AB ■□□□□■■□■■□□□■■■□□□ 012345 012345 この場合は、AもしくはBの位置に着地するしかないためクリア不能になっているのです。 これを避けるためには、一端ステージを生成した後にA、もしくはBの位置がブロックになるように生成しなおせば良いですね。 これは「5ブロック前に足場があり、4ブロック前に隙間がある」というように判定を行えば簡単に実装が可能です。 しかし、これだけでは隙間の直前ギリギリでジャンプすれば常に足場に着地できるため足場の状態によって条件式を随時変える必要があります。 ただし、それでもジャンプの飛距離や画面に出てくるブロックの数やブロックの幅やスクロール速度によってはブロックが出てきた瞬間に判断しても間に合わなかったり、判断が難しい運任せのゲームになってしまう場合があります。 「運任せ」にしないため十分な長さの足場を確保した場合には簡単すぎたり、攻略性の乏しいステージになってしまいます。 このように様々な問題を抱えている乱数による自動生成ですが、これも再現性のある疑似乱数によってステージ生成をする場合は、下記のように意図的にクリア不能にして攻略性を高めるという方法もあるため「自動生成だから攻略性の低いステージしかできない」というものではありません。 |
@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 |
足場の幅 | 隙間の幅 | |
ステージ1 | 22〜32ドット | 8〜98ドット |
ステージ9 | 6〜16ドット | 72〜162ドット |
コラム 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倍高速になります。しかし、コンソール、スプライト用のパレットとグラフィック用のパレットが異なっているためにこのような問題があり、使用する際にはそれに関して知っておかないとゲームを作る際にも思わぬ誤動作を招いてしまうため注意する必要があります。 |
@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 |
足場の幅 | 隙間の幅 | |
ステージ1 | 28〜38ドット | 2〜52ドット |
ステージ9 | 12〜22ドット | 18〜68ドット |
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 |
クリア不能なルートを意図的に入れる 1回のジャンプで5ブロック分の距離ジャンプできる横スクロールゲームの場合 @=自キャラ、■=ブロック、□=ブロックのない隙間 上記では乱数によるステージ生成をした場合にはジャンプ可能な幅の隙間であってもクリア不能になる場合があるということを書きました。 では、次の場合はどうでしょうか? @ ゴール ■■■■□□■□■■□■■□■□□■■□■■■■ ↓ ↓ ↓ ↓ @12345 ■■■■□□■□■■□■■□■□□■■□■■■■ ↓ ↓ ↓ ↓ @12345 ■■■■□□■□■■□■■□■□□■■□■■■■ ↓ ↓ ↓ ↓ @12345 ■■■■□□■□■■□■■□■□□■■□■■■■ ↓ ↓ ↓ ↓ lll ■■■■□□■□■■□■■□■□□■■@■■■■ ウワァー ということで、クリア不能に見えます。 しかし、これは最初のジャンプ位置が悪かったのです。 Aの位置からではなくBの位置からジャンプしてみましょう。 @12345 ■■■■□□■□■■□■■□■□□■■□■■■■ B A ↓ ↓ ↓ ↓ @12345 ■■■■□□■□■■□■■□■□□■■□■■■■ ↓ ↓ ↓ ↓ @12345 ■■■■□□■□■■□■■□■□□■■□■■■■ ↓ ↓ ↓ ↓ @12345 ■■■■□□■□■■□■■□■□□■■□■■■■ ↓ ↓ ↓ ↓ @ ■■■■□□■□■■□■■□■□□■■□■■■■ 見事クリアできました。 Aの位置ではなくBの位置からジャンプすることがこのステージのクリアには不可欠だったというわけです。 しかし、再現性のない乱数によるステージ生成ではやり直しができないためこういったステージはクリア不能になってしまいます。 最初から3回ジャンプした先まで見通せる場合にはクリア不能にはなりませんが、ステージが表示されて瞬間的に数回ジャンプした先の座標をすべて把握して「AではなくBの位置からジャンプしなくてはならない」と判断するのは極めて困難であり、スクロール速度によってはクリアできるかは運任せといえるでしょう。 これが再現性のある疑似乱数での生成ならば何度かプレイしていくうちに「AではなくBでジャンプしなくてはならない」ということが分かるかもしれません。こういう「一見クリア不能に見えるルート」を意図的に入れるということはゲームの攻略性アップに繋がると思います。 ただし、これを乱数によって自動生成するのは簡単ではありません。 |
剰余を使った場合 | ||
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 |