2013/12/22(日) [n年前の日記]
#2 [dxruby][game] DXRubyでtmxファイルを使ってBG表示
_Tiled Map Editor
の .tmxファイルを、tmx ライブラリを使って読み込んで、DXRuby で表示、かつ、地形アタリ処理をしてみるソレが、そこそこなんとか動くようになってきた…ような気がするのでアップロード。
以下のソースで動いてます。(メイン処理のみ)
mapdisp.rb
使い回しができるように、別クラスにしてみました。
.tmxファイル、画像ファイル、スクリプト、各クラスのドキュメントを、zip にして Dropbox に置いておきます。Public Domain ってことで。
_dxruby_map_disp_20131222.zip
以下のソースで動いてます。(メイン処理のみ)
mapdisp.rb
#= Tiledの .tmx を読んで、表示と地形アタリ判定をするサンプル
#
# * カーソルキーでカーソル移動
# * WASDキーでスクロール
# * Bキーで、BGアタリレイヤーを表示/非表示
# * Hキーで、ぶら下がり棒とアタリをとるか否かを切替
# * ESCキーで終了
require 'dxruby'
require 'tmx'
require_relative 'dxrbtmx'
require_relative 'bgatari'
font = Font.new(14)
Window.frameskip = false
# 画面サイズ(ドット数)を取得
scrw = Window.width
scrh = Window.height
# BG描画に使うタイル画像を読み込む
bgimg = Image.loadTiles("bg_attari.png", 256/16, 256/16)
# BGマップファイル(.tmx)を読み込む
tmx = DxrbTmx.new("./bg_atari_test.tmx", scrw, scrh)
# レイヤー名を指定して、DXRubyの描画用マップデータ(二次元配列)を取得
atari_layer = tmx.get_layer("bg_atari")
bg_layer0 = tmx.get_layer("layer0")
bg_layer1 = tmx.get_layer("layer1")
bg_layer2 = tmx.get_layer("layer2")
bg_layer3 = tmx.get_layer("layer3")
# 画面を占めるタイル個数を取得
bgtw = tmx.screenwidth
bgth = tmx.screenheight
# BGアタリ判定用テーブルを作成
#
# 以下の引数を渡す
#
# 1. アタリ判定用の画像配列(Image.loadTiles() で読み込んだ画像)
# 2. BGアタリマップデータ(二次元配列)
# 3. 画面横幅ドット数
# 4. 画面縦幅ドット数
# 5. 抜けアタリ番号 (省略時は 0番を抜けとして扱う)
# 6. 非床アタリ開始番号 (省略可能)
# 7. 非床アタリ終了番号 (省略可能)
#
bgatari = BgAtari.new(bgimg, atari_layer, scrw, scrh, 0, 22, 30)
bx, by = 0, 0 # スクロール位置初期化
px, py = scrw / 2, scrh / 2 # カーソル位置初期化
atari_disp = true # BGアタリを描画するかどうかのフラグ
floor = true # ぶら下がり棒のアタリは無視するか否かのフラグ
# カーソル描画用メソッド
def draw_cursor(x, y, col)
w = 2
Window.drawLine(x - w, y, x + w, y, col)
Window.drawLine(x, y - w, x, y + (w + 1), col)
end
# メインループ
Window.loop do
break if Input.keyPush?(K_ESCAPE) # ESCキー押しで終了
# Bキーで、BGアタリを描画するかしないかを切り替える
atari_disp = !atari_disp if Input.keyPush?(K_B)
# Hキーで、ぶら下がり棒アタリを無視するかしないかを切り替える
floor = !floor if Input.keyPush?(K_H)
# WASDキーでスクロール
spd = 4.0
bx -= spd if Input.keyDown?(K_A)
bx += spd if Input.keyDown?(K_D)
by -= spd if Input.keyDown?(K_W)
by += spd if Input.keyDown?(K_S)
# カーソルキーでカーソル移動
spd = 1.0
px += Input.x * spd
py += Input.y * spd
# BG描画
if atari_disp
# BGアタリのみ描画
Window.drawTile(0, 0, atari_layer, bgimg, bx, by, bgtw, bgth)
else
# 通常BGのみ描画
Window.drawTile(0, 0, bg_layer3, bgimg, bx / 8, by / 8, bgtw, bgth)
Window.drawTile(0, 0, bg_layer2, bgimg, bx / 4, by / 4, bgtw, bgth)
Window.drawTile(0, 0, bg_layer1, bgimg, bx / 2, by / 2, bgtw, bgth)
Window.drawTile(0, 0, bg_layer0, bgimg, bx, by, bgtw, bgth)
end
# カーソル座標のBGアタリ番号を取得
x = bx + px
y = by + py
bg_code = bgatari.get_bg_code(x, y)
# カーソル座標がBGと当たってるかチェック
chk_hit = bgatari.check_bg_hit(x, y, BgAtari::ADJUST_NONE, floor)
col = (chk_hit)? [192, 255, 0, 0] : [192, 0, 128, 255]
# カーソル座標描画
draw_cursor(px, py, col)
# 床補正座標を得る
nx, ny = bgatari.adjust_bg_hit(x, y, BgAtari::ADJUST_UP, floor)
hosei_code = bgatari.bg_code
draw_cursor(nx - bx, ny - by, C_CYAN) if nx
# 天井補正の座標を得る
nx, ny = bgatari.adjust_bg_hit(x, y, BgAtari::ADJUST_DOWN, floor)
draw_cursor(nx - bx, ny - by, C_CYAN) if nx
# 壁補正の座標を得る
nx, ny = bgatari.adjust_bg_hit(x, y, BgAtari::ADJUST_LEFT, floor)
draw_cursor(nx - bx, ny - by, C_YELLOW) if nx
nx, ny = bgatari.adjust_bg_hit(x, y, BgAtari::ADJUST_RIGHT, floor)
draw_cursor(nx - bx, ny - by, C_YELLOW) if nx
# BGアタリ番号その他の情報を描画
yh = 16
Window.drawFont(16, yh * 0, "FPS: #{Window.real_fps}", font)
Window.drawFont(16, yh * 1, "input CURSOR、WASD,B,H key", font)
Window.drawFont(16, yh * 2, "#{bg_code} : BG code", font)
Window.drawFont(16, yh * 3, "#{hosei_code} : BG code (Floor adjust)", font)
end
一見すると長いのですが、見栄えを良くするために行数が増えちゃってるだけで…。処理の実体は、
- .tmx の読み込みと変換
- tmx = DxrbTmx.new( tmxファイル名, 画面横幅, 画面縦幅 )
- レイヤー名を指定してBGマップデータ(二次元配列)を取得
- atari_layer = tmx.get_layer(レイヤー名)
- BGアタリ処理用のテーブル作成
- bgatari = BgAtari.new(BGアタリ用画像配列, BGアタリに使うマップデータ, 画面横幅, 画面縦幅, 抜けアタリ番号, 床として扱わないアタリ開始番号, 床として扱わないアタリ終了番号)
- BGアタリ番号を取得
- bg_code = bgatari.get_bg_code(x, y)
- BGと当たってるかチェック
- chk_hit = bgatari.check_bg_hit(x, y, BgAtari::ADJUST_NONE, 床のみチェックするかをtrue/falseで)
- BGアタリを元に座標補正(床補正)
- nx, ny = bgatari.adjust_bg_hit(x, y, BgAtari::ADJUST_UP, 床のみチェックするかをtrue/falseで)
使い回しができるように、別クラスにしてみました。
- dxrbtmx.rb ... tmxライブラリを使って .tmxファイルを読み込み、DXRuby で使えるマップデータ(二次元配列)に変換するクラス。
- bgatari.rb ... BGアタリ処理(地形アタリ処理)を担当するクラス。
.tmxファイル、画像ファイル、スクリプト、各クラスのドキュメントを、zip にして Dropbox に置いておきます。Public Domain ってことで。
_dxruby_map_disp_20131222.zip
- 解凍した中の、doc/index.html がドキュメントです。右上の Class List をクリックすれば、クラスの説明ページに飛べます。
- おまけで、.exe 化したファイルも入れておきました。mapdisp.exe を実行すれば、Ruby + DXRuby が入ってない Windows環境でも動くはず…。動くといいな…。ちなみに、exe 化には ocra を使いました。
◎ tmxライブラリについて。 :
_tmxライブラリ
は、
tmx、nokogiri、multi_json、どれも MIT License のようですね。
gem install tmxでインストールできます。 _multi_json と _nokogiri も一緒にインストールされる模様。
tmx、nokogiri、multi_json、どれも MIT License のようですね。
◎ アタリ判定用画像について。 :
アタリ判定用画像は、以下の画像(1タイル = 16x16ドット)を使ってます。

