3D ダンジョン BASIC 版(後編)

前編では通路/壁だけの最低限の迷路を移動する処理を作りました。 ここまでできれば、あとはコツコツと要素を追加していくだけです (と言っても、完成までは長いですが)。

後編ではプログラムを追加していく形ではなく、 完成版の要点のみ解説していきます。下記リンクが完成版プログラムです。



仕様を詰める

前編ではマップの 3 次元配列 M(Z,Y,X) の値は、「0:未探索、1:通路、2:壁」の 3 つしかありませんでした。 まずはこのあたりを拡張します。


種類 M() の値 DATA マップ表示
未探索 0 (無し) 空白
通路 1 空白 空白
2 *
扉(開き) 3 @
下り階段 4 -
上り階段 5 +
開始地点 6 #
ワープ 7 ?
宝箱(閉じ) 8 $
宝箱(開き) 9 (無し)
扉(閉じ) 33+n A~Z ×
65+n a~z

閉じた扉と鍵はペアで、A~Za~z を対応させます。ただし今回は(表示の都合で)8 個を上限としました。

ワープは一方通行もありで、飛び先の情報は別途 DATA に記述することにしました。 ワープ元座標と飛び先の座標を並べます。-1 で終了です(データの間違いを検出するために記述します)。

4400 DATA 1,8,0,14,11,0
4410 DATA 14,11,0,1,8,0
(中略)
4550 DATA -1



3D 描画の追加

配置物を増やしたので、それぞれの 3D 形状をデザインする必要があります。1000 行以降に描画処理を追加しつつ、DATA を一時的に書き換えながらテストします。

なおデザインの都合上、 扉は必ず壁に挟まれているものとします。 このような制限はなるべく増やしたくないのですが、時には妥協も必要です。


画像 01 画像 02 画像 03
画像 04 画像 05
画像 06 画像 07 画像 08
画像 09 画像 10 画像 11
画像 12 画像 13
画像 14 画像 15



描画の高速化

3D 描画では LOCATEPRINT をひたすら並べるよりも、PUT@ を使うと比較的高速に描画できます。 ただし矩形範囲しか描画できないのと、 配列変数を確保する必要があります。

今回はまず破線の描画に使用しています。

3000 COLOR 7,0,0:PRINT CHR$(12):LOCATE 12,12:PRINT"Wait a moment":LINE 12,1:COLOR 0
3010 LINE(9,0)-(9,16),"█":FOR Y=1 TO 15 STEP 2:LOCATE 9,Y:PRINT"▉":NEXT:GET@(9,0)-(9,16),WB

整数型の配列 1 要素に 2 文字格納できるので、サイズは「総文字数÷2」を切り上げして求めます。上記の破線は 17 文字なので、必要な要素数は 9 です(DIM で指定する値は最後の要素番号なので DIM WB(8) とします)。

なお、この配列には幅や高さの情報が格納されていません。 そのため PUT@ 時に高さを小さくすることで、上部だけを描画することができます。 これで破線の長さを調節しています。

同様に、いろいろなパターンを描画して GET@ しています(3020~3090 行)。3000 行の COLOR 0 を削る(もしくは COLOR 7 にする)ことで様子が見えます。




他の表示物

画面左下/右下の表示に関する部分を、一部抜粋します。

3110 PX=1:PY=1:PZ=0:PD=2:MC=0:KY$="♠♥♦♣●○*#":TR=0:TF=0:LG=0:EV=0

3170 LOCATE 21,18:PRINT"Moves:"
3180 LOCATE 21,19:PRINT"Key:":GOSUB 2060
3190 LOCATE 21,20:PRINT"Treasure:":GOSUB 2080
3200 LG=1:LOCATE 1,21:PRINT"タカラモノヲ ミツケテ モチカエロウ!"

2060 LOCATE 26,19:FOR I=1 TO 8:IF KY(I-1)<>0 THEN PRINT MID$(KY$,I,1);ELSE PRINT" ";
2070 NEXT:RETURN
2080 LOCATE 30,20:PRINT TR:RETURN

40 LOCATE 27,18:PRINT MC:ON PD GOTO 60,70,80

250 PX=XX:PY=YY:MC=MC+1:IF MC>9999 THEN MC=9999



マップ描画変更点

配置物の種類が増えたので、マップの表示を拡張しています。

2000 A=M(Z,Y,X):IF A<>0 THEN RETURN
2010 A$=MID$(M$(Z*16+Y),X+1,1):A=INSTR(" *@-+#?$",A$):IF A=0 THEN A=ASC(A$)-32
2020 M(Z,Y,X)=A
2030 LOCATE X+21,Y+1:IF A<32 THEN PRINT MID$(" █ロv^#?$$",A,1):RETURN
2040 PRINT MID$("╳!",A\32,1):RETURN
2050 A=M(Z,Y,X):GOTO 2030

配列 T にマップをコピーする処理も、多少変更しています。

