天天看点

【专家篇】将3DXNA模型嵌入到Silverlight应用程序  

摘要: 当用户选择本地模型,应用程序导航到GamePage页面,用Silverlight内容来显示XNA模型。本次任务将演示如何创建Silverlight/XNA混合应用程序......

    当用户选择本地模型,应用程序导航到GamePage页面,用Silverlight内容来显示XNA模型。本次任务将演示如何创建Silverlight/XNA混合应用程序。

    实现整合功能的关键组件是SharedGraphicsDeviceManager类,该类让应用程序开发者使用XNA绘制的直接模式来代替Silverlight绘制的保留模式。

    当您安装WindowsPhone Development Tools,两个工程模板支持Silverlight/XNA混合模式的应用程序,SilverlightforWindowsPhone文件夹下的Windows Phone 3D Graphics Application模板和XNAGameStudio4.0文件夹下的WindowsPhoneRichGraphicsApplication(4.0)模板。

提示:这些模板在标准的Windows Phone Application模板基础上增加了一些小的改进。首先在App.xaml.cs中声明SharedGraphicsDeviceManager变量;其次,App类实现IServiceProvider接口为了模拟纯粹XNA应用程序的行为。IServiceProvider.GetService方法返回存在于ApplicationLifetimeObjects集合中的对象。

1.      为了在Silverlight页面中,显示3D XNA模型。打开GamePage.xaml.cs文件,添加OnNavigatedTo方法:

C#

protectedoverridevoidOnNavigatedTo(NavigationEventArgse)

{

//SetthesharingmodeofthegraphicsdevicetoturnonXNArendering

SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(true);

base.OnNavigatedTo(e);

}

2.      只有这一页需要XNA渲染,因此你需要在离开该页面前关闭XNA渲染。添加OnNavigatedFrom方法:

C#

protectedoverridevoidOnNavigatedFrom(NavigationEventArgse)

{

//SetthesharingmodeofthegraphicsdevicetoturnoffXNArendering

SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(false);

base.OnNavigatedFrom(e);

}

3.      通过SharedGraphicsDeviceManager的操作打开XNA渲染模式,让操作系统使用XNA渲染取代Silverlight渲染,但是不需要实际打开XNA游戏循环。Windows Phone Mango使用GameTimer类,用来运行XNA引擎并将一些标准的Microsoft.Xna.Framework.Game生命周期方法作为事件暴露出来,使你的应用程序可以被控制。

在绘制任何XNA内容之前,你不得不创建GameTimer类型,并启动游戏引擎。在GamePage类中,声明GameTimer的变量timer。

C#

publicpartialclassGamePage:PhoneApplicationPage

{

GameTimertimer;

}

找到GamePage构造函数,创建实际的对象,注册Update和Draw事件。设置GameTimer的UpdateInterval属性,指定触发Update事件的时间间隔,设备触发Draw事件的时间间隔与此相同。

C#

publicGamePage()

{

InitializeComponent();

//Createatimerforthispage

timer=newGameTimer();

timer.UpdateInterval=TimeSpan.FromTicks(333333);

timer.Update+=timer_Update;

timer.Draw+=timer_Draw;

}

privatevoidtimer_Update(objectsender,GameTimerEventArgse)

{

}

privatevoidtimer_Draw(objectsender,GameTimerEventArgse)

{

SharedGraphicsDeviceManager.Current.GraphicsDevice.Clear(Color.CornflowerBlue);

}

4.      为了启动和关闭XNA引擎,在OnNavigatedTo方法中调用GameTimer的Start方法,在OnNavigatedFrom方法中调用Stop方法。

C#

protectedoverridevoidOnNavigatedTo(NavigationEventArgse)

{

//SetthesharingmodeofthegraphicsdevicetoturnonXNArendering

SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(true);

//Startthegametimer

timer.Start();

base.OnNavigatedTo(e);

}

protectedoverridevoidOnNavigatedFrom(NavigationEventArgse)

{

//Stopthegametimer

timer.Stop();

//SetthesharingmodeofthegraphicsdevicetoturnoffXNArendering

SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(false);

base.OnNavigatedFrom(e);

}

