プチコン3号 26日目 擬似3D(3) 一人称視点・三人称視点

pc26-1.jpg

今回は前回のプログラムのバリエーションとして、視点がスクリーンより手前にある場合、およびスクリーンより奥にある場合のプログラムを作ってみます。
前者はいわゆるFPS(First Person Shooting)ゲーム、後者はRPGなどキャラクタが画面中央に居るタイプのゲームなどでよく用いられます。

前回、三次元空間の中で見回す動作を実現するために、座標の回転を行いました。
このとき、回転の中心は下図のように、座標系の原点、すなわちスクリーンの中央としていました。
これは図右のように、カメラを三脚で固定してから水平方向に回転する(パンする)見え方になっています。

pc26-2.jpg

カメラを三脚ではなく、スクリーンを見る人自身に固定する方法もあります。
これは、人が見ている風景をそのままスクリーンに再現しますので、「一人称視点」と呼ばれます。

一人称視点では、回転の中心はスクリーンより手前、台形の2つの斜辺が交わる点になります。
前回のプログラムでは、Zが0から2000まで変化するとき、スクリーンの幅が400から2000まで変化していましたので、計算すると斜辺が交わる点=スクリーンの幅が0になる点=スクリーンの手前500の位置であることが分かります。

pc26-3.jpg

前回のプログラムを、一人称視点に変更すると以下のようになります。
赤い部分が変更点です。

DEF ROTATE X,Y,T OUT X1,Y1
X1=COS(T)*X - SIN(T)*Y
Y1=SIN(T)*X + COS(T)*Y
END

ACLS
DIM X[100],Z[100]
FOR I=0 TO 99
X[I] = -1800 + 400*(I MOD 10)
Z[I] = -1800 + 400*FLOOR(I / 10)
SPSET I,2067
NEXT

T=0:U=0:V=0:W=0
@LOOP
B=BUTTON(0)
IF B AND 256 THEN T=T-0.02
IF B AND 512 THEN T=T+0.02
IF B AND 1 THEN V=V-10
IF B AND 2 THEN V=V+10
STICK OUT DX,DY
ROTATE DX,DY,-T OUT DX,DY
U=U + DX*10
W=W + DY*10
FOR I=0 TO 99
ROTATE X[I]-U,Z[I]-W,T OUT X1,Z1
SCALE=500/Z1
IF Z1<244 THEN SPHIDE I:CONTINUE
SPSHOW I
IF Z1>500 THEN Z1=500+(Z1-500)/4
IF Z1>1024 THEN Z1=1024
C=127+128*SCALE
SPCOLOR I,RGB(255,C,C,C)
SPSCALE I,12*SCALE,12*SCALE
SPOFS I,X1*SCALE+200,(-V+120)*SCALE+120,Z1-500
NEXT
VSYNC 1
GOTO @LOOP

このプログラムでは、座標系の原点を視点と一致させています。
すると、遠近感を表現するための縮小率の計算は、

 SCALE = 500/Z

という非常に単純な式になります。
スクリーンはZ=500の位置にありますので、Z=500のとき縮小率が1.0になります。

また、スクリーンの位置がZ=500となったのを反映して、Zの値に関する定数がすべて500ずつ増えています。
ただしSPOFS命令ではスクリーンの位置がZ=0ですから、このときは逆にZの値を500減らしています。

なお実際に動かしてみると、左右に見回す動作が少し遅く感じられたので、回転角の変化量を2倍にしています。

一人称視点は、ゲームの空間の中に入り込んだら見えるはずのものがスクリーンに表示されるので、ゲームへの没入感が強調されます。
その一方、自分のキャラクタの周囲の状況を把握しづらいので、ゲームをデザインする上では制約になる場合もあります。

これに対し、三人称視点は、画面内にプレイヤーのキャラクタを表示します。
一人称視点に比べ没入感は減るものの、周囲の状況が把握しやすくなります。

三人称視点の表示では、下図のようにスクリーンよりも奥にプレイヤーのキャラクタを置き、そこが視点の回転軸になります。
回転軸とスクリーンとの距離をPとすると、X=0,Z=Pの位置が回転軸となります。

pc26-4.jpg

前回のプログラムを三人称視点にしたものが以下のプログラムです。
赤い箇所が前回からの変更点です。

この記事の最初に載せた画面は、このプログラムの画像です。
画面中央部に、後ろ向きのプレイヤーキャラクタが表示されています。

