简单动画-坦克1

这篇文章主要参考了清华大学出版社2009年1月版的《Direct 3D和XNA游戏开发基础》第15章。

阶层关系

在3D动画程序中的人物、动物以及机器,可以设计为由不同的部件组成,例如一辆坦克可由3个部件组成,包括坦克车身、可旋转的炮塔以及上下可运动的火炮。这些部件一般都是由建模软件创建的,它们采用建模坐标。程序导入这些部件后,必须把这些部件摆到合适的位置,例如要把坦克车身、可旋转的炮塔以及上下可运动的火炮,摆放到合适位置以形成坦克的形状。这些工作是由初始世界变换矩阵来完成的,每个部件都有自己的初始变换矩阵。所有部件在完成同一动作时(例如坦克向前运动),其他部件可能还要完成不同的动作,也许炮塔要同时旋转,火炮还要随着炮塔旋转同时进行上下运动。一般使用父子关系来描述这种关系,整个坦克运动是“父”运动,炮塔旋转是“子”运动,而火炮的上下运动相对于炮塔旋转又是“子”运动,这种关系被称作阶层关系。每个部件的运动都是由世界变换定义的,部件的世界变换要包括所要求的运动,即部件本身的运动以及随父部件的运动。现在要解决的问题是如何写出每个部件的世界变换。

假设火炮的上下运动用矩阵Mup表示,炮塔旋转用矩阵Mr表示,坦克前后运动用矩阵Mgo表示,坦克车身、可旋转炮塔以及上下可运动的火炮的完整世界变换矩阵分别用bodyM、TrurretM和gunM表示,它们相应的初始世界变换矩阵分别是bodyIM、TurretIM和gunIM,它们之间有如下关系:

bodyM=bodyM*Mgo

TurretM=TurretIM*Mr*Mgo

gunM=gunIM*Mup*Mr*Mgo

从以上公式可以看出,一个部件的完整世界变换矩阵包括三部分:部件本身的初始变换矩阵、部件本身运动变换矩阵和部件的父部件运动变换矩阵,用公式表示如下:

部件完整变换矩阵:部件初始变换矩阵*部件运动变换矩阵*部件父部件运动变换矩阵

例如上面的坦克,bodyM是坦克车身的完整世界变换矩阵,Mgo是坦克车身运动的变换矩阵,坦克车身是根部件,没有父部件;TurretM是炮塔的完整世界变换矩阵,Mr是坦克炮塔运动的变换矩阵,炮塔父部件运动变换矩阵为Mgo;gunM是火炮的完整世界变换矩阵,Mup是坦克火炮运动的变换矩阵,炮塔父部件运动变换矩阵为Mr*Mgo。请完全理解这些关系,因为后面的例子中要用到这些关系。

本例用一个长方体模拟坦克车身,用圆柱体模拟炮塔,用一个细圆柱体模拟上下可运动的火炮,一辆坦克由这三部分组成,能完成坦克前后运动、炮塔旋转及火炮上下运动三种动作。所以首先在3DS中创建三个模型,分别代表车身,炮塔和炮管,如下图所示。请注意朝向,可参考前一篇文章:X文件的导出系列2—纹理和朝向。

车身模型

炮塔模型

炮管模型

XNA代码

1.在Game1中添加如下变量:

//分别代表坦克车身、炮塔和火炮框架
Model BodyModel, TurretModel, GunModel; 

//三个部件的完整世界变换矩阵
Matrix BodyMatrixWorld, TurretMatrixWorld, GunMatrixWorld;  

//三个部件的初始世界变换矩阵
Matrix initialBodyMatrixWorld, initialTurretMatrixWorld, initialGunMatrixWorld; 

//观察和投影变换矩阵
Matrix ViewmMatrix, ProjectionMatrix; 

//分别为坦克车身移动距离,坦克火炮上下转动角度,坦克炮塔旋转角度
float BodyMove = 0, GunUpDown = 0, TurreRound=0; 

2.修改Game1类Initialize方法:

protected override void Initialize()
{
    initialBodyMatrixWorld = Matrix.Identity ;
    initialTurretMatrixWorld = Matrix.Identity*Matrix.CreateTranslation(0.0f,20f,0.0f); 
    initialGunMatrixWorld = Matrix.Identity *Matrix.CreateTranslation(0, 20f, 25f); 
    
    //框架完整变换矩阵=框架初始变换矩阵*框架运动变换矩阵*父框架运动变换矩阵
    //程序开始运行后,框架应在初始位置,框架运动变换矩阵都为单位变换矩阵
    BodyMatrixWorld = initialBodyMatrixWorld; 
    TurretMatrixWorld = initialTurretMatrixWorld; 
    GunMatrixWorld = initialGunMatrixWorld; 
    
    ViewmMatrix = Matrix.CreateLookAt(new Vector3(0, 80, 200), new Vector3(0, 0, 0), new Vector3(0, 1, 0)); 
    ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), 
    (float)graphics.GraphicsDevice.Viewport.Width / float)graphics.GraphicsDevice.Viewport.Height, 1.0f, 1000.0f); 
    
    base.Initialize(); 
} 

