スプライトを描く(1)
ゲームのメインロジックを書く前にやっておきたいのは、キャラクタ表示ルーチンの整備です。何かしらの動作確認をしたい場合、何かを表示するのが最も手っ取り早いです。
OpenSiv3Dは文字や絵文字、アイコンなどを簡単に表示させることができますが、やはり自前のキャラクタを画面に描画したいというのが人情だと思います。そこでスプライトを表示させるクラスを先に作ってしまいます。
s3d::Sprite
OpenSiv3Dにもs3d::Spriteというクラスがあります。s3d::Spriteは画面の絶対ピクセル座標に対し、マッピングするUV座標、透明度つき頂点カラー指定をあらかじめ設定する機能です。そして描画時にテクスチャを指定します。
同じ設定値を使い、テクスチャだけを切り替えて表示することができるので、キャラクタの表示よりは画面エフェクトの描画に向いています。機能的にはテクスチャの表示フィルターと考えてもよいかもしれません。
s3d::Spriteを使えば、たとえば次の画像をモザイクガラスっぽくアレンジして表示することも簡単にできます。
プログラム上で加工したのが次の画像です。わかりやすくするため、2倍に拡大表示して描画させました。
この画像へ適用した効果を他のテクスチャにも適用することができるのです。
ゲーム開発で使いたいスプライトはこのようなフィルター的なものではなく、任意の座標へキャラクタを描画させるためのものですから、s3d::Spriteは向きません。
次のソースコードはモザイクガラスを表示させたサンプルです。頂点数の最適化などはしていません。
#include <Siv3D.hpp>
#define px2uv(p, size) ((p) ? (float)(p) / (float)(size) : 0)
#define NumOfPieces(w, h, div) (((w) / (div)) * ((h) / (div)))
#define Vertices(w, h, div) (NumOfPieces(w, h, div) * 4)
#define Indicies(w, h, div) (NumOfPieces(w, h, div) * 6)
void Main()
{
Texture tex(U"bg.jpg");
constexpr int width = 320;
constexpr int height = 240;
constexpr int divides = 4;
constexpr int scale = 2;
Sprite sp {
Vertices(width, height, divides),
Indicies(width, height, divides) };
int indexOfVertices = 0;
int indexOfIndices = 0;
for (int y = 0; y < height; y += divides)
{
for (int x = 0; x < width; x += divides)
{
sp.vertices[indexOfVertices + 0].set(
{ (x + divides) * scale, (y + divides) * scale },
{ px2uv(x, width), px2uv(y, height) },
{ 1.0, 1.0, 1.0, 1.0 });
sp.vertices[indexOfVertices + 1].set(
{ (x + divides) * scale, y * scale },
{ px2uv(x + divides, width), px2uv(y, height) },
{ 1.0, 1.0, 1.0, 1.0 });
sp.vertices[indexOfVertices + 2].set(
{ x * scale, (y + divides) * scale },
{ px2uv(x, width), px2uv(y + divides, height) },
{ 1.0, 1.0, 1.0, 1.0 });
sp.vertices[indexOfVertices + 3].set(
{ x * scale, y * scale },
{ px2uv(x + divides, width), px2uv(y + divides, height) },
{ 1.0, 1.0, 1.0, 1.0 });
sp.indices[indexOfIndices + 0] = indexOfVertices + 0;
sp.indices[indexOfIndices + 1] = indexOfVertices + 1;
sp.indices[indexOfIndices + 2] = indexOfVertices + 2;
sp.indices[indexOfIndices + 3] = indexOfVertices + 2;
sp.indices[indexOfIndices + 4] = indexOfVertices + 1;
sp.indices[indexOfIndices + 5] = indexOfVertices + 3;
indexOfVertices += 4;
indexOfIndices += 6;
}
}
while (System::Update())
{
sp.draw(tex);
}
}
ハードウェアスプライト
スプライトは本来ハードウェアの機能で実現されています。グラフィックライブラリで描画するものは厳密にはソフトウェアスプライトです。
ソフトウェアスプライトを実装する前に、ハードウェアスプライトがどのような機能を持っているのか、再確認してみましょう。
大抵のハードウェアスプライトは以下の描画オプションを持っています。
・透過処理
・パレット番号指定
・プライオリティ設定
・反転(水平方向、垂直方向)
・拡大縮小
・回転
ソフトウェアスプライトで気軽に実装できないのは、この中ではパレット番号指定とプライオリティ設定の2つです。
パレット番号指定を気軽にできない理由は、スプライトデータが画像ファイルのデータそのものを扱っているためです。
プライオリティ設定は重なりの優先順位設定のことですが、これもまた気軽に実装できません。専用の管理クラスを作って実装できないこともないのですが、管理コストもバカになりません。
描画レイヤを使って実装をしたほうが個人的には楽だと考えているので、プライオリティ設定機能は思い切って省いてしまいます。
※ 日本のゲームではじめてハードウェアスプライトを利用したのは、1979年に発売されたナムコ(現バンダイナムコホールディングス)の『ギャラクシアン』です。
実装方法の検討
ハードウェアスプライトの機能を確認したところで、ソフトウェアスプライトの実装方法を検討します。
私はスプライトを実装する場合、次の手順に従うようにコーディングします。この手順はハードウェアスプライトでもソフトウェアスプライトでも共通だからです。
(1) スプライトデータの読み込み
(2) スプライトの定義
(3) スプライトの描画
この手順でソフトウェアスプライトを実装しておけばハードウェアスプライトを使おうが、ほかのプログラミング言語を使おうが移植しやすくなります。
スプライトデータの読み込み
スプライトデータは画像ファイルのデータそのものです。画像ファイルを指定してTextureクラスへ読み込みます。テクスチャの読み込みはソフトウェアスプライトクラスの外側で行います。
スプライトの定義
テクスチャとして読み込まれた画像から、キャラクタを切り出します。具体的にはスプライトデータを読み込んだテクスチャと、テクスチャ上のピクセル座標、ピクセルサイズを指定します。
スプライトの描画
定義された内容に従い、スプライトを描画します。メインループ内では、この描画処理だけを呼べば好きな位置へスプライトを描画させることができます。
スプライトを反転させたり、拡大縮小させたり、あるいは回転させる場合は、このタイミングで行います。