DEF ROTATE X,Y,T OUT X1,Y1
X1=COS(T)*X - SIN(T)*Y
Y1=SIN(T)*X + COS(T)*Y
END

ACLS
DIM X[100],Z[100]
FOR I=0 TO 99
X[I] = -1800 + 400*(I MOD 10)
Z[I] = -1800 + 400*FLOOR(I / 10)
SPSET I,2067
NEXT

T=0:U=0:V=0:W=0:P=200
SPSET 100,2556
SPANIM 100,"I+",10,0,10,1,10,2,10,3,0
SPOFS 100,200,206,P/4
SPSCALE 100,5,5
@LOOP
B=BUTTON(0)
IF B AND 256 THEN T=T-0.01
IF B AND 512 THEN T=T+0.01
IF B AND 1 THEN V=V-10
IF B AND 2 THEN V=V+10
STICK OUT DX,DY
ROTATE DX,DY,-T OUT DX,DY
U=U + DX*10
W=W + DY*10
FOR I=0 TO 99
ROTATE X[I]-U,Z[I]-W-P,T OUT X1,Z1
Z1=Z1+P
SCALE=400/(400+1600*(Z1/2000))
IF Z1<0 THEN SPHIDE I:CONTINUE
SPSHOW I
Z1=Z1/4
IF Z1>1024 THEN Z1=1024
C=127+128*SCALE
SPCOLOR I,RGB(255,C,C,C)
SPSCALE I,12*SCALE,12*SCALE
SPOFS I,X1*SCALE+200,(-V+120)*SCALE+120,Z1
NEXT
VSYNC 1
GOTO @LOOP

まず、回転軸の中心のZ座標をP=200と決めています。
その直後にプレイヤーのキャラクタを表示していますが、ここの説明は後回しにします。

ループの中の座標の計算ですが、ROTATEに与えるZ座標は、回転の中心から見た座標ですので、「Z[I]-W-P」となっています。
そして、回転が終わった後、「Z1=Z1+P」で元の座標系に戻しています。
基本的な計算はこれで終わりです。

今回はプレイヤーのキャラクタがスクリーンよりも奥にあります。
そのため、Zの座標値が-256~Pの範囲にスプライトを表示すると、プレイヤーのキャラクタが隠れてしまいます。
これをなるべく減らすため、Z値が0より小さいスプライトは表示しないようにしています。
これでもZ値が0~199の場合はプレイヤーを隠してしまいますが、この範囲に全くキャラクタが表示されないのもかえって不自然になります。

さて、先ほど省略したプレイヤーキャラクタの表示ですが、SPOFSで指定する座標を計算する必要があります。

キャラクタの位置は、スクリーン中央を原点とした座標系で、X=0、Y=120、Z=Pです。
これを遠近感を考慮したスクリーン上での座標(X’,Y’)に変換すると、X’=200、Y’は以下のように求められます。

 SCALE = 400 / (400 + 1600 * (P / 2000))
= 400 / (400 + 1600 * (200 / 2000))
= 400 / (400 + 160)
= 400 / 560

Y’ = Y * SCALE + 120
= 120 * 40/56 + 120
= 205.7142857142857 …約 206

Z値は、他のスプライトの場合と整合させて、三次元空間内でのZ座標の1/4としています。

SPSCALEのパラメータは5倍としました。表示サイズは16×5=80ピクセルになります。
縦のサイズがスクリーンの1/3ですので、表示のバランスとしては、これくらいの大きさが上限ではないかと思います。

プチコン3号 25日目 擬似3D(2) 視点の移動

pc25-1.jpg

今回は、3次元の空間の中を視点が動くプログラムを作ってみます。
上の画面が今回の最終形です。
視点をスライドパッドで前後左右に移動し、十字キー上下で上下に移動し、L・Rボタンで左右に回転します。
物体のほうは水平面上に格子状に等間隔に並べてあり、位置は固定です。

前回は、スクリーンは3次元の立方体の空間の1つの面に固定されていました。
今回はスクリーンが下図のように空間の中を移動します。

pc25-2.jpg

ただし、視点から見た画像をスクリーン上に構成するためには、最終的にはすべての物体の座標を「スクリーンの中央を原点とした座標系」の上で表現する必要があります。
これは、スクリーンがある方向に移動したということを、空間全体を反対方向に移動させて表現するということになります。
つまり「プチコン3号 4日目 背景をスクロールさせる」と同じです。

