【问题标题】:Drawing 2D Graphics To A Form Seems To Lag/Slow Down My Program将 2D 图形绘制为表单似乎滞后/减慢我的程序
【发布时间】:2012-10-15 01:30:32
【问题描述】:

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

我正在尝试制作一个简单的乒乓球游戏,使用表格然后 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),它的速度要慢得多,所以我必须做的是每次移动桨/球更远的距离以补偿它不更新很快,而且看起来很糟糕。

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

【问题讨论】:

    标签: c# .net graphics 2d lag


    【解决方案1】:

    此代码存在几个可能严重影响应用性能的问题:

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

    代码示例,它使用单独的线程来计算游戏滴答数和 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
            }
        }
    }
    

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

    【讨论】:

    • 太棒了!我实施了你的策略,我得到了一个很好的 FPS,几乎没有任何闪烁!非常感谢!
    【解决方案2】:

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

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

    【讨论】:

    • 我试过计时器,但它只每 55 毫秒调用一次我的函数,这给了我一个糟糕的 FPS
    • @Aaronman8:尝试使用timeBeginPeriod 使计时器和睡眠更准确。 (但你不需要timeBeginPeriod(1),5 毫秒的精度应该足够了)
    • 好吧,我使用了计时器,还添加了下面条目中“max”建议的双缓冲。它现在运行良好,我唯一的问题是虽然闪烁少了很多,但我仍然大约每秒一次......有什么办法可以完全消除它吗?
    • @Aaronman8:看看OnPaintBackground。覆盖它并且不要调用基本实现。 AllPaintingInWmPaint 应该解决这个问题,但根据我的经验,它并不总是这样。
    猜你喜欢
    • 2012-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-29
    • 2014-04-06
    • 1970-01-01
    相关资源
    最近更新 更多