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 のほうが速くなったりもした。なんでだろ。
[ ツッコむ ]
以上です。





