Reputation: 1572
I'm making C# windows forms application in which I want to make smooth animation movement of a black bar over a white background. I tried the following code but the movement is flickering and not achieving smooth rapid movement even on 1 ms interval timer.
I tried the following code :
public Form1()
{
InitializeComponent();
DoubleBuffered = true;
System.Windows.Forms.Timer timer1 = new System.Windows.Forms.Timer();
timer1.Interval = 30;
timer1.Tick += Timer1_Tick;
timer1.Enabled = true;
}
int x_position = 0;
int bar_width = 40;
int speed = 1;
private void Timer1_Tick(object sender, EventArgs e)
{
x_position += speed;
if (x_position + bar_width > this.Width) x_position = 0;
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.FillRectangle(Brushes.Black, x_position, 0, bar_width, 500);
}
The result is not smooth movement at all. I found on another questions something like Invalidate(true)
or DoubleBuffered = true;
but they didn't solve the issue.
Waiting for help. Thank you
Upvotes: 2
Views: 4093
Reputation: 70652
The Winforms API, and the native Win32 window-messaging API on which it's built, is simply not designed for "smooth animation". It will be difficult, if not impossible, to achieve perfectly smooth animation.
That said, much can be done to improve on your attempt above:
CreateGraphics()
. Instead, always draw in response to the Paint
event.Applying these ideas, here's a version of your code that works much better:
const float speedXPerSecond = 1000f / 30;
const int bar_width = 40;
public Form1()
{
InitializeComponent();
DoubleBuffered = true;
}
float x_position = 0;
TimeSpan _lastFrameTime = TimeSpan.Zero;
Stopwatch _frameTimer = Stopwatch.StartNew();
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
TimeSpan currentFrameTime = _frameTimer.Elapsed;
float distance = (float)(currentFrameTime - _lastFrameTime).TotalSeconds * speedXPerSecond;
x_position += distance;
while (x_position > this.Width) x_position -= this.Width;
e.Graphics.FillRectangle(Brushes.Black, x_position, 0, bar_width, 500);
_lastFrameTime = currentFrameTime;
Invalidate();
}
You can apply this general technique to any interactive scenario. A real game will have other elements to this general "render loop", including user input and updating game state (e.g. based on a physics model). These will add overhead to the render loop, and will necessarily reduce frame rate. But for any basic game on any reasonably recent hardware (e.g. built in the last couple of decades), the net frame rate will still be well above that needed for acceptably smooth game-play.
Do note that in the managed .NET/Winforms context, there will always be limits to the success of this approach as compared to using a lower-level API. In particular, without some extra work on your part, garbage collection will interrupt periodically, causing the frame rate to stutter slightly, as well unevenness in thread scheduling and the thread's message queue.
But it's been my experience that people asking this sort of question don't need things to be absolutely perfect. They need "good enough", so that they can move on to learning about other topics involved in game development.
Upvotes: 2