やっほー、もうみんな、
「テクスチャ」と
「スプライト」っていう2つの言葉は聞いたことあるよね?
じゃあどんな意味なのか、わかるかな?
「テクスチャ」って なぁに?
画像です。でも、どうして「テクスチャ」って呼ぶのかな?
それはですね、「2Dの物体に貼り付ける」っていう特別な役割があるからだよ。
「スプライト」って、なぁに?
テクスチャが貼り付けられた四角形、のことだよ。
わかったかな?
まだよくわかんないっていうお友達は、wikipedia先生にきいてみてね。
つまり、スプライトを作る前に、テクスチャが必要ってことだね。
SFMLでテクスチャを扱うためのクラスは、sf::Texture だよ。そのまんまだね。
テクスチャの役割は「画像を読み込んで」「何かに貼り付ける」ことだから、
このクラスのほとんどのメソッドは、読み込みと更新に関するものだよ。
さて、画像ファイルをハードディスクから読み込んでテクスチャにするには loadFromFile関数を使うよ。
sf::Texture texture;
if (!texture.loadFromFile("image.png"))
{
// error...
}
この loadFromFile関数なんだけど、ときどき、よくわかんない理由で失敗しちゃうんだ。
そんなときは、まず、エラーメッセージをチェックしてね。コンソールに出力されてるよ。
さて、なんていってるかな? 「unable to open file」?
そんなときは、カレントディレクトリを確認してね。
カレントディレクトリっていうのは、キミのアプリケーションがファイルを読み込むときの、
相対パスの出発点になるフォルダのことだよ。
どうかな? キミの思ってる通りのフォルダかな?
アプリケーションをエクスプローラーから起動しているときは、
そのアプリケーションが置いてあるフォルダがカレントディレクトリなんだけど、
IDE(Visual Studio とか、Code::Blocksとか)から起動してるときは、
そのプロジェクトのフォルダになってるかもしれないね。
きっとプロジェクトの設定でカンタンに変更できると思うよ。
テクスチャはメモリから読み込むこともできます ( loadFromMemory関数 )。
ストリームから読み込むこともできます (loadFromStream関数)。
読み込み済みの画像を元に作ることもできます (loadFromImage関数)。
最後のやつについておはなしするよ。
読み込み済みの画像っていうのは、つまり、 sf::Image ってことです。
メモリ上にデータを持ってる状態だから、処理はすごく速いよ。
これとは正反対に、ビデオメモリ上にあるテクスチャのピクセルはアクセスするのも更新するのも遅いんだ。描画はすごく早いんだけどね。
SFMLで扱える画像のフォーマットなんだけど、
だいたいみんなが知ってるフォーマットは、使えるようになってるよ。
APIドキュメントに一覧が載ってるから、よかったら見てみてね。
じゃあ次に、画像の一部だけをロードするやり方だよ。
今までお話してきたローディングの関数には引数がもう1つあって、そこに値を指定するよ。
// 座標(10, 10) から サイズ32x32 の矩形を読み込む
if (!texture.loadFromFile("image.png", sf::IntRect(10, 10, 32, 32)))
{
// error...
}
おや、また新しいお友達ですね。sf::IntRectちゃん。
この子は四角形を表すシンプルなユーティリティクラスだよ。コンストラクタに左上の座標と、四角形のサイズを入れてあげてね。
テクスチャを画像から直接作成するんじゃなくて、ピクセルのリストで作ることもできます。
その場合、まずは空のテクスチャを作って、そのあとで更新するよ。
// サイズ 200x200 の 空のテクスチャを生成
if (!texture.create(200, 200))
{
// error...
}
この時点では、まだテクスチャの中身は作成されてません。
sf::Textureクラスの update関数にピクセルのリストを指定すると、その部分だけ更新されるよ。
ちなみに、このupdate関数は他にもいろんな種類のものを、テクスチャを更新する材料として、引数に受け取れるようになってます。
// テクスチャを更新:ピクセルのリストを使って
// 「* 4」してるのは、色の要素が4つ(RGBA)だから
sf::Uint8* pixels = new sf::Uint8[width * height * 4];
...
texture.update(pixels);
// テクスチャを更新:画像を使って
sf::Image image;
...
texture.update(image);
// テクスチャを更新:ウィンドウに表示されてる内容を使って。
sf::RenderWindow window;
...
texture.update(window);
上のサンプルでは、テクスチャを更新する材料は「そのテクスチャと同じサイズなのだ!」と勝手に仮定されてます。
もし同じサイズじゃない場合、たとえばテクスチャの一部分だけ更新したいとか、
そういうときは、更新したい部分の矩形の座標を指定することができるよ。詳しくは、APIドキュメントを見てね。
最後にもう1つ。
テクスチャには設定項目が2つあって、どんなふうに描画されるのかを変更できるんだ。
1つ目の設定項目は、テクスチャをスムーズに描画するかどうか、の設定だよ。
スムーズに描画すると、ピクセルの粒がぼやけて、見分けにくくなるんだ。拡大して表示するときに、違いが目立つよ。
texture.setSmooth(true);
スムーズ表示の機能はピクセルさんたちに、あれこれと口出しをするってことだから、
ときどき、見た目がおかしくなってしまうこともあるよ。
どんなときにそうなるかっていうと、スプライトを、整数じゃない座標に表示しているとき、だよ。
2つ目の設定項目は、リピート表示です。
1つのスプライトの中で、同じテクスチャをたくさん並べた感じにするよ。
texture.setRepeated(true);
この設定は、スプライトの方がテクスチャより大きい場合だけ効果があるよ。
作ろう作ろうー!
sf::Sprite sprite;
sprite.setTexture(texture);
そんでもって、描画だ!
// メインループの中、window.clear() と window.display()の間に書く
window.draw(sprite);
矩形を指定して、スプライトの中に、テクスチャを一部分だけ表示することもできます。
sprite.setTextureRect(sf::IntRect(10, 10, 32, 32));
スプライトの色も指定できます。
このとき、テクスチャの色と乗算合成されて表示されるよ。
透明度(アルファ値)も設定できます。
sprite.setColor(sf::Color(0, 255, 0)); // 緑
sprite.setColor(sf::Color(255, 255, 255, 128)); // 半透明
このスプライトは、みんな同じテクスチャで、色の設定だけ変えてます。
表示する位置、向き、倍率も設定できるよ。
// 位置
sprite.setPosition(sf::Vector2f(10, 50)); // 絶対位置
sprite.move(sf::Vector2f(5, 10)); // 相対位置(移動)
// rotation
sprite.setRotation(90); // 絶対角度
sprite.rotate(15); // 相対角度(回転)
// scale
sprite.setScale(sf::Vector2f(0.5f, 2.f)); // 絶対倍率
sprite.scale(sf::Vector2f(1.5f, 3.f)); // 相対倍率(拡大)
デフォルトでは、スプライトの左上の点が、位置や回転や拡大の基準点になってます。
左上じゃなくて、真ん中とかの方がいいかもしれないですよね。
setOrigin関数で変更できるよ。
sprite.setOrigin(sf::Vector2f(25, 25));
こういう、移動や回転などの操作は SFMLの他の物体でも同じやり方です。
なので、
別のチュートリアルにまとめてあるよ。
テクスチャ作成成功! スプライトもちゃんと作ったよ。
だけど……画面にはまっしろな四角形が出るだけ。なんでなんで!
それはね、よくある失敗なの。
スプライトにテクスチャを設定したとき、スプライトが内部で保持しているのは、
そのテクスチャへの「ポインタ」なの。
だから、テクスチャの本体が削除されたり、メモリ上の別の場所に移動したりしたら、
スプライトはテクスチャを見失ってしまうの。
この失敗は、こんなふうにコードを書くと起きるよ。
sf::Sprite loadSprite(std::string filename)
{
sf::Texture texture;
texture.loadFromFile(filename);
return sf::Sprite(texture);
} // 関数が終了する時点で、テクスチャが破棄されてしまう
スプライトに使われる間、テクスチャが有効なままでいてくれるように、
テクスチャがいつ生成されていつ破棄されるのか、しっかり管理するようにしようね。
理由はカンタンだよ。
テクスチャを切り替えるのはグラフィックカードさんにとって、すごく疲れるお仕事なんだ。
同じテクスチャのままで、たくさんのスプライトを使う方が、カードさんは高速に動いてくれるよ。
それと、1枚のテクスチャだけを使うようにすると、スタティックジオメトリをグループ化して、
1つの物体にできるんだ。そうすると、1回の draw関数で1枚のテクスチャしか使わなくてすむから、
たくさんの物体を描画するよりも早く処理できるよ。
スタティックジオメトリをグループ化するおはなしをするには、他のクラスのお話もしなきゃいけなくなって、
今日のおはなしのテーマを越えてしまうから、詳しくは
頂点リストの日におはなしするね。
SFMLのグラフィックスモジュールじゃなくて OpenGLを使うときにも、sf::Textureさんは活躍してくれるよ。
OpenGLのテクスチャのラッパーになって、他のOpenGLのデータと仲良くできるよ。
描画のために sf::Textureを使うには( glBindTexture と同じ )、スタティック関数の bind を使ってね。
sf::Texture texture;
...
// バインド
sf::Texture::bind(&texture);
// ……テクスチャを貼った OpenGLの物体を描画……
// バインドを解除
sf::Texture::bind(NULL);