如何准确地使用调度程序计时器来控制 WPF 中的帧速率? [英] How to control frame rate in WPF by using dispatcher timer accurately?

查看:22
本文介绍了如何准确地使用调度程序计时器来控制 WPF 中的帧速率?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在 WPF 中尝试使用 System.Windows.Threading.DispatcherTimer 实例控制帧率时遇到问题.

为了尝试 DispatcherTimer 的有效性,我创建了一个简单的 WPF 演示,其中包含一个带有文本框和按钮的单个窗口.当按钮被点击时,一个 DispatcherTimer 实例开始根据文本框中的双数作为间隔,同时一个 StopWatch 启动,计数器变量在每个 DispatcherTimer 滴答时增加 1.当 StopWatch.ElaspedMilliSeconds > 1000(超过 1 秒)时,计时器停止计时并且秒表也重置,弹出消息框以显示计数器的值.

所以如果我输入 33.3(1000/30),计数器值应该在 30 左右.但结果竟然是 20 左右.我寻求帮助是否有人可以帮助检查下面我的源代码中似乎是什么问题.提前致谢.

使用系统;使用 System.Collections.Generic;使用 System.Linq;使用 System.Text;使用 System.Threading.Tasks;使用 System.Windows;使用 System.Windows.Controls;使用 System.Windows.Data;使用 System.Windows.Documents;使用 System.Windows.Input;使用 System.Windows.Media;使用 System.Windows.Media.Imaging;使用 System.Windows.Navigation;使用 System.Windows.Shapes;命名空间 TimerDemo{公共部分类 MainWindow : 窗口{私有 System.Windows.Threading.DispatcherTimer _timer = new System.Windows.Threading.DispatcherTimer();公共主窗口(){初始化组件();双刻度 = 0L;System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();int frameCount = 0;//有一个接受毫秒值的文本框txtTicks"//还有一个按钮btn",通过点击dispatchertimer &按钮//秒表启动._timer.Tick += (sender, e) =>{帧数++;System.Diagnostics.Debug.WriteLine(watch.ElapsedMilliseconds);if (watch.ElapsedMilliseconds > 1000){_timer.Stop();手表.重置();MessageBox.Show(string.Format("Already 1 second! FrameCount: {0}", frameCount));帧数 = 0;}};this.btn.Click += (sender, e) =>{double.TryParse(this.txtTicks.Text, out ticks);如果(滴答!= 0.0){_timer.Interval = TimeSpan.FromMilliseconds(ticks);}_timer.Start();看.开始();};}}}

运行结果如下(Stackoverflow新手,还不能上传图片):

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

解决方案

由于 WPF(和大多数其他渲染引擎)渲染每一帧所需的时间不同,而且由于不同的计算会进一步延迟帧之间的间隔,您使用 CompositionTarget.Rendering 来计时"渲染间隔是正确的,但您仍然可以使用挂钟"计时器来定期、严格控制更新您的游戏间隔.

在优秀的游戏编程模式"(可在线免费获得)一书中,您可以找到非常有用的游戏循环模式.

在其完整形式中,这是您将在 CompositionTarget.Rendering 事件的处理程序中放入的内容(以伪代码形式):

<前>双电流 = getCurrentTime();双经过 = 当前 - 前一个;上一个 = 当前;滞后 += 已过;进程输入();而(滞后 >= MS_PER_UPDATE){更新();滞后-= MS_PER_UPDATE;}使成为();

在 C# 中,您可以通过调用 DateTime.Now 或使用 Stopwatch.Elapsed 来实现 getCurrentTime(),例如.

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

解决方案

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.

这篇关于如何准确地使用调度程序计时器来控制 WPF 中的帧速率?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