レイヤーパラメータを使用してスプライトの前後関係を作る

Siden oppdatert :
ページ作成日 :

概要

レイヤーの深度値を使用してスプライトの前後関係を指定します。

レイヤーパラメータを使用してスプライトの前後関係を作る

動作環境

必須環境

対応 XNA バージョン
  • 2.0
  • 3.0
  • 3.1
  • 4.0
対応プラットフォーム
  • Windows (XP SP2 以降, Vista, 7)
  • Xbox 360
  • Windows Phone 7
Windows 必須頂点シェーダ バージョン 2.0
Windows 必須ピクセルシェーダ バージョン 2.0

動作確認環境

プラットフォーム
  • Windows 7
  • Xbox 360
  • Windows Phone 7 エミュレーター

内容

通常スプライトを描画すると、後に描画したものが手前に来るように描画されますが、深度値を使ってソートすることによって、SpriteBatch.Draw メソッドの呼び出し順に関係なく前後関係を明確にして表示させることができます。

スプライトの深度値でソートさせるには「SpriteBatch.Begin」メソッドの第1引数に「SpriteSortMode.BackToFront」を指定するようにします。後ろにあるスプライトから描画していって手前にあるスプライトで上書き描画していく処理方法になります。

// 一つでも半透明、透明要素があるスプライトの場合はこちらの引数を使用
this.spriteBatch.Begin(SpriteSortMode.BackToFront, null);

しかし、半透明、透明要素が全くないスプライトのみを描画する場合は以下のように指定した方が高速に描画できます。

// 深度バッファを使用して前後関係を有効にし描画するように指定
// 完全な不透明スプライトのみ描画する場合はこちらが高速
this.spriteBatch.Begin(SpriteSortMode.FrontToBack,
                       BlendState.Opaque,
                       null,
                       DepthStencilState.Default,
                       null);

第4引数に「DepthStencilState.Default」を指定していますが、これを指定するとスプライトを描画したときに各ピクセルに「深度」情報も書き込まれます。深度情報が書き込まれるとその位置より後ろにある描画対象オブジェクト(ピクセル単位)は書き込む必要がないという判断が可能になるため、描画コストが格段に下がります。

上記の理由により最初に手前にスプライトを描画すれば、後ろに重なるスプライトの描画コストが下がるため、手前のスプライトから描画するように第1引数に「SpriteSortMode.FrontToBack」を指定して手前から描画するようにソートしています。

ただしこの方法が有効なのは後ろの色要素がまったく無視可能な不透明スプライトを描画するときだけです。半透明や不透明なスプライトの場合、半透明、透明のピクセルでも深度値は各ピクセルに書き込まれるので手前から描画してしまうと後ろのスプライトが表示されなくなってしまいます。文字描画で「SpriteSortMode.BackToFront」を指定しているのはこのためです。(文字の形状以外のピクセルが透明であるため)

ちなみに私の環境でスプライトを 30000枚描画するテストを行った結果、奥から描画するよりも、深度値を使用して手前から描画した方が3倍ほど高速に処理されました。もちろん描画する枚数、重なり具合、大きさなど、また、実行環境に依存するのでそこは各自試してみてください。

SpriteBatch.Begin メソッド

スプライトを描画する前に呼び出しておきます。内部ではスプライトの描画に必要な設定を行っています。

sortMode SpriteSortMode スプライトを描画する際にどの順番で描画するかを SpriteSortMode 列挙から指定します。奥から描画させる場合は SpriteSortMode.BackToFront を指定します。手前から描画させる場合は SpriteSortMode.FrontToBack を指定します。
blendState BlendState 描画するスプライトの色と背景色のブレンド方法。既定では BlendState.AlphaBlend が指定されますが、今回の Tips では描画するスプライトが完全な不透明であるため、背景色を考慮しない BlendState.Opaque を指定しています。
samplerState SamplerState テクスチャーのサンプリング方法。null を指定すると既定の SamplerState.LinearClamp が指定されます。
depthStencilState DepthStencilState 深度ステンシルバッファをどのように使用するか指定します。null を指定すると深度ステンシルバッファを使用しない DepthStencilState.None が使用されます。深度ステンシルバッファを使用する場合は DepthStencilState.Default を指定します。
rasterizerState RasterizerState 背面カリングなどのラスタライズの方法を指定します。null を指定すると既定の RasterizerState.CullCounterClockwise が指定されます。

