WisdomSoft - for your serial experiences.

スプライトの重ね合わせ

複数回 SpriteBatch クラスの Draw() メソッドを呼び出し、スプライトを重ねて描画するときの並べ替えや合成について解説します。

背景の透過

通常、ゲームの画面は個別のスプライトを複雑に重ね合わせて構築されます。ベースとなる背景の上に、建物や木々、キャラクターや戦艦、ミサイル、爆発など、個別に用意されたスプライトを Draw() メソッドで重ね合わせます。そうすることで、画面上の特定の物体だけを動かしたり、消したり、または追加したりできます。

SpriteBatch クラスを使った描画では、Begin() メソッドと End() メソッドの間で Draw() メソッドを何度でも呼び出してスプライトを重ねることができます。元のテクスチャがアルファ値をサポートしている場合、アルファ値に従ってスプライトを透過させることができます。ゲームのオブジェクトの多くは、単純な矩形ではなく複雑な形をしています。複雑な形をしたキャラクターを背景画像に重ねるには、アルファ値を持つテクスチャを使ってピクセルの一部を透過させます。

もっとも簡単な方法は、アルファ値を持つ PNG フォーマットをキャラクター画像に用いることです。この方法であれば、プログラム側で特別な処理を行う必要はありません。PNG フォーマットのファイルから読み込んだテクスチャはアルファ値を持つため、イメージの透明なピクセルを保持します。キャラクター画像の背景部分を透明にした PNG ファイルを読み込めば、そのまま背景に重ねることができます。

スプライトは、基本的には SpriteBatch クラスの Draw() メソッドを呼び出した順番で重ねられます。よって、最初に背景画像を描画し、その後に前景となるキャラクターやアイテムなどを描画します。

コード1
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

public class TestGame : Game
{
    public static void Main(string[] args)
    {
        using (Game game = new TestGame()) game.Run();
    }

    private GraphicsDeviceManager graphicsDeviceManager;
    private SpriteBatch sprite;
    private Texture2D fore, back;

    public TestGame()
    {
        graphicsDeviceManager = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }

    protected override void LoadContent()
    {
        sprite = new SpriteBatch(GraphicsDevice);
        fore = Content.Load<Texture2D>("TestImage");
        back = Content.Load<Texture2D>("Background");

        base.LoadContent();
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.White);

        sprite.Begin();
        sprite.Draw(back, Vector2.Zero, Color.White);
        sprite.Draw(fore, new Vector2(200, 200), Color.White);
        sprite.End();

        base.Draw(gameTime);
    }
}
実行結果
コード1 実行結果

コード1は、背景写真の上にキャラクター画像を重ねています。キャラクターのテクスチャは、背景が透明になっている PNG ファイルから読み込んでいます。この方法であれば、プログラムで特別な処理をすることはありません。

並べ替えと合成

重ね合わせたスプライとの描画は、通常は Draw() メソッドを呼び出した順番に行われます。よって、より後に描画したスプライトが前面に張り付けられます。しかし、デフォルトの設定では Draw() メソッドが呼び出された瞬間に描画されるのではなく、End() メソッドが呼び出されたタイミングで Draw() メソッドの手順に従って描画されます。

オーバーロードされた次の Begin() メソッドを使うことで、スプライトの深度に従って並べ替え、合成に関するオプションを指定できます。

SpriteBatch クラス Begin() メソッド
public void Begin (SpriteSortMode sortMode, BlendState blendState)

sortMode パラメータはどのような順番でスプライトの描画を行うかを表す Microsoft.Xna.Framework.Graphics.SpriteSortMode 列挙型の値を指定します。blendState パラメータには、合成方法を提供する Microsoft.Xna.Framework.Graphics.BlendState クラスのオブジェクトを指定します。

SpriteSortMode 列挙型は Begin() メソッドから End() メソッドまでの間に Draw() メソッドで描画されたスプライトをどのように重ねるか、その順番を指定するメンバを提供します。

Microsoft.Xna.Framework.Graphics.SpriteSortMode 列挙型
public enum SpriteSortMode
メンバ 解説
BackToFront スプライト深度の後ろから前の順番に並べ替えて描画する点以外は Deferred と同じ。
Deferred End() メソッドが呼び出されるまでスプライトを描画しない。Draw() メソッドが呼び出された順番で描画する。
FrontToBack スプライト深度の前から後ろの順番に並べ替えて描画する点以外は Deferred と同じ。
Immediate Draw() メソッドを実行すると、直ちにスプライトを描画する。
Texture 使用されているテクスチャの順に並べ替えて描画する点以外は Deferred と同じ。

