sdl2rでゲームライブラリを作る(4)
ここらで方針を決めておこう。どういったものを作るつもりでいるのか。それが定まっていないと二転三転してしまうから。
まず、DXRuby完全互換は目指さない。もっといいアイデアがあればそのように作るし、それがよさげであればDXRubyのほうに取り込まれちゃったりもするかもしれない。ともあれ、SDL2を使うわけだし、同じものが作れるわけでもない。SDL2の良さを引き出す方向性のほうがいいはずだ。じゃあなんで逆にDXRuby的なものを作るのかというと、俺にとってそれが扱いやすいから、である。結局のところ、自分で使うものを作っているだけとも言える。
DXRubyでできることでもSDL2ではできない、ということもあるだろうが、その逆に、SDL2でしかできないこともあるだろう。そういったものは、せっかくできるのだから活用する方向で考える。例えばDXRubyとは違ってRubyで構築するのだから、sdl2rオブジェクトはユーザが操作できる。動作がわかっていてsdl2rのオブジェクトを直接操作するのであれば、それは問題ないので許可する方向となる。
こういう考え方でもって昨日のImageクラスを作り直す。
Imageクラス再び
昨日の時点のsdl2rに対してこのようにコードを書いて、ImageクラスとWindow.drawを再定義する。
require_relative 'dxsdl2r' module DXRuby C_RED = [255, 0, 0] module Window def self.draw(x, y, image, z=0) prc = ->{ image._create_texture unless image._texture SDL.render_copy(@renderer, image._texture, nil, SDL::Rect.new(x, y, image.width, image.height)) } @reservation << [z, prc] end end class Image attr_accessor :_surface, :_texture, :_pixels def initialize(w, h, color=[0, 0, 0, 0]) # wとhの両方が0の場合はSurfaceを生成しない return if w == 0 and h == 0 # IntelCPUはリトルエンディアンだがビッグエンディアンにも一応対応しておく # 画像フォーマットは32bit固定 if SDL::BYTEORDER == SDL::BIG_ENDIAN @_surface = SDL.create_rgb_surface(0, w, h, 32, 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff) else @_surface = SDL.create_rgb_surface(0, w, h, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000) end # 指定色で塗りつぶす SDL.fill_rect(@_surface, nil, DXRuby._convert_color_dxruby_to_sdl(color)) # Pixelsオブジェクト取得 @_pixels = @_surface.pixels end def width @_surface.w end def height @_surface.h end # テクスチャを破棄する # 次に描画で使われる際に再生成される def _modify if @_texture SDL.destroy_texture(@_texture) @_texture = nil end end # テクスチャ生成 def _create_texture # テクスチャ生成 @_texture = SDL.create_texture_from_surface(Window._renderer, @_surface) end # ピクセルに色を置く def []=(x, y, color) @_pixels[x, y] = DXRuby._convert_color_dxruby_to_sdl(color) self._modify end # ピクセルの色を取得する def [](x, y) @p_ixels[x, y] self._modify end end end image = Image.new(100, 100, C_WHITE) image[50,50] = C_RED Window.loop do x, y = Input.mouse_x, Input.mouse_y Window.draw(x, y, image) end
解説
昨日のImageクラスと大きく変わったところは、まずattr_readerがattr_accessorに変わったところだろう。sdl2rオブジェクトはユーザが差し替えることも可能にするわけだ。当然、使い方を間違えるとおかしな動きをするだろうが、それはユーザの責任である。
あと、initializeの頭でwとhが0の場合はSurfaceを生成しないようにしている。これはImage.loadを作るための布石で、Image.loadはクラスメソッドであり、外部からImageインスタンスを生成する必要があるのだが、普通にnewすると無駄にSurfaceを生成してしまうので、Surfaceを作らずあとで外部から設定できるようにした。dxruby_sdlで使われていた手をパクった。
他の大きな違いとしては、Textureをinitializeで生成しないようにした。Imageは中身を書き換えることができるオブジェクトになるので、それはつまりSurfaceを変更できるということで、Surfaceが変わればTextureは再生成しなければならない。直接Textureを更新するのもできるかどうかわからないのだが、なんか調べるのもめんどいので再生成である。んで、Surfaceを変更した場合にImage#_modifyを呼んでTextureを破棄し、描画系メソッド内で破棄されていたら再生成する感じになる。このルールに則ってさえいれば、例えばユーザが勝手にSurfaceを書き換えるとか、他のものに差し替えるだとかしても問題は無い。ということになる。
また、描画時に渡すImageオブジェクトはクラスをチェックしないので、必要なメソッドが存在し、想定される動作をしてくれれば、それがImageオブジェクトでなくても構わない。DXRubyではC構造体を持っているので厳密にチェックせざるをえないが、dxsdl2rの場合はRubyで書かれていて必要な情報はsdl2rオブジェクトなどでありRubyから扱えるので、似たようなものをユーザが作ればImageクラスである必要が無い。このあたりもDXRubyとは一味違う、Rubyならではのダックタイピングを活用した自由度となる。
ついでにピクセルの設定と読み出しのメソッドを追加しておいた。これはSDL::Surface::Pixelsオブジェクトを使う。このオブジェクトはSDL2には無くて、sdl2r独自のものである。SDL_Surfaceは様々な色フォーマットを生データで管理するので、Rubyでそれを書くのは面倒すぎるということで、抽象化した。ピクセルの読み書きを簡単にできるはずだ。