スプライトの描画では、SpriteBatch.Draw メソッドの第9引数に深度値を指定します。ここに設定できる値は 0.0~1.0 の範囲で、0.0 が最も手前、1.0 が最も奥として設定されます。

// 最背面に描画(赤)
this.spriteBatch.Draw(this.texture, new Vector2(150.0f, 50.0f), null,
    Color.Red, 0.0f, Vector2.Zero, 1.0f, SpriteEffects.None, 1.0f);

// 最前面に描画(緑)
this.spriteBatch.Draw(this.texture, new Vector2(110.0f, 90.0f), null,
    Color.Green, 0.0f, Vector2.Zero, 1.0f, SpriteEffects.None, 0.0f);

// 2つのスプライトの間に描画(青)
this.spriteBatch.Draw(this.texture, new Vector2(190.0f, 130.0f), null,
    Color.Blue, 0.0f, Vector2.Zero, 1.0f, SpriteEffects.None, 0.5f);

SpriteBatch.Draw メソッド

スプライトの描画バッチリストにスプライトを追加します。

texture Texture2D スプライトとして表示するテクスチャーを指定します。
position Vector2 スプライトを表示する位置。スクリーンの左上を基準にしたスクリーン座標で指定します。スプライトの原点は左上の位置になります。
sourceRectangle Nullable<Rectangle> テクスチャーの転送領域を指定します。テクスチャー全体をスプライトとして表示させる場合は null を指定できます。このパラメータを指定すると、任意の領域だけをスプライトとして表示させることができます。
color Color スプライトの色を掛け合わせる色を指定します。Color.White を指定した場合は、スプライトの原色で表示されます。Color.Black を指定した場合は、スプライトの色に関係なく完全な黒で表示されます。計算式は「結果 = スプライトの色 * color」になります。
rotation float スプライトの回転角度。単位は radian で指定する。回転軸はスプライトの左上になります。
origin Vector2 スプライトを回転させるときの回転軸の位置を指定します。スプライトのどの位置を回転軸にするかを指定しますが、実際には回転軸の位置はスプライトの左上固定で、スプライトの表示位置が -origin 移動します。
scale float スプライトの拡大率を指定します。1.0 を基準として、縦と横に拡大縮小します。拡大の原点はスプライトの左上になります。
effects SpriteEffects スプライトの表示反転効果を指定します。特に何もしなければ SpriteEffects.None を指定します。
layerDepth float スプライトを表示する深度を指定します。主にスプライトを手前に表示したり奥に表示するのに使用します。0.0~1.0 の範囲で指定し、0.0 が最前面、1.0 が最背面になります。

上記のプログラムでは「赤」「緑」「青」の順番に SpriteBatch.Draw メソッドを呼び出していますが、各深度値が「赤(1.0)」「緑(0.0)」「青(0.5)」と設定しているので、いちばん奥に赤、いちばん手前に緑が描画されていることがわかります。

深度値を使用したスプライトの描画

全コード

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
#if WINDOWS_PHONE
using Microsoft.Xna.Framework.Input.Touch;
#endif

namespace LayerDepthSprite
{
    /// <summary>
    /// ゲームメインクラス
    /// </summary>
    public class GameMain : Microsoft.Xna.Framework.Game
    {
        /// <summary>
        /// グラフィックデバイス管理クラス
        /// </summary>
        private GraphicsDeviceManager graphics = null;

        /// <summary>
        /// スプライトのバッチ化クラス
        /// </summary>
        private SpriteBatch spriteBatch = null;

        /// <summary>
        /// テクスチャー
        /// </summary>
        private Texture2D texture = null;


