完成配置载入类Config
This commit is contained in:
@@ -33,6 +33,7 @@ add_executable(${TARGET}
|
||||
src/main.cpp
|
||||
src/engine/core/game_app.cpp
|
||||
src/engine/core/time.cpp
|
||||
src/engine/core/config.cpp
|
||||
src/engine/resource/resource_manager.cpp
|
||||
src/engine/resource/texture_manager.cpp
|
||||
src/engine/resource/audio_manager.cpp
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"window": {
|
||||
"title": "SunnyLand",
|
||||
"width": 1280,
|
||||
"height": 720,
|
||||
"resizable": true
|
||||
},
|
||||
"graphics": {
|
||||
"vsync": true
|
||||
},
|
||||
"performance": {
|
||||
"target_fps": 144
|
||||
},
|
||||
"audio": {
|
||||
"music_volume": 0.5,
|
||||
"sound_volume": 0.5
|
||||
},
|
||||
"input_mappings": {
|
||||
"pause": [
|
||||
"P",
|
||||
"Escape"
|
||||
],
|
||||
"move_down": [
|
||||
"S",
|
||||
"Down"
|
||||
],
|
||||
"jump": [
|
||||
"J",
|
||||
"Space"
|
||||
],
|
||||
"move_up": [
|
||||
"W",
|
||||
"Up"
|
||||
],
|
||||
"move_right": [
|
||||
"D",
|
||||
"Right"
|
||||
],
|
||||
"attack": [
|
||||
"K",
|
||||
"MouseLeft"
|
||||
],
|
||||
"move_left": [
|
||||
"A",
|
||||
"Left"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
#include "config.h"
|
||||
#include <fstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
namespace engine::core {
|
||||
|
||||
Config::Config(const std::string &filepath)
|
||||
{
|
||||
loadFromFile(filepath);
|
||||
}
|
||||
|
||||
bool Config::loadFromFile(const std::string& filepath) {
|
||||
std::ifstream file(filepath);
|
||||
if (!file.is_open()) {
|
||||
spdlog::warn("配置文件 '{}' 未找到。使用默认设置并创建默认配置文件。", filepath);
|
||||
if (!saveToFile(filepath)) {
|
||||
spdlog::error("无法创建默认配置文件 '{}'。", filepath);
|
||||
return false;
|
||||
}
|
||||
return false; // 文件不存在,使用默认值
|
||||
}
|
||||
|
||||
try {
|
||||
nlohmann::json j;
|
||||
file >> j;
|
||||
fromJson(j);
|
||||
spdlog::info("成功从 '{}' 加载配置。", filepath);
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("读取配置文件 '{}' 时出错:{}。使用默认设置。", filepath, e.what());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Config::saveToFile(const std::string& filepath) {
|
||||
std::ofstream file(filepath);
|
||||
if (!file.is_open()) {
|
||||
spdlog::error("无法打开配置文件 '{}' 进行写入。", filepath);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
nlohmann::ordered_json j = toJson();
|
||||
file << j.dump(4);
|
||||
spdlog::info("成功将配置保存到 '{}'。", filepath);
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("写入配置文件 '{}' 时出错:{}", filepath, e.what());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Config::fromJson(const nlohmann::json& j) {
|
||||
if (j.contains("window")) {
|
||||
const auto& window_config = j["window"];
|
||||
window_title_ = window_config.value("title", window_title_);
|
||||
window_width_ = window_config.value("width", window_width_);
|
||||
window_height_ = window_config.value("height", window_height_);
|
||||
window_resizable_ = window_config.value("resizable", window_resizable_);
|
||||
}
|
||||
if (j.contains("graphics")) {
|
||||
const auto& graphics_config = j["graphics"];
|
||||
vsync_enabled_ = graphics_config.value("vsync", vsync_enabled_);
|
||||
}
|
||||
if (j.contains("performance")) {
|
||||
const auto& perf_config = j["performance"];
|
||||
target_fps_ = perf_config.value("target_fps", target_fps_);
|
||||
if (target_fps_ < 0) {
|
||||
spdlog::warn("目标 FPS 不能为负数。设置为 0(无限制)。");
|
||||
target_fps_ = 0;
|
||||
}
|
||||
}
|
||||
if (j.contains("audio")) {
|
||||
const auto& audio_config = j["audio"];
|
||||
music_volume_ = audio_config.value("music_volume", music_volume_);
|
||||
sound_volume_ = audio_config.value("sound_volume", sound_volume_);
|
||||
}
|
||||
|
||||
// 从 JSON 加载 input_mappings
|
||||
if (j.contains("input_mappings") && j["input_mappings"].is_object()) {
|
||||
const auto& mappings_json = j["input_mappings"];
|
||||
try {
|
||||
// 直接尝试从 JSON 对象转换为 map<string, vector<string>>
|
||||
auto input_mappings = mappings_json.get<std::unordered_map<std::string, std::vector<std::string>>>();
|
||||
// 如果成功,则将 input_mappings 移动到 input_mappings_
|
||||
input_mappings_ = std::move(input_mappings);
|
||||
spdlog::trace("成功从配置加载输入映射。");
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::warn("配置加载警告:解析 'input_mappings' 时发生异常。使用默认映射。错误:{}", e.what());
|
||||
}
|
||||
} else {
|
||||
spdlog::trace("配置跟踪:未找到 'input_mappings' 部分或不是对象。使用头文件中定义的默认映射。");
|
||||
}
|
||||
}
|
||||
|
||||
nlohmann::ordered_json Config::toJson() const {
|
||||
return nlohmann::ordered_json{
|
||||
{"window", {
|
||||
{"title", window_title_},
|
||||
{"width", window_width_},
|
||||
{"height", window_height_},
|
||||
{"resizable", window_resizable_}
|
||||
}},
|
||||
{"graphics", {
|
||||
{"vsync", vsync_enabled_}
|
||||
}},
|
||||
{"performance", {
|
||||
{"target_fps", target_fps_}
|
||||
}},
|
||||
{"audio", {
|
||||
{"music_volume", music_volume_},
|
||||
{"sound_volume", sound_volume_}
|
||||
}},
|
||||
{"input_mappings", input_mappings_}
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace engine::core
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <nlohmann/json_fwd.hpp> // nlohmann_json 提供的前向声明
|
||||
|
||||
namespace engine::core {
|
||||
|
||||
/**
|
||||
* @brief 管理应用程序的配置设置。
|
||||
*
|
||||
* 提供配置项的默认值,并支持从 JSON 文件加载/保存配置。
|
||||
* 如果加载失败或文件不存在,将使用默认值。
|
||||
*/
|
||||
class Config final {
|
||||
public:
|
||||
// --- 默认配置值 --- (为了方便拓展,全部设置为公有)
|
||||
// 窗口设置
|
||||
std::string window_title_ = "SunnyLand";
|
||||
int window_width_ = 1280;
|
||||
int window_height_ = 720;
|
||||
bool window_resizable_ = true;
|
||||
|
||||
// 图形设置
|
||||
bool vsync_enabled_ = true; ///< @brief 是否启用垂直同步
|
||||
|
||||
// 性能设置
|
||||
int target_fps_ = 144; ///< @brief 目标 FPS 设置,0 表示不限制
|
||||
|
||||
// 音频设置
|
||||
float music_volume_ = 0.5f;
|
||||
float sound_volume_ = 0.5f;
|
||||
|
||||
// 存储动作名称到 SDL Scancode 名称列表的映射
|
||||
std::unordered_map<std::string, std::vector<std::string>> input_mappings_ = {
|
||||
// 提供一些合理的默认值,以防配置文件加载失败或缺少此部分
|
||||
{"move_left", {"A", "Left"}},
|
||||
{"move_right", {"D", "Right"}},
|
||||
{"move_up", {"W", "Up"}},
|
||||
{"move_down", {"S", "Down"}},
|
||||
{"jump", {"J", "Space"}},
|
||||
{"attack", {"K", "MouseLeft"}},
|
||||
{"pause", {"P", "Escape"}},
|
||||
// 可以继续添加更多默认动作
|
||||
};
|
||||
|
||||
explicit Config(const std::string& filepath); ///< @brief 构造函数,指定配置文件路径。
|
||||
|
||||
// 删除拷贝和移动语义
|
||||
Config(const Config&) = delete;
|
||||
Config& operator=(const Config&) = delete;
|
||||
Config(Config&&) = delete;
|
||||
Config& operator=(Config&&) = delete;
|
||||
|
||||
bool loadFromFile(const std::string& filepath); ///< @brief 从指定的 JSON 文件加载配置。成功返回 true,否则返回 false。
|
||||
[[nodiscard]] bool saveToFile(const std::string& filepath); ///< @brief 将当前配置保存到指定的 JSON 文件。成功返回 true,否则返回 false。
|
||||
|
||||
private:
|
||||
void fromJson(const nlohmann::json& j); ///< @brief 从 JSON 对象反序列化配置。
|
||||
nlohmann::ordered_json toJson() const; ///< @brief 将当前配置转换为 JSON 对象(按顺序)。
|
||||
};
|
||||
|
||||
} // namespace engine::core
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "../resource/resource_manager.h"
|
||||
#include "../render/renderer.h"
|
||||
#include "../render/camera.h"
|
||||
#include "config.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
@@ -22,7 +23,7 @@ void GameApp::run() {
|
||||
spdlog::error("初始化失败,无法运行游戏。");
|
||||
return;
|
||||
}
|
||||
time_->setTargetFps(144); // 设置目标帧率(临时,未来会从配置文件读取)
|
||||
|
||||
while (is_running_) {
|
||||
time_->update();
|
||||
float delta_time = time_->getDeltaTime();
|
||||
@@ -39,6 +40,7 @@ void GameApp::run() {
|
||||
|
||||
bool GameApp::init() {
|
||||
spdlog::trace("初始化 GameApp ...");
|
||||
if (!initConfig()) return false;
|
||||
if (!initSDL()) return false;
|
||||
if (!initTime()) return false;
|
||||
if (!initResourceManager()) return false;
|
||||
@@ -96,13 +98,26 @@ void GameApp::close() {
|
||||
is_running_ = false;
|
||||
}
|
||||
|
||||
bool GameApp::initSDL() {
|
||||
bool GameApp::initConfig()
|
||||
{
|
||||
try {
|
||||
config_ = std::make_unique<engine::core::Config>("assets/config.json");
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("初始化配置失败: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
spdlog::trace("配置初始化成功。");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GameApp::initSDL()
|
||||
{
|
||||
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)) {
|
||||
spdlog::error("SDL 初始化失败! SDL错误: {}", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
window_ = SDL_CreateWindow("SunnyLand", 1280, 720, SDL_WINDOW_RESIZABLE);
|
||||
window_ = SDL_CreateWindow(config_->window_title_.c_str(), config_->window_width_, config_->window_height_, SDL_WINDOW_RESIZABLE);
|
||||
if (window_ == nullptr) {
|
||||
spdlog::error("无法创建窗口! SDL错误: {}", SDL_GetError());
|
||||
return false;
|
||||
@@ -114,8 +129,13 @@ bool GameApp::initSDL() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置逻辑分辨率
|
||||
SDL_SetRenderLogicalPresentation(sdl_renderer_, 640, 360, SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||
// 设置 VSync (注意: VSync 开启时,驱动程序会尝试将帧率限制到显示器刷新率,有可能会覆盖我们手动设置的 target_fps)
|
||||
int vsync_mode = config_->vsync_enabled_ ? SDL_RENDERER_VSYNC_ADAPTIVE : SDL_RENDERER_VSYNC_DISABLED;
|
||||
SDL_SetRenderVSync(sdl_renderer_, vsync_mode);
|
||||
spdlog::trace("VSync 设置为: {}", config_->vsync_enabled_ ? "Enabled" : "Disabled");
|
||||
|
||||
// 设置逻辑分辨率为窗口大小的一半(针对像素游戏)
|
||||
SDL_SetRenderLogicalPresentation(sdl_renderer_, config_->window_width_ / 2, config_->window_height_ / 2, SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||
spdlog::trace("SDL 初始化成功。");
|
||||
return true;
|
||||
}
|
||||
@@ -127,6 +147,7 @@ bool GameApp::initTime() {
|
||||
spdlog::error("初始化时间管理失败: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
time_->setTargetFps(config_->target_fps_);
|
||||
spdlog::trace("时间管理初始化成功。");
|
||||
return true;
|
||||
}
|
||||
@@ -155,7 +176,7 @@ bool GameApp::initRenderer() {
|
||||
|
||||
bool GameApp::initCamera() {
|
||||
try {
|
||||
camera_ = std::make_unique<engine::render::Camera>(glm::vec2(640, 360));
|
||||
camera_ = std::make_unique<engine::render::Camera>(glm::vec2(config_->window_width_ / 2, config_->window_height_ / 2));
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("初始化相机失败: {}", e.what());
|
||||
return false;
|
||||
|
||||
@@ -16,6 +16,7 @@ class Camera;
|
||||
|
||||
namespace engine::core { // 命名空间的最佳实践:与文件路径一致
|
||||
class Time;
|
||||
class Config;
|
||||
|
||||
/**
|
||||
* @brief 主游戏应用程序类,初始化SDL,管理游戏循环。
|
||||
@@ -31,7 +32,7 @@ private:
|
||||
std::unique_ptr<engine::resource::ResourceManager> resource_manager_;
|
||||
std::unique_ptr<engine::render::Renderer> renderer_;
|
||||
std::unique_ptr<engine::render::Camera> camera_;
|
||||
|
||||
std::unique_ptr<engine::core::Config> config_;
|
||||
public:
|
||||
GameApp();
|
||||
~GameApp();
|
||||
@@ -55,6 +56,7 @@ private:
|
||||
void close();
|
||||
|
||||
// 各模块的初始化/创建函数,在init()中调用
|
||||
[[nodiscard]] bool initConfig();
|
||||
[[nodiscard]] bool initSDL();
|
||||
[[nodiscard]] bool initTime();
|
||||
[[nodiscard]] bool initResourceManager();
|
||||
|
||||
Reference in New Issue
Block a user