我正在使用Winforms应用程序并可以使用一些建议 我有几百个50x50的精灵随着时间的推移在2000x2000的比赛场地上移动。 最初我创建了它,精灵是由progamatically生成的图片框添加到表单并移动。它完成了工作,但它是闪烁而且很慢。
经过大量的谷歌搜索,看起来像创建一个帧缓冲区并直接绘制到那个,然后将缓冲区应用到表单上的静态图像框似乎是要走的路。
所以我装备了所有这些,结束比使用图片框慢得多。这似乎是由于缓冲区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();
}
有关更好方法的建议吗?
我尝试提前静态创建imagebuffer并重新绘制背景。这有帮助,但它仍然比使用图片框慢得多。但是,诚然,上述方法允许适当的透明度。
答案 0 :(得分: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
。在您的情况下,您可能只想创建一个位图作为缓冲区,并且在处理Control
时应该将其丢弃。
您犯的另一个错误是调用Refresh
,这会导致同步绘制,在您的情况下会导致控件冻结。我认为你没有充分的理由这样做。使用Invalidate
代替Refresh
。
每次绘制单个精灵时,也会调用Graphics.FromImage
。所以你每帧呼叫这几百次。显然你不想这样做。你应该每次抽奖只调用一次。
答案 1 :(得分:1)
你正在初始化一个新的Bitmap(2000,2000)动画的每一步,并且永远不会重复使用它们,这将破坏垃圾收集器。而是将缓冲区的实例保存为成员变量并保存相同大小的空白精灵。在每个绘制循环开始时,将空白精灵绘制到缓冲区上,然后绘制变形虫精灵。
由于屏幕刷新率与您的绘制率之间的不一致,这仍然可能导致闪烁。要解决此问题,请使用双缓冲。这是一个简单的双缓冲绘制方法的伪C#。
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);
}
你也在运行整个动画而不控制另一个方法,所以我还建议你转到一个异步模型,你有一个负责管理缓冲区的绘图管理器,并告诉它运行它的绘图功能设定的间隔。每个变形虫应该控制动画所在的帧,并且当绘制管理器传递给它时,它应该能够将自己绘制到缓冲区。