        /// <summary>
        /// GameMain コンストラクタ
        /// </summary>
        public GameMain()
        {
            // グラフィックデバイス管理クラスの作成
            this.graphics = new GraphicsDeviceManager(this);

            // ゲームコンテンツのルートディレクトリを設定
            this.Content.RootDirectory = "Content";

#if WINDOWS_PHONE
            // Windows Phone のデフォルトのフレームレートは 30 FPS
            this.TargetElapsedTime = TimeSpan.FromTicks(333333);

            // バックバッファサイズの設定
            this.graphics.PreferredBackBufferWidth = 480;
            this.graphics.PreferredBackBufferHeight = 800;

            // フルスクリーン表示
            this.graphics.IsFullScreen = true;
#endif
        }

        /// <summary>
        /// ゲームが始まる前の初期化処理を行うメソッド
        /// グラフィック以外のデータの読み込み、コンポーネントの初期化を行う
        /// </summary>
        protected override void Initialize()
        {
            // TODO: ここに初期化ロジックを書いてください

            // コンポーネントの初期化などを行います
            base.Initialize();
        }

        /// <summary>
        /// ゲームが始まるときに一回だけ呼ばれ
        /// すべてのゲームコンテンツを読み込みます
        /// </summary>
        protected override void LoadContent()
        {
            // テクスチャーを描画するためのスプライトバッチクラスを作成します
            this.spriteBatch = new SpriteBatch(this.GraphicsDevice);

            // テクスチャーをコンテンツパイプラインから読み込む
            this.texture = this.Content.Load<Texture2D>("Texture");
        }

        /// <summary>
        /// ゲームが終了するときに一回だけ呼ばれ
        /// すべてのゲームコンテンツをアンロードします
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: ContentManager で管理されていないコンテンツを
            //       ここでアンロードしてください
        }

        /// <summary>
        /// 描画以外のデータ更新等の処理を行うメソッド
        /// 主に入力処理、衝突判定などの物理計算、オーディオの再生など
        /// </summary>
        /// <param name="gameTime">このメソッドが呼ばれたときのゲーム時間</param>
        protected override void Update(GameTime gameTime)
        {
            // Xbox 360 コントローラ、Windows Phone の BACK ボタンを押したときに
            // ゲームを終了させます
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
            {
                this.Exit();
            }

            // TODO: ここに更新処理を記述してください

            // 登録された GameComponent を更新する
            base.Update(gameTime);
        }

        /// <summary>
        /// 描画処理を行うメソッド
        /// </summary>
        /// <param name="gameTime">このメソッドが呼ばれたときのゲーム時間</param>
        protected override void Draw(GameTime gameTime)
        {
            // 画面を指定した色でクリアします
            this.GraphicsDevice.Clear(Color.CornflowerBlue);

            // 深度バッファを使用して前後関係を有効にし描画するように指定
            // 完全な不透明スプライトのみ描画する場合はこちらが高速
            this.spriteBatch.Begin(SpriteSortMode.FrontToBack,
                                   BlendState.Opaque,
                                   null,
                                   DepthStencilState.Default,
                                   null);

            // 一つでも半透明、透明要素があるスプライトの場合はこちらの引数を使用
            //this.spriteBatch.Begin(SpriteSortMode.BackToFront, null);

            // 最背面に描画(赤)
            this.spriteBatch.Draw(this.texture, new Vector2(150.0f, 50.0f), null,
                Color.Red, 0.0f, Vector2.Zero, 1.0f, SpriteEffects.None, 1.0f);

            // 最前面に描画(緑)
            this.spriteBatch.Draw(this.texture, new Vector2(110.0f, 90.0f), null,
                Color.Green, 0.0f, Vector2.Zero, 1.0f, SpriteEffects.None, 0.0f);

            // 2つのスプライトの間に描画(青)
            this.spriteBatch.Draw(this.texture, new Vector2(190.0f, 130.0f), null,
                Color.Blue, 0.0f, Vector2.Zero, 1.0f, SpriteEffects.None, 0.5f);

            // スプライトの一括描画
            this.spriteBatch.End();

            // 登録された DrawableGameComponent を描画する
            base.Draw(gameTime);
        }
    }
}