既定では Deferred メンバが設定されています。

GPU は性質上、状態の変更がパフォーマンスを落とします。よって、グラフィックスデバイスの設定を変更することなく、多くの描画を一度に実行することが理想的です。SpriteBatch クラスは Draw() メソッドを呼び出しても直ちに描画するのではなく、End() メソッドを実行したときに設定に基づいて適切な順序に並び替えて描画します。

既定の設定では Draw() メソッドを呼び出した順番で描画されますが、最も効率的なのは Texture メンバによる並べ替えです。この場合、Draw() メソッドのパラメータに指定されたテクスチャごとに並べ替えられ、グラフィックスデバイスにテクスチャを設定する回数が最小化されます。順序はテクスチャごとに描画されるため、重ね合わせられる描画には適しません。背景マップの描画などに適します。

BackToFront メンバと FrontToBack メンバは、Draw() メソッドの描画時にスプライトの深度が設定されている場合、スプライトの深度に基づいて並べ替えます。スプライトの深度は前述した Draw() メソッドの layerDepth パラメータに指定する 0 ~ 1 までの値です。0 であれば前、1 であれば後ろと判断されます。

Immediate メンバは上記のような SpriteBatch クラスによる並べ替えを無効化します。Draw() メソッドを呼び出すと、直ちにスプライトが描画されます。上記以外の方法で、独自の並べ替えを行いたい場合などに応用できますが、特別な理由がない限り使用するべきではありません。

重ねられたスプライトは、テクスチャや Draw() メソッドに指定された合成色に基づいて、背景と合成されます。このときの合成方法が Microsoft.Xna.Framework.Graphics.BlendState クラスのオブジェクトによって指定されます。

Microsoft.Xna.Framework.Graphics.BlendState クラス
public class BlendState : GraphicsResource

合成の演算方法などを指定する BlendState オブジェクトを作ることもできますが、主要な合成については BlendState クラスの静的なプロパティから得られます。既定では AlphaBlend プロパティが返す BlendState オブジェクトが用いられています。 

BlendState クラス AlphaBlend プロパティ
public static readonly BlendState AlphaBlend

このプロパティは標準的なアルファ合成を表す BlendState オブジェクトを返します。AlphaBlend プロパティのような、BlendState クラスが事前に用意している合成を、組み込みの合成ステートオブジェクトと呼びます。

コード2
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

public class TestGame : Game
{
    public static void Main(string[] args)
    {
        using (Game game = new TestGame()) game.Run();
    }

    private GraphicsDeviceManager graphicsDeviceManager;
    private SpriteBatch sprite;
    private Texture2D texture;

    public TestGame()
    {
        graphicsDeviceManager = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }

    protected override void LoadContent()
    {
        sprite = new SpriteBatch(GraphicsDevice);
        texture = Content.Load<Texture2D>("TestImage");

        base.LoadContent();
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.White);

        sprite.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend);
        sprite.Draw(texture, Vector2.Zero, null, Color.White, 0, Vector2.Zero, 1, SpriteEffects.None, 0);
        sprite.Draw(texture, new Vector2(64, 32), null, Color.White, 0, Vector2.Zero, 1, SpriteEffects.None, 0.5F);
        sprite.Draw(texture, new Vector2(128, 64), null, Color.White, 0, Vector2.Zero, 1, SpriteEffects.None, 1);
        sprite.End();

        base.Draw(gameTime);
    }
}
実行結果
コード2 実行結果

コード2は、Draw() メソッド呼び出し順序とは無関係に、並び変え深度に従って背面から順に描画する例です。SpriteBatch クラスの Begin() メソッドで、SpriteSortMode 列挙型の BackToFront メンバを指定しています。よって、End() メソッドを呼び出した時点で、Draw() メソッドに指定した並び変え深度が背面になっているスプライトから順に描画されます。実行結果を見ると、並び変え深度が 1 のスプライトが、並び変え深度 0 のスプライトに上書きされています。ここから、並び変え深度が背面のスプライトが先に描画されていることを確認できます。

