11

通常のGameMicrosoft.Xna.Framework.Gameクラスを使用せずに、Windowsゲームでゲームループ全体を手動で管理する方法を理解しようとしています。

これは、通常のGameクラスを使用すると、ゲームが途切れる原因になるためです。それほど多くはありませんが、ゲームの特定の性質のために、それはまだかなり目に見えます。

さまざまな設定(vsync、fixedtimestep、さまざまなフレームレートなど)を試した後、タイミングを完全に制御できるように独自のゲームクラスを作成してみることにしました。それで問題が解決するかどうかはわかりませんが、少なくともこの方法で完全に制御できます。

基本的に私はする必要があります:

  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();
        }
    }

「whilePeekMessage」を有効にすると、ループは継続的に実行されますが、スリープは無視され、マウスがウィンドウ上を移動すると停止します。ここで何が起こっているのかわからない...

最適なのは、メインのレンダリングループで次のような単純なことをしたいだけだと思います。

    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の場合、ゲームループを使用して独自のカスタム実行関数をスロットルされた真のループに配置できる場合があります。真の間に実行される内部では、おそらく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を実装するクラスを作成する必要があります。このクラスは、IGraphicsDeviceProviderを実装する次のクラスを作成するために、コンストラクターでGraphicsDeviceを取ります。さらに、私はこのように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 に答える