完成配置载入类Config
This commit is contained in:
@@ -33,6 +33,7 @@ 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
|
src/engine/core/time.cpp
|
||||||
|
src/engine/core/config.cpp
|
||||||
src/engine/resource/resource_manager.cpp
|
src/engine/resource/resource_manager.cpp
|
||||||
src/engine/resource/texture_manager.cpp
|
src/engine/resource/texture_manager.cpp
|
||||||
src/engine/resource/audio_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 "../resource/resource_manager.h"
|
||||||
#include "../render/renderer.h"
|
#include "../render/renderer.h"
|
||||||
#include "../render/camera.h"
|
#include "../render/camera.h"
|
||||||
|
#include "config.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ void GameApp::run() {
|
|||||||
spdlog::error("初始化失败,无法运行游戏。");
|
spdlog::error("初始化失败,无法运行游戏。");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
time_->setTargetFps(144); // 设置目标帧率(临时,未来会从配置文件读取)
|
|
||||||
while (is_running_) {
|
while (is_running_) {
|
||||||
time_->update();
|
time_->update();
|
||||||
float delta_time = time_->getDeltaTime();
|
float delta_time = time_->getDeltaTime();
|
||||||
@@ -39,6 +40,7 @@ void GameApp::run() {
|
|||||||
|
|
||||||
bool GameApp::init() {
|
bool GameApp::init() {
|
||||||
spdlog::trace("初始化 GameApp ...");
|
spdlog::trace("初始化 GameApp ...");
|
||||||
|
if (!initConfig()) return false;
|
||||||
if (!initSDL()) return false;
|
if (!initSDL()) return false;
|
||||||
if (!initTime()) return false;
|
if (!initTime()) return false;
|
||||||
if (!initResourceManager()) return false;
|
if (!initResourceManager()) return false;
|
||||||
@@ -96,13 +98,26 @@ void GameApp::close() {
|
|||||||
is_running_ = false;
|
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)) {
|
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)) {
|
||||||
spdlog::error("SDL 初始化失败! SDL错误: {}", SDL_GetError());
|
spdlog::error("SDL 初始化失败! SDL错误: {}", SDL_GetError());
|
||||||
return false;
|
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) {
|
if (window_ == nullptr) {
|
||||||
spdlog::error("无法创建窗口! SDL错误: {}", SDL_GetError());
|
spdlog::error("无法创建窗口! SDL错误: {}", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
@@ -114,8 +129,13 @@ bool GameApp::initSDL() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置逻辑分辨率
|
// 设置 VSync (注意: VSync 开启时,驱动程序会尝试将帧率限制到显示器刷新率,有可能会覆盖我们手动设置的 target_fps)
|
||||||
SDL_SetRenderLogicalPresentation(sdl_renderer_, 640, 360, SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
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 初始化成功。");
|
spdlog::trace("SDL 初始化成功。");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -127,6 +147,7 @@ bool GameApp::initTime() {
|
|||||||
spdlog::error("初始化时间管理失败: {}", e.what());
|
spdlog::error("初始化时间管理失败: {}", e.what());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
time_->setTargetFps(config_->target_fps_);
|
||||||
spdlog::trace("时间管理初始化成功。");
|
spdlog::trace("时间管理初始化成功。");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -155,7 +176,7 @@ bool GameApp::initRenderer() {
|
|||||||
|
|
||||||
bool GameApp::initCamera() {
|
bool GameApp::initCamera() {
|
||||||
try {
|
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) {
|
} catch (const std::exception& e) {
|
||||||
spdlog::error("初始化相机失败: {}", e.what());
|
spdlog::error("初始化相机失败: {}", e.what());
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class Camera;
|
|||||||
|
|
||||||
namespace engine::core { // 命名空间的最佳实践:与文件路径一致
|
namespace engine::core { // 命名空间的最佳实践:与文件路径一致
|
||||||
class Time;
|
class Time;
|
||||||
|
class Config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 主游戏应用程序类,初始化SDL,管理游戏循环。
|
* @brief 主游戏应用程序类,初始化SDL,管理游戏循环。
|
||||||
@@ -31,7 +32,7 @@ private:
|
|||||||
std::unique_ptr<engine::resource::ResourceManager> resource_manager_;
|
std::unique_ptr<engine::resource::ResourceManager> resource_manager_;
|
||||||
std::unique_ptr<engine::render::Renderer> renderer_;
|
std::unique_ptr<engine::render::Renderer> renderer_;
|
||||||
std::unique_ptr<engine::render::Camera> camera_;
|
std::unique_ptr<engine::render::Camera> camera_;
|
||||||
|
std::unique_ptr<engine::core::Config> config_;
|
||||||
public:
|
public:
|
||||||
GameApp();
|
GameApp();
|
||||||
~GameApp();
|
~GameApp();
|
||||||
@@ -55,6 +56,7 @@ private:
|
|||||||
void close();
|
void close();
|
||||||
|
|
||||||
// 各模块的初始化/创建函数,在init()中调用
|
// 各模块的初始化/创建函数,在init()中调用
|
||||||
|
[[nodiscard]] bool initConfig();
|
||||||
[[nodiscard]] bool initSDL();
|
[[nodiscard]] bool initSDL();
|
||||||
[[nodiscard]] bool initTime();
|
[[nodiscard]] bool initTime();
|
||||||
[[nodiscard]] bool initResourceManager();
|
[[nodiscard]] bool initResourceManager();
|
||||||
|
|||||||
Reference in New Issue
Block a user