好吧,所以我已经对这个主题做了一些研究,我发现的大多数解决方案都声称可以解决这个问题,但我发现它们的工作效果并不是很好.我正处于实现一个简单的小粒子引擎的早期阶段,没有什么可疯狂的我只是出于无聊而做到这一点.我以前没有用WinForms做过这样的事情,我肯定用C/C++,但这对我来说是个新事物.以下是我用于将粒子绘制到屏幕上的代码,粒子的锅炉板代码不相关,因为它工作正常,我对我的实际游戏循环更加好奇.
这是更新和重绘的主要代码
public MainWindow() { this.DoubleBuffered = true; InitializeComponent(); Application.Idle += HandleApplicationIdle; } void HandleApplicationIdle(object sender, EventArgs e) { Graphics g = CreateGraphics(); while (IsApplicationIdle()) { UpdateParticles(); RenderParticles(g); g.Dispose(); } } //Variables for drawing the particle Pen pen = new Pen(Color.Black, 5); Brush brush = new SolidBrush(Color.Blue); public bool emmiter = false; private void EmitterBtn_Click(object sender, EventArgs e) { //Determine which emitter to use if (emmiter == true) { //Creates a new particle Particle particle = new Particle(EmitterOne.Left, EmitterOne.Top, .5f, .5f, 20, 20); emmiter = false; } else if(emmiter == false) { Particle particle = new Particle(EmitterTwo.Left, EmitterTwo.Top, -.5f, .5f, 20, 20); emmiter = true; } } public void RenderParticles(Graphics renderer) { Invalidate(); Thread.Sleep(0); //Iterate though the static list of particles for (int i = 0; i < Particle.activeParticles.Count; i++) { //Draw Particles renderer.DrawRectangle(pen, Particle.activeParticles[i].x, Particle.activeParticles[i].y, Particle.activeParticles[i].w, Particle.activeParticles[i].h); } } public void UpdateParticles() { for (int i = 0; i < Particle.activeParticles.Count; i++) { //Move particles Particle.activeParticles[i].MoveParticle(); } }
我遇到的问题是,无论何时屏幕被清除和更新,它都会出现这种可怕的闪烁,不仅如此,但每当我发射粒子时它有时都不会.
表单基本上只是使用标签作为屏幕上的不可见位置来说明每个粒子的渲染位置.
无论如何,我之前已经看过这个话题,但没有任何修正,目前的实现是最少的flickery/laggy但是没有解决问题.
任何帮助表示赞赏,谢谢!
编辑*我意识到我从来没有取消分配每个循环的图形对象,所以我这样做,每当我点击发射器按钮时没有更多的延迟,但闪烁仍然存在,我相应地更新了代码.
摆脱可见的油漆工件需要双缓冲.换句话说,将场景渲染成后缓冲区,当准备好后,可以在一个步骤中快速进入屏幕表面.这是Winforms中的内置功能,只需在表单构造函数中将DoubleBuffered属性设置为true即可.您必须使用Paint事件来利用它.覆盖OnPaint()并调用RenderParticles(e.Graphics).
你需要注意计时,现在你的UI线程正在燃烧100%核心,动画速度完全取决于粒子的数量和机器的速度.而不是Application.Idle,将Timer从工具箱中拖放到表单上.在Tick事件处理程序中,调用UpdateParticles()和this.Invalidate()以再次触发Paint事件.计时器的Interval属性值至关重要,您可以通过选择15或31毫秒(64或32 FPS)获得最可重现的更新速率.
您并不总是能够获得所需的FPS速率,如果计算机繁忙或速度太慢或者UI线程上的其他代码需要运行,计时器将只是延迟或跳过Tick事件.要确保不影响动画,您必须测量实际经过的时间,而不是将粒子移动固定的量.Environment.TickCount,DateTime.UtcNow或Stopwatch是测量真实经过时间的合适方法.