5.      现在XNA绘制的引擎已经开始运行了,在实际绘制内容之前,您需要一个最终的对象。SpriteBatch类负责绘制多个基于可视化元素的精灵。在接下来的几步中,您将使用SpriteBatch实例来绘制背景图片和Silverlight内容。但是,首先您需要初始化它。

声明SpriteBatch变量叫做spriteBatch:

C#

publicpartialclassGamePage:PhoneApplicationPage

{

GameTimertimer;

SpriteBatchspriteBatch;

}

6.      在OnNavigatedTo方法中实现该对象:

C#

protectedoverridevoidOnNavigatedTo(NavigationEventArgse)

{

//SetthesharingmodeofthegraphicsdevicetoturnonXNArendering

SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(true);

//InitializeSpriteBatch

spriteBatch=newSpriteBatch(SharedGraphicsDeviceManager.Current.GraphicsDevice);

//Startthegametimer

timer.Start();

base.OnNavigatedTo(e);

}

7.      下一步来绘制位于ModelViewerLibraryContent工程的背景图片background.jpg。为了绘制图片,通过ContentManager进行加载,然后通过spriteBatch进行绘制。

在GamePage类中,声明Texture2D类型的变量叫做background,来加载背景图片。这是一个简单的材质,用来在XNA绘制引擎里进行绘制。

C#

publicpartialclassGamePage:PhoneApplicationPage

{

SpriteBatchspriteBatch;

Texture2Dbackground;

}

8.      ModelViewerLibraryContent工程中包含预编译的资源,当ModelViewer工程编译时,并将其嵌入到Silverlight程序集里。应用程序需要ContentManager在运行时加载这些资源。

根据XNA的需求,在App类中实现IServiceProvider接口。现在你可以使用它在OnNavigatedTo方法中来创建ContentManager。使用ContentManager将预编译的背景图片,加载为材质,并且设置到background变量中去。

C#

protectedoverridevoidOnNavigatedTo(NavigationEventArgse)

{

//InitializeSpriteBatch

spriteBatch=newSpriteBatch(SharedGraphicsDeviceManager.Current.GraphicsDevice);

Appapp=(App)Application.Current;

ContentManagerappContentManager=newContentManager(app,"Content");

background=appContentManager.Load<Texture2D>("background");

//Startthegametimer

timer.Start();

base.OnNavigatedTo(e);

}

9.      最后,在timer_Draw事件处理函数中使用SpriteBatch对象绘制图片。

C#

privatevoidtimer_Draw(objectsender,GameTimerEventArgse)

{

spriteBatch.Begin();

spriteBatch.Draw(background,Vector2.Zero,Color.White);

spriteBatch.End();

}

运行程序确认XNA可以正常显示背景图片。

10.  绘制3D模型比绘制背景图片略微苦难一些。首先需要替代预编译资源,需要从独立存储中加载3D模型资源。首先要声明嵌入3D模型和其元数据的成员变量:

C#

publicpartialclassGamePage:PhoneApplicationPage

{

XnaModelWrappermodel;

ModelMetadatamodelMetadata;

}

11.  使用content manager来加载模型,就像加载背景图片一样。因为这些模型通过查询字符串,被传递到GamePage页面,而不是预编译的,你不得不使用Model\CustomContentManager.cs位于的自定义内容管理器,从独立存储中加载模型。在OnNavigatedTo方法中加载模型和元数据:

C#

protectedoverridevoidOnNavigatedTo(NavigationEventArgse)

{

background=appContentManager.Load<Texture2D>("background");

//Getquerystringparameterandinitializelocalmetadatavariable

IDictionary<string,string>data=NavigationContext.QueryString;

modelMetadata=app.ModelsStore[data["ID"]];

//Initializethemodel

model=newXnaModelWrapper();

model.Lights=newbool[]{true,true,true};

ContentManagercontentManager=modelMetadata.IsContent?appContentManager:newCustomContentManager();

model.Load(contentManager,modelMetadata.Assets[0]);

//Startthegametimer

timer.Start();

base.OnNavigatedTo(e);

}

提示:下面的代码中包含XNA和3D模型,这些超出了本次实验的范围。为了学习更多关于XNA的知识,可以参考XNA的文档。

