2013年4月7日 星期日

C# 繪圖閃爍 - 使用雙緩衝圖形解決

最近在看 Gary Lin 的 C# 遊戲程式設計,強力推薦,很不錯的教學,有教很多寫遊戲程式的技巧。
其中一個技巧是如何讓畫面重繪時不閃爍,除了裡面提到的方法外,我也在網路上參考了其他資料,並做個整理。

重繪畫面會閃爍的原因,是因為畫面更新時,程式會先將畫面清空,再將新的畫面畫上去。
因此清空的那一瞬間,就會產生閃爍的感覺。

解決的方式:
照 MSDN 的文件說明,使用「雙重緩衝圖形」即可。
也就是畫面重繪時,不直接在螢幕上重繪,而是先在緩衝區重繪,等整個畫面都重繪完成,才一次放到螢幕上。


使用雙緩衝繪圖的方法,目前我找到的方式有以下幾種
  1. 將 DoubleBuffered 屬性設為 true。
    可在視窗屬性面版將 DoubleBuffered 設為 true,
    或在程式碼中設定。此為 MSDN 上的方法
    public Form1()
    {
        InitializeComponent();
        this.DoubleBuffered = true;
    }
    
  2. 設定 ControlStyles.AllPaintingInWmPaint、ControlStyles.OptimizedDoubleBuffer 為true,此為 MSDN 上的方法,MSDN 只提到 ControlStyles.OptimizedDoubleBuffer,但我在 VS 2012 測試時, VS 2012 提示還要設定 ControlStyles.AllPaintingInWmPaint
    public Form1()
    {
        InitializeComponent();
        this.SetStyle(ControlStyles.AllPaintingInWmPaint |
                ControlStyles.OptimizedDoubleBuffer, true);
    }
    
  3. 設定 ControlStyles.UserPaint、ControlStyles.AllPaintingInWmPaint、ControlStyles.DoubleBuffer 為true,此為 MSDN上的方法
    public Form1()
    {
        InitializeComponent();
        this.SetStyle(
                    ControlStyles.UserPaint |
                    ControlStyles.AllPaintingInWmPaint |
                    ControlStyles.DoubleBuffer, true);
        this.UpdateStyles();//這行我測試沒設定似乎也可以,但MSDN上有此行
    }
    
  4. 使用 BufferedGraphicsContext 手動管理雙緩衝,此為 MSDN上的方法
    namespace WindowsFormsBufferedGraphics
    {
        public partial class Form1 : Form
        {
            private float x=0;
            private float y=0;
    
            public Form1()
            {
                InitializeComponent();
                this.timer1.Enabled = true;
                this.timer1.Interval = 50;
            }
    
            private void timer1_Tick(object sender, EventArgs e)
            {
                this.x += 1;
                BufferedGraphicsContext currentContext = BufferedGraphicsManager.Current;
                BufferedGraphics myBuffer = currentContext.Allocate(this.CreateGraphics(), this.DisplayRectangle);
                
                //清除繪圖畫面,並用原本的背景色填充,否則呈現黑色背景
                myBuffer.Graphics.Clear(this.BackColor);
                
                myBuffer.Graphics.DrawEllipse(Pens.Blue, this.x, this.y, 50, 50);
                myBuffer.Render(this.CreateGraphics());
                myBuffer.Dispose();
            }
        }
    }
    
  5. 另一個手動管理雙緩衝,我在 Gary Lin 的 C# 遊戲程式設計 學到的方法
    namespace WindowsFormsBufferedGraphics2
    {
        public partial class Form1 : Form
        {
            private float x = 0;
            private float y = 0;
    
            private Graphics backGraphics;
            private Bitmap backBmp;
    
            public Form1()
            {
                InitializeComponent();
                this.backBmp = new Bitmap(this.DisplayRectangle.Width, this.DisplayRectangle.Height);
                this.backGraphics = Graphics.FromImage(backBmp);
    
                this.timer1.Enabled = true;
                this.timer1.Interval = 50;
            }
    
            private void timer1_Tick(object sender, EventArgs e)
            {
                this.x += 1;
    
                //重新填充背景色,否則會殘留之前的圖形
                this.backGraphics.FillRectangle(Brushes.White, 0, 0, this.DisplayRectangle.Width, this.DisplayRectangle.Height);
    
                this.backGraphics.DrawEllipse(Pens.Blue, this.x, this.y, 50, 50);
                this.CreateGraphics().DrawImageUnscaled(this.backBmp, 0, 0);
            }
        }
    }
    

參考資料:

沒有留言:

張貼留言