さらにこれに加えて、視点の回転を表現するために、座標をY軸周りに回転することが必要です。
回転による座標の変換は、三角関数を使います。

pc25-3.jpg

円周の上にある点が、円周上を角度θだけ回転するとき、

 ・座標(x,0)は(x cosθ, x sinθ)に
 ・座標(0,y)は(-y sinθ, y cosθ)に

それぞれ移ります。

座標(x, y)を角度θだけ回転すると、上の2つを重ね合わせた

 (x cosθ-y sinθ, x sinθ+y cosθ)

へ移ります。

以上を使って、まず「2次元平面の上をスプライトが方向転換しながら移動するプログラム」を作ってみました。
ちょうど、3次元の立方体の空間を真上から見ているようなイメージです。
座標系で言うと、XZ平面を表示していることになります。

といっても、実際には普通の2次元のプログラムです。
ただし操作がちょっと変わっています。
△のスプライトを画面内で動かすのですが、動きは「回転+前後左右への移動」で表現します。

L,Rのボタンは、その場でスプライトが左回り・右回りに回転します。
スライドパッドは、上方向が「前」、下方向が「後」への移動です。
つまり、スライドパッドを上に動かすと、そのときスプライトが向いている方向に進むわけです。
スライドパッドの左右は、そのときスプライトが向いている方向に対して、左右へ(向きを変えずに)移動します。

pc25-4.jpg
DEF ROTATE X,Y,T OUT X1,Y1
X1=COS(T)*X - SIN(T)*Y
Y1=SIN(T)*X + COS(T)*Y
END

ACLS
DIM X[100],Z[100]
FOR I=0 TO 99
X[I] = -90 + 20*(I MOD 10)
Z[I] = -90 + 20*FLOOR(I / 10)
NEXT
FOR I=0 TO 99
GPSET X[I]+200,-Z[I]+120
NEXT

SPSET 0,2353
T=0:U=0:W=0
@LOOP
B=BUTTON(0)
IF B AND 256 THEN T=T-0.1
IF B AND 512 THEN T=T+0.1
SPROT 0,DEG(T)
STICK OUT DX,DY
ROTATE DX,DY,-T OUT DX,DY
U=U + DX*5
W=W + DY*5
SPOFS 0,U+200,-W+120
VSYNC 1
GOTO @LOOP

手続きROTATE X,Y,Tは、座標(X,Y)を角度Tだけ回転し、その結果をX1,Y1へ返します。
中で使っている数式は、先ほど説明したとおりのものです。

点を100個用意し、(-90,-90)から(90,90)の範囲に10×10の格子状に並べています。
点の座標は、配列XとZに格納しています。
表示はグラフィック画面上に1度だけ行っています。
なお、座標はXZ平面ですので、原点がスクリーン中央、X軸は左から右、Z軸は下から上に向かいます。

ループの中で、スライドパッドとL・Rボタンでスプライトを操作しています。
スプライトのXZ平面上での座標は、(U, W)で、向き(回転角)がT(単位はラジアン)です。

3次元空間でのY軸は、画面の手前から奥に向かう方向になります。
このとき、Y軸周りの回転の角度は、画面上では時計回りが正となります。
従って、Lボタンを押したときはTを減らし、Rボタンを押したときTを増やしています。

そして、SPROTでスプライトを回転させます。
SPROTの角度指定はラジアンではなく度(DEGREE)なので、関数DEGで変換します。
なおSPROTの角度指定は、時計回りが正です。

スライドパッドから読み取った移動量も、スプライトの向きに合わせて回転させます。
この計算は最初に定義した関数ROTATEを使いますが、この関数では角度指定が反時計回りが正です。
そのため、回転角はTではなく-Tを指定しています。

さて、実際に上のプログラムを動作させてみると、実に操作しづらいです。
スライドパッドを動かす方向と、スプライトが動く方向とが一致しないのだから当たり前です。
このプログラムを、スプライトは位置も向きも固定して、格子点のほうを移動させたり回転させたりするように変更したのが、次のプログラムです。

pc25-5.jpg
DEF ROTATE X,Y,T OUT X1,Y1
X1=COS(T)*X - SIN(T)*Y
Y1=SIN(T)*X + COS(T)*Y
END