12.  SpriteBatch只能绘制Texture2D对象,因此你不能使用它来绘制3D模型。为了绘制模型,调用XnaModelWrapper类的Draw方法。编辑GamePage类中timer_Draw事件处理函数来绘制模型:

C#

privatevoidtimer_Draw(objectsender,GameTimerEventArgse)

{

spriteBatch.Begin();

spriteBatch.Draw(background,Vector2.Zero,Color.White);

spriteBatch.End();

//Setrenderstates.

SharedGraphicsDeviceManager.Current.GraphicsDevice.DepthStencilState=DepthStencilState.Default;

SharedGraphicsDeviceManager.Current.GraphicsDevice.BlendState=BlendState.Opaque;

SharedGraphicsDeviceManager.Current.GraphicsDevice.RasterizerState=RasterizerState.CullCounterClockwise;

SharedGraphicsDeviceManager.Current.GraphicsDevice.SamplerStates[0]=SamplerState.LinearWrap;

//Drawthemodel

model.Draw();

}

13.  您也会更新模型的属性,在3D空间中正确的显示。当GameTimer触发Draw事件时,模型会被绘制;当触发Update事件时,执行应用程序的逻辑,并计算模型位置。

在timer_Update事件处理函数中更新模型:

C#

privatevoidtimer_Update(objectsender,GameTimerEventArgse)

{

floatyaw=MathHelper.Pi+MathHelper.PiOver2;//rotationaroundthey-axis

floatpitch=0;//rotationaroundthex-axis

floatfieldOfView=MathHelper.ToRadians(45f)/modelMetadata.FieldOfViewDivisor;//zoom

model.Rotation=modelMetadata.World*Matrix.CreateFromYawPitchRoll(yaw,pitch,0);

model.Projection=Matrix.CreatePerspectiveFieldOfView(fieldOfView,modelMetadata.AspectRatio,modelMetadata.NearPlaneDistance,modelMetadata.FarPlaneDistance);

model.View=modelMetadata.ViewMatrix;

model.IsTextureEnabled=true;

model.IsPerPixelLightingEnabled=true;

}

14.  因为要在Update事件处理函数中进行的计算在不同调用中不会发生变化,每一帧看起来都是相同的,并且绘制模型会出现在3D空间相同的位置上。为了响应手势识别使XNA绘制能够缩放、旋转3D模型,首先需要在GamePage构造函数里激活手势识别的句柄:

C#

publicGamePage()

{

InitializeComponent();

//Initializegesturessupport-PinchforZoomandhorizontaldragforrotate

TouchPanel.EnabledGestures=GestureType.FreeDrag|GestureType.Pinch|GestureType.PinchComplete;

}

声明声明下列的变量,在timer_Update事件处理函数中,我们会使用这些变量来旋转和缩放这些模型:

C#

publicpartialclassGamePage:PhoneApplicationPage

{

floatxRotation=0.0f;

floatyRotation=0.0f;

float?prevLength;

floatcameraFOV=45;//InitialcameraFOV(servesasazoomlevel)

}

15.  在OnNavigatedTo方法中根据初始化xRotation和yRotation变量:

C#

protectedoverridevoidOnNavigatedTo(NavigationEventArgse)

{

modelMetadata=app.ModelsStore[data["ID"]];

xRotation=modelMetadata.DefaultXRotation;

yRotation=modelMetadata.DefaultYRotation;

}

16.  添加HandleInput方法读取用户输入到xRotation, yRotation, cameraFOV和prevLength变量中:

C#

privatevoidHandleInput()

{

while(TouchPanel.IsGestureAvailable)

{

GestureSamplegestureSample=TouchPanel.ReadGesture();

switch(gestureSample.GestureType)

{

caseGestureType.FreeDrag:

xRotation+=gestureSample.Delta.X;

yRotation-=gestureSample.Delta.Y;

break;

caseGestureType.Pinch:

floatgestureValue=0;

floatminFOV=80;

floatmaxFOV=20;

floatgestureLengthToZoomScale=10;

Vector2gestureDiff=gestureSample.Position-gestureSample.Position2;

gestureValue=gestureDiff.Length()/gestureLengthToZoomScale;

if(null!=prevLength)//Skipthefirstpinchevent

cameraFOV-=gestureValue-prevLength.Value;

cameraFOV=MathHelper.Clamp(cameraFOV,maxFOV,minFOV);

prevLength=gestureValue;

break;

caseGestureType.PinchComplete:

prevLength=null;

break;

default:

break;

}

}

}

