简单动画-坦克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