完成帧率控制

This commit is contained in:
Ziyu
2025-06-16 14:44:47 +08:00
parent 3bef8e84ba
commit d2eb4a625d
5 changed files with 186 additions and 6 deletions
+10 -3
View File
@@ -1,10 +1,13 @@
#include "game_app.h"
#include "time.h"
#include <SDL3/SDL.h>
#include <spdlog/spdlog.h>
namespace engine::core {
GameApp::GameApp() = default;
GameApp::GameApp() {
time_ = std::make_unique<Time>();
}
GameApp::~GameApp() {
if (is_running_) {
@@ -18,12 +21,16 @@ void GameApp::run() {
spdlog::error("初始化失败,无法运行游戏。");
return;
}
time_->setTargetFps(144); // 设置目标帧率(临时,未来会从配置文件读取)
while (is_running_) {
float delta_time = 0.01f; // 每帧的时间间隔(临时设定)
time_->update();
float delta_time = time_->getDeltaTime();
handleEvents();
update(delta_time);
render();
// spdlog::info("delta_time: {}", delta_time);
}
close();
+8 -3
View File
@@ -1,20 +1,25 @@
#pragma once
#include <memory>
// 前向声明, 减少头文件的依赖,增加编译速度
struct SDL_Window;
struct SDL_Renderer;
namespace engine::core {
namespace engine::core { // 命名空间的最佳实践:与文件路径一致
class Time;
/**
* @brief 主游戏应用程序类,初始化SDL,管理游戏循环。
*/
class GameApp final {
class GameApp final { // final 表示该类不能被继承
private:
SDL_Window* window_ = nullptr;
SDL_Renderer* sdl_renderer_ = nullptr;
bool is_running_ = false;
// 引擎组件
std::unique_ptr<engine::core::Time> time_;
public:
GameApp();
~GameApp();
@@ -31,7 +36,7 @@ public:
GameApp& operator=(GameApp&&) = delete;
private:
[[nodiscard]] bool init();
[[nodiscard]] bool init(); // nodiscard 表示该函数返回值不应该被忽略
void handleEvents();
void update(float delta_time);
void render();
+78
View File
@@ -0,0 +1,78 @@
#include "time.h"
#include <spdlog/spdlog.h>
#include <SDL3/SDL_timer.h> // 用于 SDL_GetTicksNS()
namespace engine::core {
Time::Time() {
// 初始化 last_time_ 和 frame_start_time_ 为当前时间,避免第一帧 DeltaTime 过大
last_time_ = SDL_GetTicksNS();
frame_start_time_ = last_time_;
spdlog::trace("Time 初始化。Last time: {}", last_time_);
}
void Time::update() {
frame_start_time_ = SDL_GetTicksNS(); // 记录进入 update 时的时间戳
auto current_delta_time = static_cast<double>(frame_start_time_ - last_time_) / 1000000000.0;
if (target_frame_time_ > 0.0){ // 如果设置了目标帧率,则限制帧率;否则delta_time_ = current_delta_time
limitFrameRate(current_delta_time);
} else {
delta_time_ = current_delta_time;
}
last_time_ = SDL_GetTicksNS(); // 记录离开 update 时的时间戳
}
void Time::limitFrameRate(float current_delta_time) {
// 如果当前帧耗费的时间小于目标帧时间,则等待剩余时间
if (current_delta_time < target_frame_time_) {
double time_to_wait = target_frame_time_ - current_delta_time;
Uint64 ns_to_wait = static_cast<Uint64>(time_to_wait * 1000000000.0);
SDL_DelayNS(ns_to_wait);
delta_time_ = static_cast<double>(SDL_GetTicksNS() - last_time_) / 1000000000.0;
}
}
float Time::getDeltaTime() const {
return delta_time_ * time_scale_;
}
float Time::getUnscaledDeltaTime() const {
return delta_time_;
}
void Time::setTimeScale(float scale) {
if (scale < 0.0) {
spdlog::warn("Time scale 不能为负。Clamping to 0.");
scale = 0.0; // 防止负时间缩放
}
time_scale_ = scale;
}
float Time::getTimeScale() const {
return time_scale_;
}
void Time::setTargetFps(int fps) {
if (fps < 0) {
spdlog::warn("Target FPS 不能为负。Setting to 0 (unlimited).");
target_fps_ = 0;
} else {
target_fps_ = fps;
}
if (target_fps_ > 0) {
target_frame_time_ = 1.0 / static_cast<double>(target_fps_);
spdlog::info("Target FPS 设置为: {} (Frame time: {:.6f}s)", target_fps_, target_frame_time_);
} else {
target_frame_time_ = 0.0;
spdlog::info("Target FPS 设置为: Unlimited");
}
}
int Time::getTargetFps() const {
return target_fps_;
}
} // namespace engine::core
+89
View File
@@ -0,0 +1,89 @@
#pragma once
#include <SDL3/SDL_stdinc.h> // 用于 Uint64
namespace engine::core {
/**
* @brief 管理游戏循环中的时间,计算帧间时间差 (DeltaTime)。
*
* 使用 SDL 的高精度性能计数器来确保时间测量的准确性。
* 提供获取缩放和未缩放 DeltaTime 的方法,以及设置时间缩放因子的能力。
*/
class Time final{
private:
Uint64 last_time_ = 0; ///< @brief 上一帧的时间戳 (用于计算 delta)
Uint64 frame_start_time_ = 0; ///< @brief 当前帧开始的时间戳 (用于帧率限制)
double delta_time_ = 0.0; ///< @brief 未缩放的帧间时间差 (秒)
double time_scale_ = 1.0; ///< @brief 时间缩放因子
// 帧率限制相关
int target_fps_ = 0; ///< @brief 目标 FPS (0 表示不限制)
double target_frame_time_ = 0.0; ///< @brief 目标每帧时间 (秒)
public:
Time();
// 简单起见,直接删除拷贝、移动和赋值运算符
Time(const Time&) = delete;
Time& operator=(const Time&) = delete;
Time(Time&&) = delete;
Time& operator=(Time&&) = delete;
/**
* @brief 每帧开始时调用,更新内部时间状态并计算 DeltaTime。
*/
void update();
/**
* @brief 获取经过时间缩放调整后的帧间时间差 (DeltaTime)。
*
* @return double 缩放后的 DeltaTime (秒)。
*/
float getDeltaTime() const;
/**
* @brief 获取未经过时间缩放的原始帧间时间差。
*
* @return double 未缩放的 DeltaTime (秒)。
*/
float getUnscaledDeltaTime() const;
/**
* @brief 设置时间缩放因子。
*
* @param scale 时间缩放值。1.0 为正常速度,< 1.0 为慢动作,> 1.0 为快进。
* 不允许负值。
*/
void setTimeScale(float scale);
/**
* @brief 获取当前的时间缩放因子。
*
* @return float 当前的时间缩放因子。
*/
float getTimeScale() const;
/**
* @brief 设置目标帧率。
*
* @param fps 目标每秒帧数。设置为 0 表示不限制帧率。负值将被视为 0。
*/
void setTargetFps(int fps);
/**
* @brief 获取当前设置的目标帧率。
*
* @return int 目标 FPS,0 表示不限制。
*/
int getTargetFps() const;
private:
/**
* @brief update 中调用,用于限制帧率。如果设置了 target_fps_ > 0,且当前帧执行时间小于目标帧时间,则会调用 SDL_DelayNS() 来等待剩余时间。
*
* @param current_delta_time 当前帧的执行时间(秒)
*/
void limitFrameRate(float current_delta_time);
};
} // namespace engine::core