17.  编辑timer_Update方法来获取用户输入,并更新3D模型:

C#

privatevoidtimer_Update(objectsender,GameTimerEventArgse)

{

HandleInput();

floatyaw=MathHelper.Pi+MathHelper.PiOver2+xRotation/100;//rotationaroundthey-axis

floatpitch=yRotation/100;//rotationaroundthex-axis

floatfieldOfView=MathHelper.ToRadians(cameraFOV)/modelMetadata.FieldOfViewDivisor;//zoom

}

18.  在同一时间只有一个Readerer能够在屏幕上进行绘制。所以如果在你的应用程序中使用3D模型和其他XNA功能,那么XNA Renderer不会触发正规的Silverlight页面生命周期时间。Silverlight内容只是存在,但不会显示出来,因为XNA不会绘制它们。

XNA框架中使用一个新的对象来解决这个问题:UIElementRenderer。UIElementRenderer将Silverlight内容作为2D材质在XNA中显示,并且触发Silverlight事件。例如,当我们的用户触摸由UIElementRenderer生成的Silverlight按钮图片时,UIElementRenderer将会把输入信息传递给真实(不可见)的Silverlight按钮。

为了显示模型的名称和描述,使用UIElementRenderer来绘制Silverlight控件树。开始声明uiRenderer变量:

C#

publicpartialclassGamePage:PhoneApplicationPage

{

UIElementRendereruiRenderer;

}

19.  为了展现整个控件树,当控件树改变时,你需要更新uiRenderer变量。在GamePage的LayoutUpdated事件中添加处理函数:

C#

publicGamePage()

{

this.LayoutUpdated+=GamePage_LayoutUpdated;

}

privatevoidGamePage_LayoutUpdated(objectsender,EventArgse)

{

if(uiRenderer==null||LayoutRoot.ActualWidth>0&&LayoutRoot.ActualHeight>0)

uiRenderer=newUIElementRenderer(LayoutRoot,(int)LayoutRoot.ActualWidth,(int)LayoutRoot.ActualHeight);

}

20.  XNA renderer只能在应用程序触发Draw事件之后,更新绘制。调用uiRenderer的Render方法来更新Silverlight UI,并在材质中进行重华。使用spriteBatch将材质绘制到屏幕上。

C#

privatevoidtimer_Draw(objectsender,GameTimerEventArgse)

{

//UpdatetheSilverlightUI

uiRenderer.Render();

//Drawthesprite

spriteBatch.Begin();

spriteBatch.Draw(uiRenderer.Texture,Vector2.Zero,Color.White);

spriteBatch.End();

}

21.  最后,设置页面的DataContext,并添加GamePanel XAML可以绑定的所依赖的属性:

C#

publicGamePage()

{

this.DataContext=this;

}

publicstringModelName

{

get{return(string)GetValue(ModelNameProperty);}

set{SetValue(ModelNameProperty,value);}

}

//UsingaDependencyPropertyasthebackingstoreforModelName.Thisenablesanimation,styling,binding,etc...

publicstaticreadonlyDependencyPropertyModelNameProperty=

DependencyProperty.Register("ModelName",typeof(string),typeof(GamePage),null);

publicstringModelDesc

{

get{return(string)GetValue(ModelDescProperty);}

set{SetValue(ModelDescProperty,value);}

}

//UsingaDependencyPropertyasthebackingstoreforModelDesc.Thisenablesanimation,styling,binding,etc...

publicstaticreadonlyDependencyPropertyModelDescProperty=

DependencyProperty.Register("ModelDesc",typeof(string),typeof(GamePage),null);

22.  在OnNavigatedTo方法中,更新依赖属性值:

C#

protectedoverridevoidOnNavigatedTo(NavigationEventArgse)

{

modelMetadata=app.ModelsStore[data["ID"]];

ModelName=modelMetadata.Name;

ModelDesc=modelMetadata.Description;

}

23.  我们完成了本次动手实验。

继续阅读