【问题标题】:Custom XNA Game loop in WindowsWindows 中的自定义 XNA 游戏循环
【发布时间】:2011-09-15 17:50:32
【问题描述】:

我试图弄清楚如何在 Windows 游戏中手动管理整个游戏循环,而不使用常规 Game Microsoft.Xna.Framework.Game 类。

原因是使用常规 Game 类会导致我的游戏出现卡顿。不多,但由于游戏的特定性质,它仍然很明显。

在尝试了一堆不同的设置(vsync、fixedtimestep、各种帧率等)之后,我决定尝试编写自己的 Game 类来完全控制时间。我不确定这会解决它,但至少这样我可以完全控制。

基本上我需要:

  1. 设置游戏窗口
  2. 在一个循环中:照常进行所有渲染,然后将结果刷新到屏幕,管理后台缓冲区等。

有人知道怎么做吗?实际上这听起来很容易,但找不到任何有关如何操作的文档。


不确定我做错了什么,但我有以下代码(仅用于测试,时间处理方式会有所不同),循环将运行一段时间然后停止。一旦我将鼠标指针传递到窗口上,循环将再次运行一段时间。

    private void Application_Idle(object pSender, EventArgs pEventArgs)
    {
        Thread.Sleep(500);
        //Message message;
        //while (!PeekMessage(out message, IntPtr.Zero, 0, 0, 0))
        {
            gametime.update();
            Update(gametime);
            Draw(gametime);
            GraphicsDevice.Present();
        }
    }

如果启用“while PeekMessage”,循环将持续运行,但忽略睡眠并在鼠标移到窗口上时停止。不知道这里发生了什么......

我认为最理想的情况是我只想在主渲染循环中做一些简单的事情:

    while (alive)
    {
      Thread.Sleep(100);
      gametime.update();
      Update(gametime);
      Draw(gametime);
      GraphicsDevice.Present();
    }

但在这种情况下,窗口仍然是空白的,因为看起来窗口实际上并没有用新内容重绘。我尝试了一个 form.Refresh(),但还是不行……有什么想法吗?

【问题讨论】:

  • XNA 游戏确实不是您延迟的根源。不可能。
  • 我强烈怀疑您的问题与Game 类本身无关,替换它不是正确的解决方案。
  • 然而,我无法解释这种结结巴巴的行为。我检查了一下,没有进行垃圾收集,但是,每次调用 draw 之间的测量时间是随机的,即使绘制的复杂性保持不变。我尝试了很多不同的方法,这是我最后的努力......
  • @VincentKoeman:我同意。我的游戏引擎以 300fps 的速度运行 4 公里 x 4 公里的场景,没有任何卡顿,它是 Microsoft.XNA.Game 的小改动。

标签: xna rendering game-loop


【解决方案1】:

(添加了 xbox 信息)

对于windows,您基本上需要创建一个表单并显示它,然后存储它的句柄和表单本身。 使用此句柄,您可以创建一个 GraphicsDevice。 然后将 Application.Idle 挂接到您自己的调用更新和渲染的函数。 例如

public class MyGame
{
public Form form;
public GraphicsDevice GraphicsDevice;

public MyGame()
{
    form = new Form();
    form.ClientSize = new Size(1280, 1024);
    form.MainMenuStrip = null;

    form.Show();
}

public void Run()
{      
    PresentationParameters pp = new PresentationParameters();
    pp.DeviceWindowHandle = form.Handle;

    pp.BackBufferFormat = SurfaceFormat.Color;
    pp.BackBufferWidth = 1280;
    pp.BackBufferHeight = 1024;
    pp.RenderTargetUsage = RenderTargetUsage.DiscardContents; 
    pp.IsFullScreen = false; 

    pp.MultiSampleCount = 16;

    pp.DepthStencilFormat = DepthFormat.Depth24Stencil8;

    GraphicsDevice = new GraphicsDevice(GraphicsAdapter.DefaultAdapter,
                                              GraphicsProfile.HiDef,
                                              pp);
    Application.Idle += new EventHandler(Application_Idle);
    Application.Run(form);
}

 private void Application_Idle(object pSender, EventArgs pEventArgs)
 {
    Message message;
    while (!PeekMessage(out message, IntPtr.Zero, 0, 0, 0))
    {
        /* Your logic goes here
         Custom timing and so on
        Update();
        Render();
        */
    }

 }

 void Render()
 {
      GraphicsDevice.Clear(ClearOptions.DepthBuffer | ClearOptions.Target, Color.Black, 1, 0);
      //Your logic here.
     GraphicsDevice.Present();
 }
    [StructLayout(LayoutKind.Sequential)]
    private struct Message
    {
        public IntPtr hWnd;
        public int msg;
        public IntPtr wParam;
        public IntPtr lParam;
        public uint time;
        public Point p;
    }

