【问题标题】:XNA/MonoGame: Getting the Frames Per SecondXNA/MonoGame:每秒获取帧数
【发布时间】:2013-12-19 07:54:08
【问题描述】:

我正在尝试获取游戏的当前 FPS,但是我只能找到每秒更新 FPS 变量的方法。例如。 https://github.com/CartBlanche/MonoGame-Samples/blob/master/Draw2D/FPSCounterComponent.cshttp://www.david-amador.com/2009/11/how-to-do-a-xna-fps-counter/

有没有办法让 FPS 标签不断更新?

【问题讨论】:

  • Fps = 帧/,无论如何:只需减少计时器的滴答声(fe TimeSpan.FromMilliseconds)并将其投影到一秒(因此,如果您每 0.5 秒获得一次 fps,则将其乘以 2)
  • 我试过了,但结果很奇怪。你介意提供一些东西让我过去吗?
  • 我不能不跳十位数(从 60 到 50 等)

标签: c# xna frame-rate monogame


【解决方案1】:

这是我不久前写的一个 FPS 计数器类。您应该可以将其放入您的代码中并按原样使用它。

public class FrameCounter
{
    public FrameCounter()
    {
    }

    public long TotalFrames { get; private set; }
    public float TotalSeconds { get; private set; }
    public float AverageFramesPerSecond { get; private set; }
    public float CurrentFramesPerSecond { get; private set; }

    public const int MAXIMUM_SAMPLES = 100;

    private Queue<float> _sampleBuffer = new Queue<float>();

    public override bool Update(float deltaTime)
    {
        CurrentFramesPerSecond = 1.0f / deltaTime;

        _sampleBuffer.Enqueue(CurrentFramesPerSecond);

        if (_sampleBuffer.Count > MAXIMUM_SAMPLES)
        {
            _sampleBuffer.Dequeue();
            AverageFramesPerSecond = _sampleBuffer.Average(i => i);
        } 
        else
        {
            AverageFramesPerSecond = CurrentFramesPerSecond;
        }

        TotalFrames++;
        TotalSeconds += deltaTime;
        return true;
    }
}

你需要做的就是在你的主游戏类中创建一个成员变量..

    private FrameCounter _frameCounter = new FrameCounter();

然后在你的 Game 的 Draw 方法中调用 Update 方法并绘制你喜欢的标签..

    protected override void Draw(GameTime gameTime)
    {
         var deltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds;

         _frameCounter.Update(deltaTime);

         var fps = string.Format("FPS: {0}", _frameCounter.AverageFramesPerSecond);

         _spriteBatch.DrawString(_spriteFont, fps, new Vector2(1, 1), Color.Black);

        // other draw code here
    }

享受吧! :)

【讨论】:

  • 谢谢,这比我迄今为止尝试过的任何东西都好——虽然它没有“感觉”实时,但这可能是因为它是平均的。
  • 不客气。您可以使用 MAXIMUM_SAMPLES 值或尝试使用 CurrentFramesPerSecond 属性。我没有付出太多努力来使它完美。我的目标只是让某些东西易于重复使用。
  • 队列集合从何而来?我在 System.Collections 中找到了一个,但它是一种非泛型类型,因此它不会按此处预期的方式工作(使用浮点类型)。我的项目不需要这个,只是感兴趣。
  • @craftworkgames:对于衰减平均值,this answer I posted a while ago 中的第二个 sn-p 可以解决问题,但最准确的方法是计算 Draw 方法中的帧数,例如 @ 987654323@.
【解决方案2】:

您可以使用以下公式在任何给定时刻获取当前帧速率:

framerate = (1 / gameTime.ElapsedGameTime.TotalSeconds);

下面介绍的其他两种方法都为您提供了修改后的帧速率,旨在更平滑,并且返回值的波动更少

这个方法是通过在对数尺度上加权所有以前的帧时间来工作的。我不喜欢使用平均值来获得游戏性能指标的想法,因为丢帧没有很好地表示(或者如果你有一个很高的平均值)并且非常低/非常高的帧率具有非常不同的准确度水平如果它们的平均运行速度相同。

为了解决这个问题,我创建了一个 SmartFramerate 类(我知道这个名字很糟糕)

class SmartFramerate
{
    double currentFrametimes;
    double weight;
    int numerator;

    public double framerate
    {
        get
        {
            return (numerator / currentFrametimes);
        }
    }

    public SmartFramerate(int oldFrameWeight)
    {
        numerator = oldFrameWeight;
        weight = (double)oldFrameWeight / ((double)oldFrameWeight - 1d);
    }

