How to control frame rate in WPF by using dispatch

2019-03-01 13:13发布

问题:

I have encountered a problem when I tried to control frame rate in WPF by using System.Windows.Threading.DispatcherTimer instance.

In order to try effectivity of DispatcherTimer, I created a simple WPF Demo with a single window which has a textbox and a button. When the button is clicked, a DispatcherTimer instance begins to tick according to the double number in the textbox as the interval and a StopWatch starts at the same time, a counter variable increases by 1 on every DispatcherTimer tick. When StopWatch.ElaspedMilliSeconds > 1000 (more than 1 second passed), the timer stops ticking and the stopwatch resets as well, a message box pops up to show value of the counter.

So the counter value is supposed to be around 30 if I input 33.3(1000/30). But the result turns out to be around 20. I ask for help whether there is anyone can help check what seems to be the problem in my source code below. Thanks in advance.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace TimerDemo
{
   public partial class MainWindow : Window
   {
      private System.Windows.Threading.DispatcherTimer _timer = new System.Windows.Threading.DispatcherTimer();
      public MainWindow()
      {
         InitializeComponent();
         double ticks = 0L;
         System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();

         int frameCount = 0;

         //There is a textbox "txtTicks" which accepts a millisecond value
         //And a button "btn", by clicking the button the dispatchertimer & 
         //stopwatcher are started.
         _timer.Tick += (sender, e) =>
         {
            frameCount++;
            System.Diagnostics.Debug.WriteLine(watch.ElapsedMilliseconds);
            if (watch.ElapsedMilliseconds > 1000)
            {
               _timer.Stop();
               watch.Reset();
               MessageBox.Show(string.Format("Already 1 second! FrameCount: {0}", frameCount));
               frameCount = 0;
            }
         };

         this.btn.Click += (sender, e) =>
         {
            double.TryParse(this.txtTicks.Text, out ticks);
            if (ticks != 0.0)
            {
               _timer.Interval = TimeSpan.FromMilliseconds(ticks);
            }
            _timer.Start();
            watch.Start();
         };
      }
   }
}

Running result as below(Rookie of Stackoverflow, cannot upload image yet):

http://i.imgur.com/Bkp1uam.png

回答1:

Since WPF (and most other rendering engines) does not take an equal time amount to render every frame, and since different computations can further delay the interval between frames, you are right in using CompositionTarget.Rendering to "clock" the rendering intervals, but you can still use a "wall clock" timer to update your game at regular, rigorously controlled intervals.

In the excellent book "Game Programming Patterns" (freely available online) you can find the very useful Game Loop Pattern.

In its full form, this is what you would put inside a handler for the CompositionTarget.Rendering event (in pseudo-code):

  double current = getCurrentTime();
  double elapsed = current - previous;
  previous = current;
  lag += elapsed;

  processInput();

  while (lag >= MS_PER_UPDATE)
  {
    update();
    lag -= MS_PER_UPDATE;
  }

  render();

In C#, you could implement getCurrentTime() either by calling DateTime.Now or by using Stopwatch.Elapsed, for example.



标签: c# wpf timer