XNA/MonoGame: получение кадров в секунду

Я пытаюсь получить текущую FPS моей игры, однако я могу только найти методы, которые обновляют переменную FPS каждую секунду. Например. https://github.com/CartBlanche/MonoGame-Samples/blob/master/Draw2D/FPSCounterComponent.cs и http://www.david-amador.com/2009/11/how-to-do-a-xna-fps-counter/

Есть ли способ постоянно обновлять метку FPS?

Ответ 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();

И вызовите метод Update в методе Game Draw и нарисуйте метку, как вам нравится.

    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
    }

Наслаждайтесь!:)

Ответ 2

Вы можете получить текущую частоту кадров в любой момент, используя следующую формулу:

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

Оба других метода, представленные ниже, дают вам измененную частоту кадров, которая должна быть более гладкой и иметь меньше колебаний в возвращаемом значении

Это работает, взвешивая все предыдущие временные рамки в логарифмическом масштабе. Мне не понравилась идея использовать среднее значение, чтобы получить показатель производительности игры, поскольку framedrops не отображаются хорошо (или вообще, если у вас высокий средний уровень), а очень низкие/очень высокие частоты кадров имеют значительно разные уровни точности, если они работают в одном и том же средстве.

Чтобы решить эту проблему, я создал класс 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;
}

Я не тестировал код, но вы должны получить эту идею.

Ответ 4

Вероятно, вы пытаетесь обновить FPS в каждой операции ничьей. Это будет очень неустойчивое значение, поскольку иногда вам нужно больше, а иногда и меньше. Чтобы сделать значение более стабильным - выведите среднее значение из многих значений.

Самый простой способ подсчета FPS - это, вероятно, подсчет рисунков. И есть функция тайм-багов, которая принимает это значение, скажем каждую секунду, вычисляет новый fps и очищает счетчик. Сделайте этот таймер более длинным (например, 2 сек) или просто возьмите последние 10 кадров в секунду и отобразите среднее значение.

В некоторых играх я видел, что они также подсчитывают максимальное время, необходимое для перерисовки сцены. Это тоже может быть интересным. Для этого вы можете измерить, как долго выполняется функция draw: зарегистрируйте время при начале и в конце, разница будет временем.

Обратите внимание, что если вы включите синхронизацию, вы отложите операцию рисования до следующей синхронизации, возможно, это причина странных результатов?

Ответ 5

Простым методом, который обновляет каждый Draw(), будет:

frameRate = 1 / gameTime.ElapsedGameTime.TotalSeconds;

Вы также можете отказаться от этого метода Update(), чтобы узнать, как часто это срабатывает, если хотите.

Если вы хотите замедлить скорость обновления...

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