2023/09/04(月) [n年前の日記]
#1 [hsp] 疑似3D道路について試してる
HSP 3.6 を使って、疑似3D道路っぽいものを作れないか試しているところ。環境は Windows10 x64 22H2。
◎ 参考ページ :
以下のページを参考にしながら作業してるのだけど、英文ページなので何が何やら…。
_Lou's Pseudo 3d Page
_How to build a racing game - straight roads | Code inComplete
_Lou's Pseudo 3d Page
_How to build a racing game - straight roads | Code inComplete
◎ ソース :
とりあえず、一直線の道路だけなら処理を書けた。
_road02a.hsp
使用画像は以下。道路用画像。
_bg_road.png
背景用画像。
_bg_16col.png
画像は、.hsp と同じフォルダ階層に置いておく。
_road02a.hsp
; 疑似3D道路を描画する / Drawing pseudo 3D roads #define BGROAD_FILE "bg_road.png" #define BG_FILE "bg_16col.png" #define SCR_W 640 #define SCR_H 360 ; 同梱ファイル / bundled files #pack BGROAD_FILE #pack BG_FILE #packopt name "road02a" ; 生成exeファイル名 / generated exe file name #packopt type 0 ; exeファイルの生成を指定 / exe file generate #packopt xsize SCR_W #packopt ysize SCR_H onkey goto *jobend ; ウインドウサイズを取得 / get windows size screen 0, SCR_W, SCR_H, 0 gsel 0 ; width SCR_W, SCR_H dispw = ginfo_winx disph = ginfo_winy cls 4 ; 道路画像を読み込み / load road image roadimgid = 6 buffer roadimgid, 1280, 1440 picload BGROAD_FILE roadimgw = ginfo_winx roadimgh = ginfo_winy ; 背景画像を読み込み / load bg image bgimgid = 4 buffer bgimgid, 1280, 720 picload BG_FILE bgw = ginfo_winx bgh = ginfo_winy dist = 150.0 ; 画面までの距離 / distance to screen angle = 0.0 scroll_z = 0 camera_x = 0 *mainloop ; ESC key to exit stick k if k & 128 : goto *jobend base_y = int(double(disph) * 0.5) road_y = 100 road_x = 300 h = disph - base_y camera_x = 0 ; camera_x = 600.0 * sin(deg2rad(angle * 0.75)) redraw 0 ; 描画開始 / draw start gsel 0 ; 背景(遠景)を描画 / draw BG gmode 0 ax = sin(deg2rad(double(angle) * 0.25)) * ((bgw - dispw) / 2) bg_x = (bgw / 2 - (dispw / 2)) + ax bg_y = bgh / 2 - base_y pos 0,0 gcopy bgimgid, bg_x, bg_y, dispw, disph ; gradf 0, (disph / 2), dispw, (disph / 2), 1, $449044, $080808 ; 道路を描画 / draw road repeat h scrn_y = h - cnt - 1 if scrn_y = 0 : continue road_z = road_y * dist / scrn_y scrn_x = road_x * dist / road_z center_x = (camera_x * dist / road_z) u = center_x + (roadimgw / 2) - (dispw / 2) addv = ((roadimgh / 2) * (((road_z + scroll_z) / 32) & $1)) v = scrn_x + addv ; 1ラスター分を描画 / drawing 1 raster pos 0, scrn_y + base_y gcopy roadimgid, u, v, dispw, 1 loop redraw 1 ; 描画終了 / draw end angle += 1 scroll_z += 10 await (1000 / 30) goto *mainloop *jobend end
使用画像は以下。道路用画像。
_bg_road.png
背景用画像。
_bg_16col.png
画像は、.hsp と同じフォルダ階層に置いておく。
◎ 考え方 :
実装する際の考え方を一応メモ。
以下は、横から見た図。
画面上の特定のy座標 = sy に道路が表示されているとして、その位置の道路のz値 = rz を知りたい。
sz,sy で作られる三角形と、rz,ry で作られる三角形は、大きさが違うだけで形は同じ(相似)なので、以下の式が成り立つ。
更に、その位置に表示されている道路の見た目の幅(片方の幅)を知りたい。上から見た図で考える。
sz,sx で作られる三角形と、rz,rx で作られる三角形は、大きさが違うだけで形は同じ(相似)なので、以下の式が成り立つ。
道路の見た目の幅が分かったので、事前に用意した道路用の画像内から、合致する1ラインを持ってきて画面に描画する。
上記の画像は45度の角度で道路の幅が広がっていくように描いたので…。例えば道路の見た目上の幅が 120 なら、y = 120 のあたりの1ラインを使って画面に描画してやればいい。
更に、奥から手前に向かって、白線が点線になってるように、かつ、道路や地面の色が縞々になるように描画したい。
道路のz値が得られているので、そのz値を元にして 0 か 1 かを決めて…。
その際、道路のz値に、別途スクロール値を加算して 0 or 1 を決めてやれば、道路が奥から手前に向かってスクロールしてるように見えるはず。
ここまでの処理を、画面の下半分程度、ループ処理で描画してやれば、疑似3D道路が描画できるはず。
余談。昔のゲーム機ならパレット書き換えができただろうから、水平帰線割り込みを使って、水平帰線期間内に道路用パレットの一部の値(白線、道路、地面の3色)を書き換えてやれば、わざわざ似たような画像を用意しなくても、点線状の白線や、縞々模様を実現できたのではないかなと想像するのだけど、実際はどうだったんだろう。
以下は、横から見た図。
画面上の特定のy座標 = sy に道路が表示されているとして、その位置の道路のz値 = rz を知りたい。
- 画面までの距離 sz は決めておく。
- 画面上のy座標 sy も決めておく。
- 道路の高さ(低さ) ry も決めておく。
- 道路のz値 rz だけが分からない。
sz,sy で作られる三角形と、rz,ry で作られる三角形は、大きさが違うだけで形は同じ(相似)なので、以下の式が成り立つ。
sz : sy = rz : ry sy * rz = sz * ry rz = ry * sz / syこれで、画面上のy座標 sy に表示されている道路のz値、rz を求めることができた。
更に、その位置に表示されている道路の見た目の幅(片方の幅)を知りたい。上から見た図で考える。
- 画面までの距離 sz は決めておく。
- 道路の実際の幅 rx も決めておく。
- 道路のz値 rz は先ほど求めることができた。
- 画面上の道路の見た目の幅 sx だけが分からない。
sz,sx で作られる三角形と、rz,rx で作られる三角形は、大きさが違うだけで形は同じ(相似)なので、以下の式が成り立つ。
sz : sx = rz : rx sx * rz = sz * rx sx = rx * sz / rzこれで、画面上のy座標 sy に表示されている、道路の見た目の幅、sx を求めることができた。
道路の見た目の幅が分かったので、事前に用意した道路用の画像内から、合致する1ラインを持ってきて画面に描画する。
上記の画像は45度の角度で道路の幅が広がっていくように描いたので…。例えば道路の見た目上の幅が 120 なら、y = 120 のあたりの1ラインを使って画面に描画してやればいい。
更に、奥から手前に向かって、白線が点線になってるように、かつ、道路や地面の色が縞々になるように描画したい。
道路のz値が得られているので、そのz値を元にして 0 か 1 かを決めて…。
- 0 なら白線のある画像側を ―― 上記の画像であれば上半分側を使って描画。
- 1 なら白線のない画像側を ―― 上記の画像であれば下半分側を使って描画。
その際、道路のz値に、別途スクロール値を加算して 0 or 1 を決めてやれば、道路が奥から手前に向かってスクロールしてるように見えるはず。
ここまでの処理を、画面の下半分程度、ループ処理で描画してやれば、疑似3D道路が描画できるはず。
余談。昔のゲーム機ならパレット書き換えができただろうから、水平帰線割り込みを使って、水平帰線期間内に道路用パレットの一部の値(白線、道路、地面の3色)を書き換えてやれば、わざわざ似たような画像を用意しなくても、点線状の白線や、縞々模様を実現できたのではないかなと想像するのだけど、実際はどうだったんだろう。
◎ 道路の画像を使わない版 :
昔のゲーム機は、基本的には事前に用意した画像を画面に表示することしかできなかったので、前述のように道路用画像を別途用意しておかなければいけなかったと思うのだけど。
今時のハードウェアは、というかPCは、えてしてフレームバッファを持っているので、点だの、線だの、矩形だのをその都度描画することができるなと。だったら、この程度のシンプルな形状であれば毎回プログラムで計算して描いちゃってもいいんじゃないかと思えてきた。
そんなわけで、道路用の画像を使わない版も書いてみた。
_road02b.hsp
見た目では違いが分からないな…。
今時のハードウェアは、というかPCは、えてしてフレームバッファを持っているので、点だの、線だの、矩形だのをその都度描画することができるなと。だったら、この程度のシンプルな形状であれば毎回プログラムで計算して描いちゃってもいいんじゃないかと思えてきた。
そんなわけで、道路用の画像を使わない版も書いてみた。
_road02b.hsp
; 疑似3D道路を描画する / Drawing pseudo 3D roads ; 道路画像を使わずにプログラムで描画する版 #define BG_FILE "bg_16col.png" #define SCR_W 640 #define SCR_H 360 ; 同梱ファイル / bundled files #pack BG_FILE #packopt name "road02b" ; exe filename #packopt type 0 ; generate ".exe" #packopt xsize SCR_W #packopt ysize SCR_H onkey goto *jobend ; get windows size screen 0, SCR_W, SCR_H, 0 gsel 0 ; width SCR_W, SCR_H dispw = ginfo_winx disph = ginfo_winy cls 4 ; load bg image bgimgid = 4 buffer bgimgid, 1280, 720 picload BG_FILE bgw = ginfo_winx bgh = ginfo_winy dist = 150.0 ; 画面までの距離 / distance to screen angle = 0.0 scroll_z = 0 camera_x = 0 *mainloop ; ESC key to exit stick k if k & 128 : goto *jobend base_y = int(double(disph) * 0.5) road_y = 100 road_x = 300 h = disph - base_y camera_x = 0 ; camera_x = 600.0 * sin(deg2rad(angle * 0.75)) redraw 0 ; 描画開始 / draw start gsel 0 ; 背景(遠景)を描画 / draw BG gmode 0 ax = sin(deg2rad(double(angle) * 0.25)) * ((bgw - dispw) / 2) bg_x = (bgw / 2 - (dispw / 2)) + ax bg_y = bgh / 2 - base_y pos 0,0 gcopy bgimgid, bg_x, bg_y, dispw, disph ; gradf 0, (disph / 2), dispw, (disph / 2), 1, $449044, $080808 ; 道路を描画 / draw road repeat h scrn_y = h - cnt - 1 if scrn_y = 0 : continue road_z = road_y * dist / scrn_y scrn_x = road_x * dist / road_z center_x = camera_x * dist / road_z addv = (((road_z + scroll_z) / 32) & $1) ; 1ラスター分を描画 / draw 1 raster road_w = scrn_x * 2 draw_x = -scrn_x + (dispw / 2) + center_x draw_y = scrn_y + base_y kind = addv gosub *draw_raster_road loop redraw 1 ; 描画終了 / draw end angle += 1 scroll_z += 10 await (1000 / 30) goto *mainloop *draw_raster_road ; 道路を1ラスター分描画 ; ; draw_x, draw_y : draw x, y ; road_w : road width ; kind : 0 or 1 ; dispw : screen width ; draw ground if kind = 0 { ; color R, G, B color 114, 185, 66 } else { color 22, 174, 63 } ; boxf x0, y0, x1, y1 boxf 0, draw_y, dispw, draw_y ; draw road base if kind = 0 { color 133, 149, 158 } else { color 149, 168, 179 } boxf draw_x, draw_y, draw_x + road_w, draw_y ; draw white line lw = double(road_w) * 48.0 / 2048.0 ; white line width if kind = 0 { color 255, 255, 255 lx = double(draw_x) boxf lx, draw_y, lx + lw, draw_y lx = lx + road_w - lw boxf lx, draw_y, lx + lw, draw_y lx = double(draw_x) + (double(road_w) * 0.5) - (lw * 0.5) boxf lx, draw_y, lx + lw, draw_y lx = double(draw_x) + double(road_w) * 0.25 - (lw * 0.5) boxf lx, draw_y, lx + lw, draw_y lx = double(draw_x) + double(road_w) * 0.75 - (lw * 0.5) boxf lx, draw_y, lx + lw, draw_y } else { color 228, 236, 252 lx = double(draw_x) + lw boxf lx, draw_y, lx + lw, draw_y lx = double(draw_x) + road_w - (lw * 2) boxf lx, draw_y, lx + lw, draw_y } return *jobend end
見た目では違いが分からないな…。
◎ lineとboxf :
HSP は、line で線を描画、boxf で矩形塗り潰しができるけど、1ラインだけ描画するならどちらが速いのか気になったので少し試してみた。環境は Windows10 x64 22H2 + HSP 3.6。
_bench.hsp
微妙に boxf のほうが速いようだなと…。まあ、劇的に違いが出るわけでもないので、今回はどっちを使っても良さそうだけど。
ただ、ループ内の color 命令をコメントアウトすると、line のほうが速くなったりもした。なんでだろ。
_bench.hsp
; line と boxf のベンチマーク #include "d3m.hsp" #define SCR_W 640 #define SCR_H 360 #packopt name "bench" ; 生成ファイル名 #packopt type 0 ; .exe生成を指定 #packopt xsize SCR_W #packopt ysize SCR_H onkey goto *jobend ; get windows size screen 0, SCR_W, SCR_H, 0 gsel 0 ; width SCR_W, SCR_H dispw = ginfo_winx disph = ginfo_winy cls 4 *mainloop ; ESC key to exit stick k if k & 128 : goto *jobend redraw 0 ; 描画開始 gsel 0 gmode 0 lp = 1000 starttime = d3timer() repeat lp repeat (disph - 1) color 16, 32, 64 ; line x1, y1, x0, y0 line dispw, cnt, 0, cnt ; pos 0, cnt ; line dispw, cnt loop loop tm1 = d3timer() - starttime starttime = d3timer() repeat lp repeat (disph - 1) color 16, 32, 64 ; boxf x0, y0, x1, y1 boxf 0, cnt, dispw, cnt loop loop tm2 = d3timer() - starttime color 255, 255, 255 pos 8, 8 mes "line : " + tm1 pos 8, 32 mes "boxf : " + tm2 redraw 1 ; 描画終了 await (1000 / 60) goto *mainloop *jobend end
微妙に boxf のほうが速いようだなと…。まあ、劇的に違いが出るわけでもないので、今回はどっちを使っても良さそうだけど。
ただ、ループ内の color 命令をコメントアウトすると、line のほうが速くなったりもした。なんでだろ。
[ ツッコむ ]
以上です。