一番左上から、右に向かって、
bgatari.rb の中で、この0〜31番を、1個ずつ、16x16ドットを全スキャンして、アルファ値が128以上ならその座標はアタリ有り、として扱ってます。
結局、やってることは、画像を読んで、ドットを見て、
先日アップした画像が参考になるかもしれないので再掲。



一番左上から、右に向かって、
- 抜けアタリ
- 進めない床
- フツーの床
- 斜め床…
bgatari.rb の中で、この0〜31番を、1個ずつ、16x16ドットを全スキャンして、アルファ値が128以上ならその座標はアタリ有り、として扱ってます。
結局、やってることは、画像を読んで、ドットを見て、
[ [ [y最小値, y最大値] x 横ドット数 ], [ [x最小値, x最大値] x 縦ドット数 ] ]という配列を作ってるだけなので、事前にそういう配列を用意して渡すように修正すれば、DXRuby 以外のライブラリでも bgatari.rb が使えるようになるのではと。…そういう作りにしといたほうがいいのかな。
先日アップした画像が参考になるかもしれないので再掲。


◎ アタリ判定用データの扱いについて。 :
今回は、画像をスキャンして、アタリ判定用データを作ってるわけですが。
このあたり、以下のような作りでもいいんですけど、というか昔の自分は、そういうやり方してたんですけど。
しかし…。
その点、今回のように、画像をスキャンして判別用データを作る方法なら…。
その代り、
とりあえず、プロトタイプをサクッと、てな段階では、今回のやり方も有効なのかもしれないなと。このあたり、判別用データを別途出力するスクリプトを書くだけでも作業は楽になると思うのですが、そのスクリプトを動かす操作すら、プロトタイプ作成段階ではメンドクサイよなと…。
アプリとして完成間近で、実行バイナリに、無駄な処理も無駄なデータも入れたくない、という状態なら…。ツールを作って、判別用データを出力して、それをソース内に列挙、あるいは、別ファイルとして同梱して読み込む、という流れがベターではないかと。
でも、LL使ってゲーム作ってる時点で…。どうせ富豪的プログラミングをしてるわけですし…。このあたりのデータを毎回作り直しちゃってもいいよな、という気もするのでした。ンMbitのROMに収めて、ンMHzのCPUで動かすわけでもないし。
このあたり、以下のような作りでもいいんですけど、というか昔の自分は、そういうやり方してたんですけど。
- あらかじめ、BGマップを作る前に、アタリ種類を決めておく。
- アタリ種類毎に、数式だの条件分岐だので、必要な値を求める処理を書いておく。(一般的には switch 〜 case、Ruby なら case 〜 when になると思う)
- BGアタリ番号と、アタリ種類の対応表を作っておく。
しかし…。
- マップエディタやドットエディタで作業してるうちに、「もうちょっとアタリ種類を増やしたいな…」と考えてしまいがち。
- アタリ種類を増やすの、メンドクサイ。数式や条件分岐を一々書くのは、メンドクサイ。
- BGアタリ番号と、アタリ種類の対応表を作るのって、地味にメンドクサイ。
- BGマップ側で使うアタリ種類の並びが変わっただけでも、対応表を作り直すので、メンドクサイ。
- 曲線的なアタリの形に対応するのが、メンドクサイ。
その点、今回のように、画像をスキャンして判別用データを作る方法なら…。
- アタリ種類を増やしたり、並びを変える作業が、CGツール上で完結する。
- 人間様が対応表を作らなくて済む。
- 曲線的なアタリにも対応できる。
その代り、
- 画像の読み込み、かつ、ドットをチェックできるライブラリが必要。どこでも使い回せるソースにならない。
- 毎回ゲームを動かすたびに、判別用データと対応表を作り直してるので、その処理時間が、ガチで無駄。
- アタリ種類分のテクスチャ領域を、無駄遣いしてる。
とりあえず、プロトタイプをサクッと、てな段階では、今回のやり方も有効なのかもしれないなと。このあたり、判別用データを別途出力するスクリプトを書くだけでも作業は楽になると思うのですが、そのスクリプトを動かす操作すら、プロトタイプ作成段階ではメンドクサイよなと…。
アプリとして完成間近で、実行バイナリに、無駄な処理も無駄なデータも入れたくない、という状態なら…。ツールを作って、判別用データを出力して、それをソース内に列挙、あるいは、別ファイルとして同梱して読み込む、という流れがベターではないかと。
でも、LL使ってゲーム作ってる時点で…。どうせ富豪的プログラミングをしてるわけですし…。このあたりのデータを毎回作り直しちゃってもいいよな、という気もするのでした。ンMbitのROMに収めて、ンMHzのCPUで動かすわけでもないし。
◎ 2017/03/19追記。 :
Dropboxのpublicフォルダが死んだのでファイルの置き場所を変更。
[ ツッコむ ]
以上です。
