3

我正在设计一个仅在预定义时间后执行操作的时间类,但我仍然无法弄清楚重置时间的方法。无论如何将时间重置为零或暂时停止并恢复时间?

所以,我的目标是在每次条件 A 遇到时重置时间,这样它就不会搞砸 delayTime 函数,它仍然保持以前的时间并最终导致错误的时间计算。

if ( condition A )
{
    if ( time.delayTime( 5.0f ) )
    {           
        doActionA();
    }
}

时间类.h

#ifndef _TIMECLASS_H_
#define _TIMECLASS_H_

#include <windows.h>


class TimeClass
{
public:
    TimeClass();
    ~TimeClass();
    bool delayTime( float second );
    float getSecond();
    void reset();

private:
    float getGameTime();

    float currentTime;
    UINT64 ticks;
    float time;
    float  timeAtGameStart;
    UINT64 ticksPerSecond;
};

#endif

时间类.cpp

#include "TimeClass.h"


TimeClass::TimeClass()
{
    // We need to know how often the clock is updated
    if( !QueryPerformanceFrequency((LARGE_INTEGER *)&ticksPerSecond) )
        ticksPerSecond = 1000;

    // If timeAtGameStart is 0 then we get the time since
    // the start of the computer when we call GetGameTime()
    timeAtGameStart = 0;
    timeAtGameStart = getGameTime();
}


float TimeClass::getGameTime()
{
    // This is the number of clock ticks since start
    if( !QueryPerformanceCounter((LARGE_INTEGER *)&ticks) )
        ticks = (UINT64)timeGetTime();

    // Divide by frequency to get the time in seconds
    time = (float)(__int64)ticks/(float)(__int64)ticksPerSecond;

    // Subtract the time at game start to get
    // the time since the game started
    time -= timeAtGameStart;

    return time;
}


bool TimeClass::delayTime( float second )
{
    currentTime = getGameTime();
    static float totalTime = second + getGameTime();

    if ( currentTime >= totalTime )
    {
        totalTime = second + getGameTime();
        return true;
    }
    else
    {
        return false;
    }
}


float TimeClass::getSecond()
{
    currentTime = getGameTime();
    static float totalTime = 1 + getGameTime();

    if ( currentTime >= totalTime )
    {
        totalTime = 1 + getGameTime();
        return currentTime;
    }
    else
    {
        return currentTime;
    }
}


void TimeClass::reset()
{
    timeAtGameStart = 0;
    timeAtGameStart = getGameTime();
}


TimeClass::~TimeClass()
{
}
4

2 回答 2

2

标准库

如前所述,只需使用std::chrono::high_resolution_clock。如果您使用 Visual C++ 编译器,则会有关于 high_resolution_clock 分辨率的实际错误报告,它与QueryPerformanceCounter不同。

例子

auto t1 = std::chrono::high_resolution_clock::now();
// Do something
auto t2 = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::duration<double>>(t2 - t1);
double dt = duration.count();

时间类

您现在面临的主要问题是您使用的是静态变量。您正在寻找一种从延迟时间以外的函数重置静态变量的方法。如果您要引入成员变量以摆脱静态变量,这会使事情变得容易得多(注意:函数中的静态变量是不好的做法,因为它们保持状态=> 对多线程不利)。那么这样的事情呢:

class Event {
    std::chrono::high_resolution_clock::time_point lastEventOccurred;
    double timeBetweenEvents;
public:
    Event(double timeBetweenEvents)
       : timeBetweenEvents(timeBetweenEvents)
       , lastEventOccurred(std::chrono::high_resolution_clock::now()) { }

    bool ready() {
       auto currentTime = std::chrono::high_resolution_clock::now();
       auto elapsedTime = std::chrono::duration_cast<std::chrono::duration<double>>(currentTime - lastEventOccurred).count();

       if(elapsedTime > timeBetweenEvents) {
           reset();
           return true;
       } else
           return false;
    }

    void reset() {
       lastEventOccurred = std::chrono::high_resolution_clock::now();
    }
};

用法:

Event evtDelay = Event(5.0);

if(condition A) {
    if(evtDelay.ready()) {
        doActionA();
    }
} else if(condition B) {
    evtDelay.reset();
}

另一种方法是使用捕获状态和更新方法。正如我所看到的,您正在将其用于游戏,因此可以在游戏的更新方法中完成更新。假设事件将具有类似 {Active, Inactive} 的状态和经过时间的成员,每次调用更新时,如果状态为活动,则添加自上次更新调用以来经过的时间。因此,就绪函数只会检查经过的时间是否大于定义的阈值。

class Event {
    std::chrono::high_resolution_clock::time_point lastUpdateCall;
    double timeBetweenEvents;
    double elapsedTime;
    bool active;
public:
    Event(double timeBetweenEvents)
       : timeBetweenEvents(timeBetweenEvents)
       , lastUpdateCall(std::chrono::high_resolution_clock::now()) 
       , elapsedTime(0.0) 
       , active(true) { }

    bool ready() {
       return elapsedTime >= timeBetweenEvents;
    }

    void reset() {
       elapsedTime = 0.0;
    }

    void setActive(bool state) { 
       active = state; 
    }