    [return: MarshalAs(UnmanagedType.Bool)]
    [SuppressUnmanagedCodeSecurity, DllImport("user32.dll", CharSet = CharSet.Auto)]
    private static extern bool PeekMessage(out Message msg, IntPtr hWnd, uint
        messageFilterMin, uint messageFilterMax, uint flags);
}

编辑 1

对于 xbox,您也许可以将您自己的自定义运行函数与您的游戏循环放在一个节流的 while true 循环中。在 while true 的顶部之外运行的内部,您可能必须使用 IntPtr.Zero 作为您的句柄进行图形设备初始化和验证

编辑 2 我用这样的东西(来自http://www.koonsolo.com/news/dewitters-gameloop/

        private long nextGameTick;

        private Stopwatch stopwatch;

        const int ticksPerSecond = 60;
        const int skipTicks = 1000 / ticksPerSecond;
        private const int maxSkip = 10;
        `constructor 
         stopwatch = Stopwatch.StartNew();

        nextGameTick = stopwatch.ElapsedMilliseconds; 

        `loop 
        int loops = 0;
        long currentTick = stopwatch.ElapsedMilliseconds;
        while ( (ulong)(currentTick - nextGameTick) > skipTicks && loops < maxSkip)
        {
            Update(16.667f);
            nextGameTick += skipTicks;
            loops++;

        }

        PreRender();
        Render();
        PostRender();

编辑 3

创建内容管理器需要更多的工作,但仍然可以管理。您需要创建一个实现 IServiceProvider 的类。此类在其构造函数中采用 GraphicsDevice,以创建实现 IGraphicsDeviceProvider 的下一个类。另外我像这样实现GetService

    //in implementer of IServiceProvider
    public object GetService ( Type serviceType )
    {
        if ( serviceType == typeof ( IGraphicsDeviceService ) )
        {
            return myGraphicsService;
        }

        return null;
    }

为方便起见,我还在类中添加了一个方法来创建和返回管理器

    //in implementer of IServiceProvider
    public ContentManager CreateContentManager( string sPath )
    {

        ContentManager content = new ContentManager(this);

        content.RootDirectory = sPath;

        return content;

    }

此外,我创建了一个实现 IGraphicsDeviceService 并引用我的 GraphicsDevice 的类。然后我像这样在其中创建一个属性和字段

    //in implementer of IGraphicsDeviceService 
    private GraphicsDevice graphicsDevice;
    public GraphicsDevice GraphicsDevice
    {
        get
        {
            return graphicsDevice;
        }
    }

所以电话最终会有点像

MyServiceProvider m = new MyServiceProvider(graphicsDevice);
ContentManager content = m.CreateContentManager("Content");

在哪里

MyServiceProvider(GraphicsDevice graphicsDevice)
{
      myGraphicsService = new MyGraphicsDeviceService(graphicsDevice);
}

MyGraphicsDeviceService(GraphicsDevice gfxDevice)
{
     graphicsDevice = gfxDevice;
}

-很抱歉将代码碎片化,但它不是我最近写的东西,所以我很难记住部分。

编辑 4

我的自定义游戏有一个奇怪的案例,当我为它新建表单时我才想起我 必须绑定

    private void IgnoreAlt(object pSender, KeyEventArgs pEventArgs)
    {
        if (pEventArgs.Alt && pEventArgs.KeyCode != Keys.F4)
            pEventArgs.Handled = true;

    }

    form.KeyUp += IgnoreAlt;
    form.KeyDown += IgnoreAlt;

否则我会得到一些可怕的摊位。

【讨论】:

  • 我认为对于 xbox,您需要使用 IntPtr.Zero 或其他方法调用 graphicsdevice 构造函数以使其创建设备,因为您无权访问 System.Windows.Forms...虽然不确定
  • 谢谢,您提供的代码很有用。我现在已经启动并运行了游戏循环,但是,我想知道如何准确计时每次更新?我需要对其进行设置,以便每 x 毫秒(或在渲染前一帧后尽快)执行一次渲染调用。上面的代码似乎只是尽可能快地呈现。我尝试添加一个 Thread.sleep 但效果不佳...
  • 为您添加了关于循环时间的新编辑。无法将其放入评论中
  • 谢谢,那部分很清楚。原来是 PeekMessage 引起了麻烦,因为 mouseevents 在不应该有更新时触发了更新。我刚刚删除了那个内部循环,然后它似乎按预期工作。 :) 顺便说一句,您是如何制作自己的 ContentManager 来加载图形和其他数据的?通常 Game 类通过 Content... 处理这个
  • @sinsro 我刚开始使用这些网站,当我更新答案时它会通知你还是我需要发表评论?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-22
  • 1970-01-01
  • 2014-01-21
  • 1970-01-01
相关资源
最近更新 更多