sdl2rでゲームライブラリを作る(3)
トウフが表示できるだけではさすがにしょんぼりなので、今回はImageクラスと、Imageを描画するWindow.drawメソッドを作ってみよう。
とりあえずコードを出そう。
module DXRuby C_WHITE = [255, 255, 255] class Image attr_reader :_surface, :_texture, :width, :height def initialize(w, h, color=[0, 0, 0, 0]) 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)) @_texture = SDL.create_texture_from_surface(Window._renderer, @_surface) @width, @height = w, h end end end
んで、SDL2で画像を扱う際のいくつかの注意点を挙げる。
SDL2の画像
SDL2では画像データはSDL_Surfaceで扱う。sdl2rではSDL::Surfaceクラスとなる。これはSDL1.xの頃からある古いもので、世の中に無数にあるアーキテクチャを柔軟にサポートできるように作られている。例えば画像の色フォーマットについてもそうだし、CPUのエンディアンについてもそう。とにかく柔軟なので、これを使うのはとにかくめんどくさい。
今回のコードに出てくるSDL::BYTEORDERなる定数はCPUのエンディアンを表していて、例えばMacとかWindowsではIntelのCPUが使われているのでリトルエンディアンになるわけだが、SDL2は他のCPUアーキテクチャもサポートするのでビッグエンディアンのCPUで使われても動くようにする必要がある。かもしれない。sdl2rはRubyとSDL2が動く環境ならどこでも動くはずであり、そういう意味ではどこでも動いて欲しいので、とりあえずビッグエンディアンをサポートするコードを入れておいた。ちなみにこの書き方はリファレンスマニュアルのコピペである。
エンディアンを見て変更しているのは32bitカラーの順番であるわけだが、SDL_Surfaceは別に32bitカラー限定ではない。モノラルから他いろいろ、たいがいのフォーマットはサポートしている。カラーパレットも使える。それらすべてで共通のコードで色を置いたり読んだりできるかというと、実は全然そうでもなくて、色データへのポインタが取得できるだけで、フォーマットごとに違うアドレス計算などはユーザ任せになっている。ひどい話である。
sdl2rではありがちなフォーマットについてはアドレス計算を網羅してあり、色フォーマットごとの色データの違いはSDL2がうまいこと処理してくれるので、Rubyから扱う場合はSDL::Surface#pixelsで座標とSDL::Colorだけで処理できるようになっている。
Image#initialize
Image#initializeではとりあえずSurfaceを指定サイズで作って、それの全体を指定色で塗りつぶして、そこからSDL_Textureオブジェクトを生成する。SDL2のRenderer経由で描画するにはSDL_Textureオブジェクトが必要だからである。これに関する処理あれこれはまたあとで。
最後に幅と高さを保存して終了する。
Window.draw
Imageだけ作れても意味がないので描画メソッドも作る。
module Window def self.draw(x, y, image, z=0) prc = ->{ SDL.render_copy(@renderer, image._texture, nil, SDL::Rect.new(x, y, image.width, image.height)) } @reservation << [z, prc] end end
Procを使ったりする部分は前回と一緒で、描画処理そのものはSDL_RenderCopyを使う。これがRenderer経由でTextureを描画してくれる関数である。
SDL_RenderCopyは地味に曲者で、描画先を表すSDL_Rectで描画サイズを変更することができる。これを使うとスケーリングが自由にできる、ということになるわけだが、指定できるのは整数だけなので、スケーリング時の位置が微妙に調整できない。これはよろしくない。
全体のコード
その他サンプルコードまで含めて今回のコードはこんな感じになる。
require_relative 'dxsdl2r' module DXRuby C_WHITE = [255, 255, 255] module Window def self.draw(x, y, image, z=0) prc = ->{ SDL.render_copy(@renderer, image._texture, nil, SDL::Rect.new(x, y, image.width, image.height)) } @reservation << [z, prc] end end class Image attr_reader :_surface, :_texture, :width, :height def initialize(w, h, color=[0, 0, 0, 0]) 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)) @_texture = SDL.create_texture_from_surface(Window._renderer, @_surface) @width, @height = w, h end end end image = Image.new(100, 100, C_WHITE) Window.loop do x, y = Input.mouse_x, Input.mouse_y Window.draw(x, y, image) end
前回までのsdl2rと一緒にこれを動かすとマウスの位置にトウフが出るはずである。Imageクラス作ったのに今回もトウフで終わってしまった。
ここまでのコードをsdl2r.rbとしてまとめたものはここにおいておいた。ほとんど使い物にはならないが使ってみたい人はどうぞ。