    void update() {
       auto currentTime = std::chrono::high_resolution_clock::now();

       if(active) {
          auto dt = std::chrono::duration_cast<std::chrono::duration<double>>(currentTime - lastUpdateCall).count();
          elapsedTime += dt;
       }

       lastUpdateCall = currentTime;
    }
};
于 2013-10-04T12:38:46.890 回答
1

从您的问题来看,您不太清楚您到底想要实现什么:时钟重置、暂停/恢复、间隔测量或定时触发,或所有这些。所以我将描述所有这些。=)

只是为了说清楚

  • 不要在成员函数(方法)中使用静态变量,这会使您的代码更难理解且容易出错。而是使用类成员变量(字段)
  • 不要以(毫秒)秒float格式存储时间。我的精度太低了。甚至double不推荐。相反,将其存储为 API 原生格式:某种 CPU 滴答声:让我们将其包装并仅在用户请求时struct time_point转换为:float

    struct time_point
    {
        LARGE_INTEGER value;
    };
    

您需要从底层 API 获得的功能是:

  • 获取当前时间的函数:我们称之为

    private: 
         time_point now(); // Here you wrap QueryPerformanceCounter() or `boost::chrono::...::now();` stuff
    
  • 重新解释两个可用单位之间的差异(持续时间)的功能time_point(例如long long(毫秒)的纳秒float

    private:
          float Duration(const time_point& from, const time_point& to); // Here you divide by frequence
    

(另外,你可以创建time_point类并使这个函数成为成员)

所有其他功能都可以与 API 无关。

按照这种设计,您可以从用户中抽象出底层 API,并使您的应用程序更具可移植性。

重置

无论如何要将时间重置为零

但是,等等,您已经实现了“将时间重置为零”:

void TimeClass::reset()
{
    //timeAtGameStart = 0;
    timeAtGameStart = getGameTime();
}

timeAtGameStart = 0;评论为不需要)

你只是说那start是,所以和之间的now()差异变为零。startnow()

停止和恢复

还是暂时停止和恢复时间?

您可以通过以下方式实现“时间停止/恢复”效果:

  • 添加bool isPaused成员变量(不要忘记false在构造函数中将其初始化)
  • 添加timePaused成员变量(您的时间单位类型:time_point在我的情况下)
  • 改变你的getGameTime()功能:暂停时它将返回timePaused而不是当前时间
  • 当然还有介绍Pause()/Resume()功能

这是示例代码(命名与您的略有不同)

    void Pause()
    {
        if (!isPaused)
        {
            timePaused = clock::now();
            isPaused = true;
        }
    }

    void Resume()
    {
        if (isPaused)
        {
            isPaused = false;
            start -= clock::now() - timePaused;
        }
    }

    float Now() const 
    { 
        if (isPaused)
        {
            return Duration(start, timePaused);
        }
        return Duration(start, clock::now()); // basically it is your getGameTime()
    }

Delta(基本间隔测量)

有时您会想知道程序中某些地方之间经过了多少时间。例如,要测量游戏中的 FPS,您需要知道帧之间的增量时间。为了实现这一点,我们需要成员:

    time_point prevTick;    // time of previous tick
    time_point currTick;    // time of current tick

            void Tick() // user must call it each frame
    { 
        prevTick = currTick;
        currTick = clock::now();
    }

    const float GetDelta() const  // user must call it to know frame time
    { 
        return Duration(prevTick, currTick);
    }

秒表(高级间隔测量)

现在,我们需要某种定时触发器。将此触发器与计时器分开是个好主意,因此:

  • 您可以有多个触发器,具有不同的开始时间和不同的触发间隔
  • 所有这些触发器必须相互独立
  • 全局时钟不会改变,因此所有触发器保持有效

所以让我们把它包装到类中:

class Stopwatch
{
private:
    float m_Interval;
    float m_Time;
    float m_PrevTime;
public:
    Stopwatch(float interval_ms = 1000.0f) :
        m_Interval(interval_ms),
        m_Time(0.0f),
        m_PrevTime(m_Time)
    {}

    float GetRefreshInterval() const { return m_Interval; }
    void SetRefreshInterval(float interval_ms) { m_Interval = interval_ms; }

    // Update as frequent as possible
    inline void Update(float dt)
    {
        m_Time += dt;
    }

    // Check if time interval reached
    inline bool IsAboutTime()
    {
        if (m_Time >= m_Interval)
        {
            m_PrevTime = m_Time;
            m_Time = 0.0f;
            return true;
        }
        return false;
    }

    // exact value of last interval
    inline float GetLastTime() const
    {
        return m_PrevTime;
    }
};

使用示例:

Stopwatch stopwatch(5000.0f); // will trigger each 5 seconds 
...

stopwatch.Update(timer.GetDelta()); // GetDelta() returns time difference between update frames
if ( condition A )
{
    if ( stopwatch.IsAboutTime() )
    {           
        doActionA();
    }
}

设计秒表的另一种可能方法是:

  • 继承自clock
  • 通过当前时间,IsAboutTime()所以不需要Update()

快乐编码!=)

于 2013-10-05T09:02:27.363 回答