C# 在窗体上绘制二维图形似乎会延迟/减慢我的程序

C# 在窗体上绘制二维图形似乎会延迟/减慢我的程序,c#,.net,graphics,2d,lag,C#,.net,Graphics,2d,Lag,我刚开始学习.NET,所以很可能这是一个n00b的大错误: 我正在尝试制作一个简单的乒乓球游戏,使用表单和 System::Drawing::Graphics类将游戏绘制到窗体 我的代码的重要部分如下所示: (主游戏循环): (表格初始化代码) 另外,这不是确切的代码,我只是根据记忆写的,所以这就是任何打字错误或错误的地方 可能会出现错误的名称,或与初始化表单有关的一些重要代码行 这就是代码。 我的问题是,游戏肯定不是每15毫秒更新一次(大约60帧/秒),它的速度要慢得多,所以我要做的是每次将球

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

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

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

(主游戏循环):

(表格初始化代码)

另外,这不是确切的代码,我只是根据记忆写的,所以这就是任何打字错误或错误的地方 可能会出现错误的名称,或与初始化表单有关的一些重要代码行

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

简言之,在绘制图形时,某些因素会大大降低游戏的速度。我有一种感觉,它与我的双缓冲有关,但我无法摆脱它,因为它会产生一些令人恶心的闪烁。我的问题是,,
如何消除这种延迟?

不要在每一帧创建一个新的
位图


不要使用
线程。睡眠
。取而代之的是查看
Timer
组件(位于
System.Windows.Forms
命名空间中的组件)。

此代码存在几个问题,可能会严重影响应用程序性能:

  • 每帧创建一个大的
    位图
    缓冲区,但不处理它
  • 在WinForms已经有了很好的这种行为实现的情况下实现双缓冲
  • UpdateName()是递归的,但不一定是递归的
  • 在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
            }
        }
    }
    
    静态类程序
    {
    /// 
    ///应用程序的主要入口点。
    /// 
    [状态线程]
    静态void Main()
    {
    运行(新建GameWindow());
    }
    类游戏窗口:窗体
    {
    私有线程(gameThread);;
    私人手册重置事件_evExit;
    公共游戏窗口()
    {
    Text=“Pong”;
    尺寸=新尺寸(800600);
    StartPosition=FormStartPosition.CenterScreen;
    FormBorderStyle=FormBorderStyle.Fixed3D;
    双缓冲=真;
    固定方式(
    ControlStyles.AllPaintingWimPaint|
    ControlStyles.OptimizedDubleBuffer|
    ControlStyles.UserPaint,
    正确的);
    }
    私有void GameThreadProc()
    {
    IAsyncResult tick=null;
    而(!\u evExit.WaitOne(15))
    {
    如果(勾选!=null)
    {
    如果(!勾选.AsyncWaitHandle.WaitOne(0))
    {
    //我们跑得太慢了,也许我们可以做点什么
    if(WaitHandle.WaitAny)(
    新WaitHandle[]
    {
    _埃维希特,
    tick.AsyncWaitHandle
    }) == 0)
    {
    返回;
    }
    }
    }
    tick=BeginInvoke(新方法调用程序(OnGameTimerTick));
    }
    }
    私有void ongametemertick()
    {
    //在这里表演游戏物理
    //不要画任何东西
    使无效();
    }
    私有void ExitGame()
    {
    Close();
    }
    受保护的覆盖无效OnPaint(PaintEventArgs e)
    {
    var g=e.图形;
    g、 清晰(颜色:白色);
    //你都在这儿画画吗
    //不要在这里做你自己的双缓冲,它已经在工作了
    //不要丢弃垃圾
    }
    受保护的覆盖无效加载(事件参数e)
    {
    基础荷载(e);
    _evExit=新手动重置事件(假);
    _gameThread=新线程(GameThreadProc);
    _gameThread.Name=“游戏线程”;
    _gamesthread.Start();
    }
    关闭时受保护的覆盖无效(事件参数e)
    {
    _evExit.Set();
    _gamesthread.Join();
    _evExit.Close();
    基础。一旦关闭(e);
    }
    PaintBackground上受保护的覆盖无效(PaintEventArgs e)
    {
    //无所事事
    }
    }
    }
    

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

    我尝试了计时器,但它每55毫秒调用一次我的函数,这给了我一个坏消息FPS@Aaronman8:尝试使用
    timeBeginPeriod
    使计时器和睡眠更准确。(但是你不需要
    timeBeginPeriod(1)
    ,5毫秒的精度应该足够了)好的,我使用了计时器,并且我还添加了下面条目中“max”建议的双缓冲。它现在运行良好,我唯一的问题是,虽然闪烁的次数少了很多,但我仍然会每秒看到一次。。。有什么方法可以完全删除这个吗?@Aaronman8:看一看
    OnPaintBackground
    。重写它,不调用基本实现
    allpaintingwmpaint
    应该会处理这个问题,但根据我的经验,并不总是这样。太好了!我实施了你的策略,我得到了一个很好的FPS,几乎没有任何闪烁!非常感谢!
    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);
    }
    
    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
            }
        }
    }