13

我写了一个组件来显示当前的 FPS。
其中最重要的部分是:

    public override void Update(GameTime gameTime)
    {
        elapseTime += (float)gameTime.ElapsedRealTime.TotalSeconds;
        frameCounter++;

        if (elapseTime > 1)
        {
            FPS = frameCounter;
            frameCounter = 0;
            elapseTime = 0;
        }
        base.Update(gameTime);
    }


    public override void Draw(GameTime gameTime)
    {
        spriteBatch.Begin();

        spriteBatch.DrawString(font, "FPS " + ((int)FPS).ToString(), position, color, 0, origin, scale, SpriteEffects.None, 0);

        spriteBatch.End();

        base.Draw(gameTime);
    }

在大多数情况下它工作正常,但最近我遇到了一个问题。
当我将以下代码放入游戏的 Update 方法时,奇怪的事情开始发生。

       if (threadPath == null || threadPath.ThreadState != ThreadState.Running)
        {
            ThreadStart ts = new ThreadStart(current.PathFinder.FindPaths);
            threadPath = new Thread(ts);
            threadPath.Priority = ThreadPriority.Highest;
            threadPath.Start();
        }

这段代码的主要思想是始终在不同的线程中运行 pathFinding 算法。

奇怪的事情是我的意思是有时 FPS 会急剧下降,这很明显,但显示的 FPS 变化的频率超过每秒一次。如果我理解此代码,FPS 的更改频率不会超过每秒一次。

有人可以解释一下发生了什么吗?

编辑 26.03.2010
我还发布了 Draw 方法的代码。

编辑 31.03.2010 Venesectrix 问题的答案
1) 您是在使用固定时间步长还是可变时间步长运行?
IsFixedTimeStep 和 SynchronizeWithVerticalRetrace 设置为 true。
2)发生这种情况时,您是否正在移动 XNA 窗口?

3) XNA 窗口是否有焦点?

的 4) 它有多引人注目(即,更新速度如此之快以至于您无法阅读,或者只是更新不到一秒钟)?
我能够读取更新,FPS 每秒更新约 3 次。
5)所有这一切都只发生在那里的线程代码?
是的

4

6 回答 6

12

肖恩·哈格里夫斯(Shawn Hargreaves)在这里有一篇很棒的帖子。我在他的代码和您的代码之间看到的第一个区别是您每次将 elapseTime 重置为 0,这会浪费一些时间,而 Shawn 只是从他的 elapsedTime 中减去 1 秒。此外,Shawn 使用 ElapsedGameTime 而不是 ElapsedRealTime。他在 Draw 函数而不是 Update 函数中更新他的 frameCounter。

至于他为什么使用 ElapsedRealTime,他在帖子后的评论中解释了这一点:

> 当然 1 / gameTime.ElapsedRealTime.TotalSeconds

> 因此将给出当前帧率。

这将告诉您自上次调用 Update 以来已经过了多长时间,但这与您的帧速率不同!

a) 如果游戏丢帧,更新会更频繁地调用以赶上。您想要对正在发生的实际抽奖次数进行计时,而不仅仅是这些额外的追赶逻辑帧。

b) 单个更新的时间可能会波动很大,因此您从中得出的数字将过于闪烁而难以阅读。

我会试试他的组件,看看它是否适合你。这篇文章已经很老了,我认为您必须将 LoadGraphicsContent 更改为 LoadContent 并将 UnloadGraphicsContent 更改为 UnloadContent,正如另一条评论指出的那样。

于 2010-03-25T00:08:17.110 回答
2

