sdl2rでゲームライブラリを作る(8)
ま、とりあえず最低限の入力はできてるので次、RenderTargetクラスを作ろう。SDL2に興味を持った理由は大きいのがGPUを使った高速描画だったのだが、RenderTargetテクスチャが扱えることも大きな要因であった。
RenderTargetテクスチャ
SDL2ではSDL_WindowオブジェクトにSDL_Rendererオブジェクトを割り当てることができるが、SDL_Rendererのデフォルト描画先はSDL_Windowのフレームバッファである。この描画先設定を変更してSDL_Textureに描画できるようにする機能がある。
SDL_Textureを描画先に設定するには、SDL_Textureの生成時にSDL_TEXTUREACCESS_TARGETフラグを指定しておく必要がある。SDL_TEXTUREACCESS_TARGETを指定するとそのテクスチャはロックができなくなるが、代わりにRendererで描画することができるようになる。DXRubyのRenderTargetクラスもDirectXの同様の機能を使っている。
ロックできないのでImageクラスのようにピクセルを操作することはできないが、保持しているのはテクスチャなので、Imageと同じように画面や他のRenderTargetに描画することができる。
RenderTargetクラス
このように作る。
module DXRuby class RenderTarget attr_accessor :_texture def initialize(w, h, bgcolor=[0, 0, 0, 0]) @_reservation = [] @_bgcolor = DXRuby._convert_color_dxruby_to_sdl(bgcolor) return if w == 0 and h == 0 @_texture = SDL.create_texture(Window._renderer, SDL::PIXELFORMAT_RGBA8888, SDL::TEXTUREACCESS_TARGET, w, h) end def draw_box_fill(x1, y1, x2, y2, color, z=0) tmp = DXRuby._convert_color_dxruby_to_sdl(color) prc = ->{ SDL.set_render_draw_blend_mode(@_renderer, SDL::BLENDMODE_BLEND) SDL.set_render_draw_color(@_renderer, *tmp) SDL.render_fill_rect(@_renderer, SDL::Rect.new(x1, y1, x2 - x1 + 1, y2 - y1 + 1)) } @_reservation << [z, prc] end def draw(x, y, image, z=0) prc = ->{ image._create_texture unless image._texture SDL.set_texture_blend_mode(image._texture, SDL::BLENDMODE_BLEND) SDL.render_copy(Window._renderer, image._texture, nil, SDL::Rect.new(x, y, image.width, image.height)) } @_reservation << [z, prc] end def clear SDL.set_render_target(Window._renderer, @_texture) SDL.set_render_draw_color(Window._renderer, *@_bgcolor) SDL.set_render_draw_blend_mode(Window._renderer, SDL::BLENDMODE_NONE) SDL.render_fill_rect(Window._renderer, nil) end def update self.clear @_reservation.sort_by!{|v|v[0]}.each{|v|v[1].call} @_reservation.clear end def width SDL.query_texture(@_texture)[2] end def height SDL.query_texture(@_texture)[3] end def bgcolor DXRuby._convert_color_sdl_to_dxruby(@_bgcolor) end def bgcolor=(bgcolor) @_bgcolor = DXRuby._convert_color_dxruby_to_sdl(bgcolor) end end end
基本的にはWindowでやっていた描画とほぼ同じである。ポイントとしてはSDL.set_render_targetを呼んで描画先を切り替えるところ。SDL2のレンダリング関連の機能はsetなんちゃらで色々変更することができるが、それ以降の操作すべてに影響するので、何かするたびに元に戻すか、毎回指定してやる必要がある。まあ、毎回指定しておけばよい。全体に影響する設定を変更しながら描画メソッドを発行していくのはOpenGL的な雰囲気がある。
Windowモジュール
SDL2ではset_render_targetの引数にnilを渡すことで描画先をデフォルト、つまりWindowのフレームバッファに変更することができる。これを利用して、@_textureにnilを設定したRenderTargetオブジェクトをWindow内部で保持し、画面への描画はそっちに任せるようにする。ただし、それを描画元に使うことはできないので、基本的にはユーザから見えないようにするべきだが、そういうRenderTargetオブジェクトを生成するためにnewの引数のwとhを0にできるようにしてしまったので、0指定でユーザが簡単に画面へ描画する用のRenderTargetオブジェクトを作れてしまう。これは正直あまりよろしくないが、まあ、どのようにブロックしたところでRubyで書いている以上、それを防ぐことはできないと思うので、諦めることにする。
Windowモジュールはこんな感じになる。
module DXRuby module Window def self.draw_box_fill(x1, y1, x2, y2, color, z=0) @_render_target.draw(x1, y1, x2, y2, color, z) end def self.draw(x, y, image, z=0) @_render_target.draw(x, y, image, z) 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 yield @_render_target.update SDL.render_present(@_renderer) end end end end end