0

我正在开发一个 Winforms 应用程序,可以使用一些建议。
随着时间的推移,我有数百个 50x50 的精灵在 2000x2000 的运动场上四处移动。最初,我用程序生成的图片框创建了它,这些图片框被添加到表单中并四处移动。它完成了工作,但它闪烁且缓慢。

经过相当多的谷歌搜索,它看起来像是创建一个帧缓冲区并直接绘制到该缓冲区,然后将缓冲区应用于表单上的静态图像框似乎是要走的路。

所以我把这一切都装好了,结果它比使用图片框要慢得多。这似乎是由于缓冲区2000x2000的大小(每次创建缓冲区大约需要100ms。)

绘制屏幕的代码:

private void animateAmoebas()
{
    for (int animationStep = 0; animationStep < 100; animationStep = animationStep + animationStepSize)
        {

        Image buffer = new Bitmap(2000, 2000);              
        buffer = imageBKG; //Redraw the grid pattern.                   
        foreach (Amoeba _Amoeba in amoebaPool)//Ameboa is a class object that has AI in it to detirmine the actions of the Amoeba.
            {
                //PBL (PictureBoxLoader) is an object that contains the sprite image, plus the cordinates for that sprite in that frame.
                pbl = _Amoeba.animateSprite(animationStep,pbl);
                drawSprite(pbl, buffer);//Draw the sprite to the buffer
            }               
            refreshScreen(buffer);//Copy the buffer to the picturebox
        }
}


private void drawSprite(PictureBoxLoader pbLoader, Image _buffer) 
{
    using (Graphics formGraphics = Graphics.FromImage(_buffer))
    {
        Point imgPoint = new Point(pbLoader.imgX, pbLoader.imgY);
        formGraphics.DrawImageUnscaled(pbLoader.imgImage, imgPoint);
    }
}

private void refreshScreen(Image _image)
{         
        pictureBox_BKG.Image = _image;
        this.Refresh();                
}

有什么更好的方法来做到这一点的建议吗?

我尝试提前静态创建图像缓冲区,然后重新绘制背景。这有帮助,但它仍然比使用图片框慢得多。虽然,不可否认,上述方法允许适当的透明度。

4

2 回答 2

2

您根本不应该使用 PictureBox。只需派生Control并覆盖OnPaint. 您可以绘制到您的缓冲区内的图像OnPaint,然后将图像绘制到控件。

public class SpriteCanvas : Control
{
    private const int AnimationSteps = 100;
    private const int AnimationStepSize = 4;

    private System.Windows.Forms.Timer _timer;
    private Bitmap _buffer;
    private int _animationStep = 0;

    public SpriteCanvas()
    {
        _buffer = new Bitmap(2000, 2000, PixelFormat.Format32bppPArgb);
        _timer = new System.Windows.Forms.Timer();
        _timer.Interval = 10;

        _timer.Tick += (s, e) =>
        {
            _animationStep += AnimationStepSize;

            if (_animationStep > AnimationSteps)
                _animationStep = 0;

            this.Invalidate();
        };

        _timer.Start();
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        using (var g = Graphics.FromImage(_buffer))
        {
            // draw sprites based on current _animationStep value
            // g.DrawImage(...)
        }

        e.Graphics.DrawImage(_buffer, new Rectangle(0, 0, _buffer.Width, _buffer.Height), new Rectangle(0, 0, _buffer.Width, _buffer.Height), GraphicsUnit.Pixel);
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);
        _timer.Dispose();
        _buffer.Dispose();
    }
}

您的代码中还有很多其他问题。首先,我假设您在 UI 线程上绘制所有内容。这是一个禁忌。Invalidate当您希望重绘控件时,您应该调用它。您应该在计时器上执行此操作。

您还将在循环的每次迭代中创建一个新的 Image 缓冲区,并且您会立即将其丢弃,甚至不丢弃它:

Image buffer = new Bitmap(2000, 2000);              
buffer = imageBKG; //Redraw the grid pattern. 

该类Bitmap实现IDisposable了,您应该始终将其包装在一个using块中,或者Dispose在您不再需要它时调用它。在您的情况下,您可能只想创建 1 个位图作为您的缓冲区,并且您应该在处置时处置它Control

您犯的另一个错误是调用Refresh,这将导致同步绘制,在您的情况下会导致您的控件冻结。我认为你没有充分的理由这样做。使用Invalidate而不是Refresh.

Graphics.FromImage每次绘制单个精灵时,您也会调用。所以你每帧调用数百次。显然你不想这样做。每次抽奖您应该只调用一次。

于 2013-11-05T18:32:04.447 回答
1

You are initializing a new Bitmap(2000, 2000) each step of the animation and never reusing them, which will wreck havoc on the garbage collector. Instead, save an instance of your buffer as a member variable and save a blank sprite of the same size. At the beginning of each draw loop, draw the blank sprite onto the buffer, then draw the amoeba sprites.

This can still cause flickering due to inconsistencies between the screen refresh rate and your draw rate. To fix that, use double buffering. Here's some pseudo-C# for a simple double buffered draw method.

private Image backBuffer = new Bitmap(2000, 2000);
private Image frontBuffer = new Bitmap(2000, 2000);
private Image clearSprite = new Bitmap(2000, 2000);

// Draws 1 frame
private void Draw()
{
  // Clear the back buffer
  backBuffer.Draw(clearSprite);

  // Draw sprites
  foreach (var sprite in sprites)
  {
    backBuffer.Draw(sprite);
  }

  // Swap buffers
  frontBuffer.Draw(backBuffer);
}

You're also running the entire animation without yielding control to another method, so I would also recommend moving to an asynchronous model where you have a drawing manager that is responsible for managing the buffers and is told to run it's draw function on a set interval. Each amoeba should be in control of which frame it's animation is on and it should be able to draw itself to a buffer when passed one by the draw manager.

于 2013-11-05T18:29:18.970 回答