3.在LoadContent方法中载入模型:

protected override void LoadContent()
{
    BodyModel = Content.Load<Model>("Body"); 
    TurretModel = Content.Load<Model>("Turret"); 
    GunModel = Content.Load<Model>("Gun"); 
} 

4.在Draw方法中添加如下代码绘制模型:

protected override void Draw(GameTime gameTime) 
{ 
    GraphicsDevice.Clear(Color.CornflowerBlue); 
    
    //渲染坦克车身
    foreach (ModelMesh mesh in BodyModel.Meshes)
     {
         foreach (BasicEffect effect in mesh.Effects)
         { 
             effect.EnableDefaultLighting(); 
             effect.World = BodyMatrixWorld; 
             effect.View = ViewmMatrix; 
             effect.Projection = ProjectionMatrix;
         }
         mesh.Draw(); 
     }
     
     //渲染坦克炮塔
     foreach (ModelMesh mesh in TurretModel.Meshes)  
     {
         foreach (BasicEffect effect in mesh.Effects)
         {
             effect.EnableDefaultLighting(); 
             effect.World = TurretMatrixWorld;
             effect.View = ViewmMatrix;
             effect.Projection = ProjectionMatrix; 
         }
         mesh.Draw(); 
     }
     
     //渲染坦克火炮
     foreach (ModelMesh mesh in GunModel.Meshes)
     {
         foreach (BasicEffect effect in mesh.Effects) 
         {
             effect.EnableDefaultLighting(); 
             effect.World = GunMatrixWorld; 
             effect.View = ViewmMatrix; 
             effect.Projection = ProjectionMatrix; 
         }
         mesh.Draw(); 
     }
     
     base.Draw(gameTime); 
 } 

5.要通过键盘控制坦克,还需要添加CheckInput方法:

private void CheckInput()
{
    KeyboardState newState = Keyboard.GetState(); 
    
    //键盘上下控制坦克前进后退
    if (newState.IsKeyDown(Keys.Down)) 
        BodyMove += 0.5f; 
    if (newState.IsKeyDown(Keys.Up)) 
        BodyMove -= 0.5f; 
        
    //键盘PageUp,PageDown控制炮管上下 
    if (newState.IsKeyDown(Keys.PageUp )) 
    {
        GunUpDown -= 0.05f; 
        if (GunUpDown <-0.6) 
            GunUpDown = -0.6f; 
    }
    if (newState.IsKeyDown(Keys.PageDown )) 
    {
        GunUpDown += 0.05f; 
        if (GunUpDown > 0) 
            GunUpDown = 0.01f; 
    }
    
    //键盘左右控制炮塔旋转 
    if (newState.IsKeyDown(Keys.Left)) 
    {
        TurreRound -= 0.05f; 
        if (TurreRound < -1f) 
            TurreRound = -1f; 
    }    
    if (newState.IsKeyDown(Keys.Right )) 
    {
        TurreRound += 0.05f; 
        if (TurreRound > 1f) 
            TurreRound = 1f; 
    }
} 

并在Update方法中调用CheckInput:

protected override void Update(GameTime gameTime) 
{
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed||Keyboard .GetState ().IsKeyDown(Keys.Escape )) 
        this.Exit(); 
    
    CheckInput(); 
    
    BodyMatrixWorld = initialBodyMatrixWorld * Matrix.CreateTranslation(0, 0, BodyMove); 
    TurretMatrixWorld = initialTurretMatrixWorld * Matrix.CreateRotationY(TurreRound) * Matrix.CreateTranslation(0, 0, BodyMove); 
    GunMatrixWorld = initialGunMatrixWorld * Matrix.CreateRotationX(GunUpDown) * Matrix.CreateRotationY(TurreRound) * 
    Matrix.CreateTranslation(0, 0, BodyMove); 
    
    base.Update(gameTime); 
} 

运行程序后,可使用键盘上下控制坦克前进后退,键盘PageUp,PageDown控制炮管上下,/键盘左右控制炮塔旋转,截图如下:

程序截图

使用这种方法,对于简单的模型还是很方便的,在《Microsoft XNA Game Studio Creator’s Guide》一书的第12章举了两个例子都是用这种方法,一个例子中将风车底座和风车叶片分别建模,产生叶片旋转的动画,另一个例子将小轿车的车身和车轮分别建模,产生汽车前进是轮子滚动的动画。但如果一个模型中包含的部件太多,那么对每个部件分别建模就太麻烦了,这时最好的方法是在建模工具中建立完整的物体,并设置它们的层级关系,请参考后继教程。


发布时间:2009/6/5 下午3:20:55  阅读次数:7588

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号