詳解Windows Phone XNA 4.0 3D游戲開發(fā)
微軟前段時(shí)間發(fā)布基于XNA框架Windows Phone 7游戲開發(fā)實(shí)例,看到Silverlight for phone和XNA 4.0的開發(fā)文章已經(jīng)有了不少,而且質(zhì)量很高。而XNA 4.0的3D開發(fā)這個(gè)領(lǐng)域的文章還不是很多,XNA 4.0的3D類庫(kù)設(shè)計(jì)的非常好,比iPhone和Android的OpenGLES類庫(kù)高出一個(gè)檔次。以后學(xué)習(xí)3D開發(fā),用XNA類庫(kù)也是個(gè)不錯(cuò)的選擇,而且Windows Phone模擬器對(duì)3D的支持也非常好。唯一的遺憾是,Windows Phone不支持C++的3D開發(fā)。
51CTO推薦專題:Windows Phone應(yīng)用開發(fā)詳解
程序代碼編譯環(huán)境Visual Stuido 2010, Windows Phone 7 SDK, XNA 4.0 Game Studio, 下載鏈接:http://files.cnblogs.com/aawolf/XNA_aawolf_3D.rar
如果做過Zune上XNA 3.1開發(fā)的朋友可能會(huì)記得,在XNA 3.1中是不支持3D開發(fā)的,XNA 4.0中加入的3D支持類,主要包含在Microsoft.Xna.Framework.Graphics命名空間中。如果XNA 4.0中的3D概念與OpenGLES十分相似,我們可以找到很多相對(duì)應(yīng)的函數(shù)、方法等,某種意義上,XNA 4.0的3D支持是對(duì)OpenGLES 2.0的封裝。
一個(gè)簡(jiǎn)單的3D程序
我們就從一個(gè)簡(jiǎn)單的3D程序開始吧,這個(gè)程序的原來介紹在下面這個(gè)鏈接里。
http://msdn.microsoft.com/en-us/library/bb203926.aspx
不過移植到Windows Phone 7上時(shí),還是遇到了一些小問題,有的是文檔的問題,有的是接口變化。如何在Visual Studio 2010里創(chuàng)建XNA 4.0的工程就不多說了,大家可以參考我寫的《Windows Phone開發(fā)工具初體驗(yàn)》,鏈接如下:
http://www.cnblogs.com/aawolf/archive/2010/08/28/1811438.html
XNA 4.0的程序是派生自Microsoft.Xna.Framework.Game的類,開發(fā)者需要重載Game的四個(gè)方法:Initialize(初始化)、LoadContent(加載內(nèi)容)、UnloadContent(卸載內(nèi)容)、Update(更新)和Draw(繪制)等方法。
首先,我們?cè)贕ame1類中加入所需要的一些私有變量:
- Matrix worldMatrix;
- Matrix viewMatrix;
- Matrix projectionMatrix;
- VertexPositionNormalTexture[] cubeVertices;
- VertexDeclaration vertexDeclaration;
- VertexBuffer vertexBuffer;
- BasicEffect basicEffect;
Martrix 的中文名叫“矩陣”,還有個(gè)翻譯叫“黑客帝國(guó)”……扯遠(yuǎn)了,什么是矩陣?我們就不解釋了,只要知道矩陣是一切3D線性變化的基礎(chǔ)就可以了。我們不知道矩陣是什么,卻身處其中。在Game1類中用了三個(gè)矩陣:worldMatrix用來描述世界坐標(biāo)系;viewMatrix用來描述攝影機(jī)坐標(biāo)系;projectionMatrix用來描述投影坐標(biāo)系。這些都是3D圖形學(xué)的概念,不解釋了。
另外兩個(gè)重要的變量是vertexBuffer和basicEffect。vertexBuffer包含了一系列的向量,這些向量構(gòu)成了我們要顯示的正方體的各個(gè)頂點(diǎn);basicEffect用來描述一個(gè)基礎(chǔ)的渲染效果,其中描述了坐標(biāo)系、顏色和燈光等基本的要素,這些要素是3D圖形顯示的基礎(chǔ)。
接下來創(chuàng)建一個(gè)叫InitMatrices的方法,對(duì)各個(gè)坐標(biāo)系進(jìn)行初始化,記得,這個(gè)InitMatrices的函數(shù)是我們自己創(chuàng)建的,代碼如下:
- private void InitMatrices()
- {
- // Initialize the world, view, and projection matrices.
- float tilt = MathHelper.ToRadians(0); // 0 degree angle
- // Use the world matrix to tilt the cube along x and y axes.
- worldMatrix = Matrix.CreateRotationX(tilt) * Matrix.CreateRotationY(tilt);
- viewMatrix = Matrix.CreateLookAt(new Vector3(5, 5, 5), Vector3.Zero, Vector3.Up);
- projectionMatrix = Matrix.CreatePerspectiveFieldOfView(
- MathHelper.ToRadians(45), // 45 degree angle
- (float)GraphicsDevice.Viewport.Width /
- (float)GraphicsDevice.Viewport.Height,
- 1.0f, 100.0f);
- }
算了,不解釋了,大家知道這段代碼在干什么就好了。接下來,創(chuàng)建一個(gè)叫做InitEffect的函數(shù)中,對(duì)basicEffect進(jìn)行初始化,代碼如下:
- private void InitEffect()
- {
- // Initialize BasicEffect with transformation and light values
- basicEffect = new BasicEffect(graphics.GraphicsDevice);
- basicEffect.World = worldMatrix;
- basicEffect.View = viewMatrix;
- basicEffect.Projection = projectionMatrix;
- // primitive color
- basicEffect.AmbientLightColor = new Vector3(0.1f, 0.1f, 0.1f);
- basicEffect.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f);
- basicEffect.SpecularColor = new Vector3(0.25f, 0.25f, 0.25f);
- basicEffect.SpecularPower = 5.0f;
- basicEffect.Alpha = 1.0f;
- basicEffect.LightingEnabled = true;
- if (basicEffect.LightingEnabled)
- {
- basicEffect.DirectionalLight0.Enabled = true; // enable each light individually
- if (basicEffect.DirectionalLight0.Enabled)
- {
- // x direction
- basicEffect.DirectionalLight0.DiffuseColor = new Vector3(1, 0, 0); // range is 0 to 1
- basicEffect.DirectionalLight0.Direction = Vector3.Normalize(new Vector3(-1, 0, 0));
- // points from the light to the origin of the scene
- basicEffect.DirectionalLight0.SpecularColor = Vector3.One;
- }
- basicEffect.DirectionalLight1.Enabled = true;
- if (basicEffect.DirectionalLight1.Enabled)
- {
- // y direction
- basicEffect.DirectionalLight1.DiffuseColor = new Vector3(0, 0.75f, 0);
- basicEffect.DirectionalLight1.Direction = Vector3.Normalize(new Vector3(0, -1, 0));
- basicEffect.DirectionalLight1.SpecularColor = Vector3.One;
- }
- basicEffect.DirectionalLight2.Enabled = true;
- if (basicEffect.DirectionalLight2.Enabled)
- {
- // z direction
- basicEffect.DirectionalLight2.DiffuseColor = new Vector3(0, 0, 0.5f);
- basicEffect.DirectionalLight2.Direction = Vector3.Normalize(new Vector3(0, 0, -1));
- basicEffect.DirectionalLight2.SpecularColor = Vector3.One;
- }
- }
- }
然后要對(duì)vertexDeclaration、cubeVertices和vertexBuffer變量進(jìn)行初始化,我們將這部分代碼放在InitVertexBuffer函數(shù)中:
- private void InitVertexBuffer()
- {
- // Create a vertex declaration for the type VertexPositionNormalTexture
- vertexDeclaration = new VertexDeclaration(new VertexElement[]
- {
- new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
- new VertexElement(12, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0),
- new VertexElement(24, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0)
- }
- );
- // Create the per vertex data
- cubeVertices = new VertexPositionNormalTexture[36];
- Vector3 topLeftFront = new Vector3(-1.0f, 1.0f, 1.0f);
- Vector3 bottomLeftFront = new Vector3(-1.0f, -1.0f, 1.0f);
- Vector3 topRightFront = new Vector3(1.0f, 1.0f, 1.0f);
- Vector3 bottomRightFront = new Vector3(1.0f, -1.0f, 1.0f);
- Vector3 topLeftBack = new Vector3(-1.0f, 1.0f, -1.0f);
- Vector3 topRightBack = new Vector3(1.0f, 1.0f, -1.0f);
- Vector3 bottomLeftBack = new Vector3(-1.0f, -1.0f, -1.0f);
- Vector3 bottomRightBack = new Vector3(1.0f, -1.0f, -1.0f);
- Vector2 textureTopLeft = new Vector2(0.0f, 0.0f);
- Vector2 textureTopRight = new Vector2(1.0f, 0.0f);
- Vector2 textureBottomLeft = new Vector2(0.0f, 1.0f);
- Vector2 textureBottomRight = new Vector2(1.0f, 1.0f);
- Vector3 frontNormal = new Vector3(0.0f, 0.0f, 1.0f);
- Vector3 backNormal = new Vector3(0.0f, 0.0f, -1.0f);
- Vector3 topNormal = new Vector3(0.0f, 1.0f, 0.0f);
- Vector3 bottomNormal = new Vector3(0.0f, -1.0f, 0.0f);
- Vector3 leftNormal = new Vector3(-1.0f, 0.0f, 0.0f);
- Vector3 rightNormal = new Vector3(1.0f, 0.0f, 0.0f);
- // Front face.
- cubeVertices[0] =
- new VertexPositionNormalTexture(
- topLeftFront, frontNormal, textureTopLeft);
- cubeVertices[1] =
- new VertexPositionNormalTexture(
- bottomLeftFront, frontNormal, textureBottomLeft);
- cubeVertices[2] =
- new VertexPositionNormalTexture(
- topRightFront, frontNormal, textureTopRight);
- cubeVertices[3] =
- new VertexPositionNormalTexture(
- bottomLeftFront, frontNormal, textureBottomLeft);
- cubeVertices[4] =
- new VertexPositionNormalTexture(
- bottomRightFront, frontNormal, textureBottomRight);
- cubeVertices[5] =
- new VertexPositionNormalTexture(
- topRightFront, frontNormal, textureTopRight);
- // Back face.
- cubeVertices[6] =
- new VertexPositionNormalTexture(
- topLeftBack, backNormal, textureTopRight);
- cubeVertices[7] =
- new VertexPositionNormalTexture(
- topRightBack, backNormal, textureTopLeft);
- cubeVertices[8] =
- new VertexPositionNormalTexture(
- bottomLeftBack, backNormal, textureBottomRight);
- cubeVertices[9] =
- new VertexPositionNormalTexture(
- bottomLeftBack, backNormal, textureBottomRight);
- cubeVertices[10] =
- new VertexPositionNormalTexture(
- topRightBack, backNormal, textureTopLeft);
- cubeVertices[11] =
- new VertexPositionNormalTexture(
- bottomRightBack, backNormal, textureBottomLeft);
- // Top face.
- cubeVertices[12] =
- new VertexPositionNormalTexture(
- topLeftFront, topNormal, textureBottomLeft);
- cubeVertices[13] =
- new VertexPositionNormalTexture(
- topRightBack, topNormal, textureTopRight);
- cubeVertices[14] =
- new VertexPositionNormalTexture(
- topLeftBack, topNormal, textureTopLeft);
- cubeVertices[15] =
- new VertexPositionNormalTexture(
- topLeftFront, topNormal, textureBottomLeft);
- cubeVertices[16] =
- new VertexPositionNormalTexture(
- topRightFront, topNormal, textureBottomRight);
- cubeVertices[17] =
- new VertexPositionNormalTexture(
- topRightBack, topNormal, textureTopRight);
- // Bottom face.
- cubeVertices[18] =
- new VertexPositionNormalTexture(
- bottomLeftFront, bottomNormal, textureTopLeft);
- cubeVertices[19] =
- new VertexPositionNormalTexture(
- bottomLeftBack, bottomNormal, textureBottomLeft);
- cubeVertices[20] =
- new VertexPositionNormalTexture(
- bottomRightBack, bottomNormal, textureBottomRight);
- cubeVertices[21] =
- new VertexPositionNormalTexture(
- bottomLeftFront, bottomNormal, textureTopLeft);
- cubeVertices[22] =
- new VertexPositionNormalTexture(
- bottomRightBack, bottomNormal, textureBottomRight);
- cubeVertices[23] =
- new VertexPositionNormalTexture(
- bottomRightFront, bottomNormal, textureTopRight);
- // Left face.
- cubeVertices[24] =
- new VertexPositionNormalTexture(
- topLeftFront, leftNormal, textureTopRight);
- cubeVertices[25] =
- new VertexPositionNormalTexture(
- bottomLeftBack, leftNormal, textureBottomLeft);
- cubeVertices[26] =
- new VertexPositionNormalTexture(
- bottomLeftFront, leftNormal, textureBottomRight);
- cubeVertices[27] =
- new VertexPositionNormalTexture(
- topLeftBack, leftNormal, textureTopLeft);
- cubeVertices[28] =
- new VertexPositionNormalTexture(
- bottomLeftBack, leftNormal, textureBottomLeft);
- cubeVertices[29] =
- new VertexPositionNormalTexture(
- topLeftFront, leftNormal, textureTopRight);
- // Right face.
- cubeVertices[30] =
- new VertexPositionNormalTexture(
- topRightFront, rightNormal, textureTopLeft);
- cubeVertices[31] =
- new VertexPositionNormalTexture(
- bottomRightFront, rightNormal, textureBottomLeft);
- cubeVertices[32] =
- new VertexPositionNormalTexture(
- bottomRightBack, rightNormal, textureBottomRight);
- cubeVertices[33] =
- new VertexPositionNormalTexture(
- topRightBack, rightNormal, textureTopRight);
- cubeVertices[34] =
- new VertexPositionNormalTexture(
- topRightFront, rightNormal, textureTopLeft);
- cubeVertices[35] =
- new VertexPositionNormalTexture(
- bottomRightBack, rightNormal, textureBottomRight);
- vertexBuffer = new VertexBuffer(
- graphics.GraphicsDevice,
- typeof(VertexPositionNormalTexture),
- cubeVertices.Length,
- BufferUsage.None
- );
- vertexBuffer.SetData(cubeVertices);
- }
請(qǐng)?jiān)徫野?6個(gè)頂點(diǎn)的代碼都貼出來了,如果不貼出來,肯定會(huì)有人不補(bǔ)全,然后就看不到完整的正方體了。
這里就要說到***個(gè)錯(cuò)誤點(diǎn)了:文章中沒有列出所有36個(gè)頂點(diǎn)的定義,不過示例代碼UseBasicEffect中列出了;另一個(gè)問題是VertexBuffer的構(gòu)造函數(shù)發(fā)生了變化,原文和示例代碼中的VertexBuffer構(gòu)造函數(shù)是這樣的:
- vertexBuffer = new VertexBuffer(
- graphics.GraphicsDevice,
- VertexPositionNormalTexture.SizeInBytes * cubeVertices.Length,
- BufferUsage.None
- );
而正確的寫法應(yīng)該是:
- vertexBuffer = new VertexBuffer(
- graphics.GraphicsDevice,
- typeof(VertexPositionNormalTexture),
- cubeVertices.Length,
- BufferUsage.None
- );
VertexBuffer增加了一個(gè)Type類型的參數(shù)(第二個(gè)),我們必須傳入一個(gè)IVertexType接口的派生類型,構(gòu)造函數(shù)會(huì)用類型和頂點(diǎn)列表的長(zhǎng)度計(jì)算VertexBuffer的size,這顯然比上邊的實(shí)現(xiàn)好了許多。
分別實(shí)現(xiàn)了這三個(gè)初始化函數(shù)后,我們要在真正的初始化函數(shù)Initialize里調(diào)用這三個(gè)函數(shù),注意Initialize函數(shù)不是自己添加的,在Game1類中本來就有:
- protected override void Initialize()
- {
- InitMatrices();
- InitEffect();
- InitVertexBuffer();
- base.Initialize();
- }
好了,我們?cè)贒raw函數(shù)里增加繪制方法:
- protected override void Draw(GameTime gameTime)
- {
- GraphicsDevice.Clear(Color.CornflowerBlue);
- // TODO: Add your drawing code here
- RasterizerState rasterizerState1 = new RasterizerState();
- rasterizerState1.CullMode = CullMode.None;
- graphics.GraphicsDevice.RasterizerState = rasterizerState1;
- GraphicsDevice.SetVertexBuffer(vertexBuffer);
- foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
- {
- pass.Apply();
- graphics.GraphicsDevice.DrawPrimitives(
- PrimitiveType.TriangleList,
- 0,
- 36
- );
- }
- base.Draw(gameTime);
- }
這里包含了第二個(gè)錯(cuò)誤點(diǎn),原文沒有下面這句(上文高亮標(biāo)出):
GraphicsDevice.SetVertexBuffer(vertexBuffer);
如果沒有SetVertexBuffer的調(diào)用,程序在運(yùn)行時(shí)會(huì)遇到下面的異常:
An unhandled exception of type 'System.InvalidOperationException' occurred in Microsoft.Xna.Framework.Graphics.dll
Additional information: A valid vertex buffer (and a valid index buffer if you are using indexed primitives) must be set on the device before any draw operations may be performed.
原文的調(diào)用方式和UseBasicEffect的實(shí)現(xiàn)方式完全不同,所以大家要注意一下。畢竟是Beta版,很多文檔還沒有***完成。
好了,到這里,其實(shí)我們編譯運(yùn)行該程序的話,就可以看到繪制出的立方體來了。但是,我還想再加點(diǎn)——讓立方體旋轉(zhuǎn)起來。
在Update函數(shù)中增加下面兩句(高亮顯示):
- protected override void Update(GameTime gameTime)
- {
- // Allows the game to exit
- if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
- this.Exit();
- // TODO: Add your update logic here
- Matrix matrix = Matrix.CreateRotationX(0.1f);
- basicEffectbasicEffect.World = basicEffect.World * matrix;
- base.Update(gameTime);
- }
我們創(chuàng)建了一個(gè)沿X軸旋轉(zhuǎn)0.1度的矩陣,與basicEffect中的世界坐標(biāo)系相乘,就可以使我們繪制出來的立方體每次Update時(shí),都沿著X軸旋轉(zhuǎn)0.1f度。因?yàn)榻嵌仁莊loat型,千萬別忘了0.1f之后的那個(gè)f。好了,程序***的樣子就是這樣的。在***篇文章里,我留了很多問題,比如3D的基本概念、坐標(biāo)系、燈光、材質(zhì)、旋轉(zhuǎn),希望在后邊能夠比較從容地解釋這些知識(shí)。我現(xiàn)在唯一的希望是,不要等到六個(gè)月后才有時(shí)間再寫第二篇……
【編輯推薦】
- Windows Phone 7核心控件Panorama和Pivot月底發(fā)布
- 微軟應(yīng)用開發(fā)大賽曝光Windows Phone 7首批應(yīng)用
- 微軟發(fā)布Windows Phone 7游戲開發(fā)實(shí)例 基于XNA框架
- Windows Phone 7 平面設(shè)計(jì)師的T型臺(tái)
- Windows Phone 7開發(fā)之Silverlight游戲編輯器