Как правильно рассчитать FPS в XNA? - PullRequest
13 голосов
/ 25 марта 2010

Я написал компонент для отображения текущего 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);
    }

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

       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) И все это происходит только с кодом потока?
Да

Ответы [ 6 ]

12 голосов
/ 25 марта 2010

У Шона Харгривза есть отличный пост об этом здесь . Первое отличие, которое я вижу между его и вашим кодом, заключается в том, что вы каждый раз сбрасываете свой elapseTime на 0, что теряет некоторое время, тогда как Шон просто вычитает 1 секунду из своего elapsedTime. Кроме того, Шон использует ElapsedGameTime вместо ElapsedRealTime. Он также обновляет свой frameCounter в функции Draw вместо функции Update.

Что касается того, почему он использует ElapsedRealTime, он объясняет это в комментарии после поста:

> Конечно 1 / gameTime.ElapsedRealTime.TotalSeconds

> поэтому даст текущую частоту кадров.

Это скажет вам, как долго это было с момента предыдущего вызова Update, но это не то же самое, что ваш битопоток!

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

б) Время для одного обновления может колеблется в широких пределах, поэтому фигура вас выбраться из этого будет слишком мерцающим быть легко читаемым.

Я бы попробовал его компонент и посмотрел, работает ли он для вас. Пост довольно старый, и я думаю, вам придется изменить LoadGraphicsContent на LoadContent и UnloadGraphicsContent на UnloadContent, как указывает другой комментарий.

2 голосов
/ 26 октября 2011

Вот как я это делаю и этим методом:

  • Вы в среднем по 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 или тот из Update Draw).

ElapsedRealTime - время с момента начала игры. Из-за этого он увеличивается линейно, так как игра продолжает работать. Действительно, через 1 секунду вы обновите каждый кадр, потому что ваша логика выглядела так:

(Предположим, вы использовали 4 кадра в секунду для простоты объяснения):

  • Кадр 1: ElapsedRealTime: 0,25. Текущий итог: 0,25
  • Кадр 2: ElapsedRealTime: 0,5 Итого сейчас: 0,75
  • Кадр 3: ElapsedRealTime: 0,75 Промежуточный итог сейчас: 1,5
  • Итоговое значение больше 1 !!! Показать FPS!
  • Set Промежуточный итог = 0
  • Кадр 4: ElapsedRealTime: 1,00 Промежуточный итог сейчас: 1,0
  • Итоговое значение больше 1 !!! Показать FPS!

Теперь, когда вы исправили счетчик, теперь вы должны получать только Elapsed Game Time с постоянными 0,25, поэтому прогрессия теперь движется:

  • Кадр 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!
  • Set Промежуточный итог = 0

Что вы ожидаете? Короче говоря, теперь, когда вы исправили первую проблему, вы должны были исправить и вторую, и «почему» объясняется выше.

1 голос
/ 25 марта 2010

В качестве отступления ... вам следует избегать установки приоритета потока. Присвоив наивысший приоритет потока тому, что должно быть фоновым потоком, вы можете в конечном итоге лишить основной поток времени процессора, поскольку планировщик будет отдавать приоритет threadPath

0 голосов
/ 29 января 2016

Привет, ребята, если вы хотите показать свою реальную частоту кадров, вы должны реализовать счетчик частоты кадров для метода Draw, потому что XNA делает это так: «Если ваш компьютер не может обслуживать метод Update, он приостанавливает Draw метод и вместо этого он обслуживает метод Update»

0 голосов
/ 12 июля 2013

Вы должны запустить свой счетчик кадров в секунду при методе розыгрыша

0 голосов
/ 02 апреля 2010

Вы активно проверяете, изменяется ли IsRunningSlowly? Даже если IsFixedTimeStep имеет значение true, если ваша программа не может выполнить столько обновлений, сколько она ожидает, она будет вызывать ее чаще.

Способ, которым я раньше смягчал это, - это непосредственный вызов ResetElapsedTime () вместо того, чтобы отслеживать его самостоятельно.

Не уверен, что это сработает для вас. Я заметил, что когда я отлаживал предыдущую проблему, я не вызывал дополнительные обновления, возможно, «функцию» при отладке.

...