sdl2rでゲームライブラリを作る(2)
前回は基本部分をとりあえず作ってみたところまでだった。今回はもうちょい進めたいところだが、その前に少しだけ周辺の話。
まず、Rubyのいいところに「既存のクラスを好きなように拡張できる」というのがある。モンキーパッチであり、オープンクラスである。これを使うと、例えば前回のサンプルコードをちょといじって、
require_relative 'dxsdl2r' module Input def self.mouse_xy [mouse_x, mouse_y] end end Window.loop do x, y = Input.mouse_xy Window.caption = "#{x}, #{y}" end
という感じで勝手にInput.mouse_xyメソッドを生やしてしまうことができる。これはsdl2rじゃなくてDXRubyでも同様のことができて、便利なメソッドを追加したいと思えば、思ったときにその場ですぐ実装することができてしまう。
dxsdl2r.rbの場合は更に、sdl2rの機能を使って機能拡張をやりたい放題となるので、これは極めて重要である。言い方を変えれば、必要とあらばSDL2の機能を直接使って処理を行うことができるし、機能を追加できる、ということだ。
こういうことをやりやすいように、Windowモジュールなどの内部情報を簡単に引き出せるようにしておくとよい。
module DXRuby module Window def self._window;@window;end def self._renderer;@renderer;end end end
こんな感じで。
とりあえずの描画機能を作る
まだ画面に何も描画できないので、簡単な描画機能を作ってみよう。SDL2にはSDL_FillRectという関数があって矩形が手軽に描画できるので、まずはそれを使ってみる。
require_relative 'dxsdl2r' module DXRuby module Window def self._renderer;@renderer;end end end Window.loop do SDL.set_render_draw_color(Window._renderer, 255, 0, 0, 255) SDL.render_fill_rect(Window._renderer, SDL::Rect.new(Input.mouse_x, Input.mouse_y, 50, 50)) end
これで赤い四角をマウスカーソルの位置に描画することができる。
Windowに機能追加する
んじゃあ、この機能をDXRubyのようにWindowモジュールのWindow.draw_box_fillとして実装してみる。
require_relative 'dxsdl2r' module DXRuby module Window def self.draw_box_fill(x1, y1, x2, y2, color, z=0) SDL.set_render_draw_color(@renderer, color[0], color[1], color[2], 255) SDL.render_fill_rect(@renderer, SDL::Rect.new(x1, y1, x2 - x1 + 1, y2 - y1 + 1)) end end end Window.loop do x, y = Input.mouse_x, Input.mouse_y Window.draw_box_fill(x, y, x + 50, y + 50, [255, 0, 0]) end
この時点で気になることが2点。まず、色の配列がRGBしか扱えなくてα値が指定できない。これをどうにかするにはDXRubyの色の指定方法を処理するメソッドなりなんなりが必要になるだろう。だがまだない。DXRubyとは違うのだよーとか言ってRGBA配列で表現してもいいっちゃいいんだけど。
次に、z値が無視されている。DXRubyではz値が小さい順にzソートされてから描画されるが、そのへんのロジックがまだない。
色配列対応
とりあえず適当に対応してみよう。
require_relative 'dxsdl2r' module DXRuby module Window def self._window;@window;end def self._renderer;@renderer;end def self.draw_box_fill(x1, y1, x2, y2, color, z=0) tmp = DXRuby._convert_color_dxruby_to_sdl(color) SDL.set_render_draw_color(@renderer, tmp[0], tmp[1], tmp[2], tmp[3]) SDL.render_fill_rect(@renderer, SDL::Rect.new(x1, y1, x2 - x1 + 1, y2 - y1 + 1)) end end def self._convert_color_dxruby_to_sdl(color) if color.size == 3 color + [255] else color[1..3] << color[0] end end end Window.loop do x, y = Input.mouse_x, Input.mouse_y Window.draw_box_fill(x, y, x + 50, y + 50, [255, 0, 0]) end
こんな感じで内部用のメソッドを作っておけばよさげか。
zソート
DXRubyでは描画は描画予約として、描画パラメータと描画関数のポインタを保持しておいて、描画直前にz値でソートをするという、地味に面倒なことをして自動zソートを実現している。それができるようにプログラムの構成やデータ構造が作られている。
じゃあ、Rubyで書く場合も同じように頑張る必要があるのか、というと、実際そんなことはなくて、Cで書くとめんどいだけで、Rubyでは思った以上に簡単に作れる。
具体的にどうするかというと、描画予約はProcオブジェクトにパラメータと描画ロジックを詰め込んでおいて、z値とProcをセットした配列を作って、zでソートする。んで、あとでProcを順番に呼び出す。
require_relative 'dxsdl2r' module DXRuby module Window def self._window;@window;end def self._renderer;@renderer;end @reservation = [] def self.draw_box_fill(x1, y1, x2, y2, color, z=0) tmp = DXRuby._convert_color_dxruby_to_sdl(color) prc = ->{ SDL.set_render_draw_color(@renderer, tmp[0], tmp[1], tmp[2], tmp[3]) SDL.render_fill_rect(@renderer, SDL::Rect.new(x1, y1, x2 - x1 + 1, y2 - y1 + 1)) } @reservation << [z, prc] end def self.loop timer = FPSTimer.instance timer.reset SDL.set_window_size(@window, @width, @height) SDL.show_window(@window) Kernel.loop do timer.wait_frame do return if Input.update SDL.set_render_draw_color(@renderer, 0, 0, 0, 255) SDL.render_clear(@renderer) yield @reservation.sort_by!{|v|v[0]}.each{|v|v[1].call} @reservation.clear SDL.render_present(@renderer) end end end end def self._convert_color_dxruby_to_sdl(color) if color.size == 3 color + [255] else color[1..3] << color[0] end end end Window.loop do x, y = Input.mouse_x, Input.mouse_y Window.draw_box_fill(100, 100, 200, 200, [0, 0, 255], 0) Window.draw_box_fill(150, 150, 250, 250, [0, 255, 0], 2) Window.draw_box_fill(x, y, x + 50, y + 50, [255, 0, 0], 1) end
Window.loopを修正する必要があったのでちょと長くなってしまった。ともあれこれで描画した順序ではなく指定したz値で描画順が制御されることになる。Cで書いたときは大変だったのにRubyだと簡単にできるので感動である。
おしまい
今日のコードを含めたライブラリ側はこちらに置いておいた。
わりと頑張ったのだが進んだような進んでないような感じである。せめてImageクラスができてくれればいいのだが。