WisdomSoft - for your serial experiences.

描画可能なゲーム部品

AIやデータ変換など、計算が中心の描画を含まないロジックは GameComponent クラスを継承することで分離できますが、描画を含む処理をコンポーネント化したい場合は DrawableGameComponent クラスを継承します。DrawableGameComponent クラスは、ほぼ Game クラスと同じ機能を持つゲーム部品を表します。

描画処理を分割する

GameComponent を継承することで、Update() メソッドによるゲームの更新処理を部品化できましたが、描画機能がありませんでした。キャラクターなどの画面に表示するオブジェクトを部品化するには、GameComponent を継承し Draw() メソッドを持つ Microsoft.Xna.Framework.DrawableGameComponent クラスを使います。これによって、Windows フォーム開発におけるコントロールに相当するような、画面に描画するキャラクターやウィンドウなどを部品化できます。

Microsoft.Xna.Framework.DrawableGameComponent クラス
public class DrawableGameComponent : GameComponent, IDrawable

このクラスは GameComponent を継承しているため、コンストラクタのパラメータに Game クラスのオブジェクトを渡さなければなりません。

DrawableGameComponent クラスのコンストラクタ
public DrawableGameComponent (Game game)

このクラスを継承する場合、基底クラスのコンストラクタに、ゲーム部品を利用するゲームの Game オブジェクトを渡してください。

DrawableGameComponent クラスは、GameComponent クラスの機能を継承し、加えて Draw() メソッドを提供します。DrawableGameComponent クラスを継承し、Update() メソッドや Draw() メソッドをオーバーライドすることで、Game クラスのように、ゲーム部品の状態の更新や描画が可能になります。 

DrawableGameComponent クラス Draw() メソッド
public virtual void Draw (GameTime gameTime)

gameTime パラメータには、現在のゲーム時間を表す GameTime 構造体の値が渡されます。

描画に必要な GraphicsDevice オブジェクトは、自分自身の GraphicsDevice プロパティから取得できます。 

DrawableGameComponent クラス GraphicsDevice プロパティ
public GraphicsDevice GraphicsDevice { get; }

Initialize() メソッドによって初期化された以降であれば、GraphicsDevice プロパティは、ゲーム部品に関連付けられている GraphicsDevice オブジェクトを返します。

描画に必要なテクスチャなどのコンテンツを読み込むために LoadContent() メソッドと UnloadContent() メソッドも用意されています。これらのメソッドが呼び出されるタイミングも、Game クラスの LoadContent() メソッドと UnloadContent() メソッドと同じです。これらのメソッドをオーバーライドし、LoadContent() メソッドで必要なコンテンツを読み込み、UnloadContent() メソッドで解放します。 

DrawableGameComponent クラス LoadContent() メソッド
protected virtual void LoadContent ()
DrawableGameComponent クラス UnloadContent() メソッド
protected virtual void UnloadContent ()

DrawableGameComponent クラスを継承し、必要な機能をオーバーライドできれば、後の処理は GameComponent クラスと同じです。インスタンスを生成し、Game クラスの Components プロパティが返すコレクションに、ゲーム部品として登録してください。

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

public class TestComponent : DrawableGameComponent
{
    private Texture2D texture;
    private SpriteBatch sprite;
    private Vector2 position;

    public Texture2D Texture
    {
        set { texture = value; }
        get { return texture; }
    }
    public Vector2 Position
    {
        set { position = value; }
        get { return position; }
    }

    public TestComponent(Game game) : base(game) { }

    protected override void LoadContent()
    {
        sprite = new SpriteBatch(Game.GraphicsDevice);
        base.LoadContent();
    }
    protected override void UnloadContent()
    {
        sprite.Dispose();
        base.UnloadContent();
    }