Begin() メソッドに渡す BlendState オブジェクトを変更することで、スプライトを重ねたときの合成方法を返られます。例えば、出力ピクセルで上書きするのではなく、加算したいという場合は Additive プロパティから得られるオブジェクトを使います。

BlendState クラス Additive プロパティ
public static readonly BlendState Additive

このプロパティは加算合成を表す組み込み合成ステートオブジェクトを返します。色を加算するため、重ねるほど白に近くなります。

コード3
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

public class TestGame : Game
{
    public static void Main(string[] args)
    {
        using (Game game = new TestGame()) game.Run();
    }

    private GraphicsDeviceManager graphicsDeviceManager;
    private SpriteBatch sprite;
    private Texture2D fore, back;

    public TestGame()
    {
        graphicsDeviceManager = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }

    protected override void LoadContent()
    {
        sprite = new SpriteBatch(GraphicsDevice);
        fore = Content.Load<Texture2D>("TestImage");
        back = Content.Load<Texture2D>("Background");

        base.LoadContent();
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.White);

        sprite.Begin();
        sprite.Draw(back, Vector2.Zero, Color.White);
        sprite.End();

        sprite.Begin(SpriteSortMode.Deferred, BlendState.Additive);
        sprite.Draw(fore, new Vector2(0, 100), Color.White);
        sprite.End();

        sprite.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
        sprite.Draw(fore, new Vector2(400, 100), Color.White);
        sprite.End();

        base.Draw(gameTime);
    }
}
実行結果
コード3 実行結果

コード3は、同じテクスチャを使って、デフォルトの合成オプションであるアルファ合成と加算合成でスプライトを描画しています。実行結果のうち、左側が加算合成、右側がアルファ合成です。

フェード処理

多くのゲームでは、画面にスプライトを表示するときの演出でフェードを使っています。フェード処理は、透明な状態から徐々に登場するフェードインと、徐々に透明になって消えるフェードアウトからなります。フェード処理は、固定されたアルファ値や合成方法ではなく、スプライト全体の透明度を実行時に変更しなければなりません。このような処理には、元のテクスチャではなく、スプライトの描画時に SpriteBatch クラスの Draw() メソッドで色を変調させましょう。Draw() メソッドの color パラメータを思い出してください。

Draw() メソッドでは、color パラメータに与えた色とスプライトとして描画するテクスチャの色が乗算されるため、特定の色を抜くことができます。アルファ値の値が低ければ、その比率に合わせてスプライト全体が透明になります。この性質を利用して、フレーム毎にアルファ値を少しずつ加算することで、フェードインやフェードアウトを表現できます。

コード4
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

public class TestGame : Game
{
    public static void Main(string[] args)
    {
        using (Game game = new TestGame()) game.Run();
    }

    private GraphicsDeviceManager graphicsDeviceManager;
    private SpriteBatch sprite;
    private Texture2D fore, back;
    private float alpha;

    public TestGame()
    {
        graphicsDeviceManager = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }

    protected override void LoadContent()
    {
        sprite = new SpriteBatch(GraphicsDevice);
        fore = Content.Load<Texture2D>("TestImage");
        back = Content.Load<Texture2D>("Background");

        base.LoadContent();
    }

    protected override void Update(GameTime gameTime)
    {
        if (gameTime.TotalGameTime.Seconds % 4 == 0)
            alpha = gameTime.TotalGameTime.Milliseconds / 1000.0F;
        else if (gameTime.TotalGameTime.Seconds % 4 == 1)
            alpha = 1;
        else if (gameTime.TotalGameTime.Seconds % 4 == 2)
            alpha = 1.0F - (gameTime.TotalGameTime.Milliseconds / 1000.0F);
        else alpha = 0;

        base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.White);

        sprite.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
        sprite.Draw(back, Vector2.Zero, Color.White);
        sprite.Draw(fore, new Vector2(200, 200), Color.White * alpha);
        sprite.End();

        base.Draw(gameTime);
    }
}
実行結果

コード4は、SpriteBatch クラスの Draw() メソッドによるキャラクターの描画で、白に alpha フィールドを乗算した値を合成色として指定しています。alpha フィールドは、Update() メソッドが呼び出されるたびに、経過時間に応じてアルファ値を更新し、キャラクターの透明度を制御します。白色は全ての要素が 1 であり、これに 0 ~ 1 までの範囲で変化する alpha フィールドを乗算することで、完全な透明である 0 から、完全な不透明である 1 までの色を生成します。