【プチコン/mkII 両対応】
今回はプチコンを使って3Dゲームを作ろうと思います。とはいっても、3D立体視や3Dポリゴンではなく単なる「疑似3D表示ゲーム」です。
(1)表示座標 (2)拡大率の変化 |
VISIBLE 1,,,,1,1 GPAGE 0 SPPAGE 0 SPSET 0,156,0,0,0,0 GLINE 0,96,255,96,2 GLINE 128,0,128,191,2 X=128 Y=96 @LOOP FOR P=1 TO 200 SPSCALE 0,P SPOFS 0,X,Y VSYNC 1 NEXT GOTO @LOOP |
VISIBLE 1,,,,1,1 GPAGE 0 SPPAGE 0 SPSET 0,156,0,0,0,0 GLINE 0,96,255,96,2 GLINE 128,0,128,191,2 X=128 Y=96 @LOOP FOR P=1 TO 200 SPSCALE 0,P SPOFS 0,X-P/12.5,Y-P/12.5 VSYNC 1 NEXT GOTO @LOOP |
VISIBLE 1,,,,1,1 GPAGE 0 SPPAGE 0 SPSET 0,156,0,0,0,0 SPHOME 0,8,8 GLINE 0,96,255,96,2 GLINE 128,0,128,191,2 X=128 Y=96 @LOOP FOR P=1 TO 200 SPSCALE 0,P SPOFS 0,X,Y VSYNC 1 NEXT GOTO @LOOP |
VISIBLE 1,,,,1,1 GPAGE 0 SPPAGE 0 SPSET 0,156,0,0,0,0 GLINE 0,96,255,96,2 GLINE 128,0,128,191,2 X=128 Y=96 @LOOP FOR P=1 TO 200 SPSCALE 0,P SPOFS 0,X-P/12.5,Y-P/12.5+P*0.4 VSYNC 1 NEXT GOTO @LOOP |
VISIBLE 1,,,,1,1 GPAGE 0 SPPAGE 0 SPSET 0,156,0,0,0,0 SPHOME 0,8,8 GLINE 0,96,255,96,2 GLINE 128,0,128,191,2 X=128 Y=96 @LOOP FOR P=1 TO 200 SPSCALE 0,P SPOFS 0,X,Y+P*0.4 VSYNC 1 NEXT GOTO @LOOP |
GPAGE 0 SPPAGE 0 SPSET 0,156,0,0,0,0 VISIBLE 1,,,,1,1 GLINE 0,96,255,96,2 GLINE 128,0,128,191,2 X=128 Y=96 @LOOP P=1 FOR I=1 TO 200 SPSCALE 0,P SPOFS 0,X-P/12.5,Y-P/12.5+P*0.4 P=P*1.026 VSYNC 1 NEXT GOTO @LOOP |
GPAGE 0 SPPAGE 0 SPSET 0,156,0,0,0,0 SPHOME 0,8,8 VISIBLE 1,,,,1,1 GLINE 0,96,255,96,2 GLINE 128,0,128,191,2 X=128 Y=96 @LOOP P=1 FOR I=1 TO 200 SPSCALE 0,P SPOFS 0,X,Y+P*0.4 P=P*1.026 VSYNC 1 NEXT GOTO @LOOP |
コラム プチコンの演算精度プチコンは32ビットの固定小数点のみをサポートしています。32ビットのうち整数部分が19ビット、小数部分が12ビット、符号が1ビットとなっています。19ビットで表すことのできる最大数は&B1111111111111111111(「1」が19個)となるのですが、これは2の19乗-1であり、524287になります。符号部(1ビット)+整数部(19ビット)+小数部(12ビット)でプチコンでは数値を表現しているのですが、負の数は符号部のビットが1(true)になるため-1は&B111111111111111111111(「1」が20個で先頭の「1」は負数を示す)のように補数で表現されています。この辺はC言語等で符号付き8ビット整数において-1が0xffになるのと同じですね。そのため0 OR Aで変数Aの値の小数部が切り捨てられるのと同じように-1 AND Aで変数Aの値の小数部が切り捨てられるわけです。 負数は補数で表現されるため-1/4096は&B1111111111111111111.111111111111(符号、整数部、小数部すべて含めて「1」が32個並ぶ)となります。ここでFLOOR関数を実行すると小数部が切り捨てられて上記のように「1」が20個並んだ-1となります。つまり、FLOOR(-1/4096)=-1となるわけです。 FLOOR関数を実行した際に負数では正数とは絶対値が異なるものになっているのですが、このように内部保存形式を理解していればその理由は簡単に分かると思います。(小数部分を切り捨てて整数化=その値を超えない最大の整数となる) プチコンで計算させる場合にはその絶対値が524288未満にしなければOverflowとなってしまうわけですが、むしろ問題なのは小数部分の方なのです。小数部分は12ビットとなっており、1/4096単位の数しか扱えないからです。そして表示の段階では小数第3位までとなっています。それでは小数第4位以下をプログラム中に記述することは無意味かというと必ずしもそうとは言い切れません。それは構文解析の段階では小数第6位まで数値として読み取っているためです。 私の解析結果では小数第1位〜第6位までは構文分析にかかる時間は1桁につき12〜14μフレーム(1μフレーム=1/60000000秒)となっているのに対して小数第7位以降は4〜5μフレームとなっているからです。この値はただのスペースの構文分析にかかる時間と同程度の時間であり小数第7位以降を記述しても単なる空白文字の扱いになっていると推測されます。その小数第6位まで数値と読み取ったあとは6桁目が四捨五入されます(もっとも、この時点では「数値扱い」であり数値ではないためアスキーコードが&H35以上かそうでないかで判断していると思われる)。その際に0.999995という値は6桁目が四捨五入されて1になるのかというとそうではなくキャリーオーバーは無視されるみたいなので0になってしまいます。(これは言い換えると小数部は内部では整数部と別々に処理しており、構文解析の段階で四捨五入によって小数部から整数部への繰り上がりを行うという例外処理が行われていないためと言える) その後に1/4096単位に丸めたものが定数用の領域に2進数で入ります。例えば0.999994は0.99999と見なされ1/4096単位に丸められた時点では小数点以下は2進数表記で111111111111(=4095/4096)になり、これは表示の段階では10進数表記で小数第4位が四捨五入されるため画面では「1」と表示されます。 この1/4096単位の丸め処理にも注意が必要となります。これはどういうことかというとn/4096で表現できる数(ただし、nは整数)以外は誤差が発生するということです。 誤差が発生するのは固定小数点だけの問題ではなく浮動小数点でも起こりうることです。浮動小数点では「桁落ち」によって大きな誤差が発生しますが、その頻度はそれほど高くはなくあくまで加減算を行わなければ浮動小数点では大きな誤差は発生しません。それに対して固定小数点では加減乗除を行わず定数として表現するだけで大きな誤差が発生する場合があります。そして、上記の表を見ても扱う数字が小さくなればなるほど相対的な誤差が大きくなります。(扱える数の最小単位が1/4096であるため小さい数字だと正確に表現するのが難しくなるため) 誤差はワーストケースだとこのようになります。
このような事態を避けるためには「なるべく小数を扱わない」「できるだけ計算途中で1未満の小さな数にならないようにする」「どうしても小さな数を扱う必要がある場合は極力2の累乗分の1にする」などの工夫が必要になる場合もあります では具体的にどうするのかというとT=T+0.02という処理が必要な場合は1ずつ加えておいて表示段階で50で割るというのは1つの手です。厄介なのは乗算、除算でしょう。計算途中で小さな数になる可能性が高いからです。 A/B*Cという計算を行う場合はA、B、Cいずれも1より大きいならばA/B*CよりもA*C/Bの方が演算誤差が小さくなる場合が多いです。(ただし数字が大きい場合は逆にOverflowの可能性もあるけど) 具体的な数字を出すと例えば250*0.03をプチコンで計算すれば7.446になります。これは0.03はプチコン内部では122/4096となってしまうため250*122/4096≒7.446となるわけです。実は、250*0.03は250*(3/100)と同じなのです。「何当たり前のことを言ってるんだ?」と思うかもしれませんが、上記の「計算途中で小さな数になる」という状態が発生していると言えます。しかし、このカッコをはずして計算すると250*3/100=7.5となります。これは上記のような1/4096単位で記録された内部記録と小数第3位までしか表示されない表示との誤差が全くない純粋な7.5です。 この表示の段階での丸め処理を見るには次のようにすればよく分かります。 A=7.5-1/4096としてAの値を表示してみてください。表示上は7.5になっていることが分かります。では次にA-7.5の値を表示してみてください。画面には「-0」というおかしな数字が表示されているでしょう。実はこれは-1/4096なのです(ちなみに&B10000000000000000000とすれば通常はありえないぴったりマイナス0の値となる)。 Aには最初に7.5から1/4096を引いた値が入っているのでそこから7.5を引けば-1/4096になるのは当たり前のことですが、こうして見ると表示された数字というのは実際は丸められた数字であるということがよく分かるでしょう。(この丸めた数字であればプチコンでは見られないはずの524288という計算結果を得ることも可能になる) プチコンの固定小数点は計算誤差という面においては不利になる場面は多くありますが、誤差の発生しやすい状況を把握してそうならないような事前対応策をとればそれほど問題はありません。また、誤差を軽減する方法も定数であれば冒頭に書いたようにn/4096(nは整数)とすることで可能になります。誤差をできるだけ少なくしたいという場合は工夫次第で何とでもなります。4096で割るとなると処理時間を考慮すると避けたいかもしれませんが、0.01をより正確に記述する場合には0.01に最も近い数字は41/4096であり、これは0.010009765625であるため上記のようにプチコンでは小数第6位まではちゃんと認識されているため0.010009とすれば問題ありません。(もっとも、0.0101でも41/4096に丸められるため小数第6位まで記述しなくても切り上げ処理で小数第4位まで記述すれば十分だけど) |
「プチコン スキー」の難易度がやたら高くなっていると考える理由(1)自分の絶対的な位置が分からないプチコン スキーの場合は辺りが一面真っ白であり、自分の位置は旗との相対的な位置しか分からない。 (2)慣性がある 旋回ボタンを押している間はどんどん進行方向が変わっている。 背景のスクロールもないためその進行方向は旗を基準にしなければ分からないし、旗は随時速度が変わっているし、進行方向を把握するためには慣れでカバーするしかない。 (3)旋回による視点移動がある 左右旋回をしているという感覚を味わってもらうためにこのゲームはプレイヤー視点となっているため純粋な後方視点とは異なり旋回をするたびに視点を変えている。 そうしないと左右旋回しているというのが視覚的に分からないためだけどそれによって(1)がさらに顕著になった。 |