    public override void Draw(GameTime gameTime)
    {
        if (texture != null)
        {
            sprite.Begin();
            sprite.Draw(texture, position, Color.White);
            sprite.End();
        }

        base.Draw(gameTime);
    }
}

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

    private GraphicsDeviceManager graphicsDeviceManager;
    private TestComponent component1, component2;

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

        component1 = new TestComponent(this);
        component2 = new TestComponent(this);
        Components.Add(component1);
        Components.Add(component2);
    }

    protected override void LoadContent()
    {
        component1.Texture = Content.Load<Texture2D>("TestImage1");
        component2.Texture = Content.Load<Texture2D>("TestImage2");
        component2.Position = new Vector2(250, 100);

        base.LoadContent();
    }
    protected override void UnloadContent()
    {
        Content.Unload();
        base.UnloadContent();
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.White);
        base.Draw(gameTime);
    }
}
実行結果
コード1 実行結果

コード1は、DrawableGameComponent クラスを継承し、指定した座標にスプライトを描画する TestComponent クラスを作成しています。このクラスでは、表示するテクスチャを Texture プロパティで、座標を Position プロパティで設定できます。Test クラスでは 2 つの TestComponent インスタンスを生成し、それぞれに異なるテクスチャを設定しています。実行結果を見れば、TestComponent クラスの Draw() メソッドが呼び出され、設定されているテクスチャと座標で、正しくスプライトが描画されていることを確認できます。

ゲーム画面に描画するキャラクターやウィンドウなどを抽象化して DrawableGameComponent を継承するクラスとして体系化することで、共通する機能を共有しながら、個々のオブジェクトの役割ごとにコードを分離できます。

可視性

Game クラスの Components プロパティが返すコレクションから自分自身を削除することで、ゲーム部品を画面から消すことができますが、一時的に非表示にするためにゲーム部品をコレクションから解除するのは非効率的です。描画可能なゲーム部品を一時的に非表示にしたい場合は Visible プロパティを使います。

DrawableGameComponent クラス Visible プロパティ
public bool Visible { get; set; }

このプロパティの値が true の場合は Draw() メソッドが呼び出されますが、false の場合は呼び出されません。

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

public class TestComponent : DrawableGameComponent
{
    private SpriteBatch sprite;
    private Texture2D texture;
    private float alpha;
    private Color color;

    public TestComponent(Game game)
        : base(game)
    {
        alpha = 1;
    }

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

    public override void Update(GameTime gameTime)
    {
        color = Color.White * alpha;
        alpha -= 0.01F;
        if (alpha < 0) alpha = 1;

        Game.Window.Title = "Color=" + color.ToString();

        base.Update(gameTime);
    }

    public override void Draw(GameTime gameTime)
    {
        sprite.Begin();
        sprite.Draw(texture, Vector2.Zero, color);
        sprite.End();

        base.Draw(gameTime);
    }
}

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

    private GraphicsDeviceManager graphicsDeviceManager;
    private TestComponent component;

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

    protected override void Update(GameTime gameTime)
    {
        GamePadState state = GamePad.GetState(PlayerIndex.One);
        if (state.Buttons.B == ButtonState.Pressed) component.Enabled = false;
        else component.Enabled = true;
        if (state.Buttons.A == ButtonState.Pressed) component.Visible = false;
        else component.Visible = true;
        base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.White);
        base.Draw(gameTime);
    }
}
実行結果
コード1 実行結果

コード2の TestComponent クラスは、スプライトを描画する単純なゲーム部品です。このクラスでは、Update() メソッドが呼び出されると、スプライトの描画に用いられる色のアルファ値を更新して少しずつ半透明になる用にしています。同時に、ウィンドウのタイトルバーに現在の色の文字列表現を設定するため、タイトルバーのテキストを見れば、ゲーム部品の Update() メソッドが呼び出されているかどうかを確認できます。

Test クラスの Update() メソッドでは、コントローラの B ボタンが押されると Enable プロパティを false に設定し、Back ボタンが押されると Visible プロパティを false に設定します。B ボタンが押されると、Update() メソッドの呼び出しが停止するため、スプライトがフェードアウトする演出が停止します。しかし Draw() メソッドは呼び出され続けるため、スプライトは描画されます。A ボタンが押されると、Draw() メソッドの呼び出しが停止するため、スプライトが表示されなくなります。しかし、Update() メソッドは呼び出され続けるため、フェードアウト処理は進行します。両方のボタンを同時に押せば、どちらの処理も停止します。