ACLS
DIM X[100],Z[100]
FOR I=0 TO 99
X[I] = -90 + 20*(I MOD 10)
Z[I] = -90 + 20*FLOOR(I / 10)
NEXT
SPSET 0,2353
SPOFS 0,200,120
T=0:U=0:W=0
@LOOP
B=BUTTON(0)
IF B AND 256 THEN T=T-0.1
IF B AND 512 THEN T=T+0.1
STICK OUT DX,DY
ROTATE DX,DY,-T OUT DX,DY
U=U + DX*5
W=W + DY*5
GCLS
FOR I=0 TO 99
ROTATE X[I]-U,Z[I]-W,T OUT X1,Z1
GPSET X1+200,-Z1+120
NEXT
VSYNC 1
GOTO @LOOP

格子点の描画をループの内側に移動し、座標はスプライトの位置(U,W)の周りに角度Tだけ回転しています。
回転方向は、スプライトを回転させる場合と逆方向にする必要がありますが、もともとTは時計回りが正であるのに対してROTATEは反時計回りが正ですので、Tをそのまま渡せば逆方向の回転になっています。

さて、以上で準備ができました。
次にこの100個の格子点の座標を、前回のようにZの値に応じて縮小し、遠近感を出してみましょう。

前回はX座標の縮小を図示しましたので、今回はY座標の縮小を図示してみました。

pc25-6.jpg

回転のことを考えなければ、スクリーンはZ=0の平面上にあり、縦方向のサイズは240ピクセルです。
Z=2000のとき、高さ1200ピクセル分がスクリーンの240ピクセルの中に納まることになります。

原点はスクリーン中央、Y軸は下向きが正です。
格子点は、青い点線のところにあるとしましょう。
これはY=120のXZ平面です。

Z=0のとき、この平面はスクリーンの最下部に表示されます。
Z=2000のときは、Y = -600~+600 の範囲がスクリーンに表示されますので、Y=120の平面はスクリーン上では1/5に縮小され、Y=25となります。

当然のことながら、この縮小率はX座標の縮小率と同じ値ですので、Y座標のための特別な計算は不要です。

では、格子点の座標の縮小を行ったプログラムです。
このプログラムで、視点の移動や回転の処理は完成です。

pc25-7.jpg
DEF ROTATE X,Y,T OUT X1,Y1
X1=COS(T)*X - SIN(T)*Y
Y1=SIN(T)*X + COS(T)*Y
END

ACLS
DIM X[100],Z[100]
FOR I=0 TO 99
X[I] = -450 + 100*(I MOD 10)
Z[I] = -450 + 100*FLOOR(I / 10)
NEXT

T=0:U=0:V=0:W=0
@LOOP
B=BUTTON(0)
IF B AND 256 THEN T=T-0.01
IF B AND 512 THEN T=T+0.01
IF B AND 1 THEN V=V-10
IF B AND 2 THEN V=V+10
STICK OUT DX,DY
ROTATE DX,DY,-T OUT DX,DY
U=U + DX*5
W=W + DY*5
GCLS
FOR I=0 TO 99
ROTATE X[I]-U,Z[I]-W,T OUT X1,Z1
 SCALE=400/(400+1600*(Z1/2000))
GPSET X1*SCALE+200,(-V+120)*SCALE+120
NEXT
VSYNC 1
GOTO @LOOP

赤い箇所が変更点です。
前回同様の座標変換処理と、Y軸方向への移動処理を追加しています。

格子点の間隔が、元のプログラムのままでは狭すぎるので、(-450,-450)から(450,450)の範囲に並ぶように変更しています。

視点=スクリーンの中央の点の3次元空間内での位置は、座標(U,V,W)で表されます。
スライドパッドと、上下ボタンでこの座標を移動させています。

前回の記事と同様に縮小率の計算を行い、格子点のX座標とY座標を縮小しています。
縮小前のY座標は、さきほど説明したとおり、Y=120で一定ですが、描画時の座標は視点からの相対座標になりますので、-V+120になります。

それでは、最後に格子点の場所にスプライトを置いて、本格的に擬似3Dにしてみましょう。
スプライトはそのままでは16×16ピクセルと小さいので、12倍に拡大し、Z=0のときに192×192ピクセルになるようにしてみました。
これがこの記事の冒頭の画面を生成したプログラムです。
お花畑の中を散歩してみてください。

DEF ROTATE X,Y,T OUT X1,Y1
X1=COS(T)*X - SIN(T)*Y
Y1=SIN(T)*X + COS(T)*Y
END

