4

我刚开始学习 .NET,所以这很可能是一个很大的 n00b 错误:

我正在尝试制作一个简单的乒乓球游戏,使用表单,然后使用 System::Drawing::Graphics 类将游戏绘制到表单上。

我的代码的重要部分如下所示:

(主游戏循环):

void updateGame()
{
    //Update Game Elements
    (Update Paddle And Ball) (Ex. paddle.x += paddleDirection;)
    //Draw Game Elements
    //Double Buffer Image, Get Graphics
    Bitmap dbImage = new Bitmap(800, 600);
    Graphics g = Graphics::FromImage(dbImage);
    //Draw Background
    g.FillRectangle(Brushes::White, 0, 0, 800, 600);
    //Draw Paddle & Ball
    g.FillRectangle(Brushes::Black, paddle);
    g.FillRectangle(Brushes::Red, ball);
    //Dispose Graphics
    g.Dispose();
    //Draw Double Buffer Image To Form
    g = form.CreateGraphics();
    g.DrawImage(dbImage, 0, 0);
    g.Dispose();
    //Sleep
    Thread.sleep(15);
    //Continue Or Exit
    if(contineGame())
    {
        updateGame();
    } 
    else
    {
        exitGame();
    }
}

(表单初始化代码)

void InitForm()
{
    form = new Form();
    form.Text = "Pong"
    form.Size = new Size(800, 600);
    form.FormBorderStyle = FormBorderStyle::Fixed3D;
    form.StartLocation = StartLocation::CenterScreen;
    Application::Run(form);
}

PS。这不是确切的代码,我只是从内存中编写的,因此任何拼写错误或错误名称,或者与初始化表单有关的一些重要代码行都是来自那里。

这就是代码。我的问题是游戏肯定不是每 15 毫秒更新一次(大约 60 fps),它的速度要慢得多,所以我必须做的是每次移动桨/球更远的距离以补偿它不更新很快,这看起来很糟糕。

简而言之,在绘制图形时,游戏速度会大大降低。我觉得这与我的双重缓冲有关,但我无法摆脱它,因为这会产生一些令人讨厌的闪烁。我的问题是,我该如何摆脱这种滞后?

4

2 回答 2

6

此代码有几个问题会严重影响应用程序的性能:

  1. Bitmap每帧创建一个大缓冲区而不是处理它
  2. 实现双缓冲,而 WinForms 已经很好地实现了这种行为
  3. updateGame() 是递归的,但不一定是递归的
  4. Thread.Sleep()在 GUI 线程中调用

代码示例,它使用单独的线程来计算游戏滴答数和 WinForms 内置的双缓冲:

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.Run(new GameWindow());
    }

    class GameWindow : Form
    {
        private Thread _gameThread;
        private ManualResetEvent _evExit;

        public GameWindow()
        {
            Text            = "Pong";
            Size            = new Size(800, 600);
            StartPosition   = FormStartPosition.CenterScreen;
            FormBorderStyle = FormBorderStyle.Fixed3D;
            DoubleBuffered  = true;

            SetStyle(
                ControlStyles.AllPaintingInWmPaint |
                ControlStyles.OptimizedDoubleBuffer |
                ControlStyles.UserPaint,
                true);
        }

        private void GameThreadProc()
        {
            IAsyncResult tick = null;
            while(!_evExit.WaitOne(15))
            {
                if(tick != null)
                {
                    if(!tick.AsyncWaitHandle.WaitOne(0))
                    {
                        // we are running too slow, maybe we can do something about it
                        if(WaitHandle.WaitAny(
                            new WaitHandle[]
                            {
                                _evExit,
                                tick.AsyncWaitHandle
                            }) == 0)
                        {
                            return;
                        }
                    }
                }
                tick = BeginInvoke(new MethodInvoker(OnGameTimerTick));
            }
        }

        private void OnGameTimerTick()
        {
            // perform game physics here
            // don't draw anything

            Invalidate();
        }

        private void ExitGame()
        {
            Close();
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            var g = e.Graphics;
            g.Clear(Color.White);

            // do all painting here
            // don't do your own double-buffering here, it is working already
            // don't dispose g
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            _evExit = new ManualResetEvent(false);
            _gameThread = new Thread(GameThreadProc);
            _gameThread.Name = "Game Thread";
            _gameThread.Start();
        }

        protected override void OnClosed(EventArgs e)
        {
            _evExit.Set();
            _gameThread.Join();
            _evExit.Close();
            base.OnClosed(e);
        }

        protected override void OnPaintBackground(PaintEventArgs e)
        {
            // do nothing
        }
    }
}

可以进行更多改进(例如,仅使部分游戏屏幕无效)。

于 2012-10-15T02:33:56.697 回答
2

不要创建一个新的Bitmap每一帧。

不要使用Thread.Sleep. 而是检查Timer组件(System.Windows.Forms命名空间中的组件)。

于 2012-10-15T02:33:17.523 回答