在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了:

在Browser Application中使用XNA相关推荐

  1. 建立Full Trust的Browser Application

    WPF 的 Browser Application,运行于IE.Firefox(需要插件)等浏览器中,方便了部署,但也受至于浏览器的安全性限制.例如,有时候,需要访问磁盘上某个文件时,普通的Brows ...

  2. 在InternetExplorer.Application中显示本地图片

    忘记了,喜欢一个人的感觉 Demon's Blog  »  程序设计  »  在InternetExplorer.Application中显示本地图片 « 对VBS效率的再思考--处理二进制数据 Wo ...

  3. Flex4 Application中与Module通信

    最近忙于公司项目开发的事情,好一段时间没写Blog了, 其中有使用到Flex4在项目开发当中,于是分享一, Application中与Module通信, 以登录为例 思路: 1. 编写接口IUserE ...

  4. 我的WCF之旅(6):在Winform Application中调用Duplex Service出现TimeoutException的原因和解决方案...

    几个星期之前写了一篇关于如何通过WCF进行 双向通信的文章([原创]我的WCF之旅(3):在WCF中实现双向通信(Bi-directional Communication) ),在文章中我提供了一个如 ...

  5. Android Application中的Context和Activity中的Context的异同

    一.Context是什么: 1.Context是维持Android程序中各组件能够正常工作的一个核心功能类,我们选中Context类 ,按下快捷键F4,右边就会出现一个Context类的继承结构图啦, ...

  6. 通过MageUi.exe修改通过ClickOnce发布过的WPF browser application 配置文件

    VS2008提供了ClickOnce功能方便开发人员发布和部署应用程序.但是,对于 WPF browser application,一旦发布后,你想去修改 app.config以改动里面的数据库连接字 ...

  7. java怎么播放不了声音,怎么在java application中播放声音

    如何在java application中播放声音 如何在java application中播放声音,求大神赐教.. 分享到: ------解决方案-------------------- public ...

  8. Android application 中使用 provided aar 并没有那么简单

    前言 首先简单讲一下这个需求的背景,大部分场景下,是没有这个需求的,这个需求出现在插件化中,当一个android插件引用aar中的类的时候,并且这个插件是使用com.android.applicati ...

  9. Bug整理——Spring boot 执行Junit Test时不加载Application中的参数的问题

    在我们的项目中,需要在Application中设置几个系统参数才能正常打印日志,所以在Application.java中,我们是这么写的: @EnableTransactionManagement @ ...

最新文章

  1. JAVA-Eclipse快捷键
  2. linux运维实战练习
  3. No Fine-Tuning, Only Prefix-Tuning
  4. Mongo服务器二进制文件修复,Mongodb-File-Server
  5. selenium操作浏览器窗口最大化和刷新
  6. 移动端页面开发通用问题解决方案
  7. python最简单的爬取邮箱地址怎么写_详解python定时简单爬取网页新闻存入数据库并发送邮件...
  8. 学有小成-php基础语法-06
  9. 程序员应该阅读的一些书籍
  10. 删除ubuntu后修复win7系统的引导
  11. 计算机英语单词大全txt,计算机英语词汇大全.txt
  12. 软件工程期末笔记整理
  13. linux 学习感悟
  14. 英语后置定语语法归纳
  15. Android 眼睛 显示隐藏密码(ImageView)
  16. 换博客拉 http://vergilwang.iteye.com/
  17. [hive 报错]:FAILED:SemanticException [Error 10025] Expression not in GROUP BY key
  18. 360卫士 是 木马?
  19. idea中设置jdk
  20. Agile PLM 物料无法删除

热门文章

  1. python文本分类_教你用python做文本分类
  2. 用matlab相关分析,基于matlab的逐像元偏相关分析
  3. 多重链表 十字链表存储稀疏矩阵,中缀表达式
  4. Flask 教程 第十八章:Heroku上的部署
  5. 关于 React ,npm run build 资源引用丢失
  6. TCExam开源在线考试系统
  7. kFreeBSD有活过来的迹象?UbuntuBSD
  8. 基于Linux C的socketEthereal程序和Package分析 (一个)
  9. WSUS3.0的安装及部署(域下)
  10. 黄金的商品属性,货币属性,金融属性