忍者ブログ

Memeplexes

プログラミング、3DCGとその他いろいろについて

かんたんXNA4.0 その12 Game.Initializeメソッド

小さなサンプルではその有効性はさっぱりわかりませんが、
大きなプログラムではGame.Initializeメソッドが
(グラフィックスで無い)データの初期化を分離するのに役に立ちます。
(これはちょうどjavaアプレットのinitメソッドのようなものです)

例えば座標データをこの中で初期化することが出来ます。

 

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;
    VertexPositionColor[] vertices;
    VertexBuffer vertexBuffer;

    public MyGame()
    {
        graphics = new GraphicsDeviceManager(this);
    }

    protected override void Initialize()
    {
        vertices = new[]
        { 
            new VertexPositionColor(new Vector3(1, 0, 0), Color.White),
            new VertexPositionColor(new Vector3(-1, 0, 0), Color.Red),
            new VertexPositionColor(new Vector3(0, 1, 0), Color.Blue),
        };
        base.Initialize();
    }

    protected override void LoadContent()
    {
        effect = new BasicEffect(GraphicsDevice)
        {
            VertexColorEnabled = true,
            View = Matrix.CreateLookAt
            (  
                new Vector3(0, 0, 3),   //カメラの位置
                new Vector3(0, 0, 0),   //カメラの見る点
                new Vector3(0, 1, 0)    //カメラの上向きベクトル
            ),
            Projection = Matrix.CreatePerspectiveFieldOfView
            (
                MathHelper.ToRadians(45),   //視野の角度。ここでは45°
                GraphicsDevice.Viewport.AspectRatio,//画面のアスペクト比(=横/縦)
                1,      //カメラからこれより近い物体は画面に映らない
                100     //カメラからこれより遠い物体は画面に映らない
            )
        };

        vertexBuffer = new VertexBuffer(
            GraphicsDevice, 
            typeof(VertexPositionColor), 
            vertices.Length,
            BufferUsage.None
            );
        vertexBuffer.SetData<VertexPositionColor>(vertices);
    }

    protected override void UnloadContent()
    {
        effect.Dispose();
        vertexBuffer.Dispose();
    }

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

        foreach (var pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();

            GraphicsDevice.SetVertexBuffer(vertexBuffer);
            GraphicsDevice.DrawPrimitives(
                PrimitiveType.TriangleList,
                0,  //startIndex
                vertices.Length / 3 //primitiveCount
                );
        }
    }
}
xna4.0SimplestInitialize.jpg
(※前回と見た目は全く変わりません。)

ここでは、今までコンストラクタ内で初期化していた三角形の頂点データを、
MyGame.Initializeメソッド内で初期化しています。

そのあとbase.InitializeでGame.Initializeメソッドを呼んでいます。

不可解です
なぜスーパークラスのInitializeメソッドを呼ばなければならないのでしょう?
その理由はもちろんWindowsFormsのControl.OnPaintメソッドの
オーバーライドでスーパークラスのOnPaintメソッドを
呼んでやらなければならない理由と同じです。
つまり、Game.Initializeメソッドが何か大切なことをしているからです。

このケースでは、Game.InitializeメソッドはLoadContentメソッドを呼んでおり、
base.Initialize
を忘れるとグラフィックス関係の変数が全て初期化されずにnullのままで、
描画すると同時に例外(NullReferenceException)が飛びます。
(同様に、DrawメソッドとUpdateメソッドも本当はスーパークラスの
メソッドを呼んでやらなければなりません。
しかしGameComponentを使わない限りは大して問題はないですし
サンプルなので割愛しました。
実際に問題が出る時に対処しても遅くは無いでしょう。
YAGNIの原則です。)


これはまぁいいでしょう。
しかしなぜ一番最後にbase.Initializeを呼んでいるのでしょうか?

コンストラクタは普通基底クラスのものから先に呼ばれます。
派生して出来たクラスのコンストラクタで基底クラスの何かを
利用するときに困らないようになっているのです。
とすると同じ初期化を表すInitializeメソッドも一番最初に
base.Initializeを呼ぶべきであるようにも思えます。
一番最後に呼ぶのではまるでデストラクタです。

一番最後にbase.Initializeとするのは
気持ち悪いように思えます。
なぜ一番最後に呼ぶのでしょう?

まず一番最初に呼ぶとどうなるかを実際に見るのがいいかもしれません。
…例外がスローされます。
verticesがまだnullだというのです。

実はこれは一つ目の疑問と関係しています。
Game.InitializeメソッドはLoadContentを呼んでいるので、
一番最初にGame.Initializeメソッドを呼ぶと
まだ三角形の頂点データが初期化されていないまま
VertexBufferが初期化されてしまうのです。

したがってVertexBufferのnew時に
例外がスローされるわけです。
 

拍手[0回]

PR