ACLS
DIM X[100],Z[100]
FOR I=0 TO 99
X[I] = -1800 + 400*(I MOD 10)
Z[I] = -1800 + 400*FLOOR(I / 10)
SPSET I,2067
NEXT

T=0:U=0:V=0:W=0
@LOOP
B=BUTTON(0)
IF B AND 256 THEN T=T-0.01
IF B AND 512 THEN T=T+0.01
IF B AND 1 THEN V=V-10
IF B AND 2 THEN V=V+10
STICK OUT DX,DY
ROTATE DX,DY,-T OUT DX,DY
U=U + DX*10
W=W + DY*10
FOR I=0 TO 99
ROTATE X[I]-U,Z[I]-W,T OUT X1,Z1
SCALE=400/(400+1600*(Z1/2000))
 IF Z1<-256 THEN SPHIDE I:CONTINUE
SPSHOW I
Z1=Z1/4
IF Z1>1024 THEN Z1=1024
C=127+128*SCALE
SPCOLOR I,RGB(255,C,C,C)
SPSCALE I,12*SCALE,12*SCALE
SPOFS I,X1*SCALE+200,(-V+120)*SCALE+120,Z1
NEXT
VSYNC 1
GOTO @LOOP

スプライトが192ピクセルだと、格子点の間隔が200ピクセルでは狭すぎますので、400ピクセル間隔にして(-1800,-1800)から(1800,1800)の範囲に配置しました。
この格子点の1つ1つに、スプライトを1つ置きます。

また、これによってフィールドが広くなったので、視点の移動速度を2倍にしています。

スプライトは、SPOFSでZ座標を指定できますが、値は(-256~1024)でなければなりません。
Zが-256より小さいものは、視点よりも後方にあるスプライトなので、SPHIDEで表示を抑制しています。

一方、Z値が1024より大きい場合ですが、全て上限値1024にしてしまうと、遠くにあるスプライトが沢山重なってしまい、違和感があります。
このため、SPOFSで指定するZ値は実際のZ値の1/4にしています。
格子点のエリア全体が3600×3600ですので、1/4にすれば全体を表示してもZ値は1024以下に収まります。

このほか、前回同様、遠くにあるスプライトほど色を暗くしています。

プチコン3号 24日目 擬似3D(1) 遠近感の表現

今回からオブジェクト指向はいったんお休みにして、3次元グラフィックスを扱います。
といっても、まずは映画のCGのような本格的なものではなく、スプライトの拡大・縮小を使った疑似3Dについて考えます。

プチコン3号では、スプライトの拡大・縮小を自由に行うことができます。
スプライトの表示を、視点から遠くなるほど縮小し、近くでは拡大すれば、遠近感を表現できます。
また、動きを遠くのものほど小さく、近くのものでは大きくすることで、距離感を強調できます。

スプライト自体は2次元の絵なのですが、大きさを変化させることによって疑似的に3次元ぽく見せることができます。
さらに、プチコン3号はSPOFS命令でZ値を指定することで視差による奥行きもつけられますので、より3次元ぽさを強調することもできます。

今回は、まず「遠くのものほど小さく表示され動きも小さくなる」プログラムを作ります。

pc24-1.jpg

図のように、2000×2000×2000(X,Yがそれぞれ-1000~1000、Zが0~2000)の立方体の空間を、スクリーンから覗くことを考えます。
スクリーンは立方体の1つの面の中央に貼りついていて、大きさは400×240です。
座標軸は、スクリーンから見て右方向をX軸、下方向をY軸、奥行き方向をZ軸と考えましょう。
原点はスクリーンの中央とします。

そして、スクリーンに表示される範囲は、立方体のスクリーンと反対側の面がスクリーンの横幅に収まるようにします。
つまり、XY座標での表示範囲は

 Z=0のとき、  400×240、座標では(-200, 120)~(200,-120)
 Z=2000のとき、2000×1200、座標では(-1000, 600)~(1000,-600)

となります。

pc24-2.jpg

上(XZ平面)から見ると、図左の明るい灰色で示した台形の範囲が、スクリーンに表示されることになります。
横(YZ平面)から見ても同様に表示範囲は台形になります。
3次元で考えると、表示される空間は四角錘の先端を切り取ったような形になります。

では、この空間の中に200×200の正方形を表示することを考えてみましょう。
Z=0のときは、正方形のサイズは200×200です。
Z=2000のときは、スクリーン全体では2000×1200の領域を400×240に縮小して表示しています。
比率は、400/2000=1/5になりますので、200×200の正方形は40×40に縮小されることになります。

