在Browser Application中使用XNA
在WPF中,我們使用Mode3D等API來繪制三維場景,當期間的“三角形”超過一定數量時,整個場景的渲染速率直線下降,無論顯卡的運行速度有多快,幀率都維持在3、5幀每秒。
XNA是微軟推出的一套游戲開發API,作為Managed DirectX的進化版,XNA同樣封裝了DirectX的底層API,此外還提供了一系列和游戲生命周期相關的類,大大減輕了傳統Win32下DirectX開發的煩瑣。
本文試圖讓XNA“嵌入”在WPF的Browser Application中,使用XNA來渲染場景,并以XBAP的方式在互聯網上發布,集XNA的高效和WPF的部署方便為一生,免除了開發人員在部署、安裝、升級應用程序的困擾。
建立應用程序
首先建立一個空白解決方案:
為它添加一個XNA應用程序,一個WPF Webbrowser 應用程序:
在XNA上畫一個人物模型:
在Browser Application中使用XNA
為Browser Application添加相關引用:
使用Winform的Panel
修改頁面文件,在上面添加一個和一個Winform的Panel:
<Page x:Class="Newinfosoft.Test.Browser.XNAPage"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:form="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"Title="XNA Page" Loaded="Page_Loaded"><Grid><WindowsFormsHost Margin="0,0,0,30"><form:Panel x:Name="drawPanel" /></WindowsFormsHost></Grid> </Page>由于我們在Browser Application中使用了Win32的資源,因此,需要修改其安全性,最簡答的方式是將安全性設置為完全信任(Full Trust)
接下來,修改XNA應用程序的構造函數,傳入一個IntPtr類型(實際上是窗體指針),并把渲染的Handle設置為該地址:
public XNAGame(IntPtr handle) {graphics = new GraphicsDeviceManager(this);Content.RootDirectory = "Content";graphics.PreferredBackBufferWidth = 480;graphics.PreferredBackBufferHeight = 320;graphics.PreparingDeviceSettings += (sender, e) =>{e.GraphicsDeviceInformation.PresentationParameters.DeviceWindowHandle = handle;}; }修改Browser Application,在Page加載時運行游戲:
private void Page_Loaded(object sender, RoutedEventArgs e) {XNAGame game = new XNAGame(drawPanel.Handle);game.Run(); }效果如下:
如果你不想見到那個游戲的原生窗口,可以在XNAGame的構造函數中把原生窗口隱藏起來:
public XNAGame(IntPtr handle) {graphics = new GraphicsDeviceManager(this);Content.RootDirectory = "Content";graphics.PreferredBackBufferWidth = 480;graphics.PreferredBackBufferHeight = 320;graphics.PreparingDeviceSettings += (sender, e) =>{e.GraphicsDeviceInformation.PresentationParameters.DeviceWindowHandle = handle;};System.Windows.Forms.Form form = (System.Windows.Forms.Form)System.Windows.Forms.Control.FromHandle((this.Window.Handle));form.Visible = false;form.VisibleChanged += (sender, e) =>{(sender as System.Windows.Forms.Form).Visible = false;}; }缺點
使用Win32原生窗口,好處在于簡單、高效。但缺點是,原生窗口打破了WPF的Z-Order,往往會伏在WPF控件上方,例如,在上面添加一個按鈕,設計效果如下:
運行時,發現Panel反而跑到按鈕上面去了:
這是由于二者采用不同的渲染模式,具體參見:The WPF Interoperation: “Airspace” and Windows Regions Overview
使用D3DImage
所幸WPF提供了一個D3DImage的ImageSource,負責在WPF中承載d3d,既然XNA底層封裝了d3d,那么,是不是WPF也能通過D3DImage承載XNA?答案是肯定的。
修改XNAGame,為它添加兩個事件,Init和DrawEnd:
public event EventHandler Init; public event EventHandler DrawEnd;添加一個RenderTarget2D對象,讓XNA把渲染的結果保存在其中,而不是back buffer里。
public RenderTarget2D RenderTarget { get;protected set; }在Initialize函數中初始化這個RenderTarget2D對象,并使用事件Init通知其他程序。
protected override void Initialize() {// TODO: Add your initialization logic herebase.Initialize();RenderTarget = new RenderTarget2D(graphics.GraphicsDevice,graphics.GraphicsDevice.Viewport.Width,graphics.GraphicsDevice.Viewport.Height,1,SurfaceFormat.Color);GraphicsDevice.SetRenderTarget(0, RenderTarget);if (Init != null){Init(this, new EventArgs());} }在Draw函數的最后,使用事件DrawEnd來通知其他程序,完成一幀的渲染:
protected override void Draw(GameTime gameTime) {GraphicsDevice.Clear(Color.CornflowerBlue);// …?…?…?…?base.Draw(gameTime);if (this.DrawEnd != null){DrawEnd(this, new EventArgs());} }修改Page,使用Image控件代替WindowsFormsHost:
<Page x:Class="Newinfosoft.Test.Browser.XNAPage"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="XNA Page"><Grid><Image x:Name="image" Margin="0,0,0,30"Source="start.png"MouseLeftButtonUp="image_MouseLeftButtonUp" /><Button Content="Click Me"VerticalAlignment="Bottom"Margin="0,0,0,10"HorizontalAlignment="Center"Padding="10"></Button></Grid> </Page>以上代碼中,用了一個hack,即首先為該Image控件設定一個ImageSource,同樣的,把創建XNAGame的步驟從Page.Load中轉移到鼠標點擊該Image上,由于不再使用WinForm的Panel,因此,使用一個HwndSource對象作為Game的載體:
private void image_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) {image.MouseLeftButtonUp-=new MouseButtonEventHandler(image_MouseLeftButtonUp);HwndSource hwnd = new HwndSource(0, 0, 0, 0, 0, "test", IntPtr.Zero);XNAGame game = new XNAGame(hwnd.Handle);game.Init += new EventHandler(game_Init);game.Run(); }添加一個D3DImage對象:
D3DImage d3dimage = new D3DImage();在game_Init中初始化該d3dimage的后臺緩存:
void game_Init(object sender, EventArgs e) {XNAGame game = sender as XNAGame;if (d3dimage.IsFrontBufferAvailable){d3dimage.Lock();d3dimage.SetBackBuffer(D3DResourceType.IDirect3DSurface9,GetRenderTargetPointer(game.RenderTarget));d3dimage.Unlock();image.Source = d3dimage;}image.Source = d3dimage;game.DrawEnd += new EventHandler(game_DrawEnd); }GetRenderTargetPointer定義如下,利用反射的機制取得RenderTarget2D的內存地址:
public unsafe IntPtr GetRenderTargetPointer(RenderTarget2D renderTarget) {FieldInfo comPtr = renderTarget.GetType().GetField("pComPtr",BindingFlags.NonPublic | BindingFlags.Instance);return new IntPtr(Pointer.Unbox(comPtr.GetValue(renderTarget))); }?
?
?
?
?
最后,在game_DrawEnd函數中更新d3dimage:
void game_DrawEnd(object sender, EventArgs e) {if (d3dimage.IsFrontBufferAvailable){d3dimage.Lock();d3dimage.AddDirtyRect(new Int32Rect(0, 0, d3dimage.PixelWidth, d3dimage.PixelHeight));d3dimage.Unlock();} }運行結果,我們發現游戲畫面不再阻擋Button了:
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的在Browser Application中使用XNA的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SpringBoot2基础,进阶,数据库
- 下一篇: 水平集分割