1.7 使用GameComponents

问题

你想将应用程序分离为GameComponent 类,实现组件的复用。

解决方案

程序的特定部分可以从程序中分离出来。在一个XNA程序中,大多数此种类型的部分都需要被更新和绘制,例如粒子系统和billboard(通常翻译为公告板或广告牌)系统(见教程3-11和3-12)。

正确的做法是为这些特定部分创建一个单独的类。在XNA Game 类中,创建这个类的实例,对它进行初始化,更新和绘制。所以你想让你的新类拥有自己的Initialize, (Un) LoadContent,Update和Draw方法,这让你就可以很容易地在Game类中调用它们了。

如果你想在这个新类中定义这些方法,最好的方法就是从GameComponent类继承。你可以将这个类添加到Game类的Components集合中,这样当Game类初始化完成后就会调用GameComponent类的Initialize方法(如果你已经定义了一个)。 而且,每次Game类的Update方法调用后,GameComponent的Update方法也会自动被调用。

如果你的组件还有绘制某些东西,你可以从DrawableGameComponent类继承而不是从GameComponent类,这样在组件类中还会包含一个Draw method,它会在Game类的Draw方法后被调用。

注意在Game类的Initialize方法最后调用了base. Initialize,就是这个代码行调用了所有GameComponent类的Initialize方法,在其他方法中你也能看到类似的base.Update(gameTime),base.Draw(gameTime)。

工作原理

例如教程3-11中的billboarding代码封装在了一个GameComponent中。而且因为这个代码还需要绘制一些东西,所以使用的是DrawableGameComponent类。

创建一个新的 (Drawable)GameComponent

通过右击项目并选择Add →New File可以添加一个新的类文件,接着选择Class,我把它命名为BillboardGC。在这个新文件中添加XNA using代码行,只需将Game类的using复制到这个新类即可。

下一步保证这个新类从GameComponent类或DrawableGameComponent类继承,如下面的代码所示。代码显示了如何创建教程3-11的billboarding。其中有些代码如CreateBBVertices 并没有写出,因为这个教程我们关注的是Initialize,LoadContent,Update和Draw方法。

class BillboardGC : DrawableGameComponent
{
    private GraphicsDevice device; 
    private BasicEffect basicEffect; 
    private Texture2D myTexture; 
    private VertexPositionTexture[] billboardVertices; 
    private VertexDeclaration myVertexDeclaration; 
    private List<Vector4> billboardList = new List<Vector4>(); 
    public Vector3 camPosition; 
    public Vector3 camForward; 
    public Matrix viewMatrix; 
    public Matrix projectionMatrix; 
    
    public BillboardGC(Game game) : base(game)
    {
    }
    
    public override void Initialize()
    {
        device = Game.GraphicsDevice; 
        base.Initialize(); 
    }
    
    protected override void LoadContent()
    {
        basicEffect = new BasicEffect(device, null); 
        myTexture = Game.Content.Load<Texture2D>("billboardtexture"); 
        AddBillboards(); 
        myVertexDeclaration = new VertexDeclaration(device, VertexPositionTexture.VertexElements); 
    }
    
    public override void Update(GameTime gameTime) 
    {
        CreateBBVertices(); 
        base.Update(gameTime); 
    }
    
    . . . 
    
    public override void Draw(GameTime gameTime) 
    {
        //draw billboards . . . 
    }
} 

注意在Initialize方法中,你的component可以获取Game类,这样component就能获取Game类的公共字段,例如Game. GraphicsDevice和Game.Content。

使用GameComponent

定义完GameComponent类后,你就可以将它添加到Game类的GameComponent集合中,一旦添加后,GameComponent的注意方法就会自动被调用。最简单的方法就是创建一个GameComponent的实例并立即添加到Components集合中,最好的位置是在Game类的构造函数中。

public Game1()
{
    graphics = new GraphicsDeviceManager(this); 
    Content.RootDirectory = "Content"; 
    Components.Add(new BillboardGC(this));
} 

这样一开始就能调用Initialize和LoadContent方法,并且在Game类的Update和Draw方法完成后调用这个新类的Update和Draw方法。有些情况下,你需要更新一些组件的公有变量。例如在billboarding组件中你需要更新camPosition和camForward变量调整View和Projection矩阵使之能正确地绘制到屏幕。因此,你应在Game类中添加一个组件变量。

BillboardGC billboardGC; 

然后存储这个组件并将它添加到Components集合中:

public Game1()
{
    graphics = new GraphicsDeviceManager(this); 
    Content.RootDirectory = "Content"; 
    billboardGC = new BillboardGC(this); 
    Components.Add(billboardGC); 
} 

在Game类的Update方法中,你可以更新组件中的四个变量,在Update方法最后调用所有组件的Update方法,让billboarding组件进行更新:

protected override void Update(GameTime gameTime)
{
     . . . 
     billboardGC.camForward = quatCam.Forward; 
     billboardGC.camPosition = quatCam.Position; 
     billboardGC.viewMatrix = quatCam.ViewMatrix; 
     billboardGC.projectionMatrix = quatCam.ProjectionMatrix; 
     base.Update(gameTime); 
}

Game类的Draw方法更简单,只是在调用所有组件的Draw方法前清除背景:

protected override void Draw(GameTime gameTime)
{
    device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer,Color.CornflowerBlue, 1, 0); 
    base.Draw(gameTime); 
} 

最后一行代码会调用billboarding 组件的Draw方法,将billboards绘制到屏幕。

代码

GameComponent和Game类的Initialize,LoadContent,Update和Draw方法的代码已经在前面写过了。

1