    public void Update(double timeSinceLastFrame)
    {
        currentFrametimes = currentFrametimes / weight;
        currentFrametimes += timeSinceLastFrame;
    }
}

您在创建变量时设置权重: (权重越高对瞬时帧率越准确,权重越低越平滑。我发现3-5是一个很好的平衡)

SmartFramerate smartFPS = new SmartFramerate(5);

在每帧运行的任何地方调用 Update 方法:

smartFPS.Update(gameTime.ElapsedGameTime.TotalSeconds);

当前帧率可以这样访问:

smartFPS.framerate

或像这样打印:

debuginfo.Update("\n\nᴥ" + smartFPS.framerate.ToString("0000"), true);

(我将其放入自定义打印类中,因此如果语法看起来很时髦,我深表歉意)

但是,如果您想简单地平均一定数量的帧,那么这个类是我想出的最有效的方法。

class SmoothFramerate
{
    int samples;
    int currentFrame;
    double[] frametimes;
    double currentFrametimes;

    public double framerate
    {
        get
        {
            return (samples / currentFrametimes);
        }
    }

    public SmoothFramerate(int Samples)
    {
        samples = Samples;
        currentFrame = 0;
        frametimes = new double[samples];
    }

    public void Update(double timeSinceLastFrame)
    {
        currentFrame++;
        if (currentFrame >= frametimes.Length) { currentFrame = 0; }

        currentFrametimes -= frametimes[currentFrame];
        frametimes[currentFrame] = timeSinceLastFrame;
        currentFrametimes += frametimes[currentFrame];
    }
}

要使用它,只需在您希望使用它的地方初始化一个 SmoothFramerate 变量,传递您想要平均的帧数:

SmoothFramerate smoothFPS = new SmoothFramerate(1000);

更新、访问和打印当前帧速率,与使用上面的 SmartFramerate 类完全一样。

感谢阅读,希望对大家有所帮助。

【讨论】:

    【解决方案3】:

    使用 double 来测量 fps:

    double frameRate = 0.0;
    

    修改方法Update如下:

    public override void Update(GameTime gameTime)
    {        
        if(gameTime.ElapsedGameTime.TotalSeconds > 0.0)
        {
            frameRate = (double)frameCounter / gameTime.ElapsedGameTime.TotalSeconds;
        }
        frameCounter = 0;
    }
    

    我没有测试代码,但你应该明白了。

    【讨论】:

    • if-block进入一次,就是我开始游戏的时候,设置frameRate为0。
    • 对不起,还没有——我已经写了一些会强调绘图的代码,在调试时,我发现当 frameCounter 为 1 时,FPS = ~60,但是当 frameCounter = 0 时, FPS = 0。
    • 通过将所有内容移到 Draw 方法中,每当我添加更多绘图数据(大量数据)时,它会从 60 变为 30 到 20 到 15 到 12...
    【解决方案4】:

    更新每个 Draw() 的简单方法是:

    frameRate = 1 / gameTime.ElapsedGameTime.TotalSeconds;
    

    如果你愿意,你也可以把它放到 Update() 方法中,看看它的触发频率。

    如果你想减慢它的更新速度……

    frameRate += (((1 / gameTime.ElapsedGameTime.TotalSeconds) - frameRate) * 0.1);
    

    【讨论】:

    • 不知道为什么你的答案这么低,这段代码“简单有效”,正是我需要的:)
    【解决方案5】:

    您可能尝试在每次绘制操作中更新 FPS。这将是一个非常不稳定的值,因为有时您需要更多,有时需要更少。为了使值更稳定 - 从许多值中取平均值。

    计算 FPS 最简单的方法可能是计算图纸。并具有基于计时器的功能,让我们每秒说一下这个值,计算新的 fps 并清除计数器。延长此计时器(例如 2 秒),或者简单地获取最后 10 fps 的值并显示平均值。

    在我看到的一些游戏中,它们还计算重绘场景所需的最长时间。这也可能是一个有趣的值。为此,您可以测量绘制函数需要多长时间:在它的开始和结束时注册时间,差异就是时间。

    注意,如果启用同步,绘制操作会延迟到下一次同步,这可能是结果奇怪的原因?

    【讨论】:

    • 我明确尝试更频繁地更新我的 FPS 计数器 - 一秒钟太长了。如果它只是每秒更新一次,我就可以正常工作 - 我试图让它每 10 毫秒更新一次。
    猜你喜欢
    • 2015-07-07
    • 1970-01-01
    • 2019-04-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-01
    • 2021-10-06
    相关资源
    最近更新 更多