![表紙の写真](https://assets.st-note.com/production/uploads/images/8795572/rectangle_large_type_2_cdfa16cd89d4effc599d1bafdf7a5779.jpg?width=1200)
スプライトを描く(2)
OpenSiv3Dで画像を描画する場合、ほぼs3d::Textureクラスを経由して実装を行います。たとえば、絵文字もアイコンもテクスチャとして描画されます。
スプライトデータの読み込み処理を作成する前にテクスチャについて少し理解しておきましょう。
次のソースコードは絵文字とアイコンをテクスチャとして読み込み、描画させるためのサンプルです。
#include <Siv3D.hpp>
void Main()
{
Texture textureCat(Emoji(U"🐈"), TextureDesc::Mipped);
Texture textureIcon(Icon(0xf26e, 128), TextureDesc::Mipped);
while (System::Update())
{
textureCat.draw(0, 0);
textureIcon.draw(128, 0);
}
}
実行結果
※ アイコンはFont Awesomeに存在するものを利用することができます。アイコンのIDは、同サイトの一覧右上にあるインフォメーションアイコンをクリックすると表示されます。
スプライトデータの読み込み
スプライトデータをテクスチャへ読み込む場合は、画像ファイル名を指定して新しいテクスチャを生成します。OpenSiv3Dが対応している画像フォーマットは次の通りです。
・ビットマップファイル形式(bmp)
・ポータブルネットワークグラフィック形式(png)
・ジョイントフォトグラフィックエキスパーツグループ(jpeg/jpg)
・グラフィックスインターチェンジフォーマット(gif)
・トゥルービジョングラフィックスアダプタ(tga)
・ポータブルピクセルマップフォーマット(ppm)
スプライトで描画する醍醐味は透過処理にありますから、透過情報を画像ファイル内に持っているpng形式などのファイルを利用するのがよいと思います。
画像ファイルを読み込む
私がC++の学習をするためにDirectXで作成した『Solar Falcon』というグラディウスクローンでは、次の画像データをスプライトデータとして用意していました。これを読み込んでみましょう。
この画像データをテクスチャとして読み込むには、次のソースコードのように記述すれば済みます。デフォルトではアプリを起動したディレクトリがカレントディレクトリになっています。
Texture textureFile(U"player.png", TextureDesc::Mipped);
読み込んだ画像データを矩形で細分化していき、それぞれを個々のスプライトとして定義していきます。
スプライトの定義
スプライトの定義は読み込んだテクスチャ上の左上ピクセル座標とキャラクタのピクセルサイズを指定して行います。さきほどのスプライトデータで自機をスプライト定義する場合、次のような値となります。
左上ピクセル座標(64, 80)
ピクセルサイズ(32, 16)
これらのパラメータを受け取るDefineメソッドを作ります。
void SoftwareSprite::Define(Texture *Tex, int Left, int Top, int Width, int Height) {
mTex = Tex;
mLeft = Left;
mTop = Top;
mWidth = Width;
mHeight = Height;
}
呼び出し側からはテクスチャを読み込み、スプライトを定義します。
Texture texPlayer(U"player.bmp");
SoftwareSprite ssp;
ssp.Define(&texPlayer, 64, 80, 32, 16);
スプライトの描画
スプライトの描画はDrawで行います。メソッドメインループ内でスプライトを描画する座標を指定して呼び出します。
while (System::Update())
{
ssp.Draw(0, 0);
}
以降、スプライトを描画したい場合はDrawメソッドだけを呼び出せば済むようになります。
初期化処理でスプライトをすべて定義し、メインループ内ではスプライトの描画のみを意識するようにしておけば、ゲームロジックのコーディングに集中することができるようになります。
たくさんのスプライトを管理する際、番号で管理するのが得意な人は配列やVectorなどで管理すればよいですし、名前で管理するのが得意な人なのであればディクショナリやmapで管理するとよいのではないかと思います。
完成したソースコード
次のソースコードは完成したソフトウェアスプライトのサンプルです。
SoftwareSprite.hpp
#pragma once
#include <Siv3D.hpp>
class SoftwareSprite
{
public:
SoftwareSprite();
~SoftwareSprite();
void Define(Texture *Tex, int Left, int Top, int Width, int Height);
void Draw(int Left, int Top);
private:
Texture *mTex;
int mLeft;
int mTop;
int mWidth;
int mHeight;
};
SoftwareSprite.cpp
#include "Sprite.hpp"
SoftwareSprite::SoftwareSprite() {}
SoftwareSprite::~SoftwareSprite() {}
void SoftwareSprite::Define(Texture *Tex, int Left, int Top, int Width, int Height) {
mTex = Tex;
mLeft = Left;
mTop = Top;
mWidth = Width;
mHeight = Height;
}
void SoftwareSprite::Draw(int Left, int Top) {
(*mTex)(mLeft, mTop, mWidth, mHeight).draw(Left, Top);
}
OpenSiv3D向けのカスタマイズ
OpenSiv3Dではテクスチャの一部を切り取った場合、TextureRegion型のデータを生成します。さきほど完成したSoftwareSpriteクラスをTextureRegion型に対応させると、次のように簡潔に記述することができます。
TextureRegion::DrawメソッドではRectF型を返すので、SoftwareSprite::Drawの戻り値として返すようにしておきます。
SoftwareSprite.hpp
#pragma once
#include <Siv3D.hpp>
class SoftwareSprite
{
public:
SoftwareSprite();
~SoftwareSprite();
void Define(Texture *Tex, int Left, int Top, int Width, int Height);
RectF Draw(int Left, int Top);
private:
TextureRegion mTexRegion;
};
SoftwareSprite.cpp
#include "Sprite.hpp"
SoftwareSprite::SoftwareSprite() {}
SoftwareSprite::~SoftwareSprite() {}
void SoftwareSprite::Define(Texture *Tex, int Left, int Top, int Width, int Height) {
mTexRegion = (*Tex)(Left, Top, Width, Height);
}
RectF SoftwareSprite::Draw(int Left, int Top) {
return mTexRegion.draw(Left, Top);
}
好みの問題かもしれませんが、もしかしたら次のようにtypedef型指定子を使ってエイリアスしたい人もいるかもしれません。これは悪くない考えだと思います。
このようにしておけばクラスを作らずに済むので、テストする必要もありませんし、関数の呼び出しオーバーヘッドもなくなるので、OpenSiv3Dらしい描画をすることができます。
#include <Siv3D.hpp>
typedef TextureRegion SoftwareSprite;
void Main()
{
Texture texPlayer(U"player.png");
SoftwareSprite ssp = texPlayer(64, 80, 32, 16);
while (System::Update())
{
ssp.draw(0, 0);
}
}