つまり、Zの値が0から2000まで変化するとき、

 ・スクリーンに表示される範囲はZに比例して400×240から2000×1200まで変化。
 ・正方形のサイズはZに反比例して200×200から40×40まで変化。

となります。

以上をプログラムにしてみると、以下のようになります。
Zの値を200ずつ変化させながら正方形を描画しました。

GCLS

LEFT  = -100
TOP   = -100
RIGHT =  100
BOTTOM=  100

FOR Z=0 TO 2000 STEP 200
W     = 400+(2000-400)*(Z/2000)
SCALE = 400/W
X1    = LEFT * SCALE + 200
X2    = RIGHT* SCALE + 200
Y1    = TOP  * SCALE + 120
Y2    =BOTTOM* SCALE + 120
GBOX X1,Y1,X2,Y2
NEXT

結果はこんな感じになります。

pc24-3.jpg

プログラム中のWは、スクリーンに表示される範囲を表し、下のグラフのようにZに応じて400から2000まで変化します。
この範囲が、スクリーン上では400ピクセルの範囲に縮小表示されます。
その縮小率がSCALEで、Zに反比例します。

pc24-4.jpg
pc24-5.jpg

さて、このプログラムで、Zの値を変化させるたびに画面をクリアしてから正方形を描画してみます。
すると、正方形が手前から奥へと飛んでいくようなアニメーションが作れます。
さらに、グラフィック画面のZの値を、正方形のZの値に合わせて設定すると、距離感が強調されます。

以上の変更を加えたものが以下のプログラムです。
アニメーションなのでZの変化幅をより小さくし、Zの最大値も5000にして、より遠くまで表現しています。

GCLS

LEFT  = -100
TOP   = -100
RIGHT =  100
BOTTOM=  100
@LOOP
FOR Z=0 TO 5000 STEP 50
 GCLS
GPRIO Z/5
W=400+1600*(Z/2000)
SCALE=400/W
X1=LEFT*SCALE+200
X2=RIGHT*SCALE+200
Y1=TOP*SCALE+120
Y2=BOTTOM*SCALE+120
GBOX X1,Y1,X2,Y2
 VSYNC 1
NEXT
GOTO @LOOP

さらに、この正方形に上下左右の動きを加えてみましょう。
スライドパッドで、正方形のXY座標を変化させてみます。
このとき「遠くほど小さく、近くほど大きく動かす」ことを考慮する必要はありません。
表示の際に行う座標変換で、移動量も変換されるからです。

GCLS

LEFT  = -100
TOP   = -100
RIGHT =  100
BOTTOM=  100
@LOOP
FOR Z=0 TO 5000 STEP 50
 STICK OUT X,Y
LEFT  = LEFT  + X*10
RIGHT = RIGHT + X*10
TOP   = TOP   - Y*10
BOTTOM= BOTTOM- Y*10
 GCLS
GPRIO Z/5
W=400+1600*(Z/2000)
SCALE=400/W
X1=LEFT*SCALE+200
X2=RIGHT*SCALE+200
Y1=TOP*SCALE+120
Y2=BOTTOM*SCALE+120
GBOX X1,Y1,X2,Y2
VSYNC 1
NEXT
GOTO @LOOP

正方形が手前から向こうへ飛び去っていくだけですが、スライドパッドで軌道を変更することができます。

では、最後にグラフィックスではなくスプライトで同じことをやってみましょう。
グラフィックスでは正方形の4つの座標値を指定して描画していましたが、スプライトでは中心座標とスケール値での指定になります。
プログラムは以下のようになります。
スプライトが遠くへ行くほど、色が暗くなるようにして、遠近感を強調しています。

ACLS
SPSET 0,3482
LEFT  = -100
TOP   = -100
@LOOP
FOR Z=0 TO 5000 STEP 50
STICK OUT X,Y
LEFT  = LEFT  + X*10
TOP   = TOP   - Y*10
W=400+1600*(Z/2000)
SCALE=400/W
X1=LEFT*SCALE+200
Y1=TOP*SCALE+120
 SPSCALE 0,6*SCALE,6*SCALE
SPOFS 0,X1,Y1,Z/5-100
C=127+128*SCALE
SPCOLOR 0,RGB(255,C,C,C)
 VSYNC 1
NEXT
GOTO @LOOP