完成帧率控制
This commit is contained in:
@@ -32,6 +32,7 @@ find_package(spdlog REQUIRED)
|
|||||||
add_executable(${TARGET}
|
add_executable(${TARGET}
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/engine/core/game_app.cpp
|
src/engine/core/game_app.cpp
|
||||||
|
src/engine/core/time.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# 链接库
|
# 链接库
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
#include "game_app.h"
|
#include "game_app.h"
|
||||||
|
#include "time.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
namespace engine::core {
|
namespace engine::core {
|
||||||
|
|
||||||
GameApp::GameApp() = default;
|
GameApp::GameApp() {
|
||||||
|
time_ = std::make_unique<Time>();
|
||||||
|
}
|
||||||
|
|
||||||
GameApp::~GameApp() {
|
GameApp::~GameApp() {
|
||||||
if (is_running_) {
|
if (is_running_) {
|
||||||
@@ -18,12 +21,16 @@ void GameApp::run() {
|
|||||||
spdlog::error("初始化失败,无法运行游戏。");
|
spdlog::error("初始化失败,无法运行游戏。");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
time_->setTargetFps(144); // 设置目标帧率(临时,未来会从配置文件读取)
|
||||||
while (is_running_) {
|
while (is_running_) {
|
||||||
float delta_time = 0.01f; // 每帧的时间间隔(临时设定)
|
time_->update();
|
||||||
|
float delta_time = time_->getDeltaTime();
|
||||||
|
|
||||||
handleEvents();
|
handleEvents();
|
||||||
update(delta_time);
|
update(delta_time);
|
||||||
render();
|
render();
|
||||||
|
|
||||||
|
// spdlog::info("delta_time: {}", delta_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
close();
|
close();
|
||||||
|
|||||||
@@ -1,20 +1,25 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
// 前向声明, 减少头文件的依赖,增加编译速度
|
// 前向声明, 减少头文件的依赖,增加编译速度
|
||||||
struct SDL_Window;
|
struct SDL_Window;
|
||||||
struct SDL_Renderer;
|
struct SDL_Renderer;
|
||||||
|
|
||||||
namespace engine::core {
|
namespace engine::core { // 命名空间的最佳实践:与文件路径一致
|
||||||
|
class Time;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 主游戏应用程序类,初始化SDL,管理游戏循环。
|
* @brief 主游戏应用程序类,初始化SDL,管理游戏循环。
|
||||||
*/
|
*/
|
||||||
class GameApp final {
|
class GameApp final { // final 表示该类不能被继承
|
||||||
private:
|
private:
|
||||||
SDL_Window* window_ = nullptr;
|
SDL_Window* window_ = nullptr;
|
||||||
SDL_Renderer* sdl_renderer_ = nullptr;
|
SDL_Renderer* sdl_renderer_ = nullptr;
|
||||||
bool is_running_ = false;
|
bool is_running_ = false;
|
||||||
|
|
||||||
|
// 引擎组件
|
||||||
|
std::unique_ptr<engine::core::Time> time_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GameApp();
|
GameApp();
|
||||||
~GameApp();
|
~GameApp();
|
||||||
@@ -31,7 +36,7 @@ public:
|
|||||||
GameApp& operator=(GameApp&&) = delete;
|
GameApp& operator=(GameApp&&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
[[nodiscard]] bool init();
|
[[nodiscard]] bool init(); // nodiscard 表示该函数返回值不应该被忽略
|
||||||
void handleEvents();
|
void handleEvents();
|
||||||
void update(float delta_time);
|
void update(float delta_time);
|
||||||
void render();
|
void render();
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user