新建一个XNA(Windowsgame)工程,取名为Windows3DGame。 右击解决方案资源管理器中的Content节点,添加两个文件夹,分别命名为Audio和Models,然后向这两个文件夹里分别添加所需资源。(http://creators.xna.com上可下载,也可从我的源码中获得,地址在下面。)注意添加Model资源时,项目中只添加后缀名为.fbx的文件。 添加一个新类,命名为GameObject.cs,其代码如下: using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.GamerServices; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Net; using Microsoft.Xna.Framework.Storage; namespace Windows3DGame { class GameObject { public Model model = null; public Vector3 position = Vector3.Zero; //位置参数 public Vector3 rotation = Vector3.Zero; //旋转参数 public float scale = 1.0f; //缩放参数 } } 一、绘制背景,载入地形。 1. 在Game.cs中添加几个变量 GameObject terrain = new GameObject(); //地形 Vector3 cameraPosition = new Vector3( //摄像机位置 0.0f, 60.0f, 160.0f); Vector3 cameraLookAt = new Vector3( //观察方向 0.0f, 50.0f, 0.0f); Matrix cameraProjectionMatrix; //Projection矩阵 Matrix cameraViewMatrix; //View矩阵 2. 在LoadContent()函数中添加如下代码: //设置View矩阵 cameraViewMatrix = Matrix.CreateLookAt( cameraPosition, cameraLookAt, Vector3.Up); //设置Projection矩阵 cameraProjectionMatrix = Matrix.CreatePerspectiveFieldOfView( MathHelper.ToRadians(45.0f), graphics.GraphicsDevice.Viewport.AspectRatio, 1.0f, 10000.0f); //载入地形 terrain.model = Content.Load<Model>( "Models//terrain"); 3. 添加函数DrawGameObject(GameObject gameobject)用来绘制游戏中的对象 //绘制对象 private void DrawGameObject(GameObject gameobject) { foreach (ModelMesh mesh in gameobject.model.Meshes) { foreach (BasicEffect effect in mesh.Effects) { effect.EnableDefaultLighting(); //默认灯光 //设置World矩阵 effect.World = Matrix.CreateFromYawPitchRoll( gameobject.rotation.Y, gameobject.rotation.X, gameobject.rotation.Z) * Matrix.CreateScale(gameobject.scale) * Matrix.CreateTranslation(gameobject.position); //设置Projection矩阵 effect.Projection = cameraProjectionMatrix; //设置View矩阵 effect.View = cameraViewMatrix; } mesh.Draw(); } } 关于这些矩阵,参考http://blog.csdn.net/changjiangboy/archive/2008/08/05/2770935.aspx。 在Draw(GameTime gameTime)函数中添加如下代码: //调用函数绘制地形 DrawGameObject(terrain); 4. 运行,效果如下图 二、绘制导弹发射装置 1. 在Game.cs中实例化两个对象 GameObject missileLauncherBase = new GameObject(); //底座 GameObject missileLauncherHead = new GameObject(); //发射头 2. 在LoadContent()函数中添加如下代码 //载入发射装置 missileLauncherBase.model = Content.Load<Model>( "Models//launcher_base"); missileLauncherBase.scale = 0.2f; //原大小的五分之一 missileLauncherHead.model = Content.Load<Model>( "Models//launcher_head"); missileLauncherHead.scale = 0.2f; //原大小的五分之一 missileLauncherHead.position = missileLauncherBase.position + new Vector3(0.0f, 20.0f, 0.0f); //head比base高20 3. 在Update(GameTime gameTime)函数中添加代码控制导弹发射方向 KeyboardState keyboardState = Keyboard.GetState(); //获取键盘状态 if (keyboardState.IsKeyDown(Keys.Left)) { //绕Y轴旋转,从上往下看逆时针方向;可看作向左 missileLauncherHead.rotation.Y += 0.05f; } if (keyboardState.IsKeyDown(Keys.Right)) { //绕Y轴旋转,从上往下看顺时针方向;可看作向右 missileLauncherHead.rotation.Y -= 0.05f; } if (keyboardState.IsKeyDown(Keys.Up)) { //绕X轴旋转,左侧看顺时针方向;可看作向上 missileLauncherHead.rotation.X += 0.05f; } if (keyboardState.IsKeyDown(Keys.Down)) { //绕X轴旋转,左侧看逆时针方向;可看作向下 missileLauncherHead.rotation.X -= 0.05f; } //左右旋转范围(-PI/3,PI/3) missileLauncherHead.rotation.Y = MathHelper.Clamp( missileLauncherHead.rotation.Y, -MathHelper.Pi/3.0f, MathHelper.Pi/3.0f); //左右旋转范围(0,PI/3) missileLauncherHead.rotation.X = MathHelper.Clamp( missileLauncherHead.rotation.X, 0, MathHelper.Pi/3.0f); MathHelper.Clamp(float value, float min, float max)函数的作用是限制value的值在min和max之间。 4. 在Draw(GameTime gameTime)函数中添加代码 //调用函数绘制发射台 DrawGameObject(missileLauncherBase); DrawGameObject(missileLauncherHead); 5. 运行后效果如下 三、发射导弹 1. 在Game.cs中添加如下变量 GameObject[] missiles; //导弹 const int numMissiles = 20; //屏幕上显示最多导弹数目 const float launcherHeadMuzzleOffset = 52.0f; //偏离炮膛参数,描述导弹初始位置,这样导弹直接从发射口 //出现,看起来有“真实”些 const float missilePower = 20.0f; //导弹动力,速度参数,大小决定导弹速度 KeyboardState previousKeyboardState; //获取(上次)当前键盘状态, //将与(当前)下一次对比 2. 在LoadContent()函数中添加代码 //载入导弹 missiles = new GameObject[numMissiles]; for (int i = 0; i < numMissiles; i++) { missiles[i] = new GameObject(); missiles[i].model = Content.Load<Model>("Models//missile"); missiles[i].scale = 3.0f; //这里为原大小的3倍 } 3. 在Game1类中添加以下几个函数 // 发射导弹 private void FireMissile() { foreach (GameObject missile in missiles) { if (!missile.alive) { missile.velocity = GetMissileMuzzleVelocity(); //初始速度 missile.position = GetMissileMuzzlePosition(); //初始位置 missile.rotation = missileLauncherHead.rotation; //旋转度与炮口一致 missile.alive = true; //“激活”状态 break; } } } // 获取导弹初始速度 private Vector3 GetMissileMuzzleVelocity() { Matrix rotationMatrix = Matrix.CreateFromYawPitchRoll( missileLauncherHead.rotation.Y, missileLauncherHead.rotation.X, 0); return Vector3.Normalize( Vector3.Transform(Vector3.Forward, rotationMatrix)) * missilePower; } // 获取导弹初始位置 private Vector3 GetMissileMuzzlePosition() { return missileLauncherHead.position + (Vector3.Normalize( GetMissileMuzzleVelocity()) * launcherHeadMuzzleOffset); } // 更新屏幕上的导弹 private void UpdateMissiles() { foreach (GameObject missile in missiles) { if (missile.alive) { missile.position += missile.velocity; //如果导弹跑出3000的距离,“肉眼”看不见了,自动消失 //如果导弹跑出窗口边缘,脱离视野,自动消失 if (missile.position.Z < -3000.0f || missile.position.X > graphics.GraphicsDevice.Viewport.Width / 2.0f || missile.position.X < -graphics.GraphicsDevice.vViewport.Width / 2.0f || missile.position.Y > graphics.GraphicsDevice.Viewport.Height / 2.0f) { missile.alive = false; } } } } 4. 在Update(GameTime gameTime)函数中添加如下代码 //按一次空格发射一枚导弹 if (keyboardState.IsKeyDown(Keys.Space) & previousKeyboardState.IsKeyUp(Keys.Space)) { FireMissile(); //发射导弹 } //调用函数更新屏幕导弹 UpdateMissiles(); previousKeyboardState = keyboardState; //当前键盘状态为下一初始状态 5. 在Draw(GameTime gameTime)函数中添加代码绘制导弹 //绘制各个导弹 foreach (GameObject missile in missiles) { if (missile.alive) { DrawGameObject(missile); } } 6. 运行,效果如下 四、添加飞船 1. 在Game.cs中添加如下常变量 GameObject[] enemyShips; //飞船 Random r = new Random(); //随机数生成器 const int numEnemyShips = 3; //屏幕飞船数目 Vector3 shipMinPosition = new Vector3(-2000.0f, 300.0f, -6000.0f); //最远位置 Vector3 shipMaxPosition = new Vector3(2000.0f, 800.0f, -4000.0f); //最近位置 const float shipMinVelocity = 5.0f; //最小速度 const float shipMaxVelocity = 10.0f; //最大速度 2. 在LoadContent()函数中加以下代码 //载入飞船 enemyShips = new GameObject[numEnemyShips]; for (int i = 0; i < numEnemyShips; i++) { enemyShips[i] = new GameObject(); enemyShips[i].model = Content.Load<Model>( "Models//enemy"); enemyShips[i].scale = 0.1f; enemyShips[i].rotation = new Vector3( 0.0f, MathHelper.Pi, 0.0f); } 3. 添加函数用来更新屏幕上的飞船 // 更新屏幕上的飞船 private void UpdateEnemyShips() { foreach (GameObject ship in enemyShips) { if (ship.alive) { ship.position += ship.velocity; //如果飞船飞到炮口后(炮位置Z坐标是0),自动消失 //如果飞船飞出视野(左、右、上),自动消失 if (ship.position.Z > 10.0f || ship.position.X > (-ship.position.Z + 160f) * Math.Tan(22.5) || ship.position.X < -(-ship.position.Z + 160f) * Math.Tan(22.5) || ship.position.Y > (-ship.position.Z + 160f) * Math.Tan(22.5) / graphics.GraphicsDevice.Viewport.AspectRatio) { ship.alive = false; } } else //如果有没“激活”的飞船,激活它,并确定其位置速度 { ship.alive = true; ship.position = new Vector3( MathHelper.Lerp( shipMinPosition.X, shipMaxPosition.X, (float)r.NextDouble()), MathHelper.Lerp( shipMinPosition.Y, shipMaxPosition.Y, (float)r.NextDouble()), MathHelper.Lerp( shipMinPosition.Z, shipMaxPosition.Z, (float)r.NextDouble())); ship.velocity = new Vector3( 0.0f, 0.0f, MathHelper.Lerp(shipMinVelocity, shipMaxVelocity, (float)r.NextDouble())); } } } MathHelper.Lerp(float value1,float value2,float amount)函数线性插入一个限定在两个值之间的值。插值=value1 + (value2 - value1) * amount,amount在0和1之间。random.NextDouble()返回一个介于0.0与1.0之间的随机数。 4. 在Update(GameTime gameTime)函数中调用刚才添加的函数 //调用函数更新飞船 UpdateEnemyShips(); 5. 在Draw(GameTime gameTime)函数中添加如下代码 //绘制各个飞船 foreach (GameObject enemyship in enemyShips) { if (enemyship.alive) { DrawGameObject(enemyship); } } 6. 运行效果如下 五、击中飞船 1. 添加一个函数检测导弹是否击中飞船 // 检测导弹是否击中飞船 private void TestCollision(GameObject missile) { BoundingSphere missilesphere = //定义导弹边界球 missile.model.Meshes[0].BoundingSphere; missilesphere.Center = missile.position; //中心 missilesphere.Radius *= missile.scale; //半径。 foreach (GameObject ship in enemyShips) { if (ship.alive) { BoundingSphere shipsphere = //定义飞船边界球 ship.model.Meshes[0].BoundingSphere; shipsphere.Center = ship.position; //中心 shipsphere.Radius *= ship.scale; //半径 //击中,导弹和飞船同时消失 if (shipsphere.Intersects(missilesphere)) { missile.alive = false; ship.alive = false; break; } } } } 边界球用来可视范围测试、碰撞检测等。如果一个对象与另一个对象的边界范围相交,就认为两对象发生碰撞。 BoundingSphere是一个结构,它有两个公共字段,Center 和Radius。一个是球中心,这里即各自对应model中心;一个是球半径,对应Model半径,因为场景中的Model是放缩了的,这里计算半径当然也要乘上其放缩参数。 BoundingSphere.Intersects (BoundingSphere)函数用来检测一个边界球与另一个边界球是否相交(发生碰撞)。 2. 在前面添加的private void UpdateMissiles()函数中的if (missile.alive){}中的if语句后添加如下代码 else { TestCollision(missile); //检测碰撞 } 3. 现在运行,当导弹击中飞船,导弹和飞船会同时“报销”。 六、给游戏添加音效 1. 在Game.cs中添加几个对象 AudioEngine audioEngine; //AudioEngin对象 SoundBank soundBank; //SoundBank对象 WaveBank waveBank; //WaveBank对象 AudioEngine对象:传入一个.xgs后缀名的XACT工程文件名作为参数。 WaveBank对象:传入你的AudioEngine对象和你在XACT工程中创建的.xwb后缀名的wave bank名字。 SoundBank 对象:传入你的 AudioEngine 对象和你在XACT工程中创建的.xsb后缀名的sound bank名字。 如果你在XACT里没有手动build工程,你可能看不到此文件,此文件由素材管理器自动生成。我们也可以利用XACT随意编辑自己喜欢的音乐文件。参考:http://www.xnadev.cn/xnahelps/GoingBeyond_Audio.htm。 2. 在LoadContent()函数中添加代码 //初始化音频引擎 加载Sound Bank和Wave Bank audioEngine = new AudioEngine("Content//Audio//TutorialAudio.xgs"); waveBank = new WaveBank(audioEngine, "Content//Audio//Wave Bank.xwb"); soundBank = new SoundBank(audioEngine, "Content//Audio//Sound Bank.xsb"); 3. 在Update(GameTime gameTime)函数中添加如下代码 //周期性的执行音频引擎 audioEngine.Update(); 4. 在private void FireMissile()函数中的if (!missile.alive){}中添加如下代码 soundBank.PlayCue("missilelaunch"); //发射导弹时音效 5. 在private void TestCollision(GameObject missile)函数中if (shipsphere.Intersects(missilesphere)){}里添加代码 soundBank.PlayCue("explosion"); //击中时爆炸音效 七、至此,这个3D游戏基本完成。没有准星,要精度相当高才可打中。 我的源程序下载地址:http://download.csdn.net/source/592262。 本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/changjiangboy/archive/2008/08/26/2835118.aspx