这是我的做法和使用这种方法:

  • 你平均超过 n 帧
  • 您可以将它与您选择的任何初始化方法一起使用
  • 它应该易于阅读和遵循
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace _60fps
{
public class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    SpriteFont OutputFont;
    float Fps = 0f;
    private const int NumberSamples = 50; //Update fps timer based on this number of samples
    int[] Samples = new int[NumberSamples];
    int CurrentSample = 0;
    int TicksAggregate = 0;
    int SecondSinceStart = 0;

    public Game1()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }

    protected override void Initialize()
    {
        base.Initialize();
        graphics.SynchronizeWithVerticalRetrace = false;
        int DesiredFrameRate = 60;
        TargetElapsedTime = new TimeSpan(TimeSpan.TicksPerSecond / DesiredFrameRate);
    }

    protected override void LoadContent()
    {
        spriteBatch = new SpriteBatch(GraphicsDevice);
        OutputFont = Content.Load<SpriteFont>("MessageFont");
    }

    protected override void UnloadContent()
    {/* Nothing to do */}

    protected override void Update(GameTime gameTime)
    {
        if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.Escape))
            this.Exit();

        base.Update(gameTime);
    }

    private float Sum(int[] Samples)
    {
        float RetVal = 0f;
        for (int i = 0; i < Samples.Length; i++)
        {
            RetVal += (float)Samples[i];
        }
        return RetVal;
    }

    private Color ClearColor = Color.FromNonPremultiplied(20, 20, 40, 255);
    protected override void Draw(GameTime gameTime)
    {
        Samples[CurrentSample++] = (int)gameTime.ElapsedGameTime.Ticks;
        TicksAggregate += (int)gameTime.ElapsedGameTime.Ticks;
        if (TicksAggregate > TimeSpan.TicksPerSecond)
        {
            TicksAggregate -= (int)TimeSpan.TicksPerSecond;
            SecondSinceStart += 1;
        }
        if (CurrentSample == NumberSamples) //We are past the end of the array since the array is 0-based and NumberSamples is 1-based
        {
            float AverageFrameTime = Sum(Samples) / NumberSamples;
            Fps = TimeSpan.TicksPerSecond / AverageFrameTime;
            CurrentSample = 0;
        }

        GraphicsDevice.Clear(ClearColor);
        spriteBatch.Begin();
        if (Fps > 0)
        {
            spriteBatch.DrawString(OutputFont, string.Format("Current FPS: {0}\r\nTime since startup: {1}", Fps.ToString("000"), TimeSpan.FromSeconds(SecondSinceStart).ToString()), new Vector2(10,10), Color.White);
        }
        spriteBatch.End();
        base.Draw(gameTime);
    }
}
}

至于:“但是为什么显示的 FPS 比每秒更频繁地变化的问题仍然悬而未决”

ElapsedGameTime 和 ElapsedRealTime 之间的区别在于,“ElapsedGameTime”是自您上次输入 Update 或 Draw 语句以来的时间量(取决于您使用的“gameTime”——来自 Update 的那个或来自 Draw 的那个)。

ElapsedRealTime 是游戏开始后的时间。正因为如此,它随着游戏的继续运行而线性增加。实际上,1 秒后,您将更新每一帧,因为您的逻辑如下所示:

(为了便于解释,假设您运行的是 4 fps):

  • 第 1 帧:ElapsedRealTime:0.25。现在运行总数:0.25
  • 第 2 帧:ElapsedRealTime:0.5 现在运行总数:0.75
  • 第 3 帧:ElapsedRealTime:0.75 现在运行总数:1.5
  • 运行总数大于 1!!!显示 FPS!
  • 设置运行总计 = 0
  • 第 4 帧:ElapsedRealTime:1.00 现在运行总数:1.0
  • 运行总数大于 1!!!显示 FPS!

现在您已经修复了计数器,您现在应该只会获得稳定的 0.25 的 Elapsed Game Time 变化,因此进度现在会移动:

  • 第 1 帧:ElapsedGameTime:0.25。现在运行总数:0.25
  • 第 2 帧:ElapsedGameTime:0.25 现在运行总数:0.50
  • 第 3 帧:ElapsedGameTime:0.25 现在运行总数:0.75
  • 第 4 帧:ElapsedGameTime:0.25 现在运行总数:1.00
  • 运行总数大于 1!!!显示 FPS!
  • 设置运行总计 = 0

  • 第 5 帧:ElapsedGameTime:0.25。现在运行总数:0.25

  • 第 6 帧:ElapsedGameTime:0.25 现在运行总数:0.50
  • 第 7 帧:ElapsedGameTime:0.25 现在运行总数:0.75
  • 第 8 帧:ElapsedGameTime:0.25 现在运行总数:1.00
  • 运行总数大于 1!!!显示 FPS!
  • 设置运行总计 = 0

这是你所期待的。简而言之,既然您已经纠正了第一个问题,那么您应该也纠正了第二个问题,并且上面解释了“为什么”。

于 2011-10-25T21:48:47.150 回答
1

顺便说一句……您应该避免设置线程优先级。通过将最高线程优先级分配给应该是后台线程的线程,您最终可能会耗尽主线程的 cpu 时间,因为调度程序会将优先级分配给 threadPath

于 2010-03-25T01:34:53.063 回答
0

您是否正在积极检查 IsRunningSlowly 是否正在更改?即使 IsFixedTimeStep 为 true,如果您的程序无法执行预期的更新次数,它也会更频繁地调用它。

我之前缓解这种情况的一种方法是直接调用 ResetElapsedTime() 而不是自己跟踪它。

不确定这是否对您有用。我确实注意到,当我调试上一个问题时,它不会调用额外的更新,可能是调试时的“功能”。

于 2010-04-02T18:22:12.707 回答
0

你应该在 draw 方法下运行你的 fps 计数器

于 2013-07-12T10:51:04.743 回答
0

大家好,如果你想显示你的真实帧率,你必须对方法实现帧率计数器Draw,因为 XNA 这样做是这样的:“如果你的计算机不能提供方法Update,它会挂起Draw方法,而不是提供Update方法”

于 2016-01-29T15:38:14.103 回答