11

我试图弄清楚如何在 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(),但仍然不行......有什么想法吗?

4

1 回答 1

7

(添加了 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;

否则我有一些可怕的摊位。

于 2011-06-15T17:58:57.900 回答