40 LOCATE 27,18:PRINT MC:ON PD GOTO 60,70,80
50 XX=-1:XY=0:C=30:GOTO 90
60 XX=0:XY=-1:C=28:GOTO 90
70 XX=1:XY=0:C=31:GOTO 90
80 XX=0:XY=1:C=29
90 X0=PX-XX:Y0=PY-XY:Z=PZ:FOR I=0 TO 2:IF X0<0 OR X0>15 OR Y0<0 OR Y0>15 GOTO 130
100 X=X0:Y=Y0:FOR J=0 TO 2:IF I<>2 GOTO 110 ELSE A=T(1,1):B=T(1,J):
    IF(A=2 OR(A AND 96)=32)AND(B=2 OR(B AND 96)=32)THEN T(I,J)=0:GOTO 120
110 GOSUB 2000:T(I,J)=M(Z,Y,X)
120 X=X+XX:Y=Y+XY:NEXT
130 X0=X0-XY:Y0=Y0+XX:NEXT:POKE&HF3A4+PY*120+PX*2,C:GOSUB 1000



移動処理変更点

BASIC では描画処理が重いので、特に 8 キー押しっぱなしで前進したいところです。INKEY$ ではキーの押しっぱなしを判定できないので、INP に変更しました。

150 IF TF=1 GOTO 190
160 A=INP(0):IF A=239 THEN PD=(PD+3)AND 3:GOTO 40
170 IF A=191 THEN PD=(PD+1)AND 3:GOTO 40
180 IF A=251 THEN PD=(PD+2)AND 3:GOTO 40
190 IF INP(1)<>254 GOTO 150
200 TF=0:XX=PX+(PD=3)-(PD=1):YY=PY+(PD=0)-(PD=2)

なお、変数 TF は宝箱を開けたときに 1 になり、前進して宝物を取ることを強制しています。

引き続き、8 キーが押された場合の処理です。

210 A=M(PZ,YY,XX):IF A<32 THEN B=A ELSE B=A\32+9:A=A AND 31
220 ON B GOTO 240,280,240,300,300,300,300,360,300,460,300
230 PRINT"Data error":STOP
240 X=PX:Y=PY:Z=PZ:GOSUB 2050

300 EV=B:GOTO 240

1 歩進んでから処理を行うもの(階段、ワープなど)の場合は、変数 EV に処理番号をセットしてから前進します。そして前進後、

140 ON EV GOTO,,,310,320,330,370,440,450,,480

ここでそれぞれの処理に分岐します。

前編では階層の移動がありませんでしたが、 階層を移動するとマップの全体書き換えが必要になります。1 マスずつ書き換えるのは非常に重たいので、ここでも GET@/PUT@ を利用します。

410 IF PZ=0 THEN GET@(21,1)-(36,16),M0 ELSE GET@(21,1)-(36,16),M1
420 PZ=TZ:IF PZ=0 THEN PUT@(21,1)-(36,16),M0 ELSE PUT@(21,1)-(36,16),M1

階層ごとに別々の配列変数が必要なのが、やや難点です。 この処理は階段だけでなく、階層をまたぐワープでも使用しています。

ちょっと横道にそれますが、一つハマった点があるので書いておきます。

370 EV=0:GOSUB 2140:RESTORE 4400
380 READ SX:IF SX<0 THEN PRINT"Data error":STOP
390 READ SY,SZ,TX,TY,TZ:IF PX<>SX OR PY<>SY OR PZ<>SZ GOTO 380

これはワープ時の処理で、DATA から該当する座標を探しています。 ここでは解説用に見やすくスペースを挿入していますが、実際には、

390 READSY,SZ,TX,TY,TZ:IFPX<>SX ORPY<>SYORPZ<>SZGOTO380

こうなっています。なぜ SX の後ろだけスペースを入れているかというと、入れないと IF PX<>S XOR と解釈されてしまうのです。 当然ながら意図した挙動をせず、しばらく悩みました。ご注意ください。




効果音

最後に、発音サブルーチン群です。

2090 BEEP 1:BEEP 0:RETURN
2100 FOR I=1 TO 10:GOSUB 2090:NEXT:RETURN
2110 FOR I=1 TO 30:GOSUB 2090:NEXT:RETURN
2120 FOR I=1 TO 3:GOSUB 2090:FOR J=1 TO 500:NEXT:NEXT:RETURN
2130 FOR I=1 TO 10:GOSUB 2090:FOR J=1 TO 100:NEXT:NEXT:RETURN
2140 FOR I=1 TO 3:GOSUB 2090:FOR J=1 TO 150:NEXT:NEXT:RETURN
2150 FOR I=1 TO 5:BEEP 1:FOR J=1 TO 100:NEXT:BEEP 0:FOR J=1 TO 100:NEXT:NEXT:RETURN

BEEP 1/0 を適当な間隔で実行しているだけです。BASIC だと遅くて、たいしたことはできないですね。




手前からの描画

オマケとして、3D 部分を手前から描画するバージョンも参考までに載せておきます。

毎回全体を消さずに、必要な部分だけを描き換えているので、 かなり複雑な処理になっています(描画を省いたところも一部あります)。

バージョン 1/バージョン 2/マシン語版の比較動画を用意しました。


どうですか?マシン語化したくなるでしょう?



マシン語版(前編)へ続く


inserted by FC2 system