DXOpal はブラウザで実行できるため、SDL などのネイティブなライブラリが不要で、他の人に見てもらいやすいところが魅力的です。
しかし、実際開発してみるとブラウザで開いてから動き出すまでにそこそこ時間がかかったり、
デバッグまわりの勝手に慣れていなかったりでトライ&エラーの効率がなかなか上がりません。
なんとかできないかなと。
DXOpal は DXRuby に合わせて作られてますから、
開発中は DXRuby で作って、公開するときに DXOpal で動くように調整するというのはどうでしょうか(最終的には DXOpal で動かすのが目的です)。
……と一瞬考えましたが、ふだん使っているのが Ubuntu なので、DXRuby は使えない……
Wine で動かす試み
(
DXRuby を Wine から使う - Qiita
)
や dxruby-sdl などの互換ライブラリ
(
DXRuby API互換のゲームライブラリを作るための頻出メソッド一覧 - Qiita
)もあるようなのですが、いろいろあって今回は見送りました。
自分が今使いたい機能はといえば、
基本的な図形描画の機能(線、矩形、円 + 塗りつぶし)と、
マウス関連の機能(左ボタンの push イベントと x, y 座標取得)と、
後は効果音を出すくらいです。
アクションゲームやシューティングゲームを作っているわけではありませんから
タイミングに関してシビアな要件はありません。
そこで思いついたのが、他のライブラリをバックエンドとして使いつつ API だけ DXOpal に合わせてお茶を濁す方法です。
やってみたら意外とすんなり動き、もうこれでいいや、となりました。
Ruby 2D に興味があったので最初は Ruby 2D を試しに使って書いてみて、
それでも十分動いたのでしばらく使っていました。
ただ、Ruby 2D は割と高水準よりの API になっていて
ミスマッチな感じがしたのと、処理がちょっとだけもたつく感じだったので(これは Ruby 2D そのものの問題ではなく使い方が悪いせいです)、
Ruby/SDL で書き直しました。
できたものが下記の dxopal_sdl.rb
です。
上述のように、自分が今必要とする要件がゆるく、
開発中だけそれっぽく動いてくれればそれでいいという代物なので、
かなり適当です(p_
とか特に……)。
上記のるびまの記事では FPS の管理に fpstimer.rb
を使っていますが、そんなに高精度なものは必要なかったのでそこも適当。
※ この時点でのスナップショットということでベタッと貼りましたが、この後いくつか修正が入りました。
GitHub のリポジトリにあるものが最新版です。
(追記 2024-01-04)
事前コンパイル方式への変更に伴い kairo-gokko では dxopal_sdl.rb
は使わなくなりました。一応 old/ ディレクトリ に移動して残しています。
require "sdl"
RUBY_ENGINE = "opal"
def require_remote(path)
path.sub!(/\.rb/, "")
require "./#{path}"
end
def p_(*args)
DXOpal.p_(*args)
end
module DXOpal
C_BLACK = [255, 0, 0, 0]
C_WHITE = [255, 255, 255, 255]
M_LBUTTON = :m_lbutton
@@p_count = 0
def self.p_(*args)
args.each { |arg| p arg } if @@p_count < 10
@@p_count += 1
end
module Window
@@width = 640
@@height = 480
@@last_update = Time.now
class << self
def width=(w)
@@width = w
end
def height=(h)
@@height = h
end
def bgcolor=(color)
@@bgcolor = color
end
def to_rgb_a(color)
if color.size == 4
[color[1..3], color[0]]
else
[color, 255]
end
end
def load_resources
SDL.init(SDL::INIT_EVERYTHING)
SDL::Mixer.open
@@screen = SDL.set_video_mode(
@@width,
@@height,
16,
SDL::SWSURFACE
)
yield
end
def handle_event(event)
case event
when SDL::Event::Quit
exit
when SDL::Event::MouseMotion
Input.mouse_x = event.x
Input.mouse_y = event.y
when SDL::Event::MouseButtonDown
if event.button == SDL::Mouse::BUTTON_LEFT
DXOpal::Input.mouse_pushed_map_set(M_LBUTTON, true)
end
when SDL::Event::MouseButtonUp
if event.button == SDL::Mouse::BUTTON_LEFT
DXOpal::Input.mouse_pushed_map_set(M_LBUTTON, false)
end
end
end
def fill_bg
draw_box_fill(0, 0, @@width, @@height, @@bgcolor)
end
def loop
fps = 10
interval_sec = 1 / fps.to_f
while true
while event = SDL::Event.poll
handle_event(event)
end
if @@last_update + interval_sec < Time.now
@@last_update = Time.now
fill_bg
yield
@@screen.update_rect(0, 0, 0, 0)
end
end
end
def draw_line(x1, y1, x2, y2, color, z=0)
rgb, alpha = to_rgb_a(color)
antialias = false
@@screen.draw_line(x1, y1, x2, y2, rgb, antialias, alpha)
end
def sdl_draw_box(x1, y1, x2, y2, color, fill)
rgb, alpha = to_rgb_a(color)
w = x2 - x1
h = y2 - y1
@@screen.draw_rect(x1, y1, w, h, rgb, fill, alpha)
end
def draw_box(x1, y1, x2, y2, color, z=0)
sdl_draw_box(x1, y1, x2, y2, color, false)
end
def draw_box_fill(x1, y1, x2, y2, color, z=0)
sdl_draw_box(x1, y1, x2, y2, color, true)
end
def sdl_draw_circle(x, y, r, color, fill)
rgb, alpha = to_rgb_a(color)
antialias = false
@@screen.draw_circle(x, y, r, rgb, fill, antialias, alpha)
end
def draw_circle(x, y, r, color, z=0)
sdl_draw_circle(x, y, r, color, false)
end
def draw_circle_fill(x, y, r, color, z=0)
sdl_draw_circle(x, y, r, color, true)
end
end
end
class Sound
@@map = {}
class << self
def register(name, *args, &block)
path, _ = args
@@map[name] = {
path: path,
sound: nil
}
end
def [](name)
sound = @@map[name][:sound]
unless sound
path = @@map[name][:path]
wave = SDL::Mixer::Wave.load(path)
sound = Sound.new(wave)
@@map[name][:sound] = sound
end
sound
end
end
def initialize(wave)
@wave = wave
end
def play
SDL::Mixer.play_channel(0, @wave, 0)
end
end
module Input
@@mouse_pushed_map = {}
@@mouse_pushed_map[M_LBUTTON] = false
@@mouse_x = 0
@@mouse_y = 0
class << self
def mouse_pushed_map_set(code, val)
@@mouse_pushed_map[code] = val
end
def mouse_x=(val)
@@mouse_x = val
end
def mouse_y=(val)
@@mouse_y = val
end
def mouse_x
@@mouse_x
end
def mouse_y
@@mouse_y
end
def mouse_push?(mouse_code)
pushed = @@mouse_pushed_map[mouse_code]
@@mouse_pushed_map[mouse_code] = nil
pushed
end
end
end
end
この dxopal_sdl.rb
を使って開発を進め、
ある程度できたらブラウザ+DXOpal で動かしたいので、
次のように Kernel::Native
の存在でブラウザかどうか判別して切り替えるようにしてみました。
def browser?
Kernel.const_defined?(:Native)
end
if browser?
require "dxopal"
else
require "./dxopal_sdl"
end
追記 2023-08-06
qiita.com
事前コンパイルする方法もおすすめです。