プチコン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ですので、表示のバランスとしては、これくらいの大きさが上限ではないかと思います。

コメント