65.9K
CodeProject 正在变化。 阅读更多。
Home

承载 XNA 在窗口中

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.64/5 (6投票s)

2007年7月28日

4分钟阅读

viewsIcon

70770

我解决在 WinForm UserControl 中承载 XNA 的旅程

引言

在撰写第一篇关于 XNA 的文章后,我对 XNA 的下一个兴趣是弄清楚如何在 WinForm 中托管一个 XNA 窗口,这样我就可以在该概念中提供额外的控件和其他 UI 元素。这已被证明是一项相当艰巨的任务。

虽然我写了一篇关于我研究的博文,但我认为,即使我没有找到最终的解决方案,为 Code Project 社区提供一篇关于该主题的简短文章也会有所裨益。相反,本文

  • 简要描述了问题
  • 快速回顾了我尝试过的另外两种解决方案
  • 列出了我一路发现的有用的参考文献

最终,我找到了这个网站,它提供了将 XNA 窗口托管为用户控件的 C# 源代码。我在这里不提供下载,因为我没有写它,所以请访问 Nuclex 网站下载。

因此,将此作为 Code Project 文章的目的是将我访问过的所有博客和网站汇总到一篇文章中,以理解问题并找到解决方案。我绝对不是通过搜索“XNA WinForm”来找到解决方案的。希望在几天之内,其他人也能通过这些关键词找到这篇 Code Project 文章!

第一个问题:Game 类构造函数

主要问题是 `Game` 类初始化自己的 `Form`,而不是让您指定您的 Form/Control。这是设计者的一个愚蠢的疏忽。让我们使用 Reflector 来理解这个问题

  1. `Game` 构造函数调用 `EnsureHost`
  2. `EnsureHost` 的实现如下:

    if (this.host == null)
    {
      this.host = new WindowsGameHost(this);
    

  3. `WindowsGameHost` 实例化一个 `WindowsGameWindow`

    public WindowsGameHost(Game game)
    {
      this.game = game;
      this.LockThreadToProcessor();
      this.gameWindow = new WindowsGameWindow();
    
  4. 而 `WindowsGameWindow` 最后实例化 `WindowsGameForm`,它继承自 `Form`

    public WindowsGameWindow()
    {
      this.mainForm = new WindowsGameForm();

这就是第一个问题。

第二个问题:GraphicsDeviceManager CreateDevice 方法

第二个问题是 `GraphicsDeviceManager`。在您的游戏派生的 `Game` 类中,构造函数中会有

graphics = new GraphicsDeviceManager(this);

`GraphicsDeviceManager` 类添加了两个服务

game.Services.AddService(typeof(IGraphicsDeviceManager), this);
game.Services.AddService(typeof(IGraphicsDeviceService), this);

然后,在 `CreateDevice` 方法中,`Game` 构造函数创建的 `Form` 控件被指定为控件宿主

GraphicsDevice device = new GraphicsDevice(
  newInfo.Adapter, 
  newInfo.DeviceType, 
  this.game.Window.Handle, // <=====
  newInfo.CreationOptions, 
  newInfo.PresentationParameters);

所以问题是双重的——`Game` 类创建了自己的 `Form`,然后该窗口被传递到 `GraphicsDevice` 构造函数。

愚蠢的解决方案 #1:向 Game 窗口添加控件

我遇到的第一个想法是将 `GameWindow` 转换为 `Control` 并向其添加控件。这些控件实际上渲染在 XNA 窗口上,并且适用于按钮等,但对编辑控件无效。我怀疑是因为宿主窗体在消息处理方面存在问题,尽管我从未进一步研究过。

一个更好的解决方案,但不是很好

我找到的第二个解决方法几乎是对的,它挂钩 `PreparingDeviceSettings` 并重写 `DeviceWindowHandle`

public Game1(Form form)
{
  graphics = new GraphicsDeviceManager(this);
  content = new ContentManager(Services);
  this.form = form;
  graphics.PreparingDeviceSettings += 
      new EventHandler<PreparingDeviceSettingsEventArgs>
                    (OnPreparingDeviceSettings);
}

void OnPreparingDeviceSettings(object sender, 
                    PreparingDeviceSettingsEventArgs e)
{
  e.GraphicsDeviceInformation.PresentationParameters.DeviceWindowHandle = 
    form.Handle;
}

这(以及我遇到的其他解决方案)都可以正常工作,但 XNA 窗口仍然会显示,需要在 `Draw` 窗口中进行一些技巧来隐藏它

protected override void Draw(GameTime gameTime)
{
  if (firstTime)
  {
    System.Windows.Forms.Control xnaWindow = 
      System.Windows.Forms.Control.FromHandle((this.Window.Handle));
    xnaWindow.Visible = false;
    firstTime = false;
  }
  ...

以及正确地处理它

static class Program
{
  static Game1 game;
  /// <summary>
  /// The main entry point for the application.
  /// </summary>
  static void Main(string[] args)
  {
    Form form = new Form();
    form.Disposed += new EventHandler(OnDisposed);
    form.ClientSize = new System.Drawing.Size(800, 600);
    form.Show();

    using (game = new Game1(form))
    {
      game.Run();
    }
  }

  static void OnDisposed(object sender, EventArgs e)
  {
    game.Exit();
  }
}

其他实现(实际上非常直接,请参阅参考文献)通常会消除 `Game` 类。这并不是我的目标,因为我想尽可能保留 `Game` 类。这让我得出结论,我可能需要使用 Reflector 来重新创建框架的特定类。此时,我搁置了整个工作,回去处理真正能赚钱的工作。

Nuclex 解决方案

在完成一些计费工作后,我重新审视了这个问题,并在下面参考文献中的一个回复中找到了一个链接,我之前没有检查过,令我惊讶的是,它正是我想要的。事实上,作者确实使用了 Reflector 来重新创建某些类,以便控制上述双重问题。链接在此,实现非常出色——它是一个用户控件,您可以从中派生自己的类,并像使用 `Game` 类一样使用可重写的方法。我使用 `Nuclex.GameControl` 类在几分钟内就将我的测试应用程序移植了过来。

不过我必须说,我仍然不满意不得不重写框架的主要部分。希望 XNA 框架的架构师将来会修复这个问题,其次,我仍然有一种挥之不去的感觉,肯定有更简单的方法。

参考文献

各种参考文献(我也从评论中找到了很多好信息)

© . All rights reserved.