mieki256's diary



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

ソース :

とりあえず、一直線の道路だけなら処理を書けた。

ss_road02a_ss01.gif

_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_road.png

背景用画像。

bg_16col.png
_bg_16col.png

画像は、.hsp と同じフォルダ階層に置いておく。

考え方 :

実装する際の考え方を一応メモ。

以下は、横から見た図。

about_road_pers_01.png

画面上の特定の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 を求めることができた。

更に、その位置に表示されている道路の見た目の幅(片方の幅)を知りたい。上から見た図で考える。

about_road_pers_02.png

  • 画面までの距離 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ラインを持ってきて画面に描画する。

bg_road.png

上記の画像は45度の角度で道路の幅が広がっていくように描いたので…。例えば道路の見た目上の幅が 120 なら、y = 120 のあたりの1ラインを使って画面に描画してやればいい。

更に、奥から手前に向かって、白線が点線になってるように、かつ、道路や地面の色が縞々になるように描画したい。

道路のz値が得られているので、そのz値を元にして 0 か 1 かを決めて…。
  • 0 なら白線のある画像側を ―― 上記の画像であれば上半分側を使って描画。
  • 1 なら白線のない画像側を ―― 上記の画像であれば下半分側を使って描画。
これで、白線は点線になるし、道路や地面の色も縞々になる。

その際、道路のz値に、別途スクロール値を加算して 0 or 1 を決めてやれば、道路が奥から手前に向かってスクロールしてるように見えるはず。

ここまでの処理を、画面の下半分程度、ループ処理で描画してやれば、疑似3D道路が描画できるはず。

余談。昔のゲーム機ならパレット書き換えができただろうから、水平帰線割り込みを使って、水平帰線期間内に道路用パレットの一部の値(白線、道路、地面の3色)を書き換えてやれば、わざわざ似たような画像を用意しなくても、点線状の白線や、縞々模様を実現できたのではないかなと想像するのだけど、実際はどうだったんだろう。

道路の画像を使わない版 :

昔のゲーム機は、基本的には事前に用意した画像を画面に表示することしかできなかったので、前述のように道路用画像を別途用意しておかなければいけなかったと思うのだけど。

今時のハードウェアは、というかPCは、えてしてフレームバッファを持っているので、点だの、線だの、矩形だのをその都度描画することができるなと。だったら、この程度のシンプルな形状であれば毎回プログラムで計算して描いちゃってもいいんじゃないかと思えてきた。

そんなわけで、道路用の画像を使わない版も書いてみた。

ss_road02b_ss01.gif

_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
    ; 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

ss_bench.gif

微妙に boxf のほうが速いようだなと…。まあ、劇的に違いが出るわけでもないので、今回はどっちを使っても良さそうだけど。

ただ、ループ内の color 命令をコメントアウトすると、line のほうが速くなったりもした。なんでだろ。

以上です。

過去ログ表示

Prev - 2023/09 - Next
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project