refactor: 移除音频系统、过渡场景和示例代码
- 删除音频引擎相关代码,包括AudioEngine和Sound类 - 移除所有过渡场景实现及基类 - 清理示例程序代码 - 简化构建配置,移除SDL2_mixer依赖 - 优化代码结构,移除不必要的资源管理代码
This commit is contained in:
parent
93d07e547f
commit
c6c90a7374
|
|
@ -6,21 +6,14 @@
|
|||
|
||||
namespace extra2d {
|
||||
|
||||
// 前向声明
|
||||
class Input;
|
||||
class AudioEngine;
|
||||
class SceneManager;
|
||||
class ResourceManager;
|
||||
class TimerManager;
|
||||
class EventQueue;
|
||||
class EventDispatcher;
|
||||
class Camera;
|
||||
class ViewportAdapter;
|
||||
|
||||
// ============================================================================
|
||||
// Application 配置
|
||||
// ============================================================================
|
||||
|
||||
enum class PlatformType { Auto = 0, PC, Switch };
|
||||
|
||||
struct AppConfig {
|
||||
|
|
@ -34,71 +27,46 @@ struct AppConfig {
|
|||
BackendType renderBackend = BackendType::OpenGL;
|
||||
int msaaSamples = 0;
|
||||
PlatformType platform = PlatformType::Auto;
|
||||
// 窗口高级配置
|
||||
bool enableCursors = true; // 是否启用光标
|
||||
bool enableDpiScale = false; // 是否启用DPI缩放
|
||||
bool enableCursors = true;
|
||||
bool enableDpiScale = false;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Application 单例 - 应用主控
|
||||
// ============================================================================
|
||||
/**
|
||||
* @brief Application 单例 - 应用主控
|
||||
*/
|
||||
class Application {
|
||||
public:
|
||||
// Meyer's 单例
|
||||
static Application &instance();
|
||||
|
||||
// 禁止拷贝
|
||||
Application(const Application &) = delete;
|
||||
Application &operator=(const Application &) = delete;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 生命周期
|
||||
// ------------------------------------------------------------------------
|
||||
bool init(const AppConfig &config);
|
||||
void shutdown();
|
||||
void run();
|
||||
void quit();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 状态控制
|
||||
// ------------------------------------------------------------------------
|
||||
void pause();
|
||||
void resume();
|
||||
bool isPaused() const { return paused_; }
|
||||
bool isRunning() const { return running_; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 子系统访问
|
||||
// ------------------------------------------------------------------------
|
||||
Window &window() { return *window_; }
|
||||
RenderBackend &renderer() { return *renderer_; }
|
||||
Input &input();
|
||||
AudioEngine &audio();
|
||||
SceneManager &scenes();
|
||||
ResourceManager &resources();
|
||||
TimerManager &timers();
|
||||
EventQueue &eventQueue();
|
||||
EventDispatcher &eventDispatcher();
|
||||
Camera &camera();
|
||||
|
||||
/**
|
||||
* @brief 获取视口适配器
|
||||
* @return 视口适配器引用
|
||||
*/
|
||||
ViewportAdapter &viewportAdapter();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 便捷方法
|
||||
// ------------------------------------------------------------------------
|
||||
void enterScene(Ptr<class Scene> scene);
|
||||
void enterScene(Ptr<class Scene> scene,
|
||||
Ptr<class TransitionScene> transitionScene);
|
||||
|
||||
float deltaTime() const { return deltaTime_; }
|
||||
float totalTime() const { return totalTime_; }
|
||||
int fps() const { return currentFps_; }
|
||||
|
||||
// 获取配置
|
||||
const AppConfig &getConfig() const { return config_; }
|
||||
|
||||
private:
|
||||
|
|
@ -108,29 +76,23 @@ private:
|
|||
void mainLoop();
|
||||
void update();
|
||||
void render();
|
||||
void prewarmObjectPools();
|
||||
|
||||
// 配置
|
||||
AppConfig config_;
|
||||
|
||||
// 子系统
|
||||
UniquePtr<Window> window_;
|
||||
UniquePtr<RenderBackend> renderer_;
|
||||
UniquePtr<SceneManager> sceneManager_;
|
||||
UniquePtr<ResourceManager> resourceManager_;
|
||||
UniquePtr<TimerManager> timerManager_;
|
||||
UniquePtr<EventQueue> eventQueue_;
|
||||
UniquePtr<EventDispatcher> eventDispatcher_;
|
||||
UniquePtr<Camera> camera_;
|
||||
UniquePtr<ViewportAdapter> viewportAdapter_;
|
||||
|
||||
// 状态
|
||||
bool initialized_ = false;
|
||||
bool running_ = false;
|
||||
bool paused_ = false;
|
||||
bool shouldQuit_ = false;
|
||||
|
||||
// 时间
|
||||
float deltaTime_ = 0.0f;
|
||||
float totalTime_ = 0.0f;
|
||||
double lastFrameTime_ = 0.0;
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <extra2d/core/types.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class Sound;
|
||||
|
||||
class AudioEngine {
|
||||
public:
|
||||
static AudioEngine& getInstance();
|
||||
|
||||
AudioEngine(const AudioEngine&) = delete;
|
||||
AudioEngine& operator=(const AudioEngine&) = delete;
|
||||
AudioEngine(AudioEngine&&) = delete;
|
||||
AudioEngine& operator=(AudioEngine&&) = delete;
|
||||
|
||||
bool initialize();
|
||||
void shutdown();
|
||||
|
||||
std::shared_ptr<Sound> loadSound(const std::string& filePath);
|
||||
std::shared_ptr<Sound> loadSound(const std::string& name, const std::string& filePath);
|
||||
|
||||
std::shared_ptr<Sound> getSound(const std::string& name);
|
||||
void unloadSound(const std::string& name);
|
||||
void unloadAllSounds();
|
||||
|
||||
void setMasterVolume(float volume);
|
||||
float getMasterVolume() const;
|
||||
|
||||
void pauseAll();
|
||||
void resumeAll();
|
||||
void stopAll();
|
||||
|
||||
private:
|
||||
AudioEngine() = default;
|
||||
~AudioEngine();
|
||||
|
||||
std::unordered_map<std::string, std::shared_ptr<Sound>> sounds_;
|
||||
float masterVolume_ = 1.0f;
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <extra2d/core/types.h>
|
||||
|
||||
struct Mix_Chunk;
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class AudioEngine;
|
||||
|
||||
class Sound {
|
||||
public:
|
||||
~Sound();
|
||||
|
||||
Sound(const Sound&) = delete;
|
||||
Sound& operator=(const Sound&) = delete;
|
||||
|
||||
bool play();
|
||||
void pause();
|
||||
void resume();
|
||||
void stop();
|
||||
|
||||
bool isPlaying() const;
|
||||
bool isPaused() const;
|
||||
|
||||
void setVolume(float volume);
|
||||
float getVolume() const { return volume_; }
|
||||
|
||||
void setLooping(bool looping);
|
||||
bool isLooping() const { return looping_; }
|
||||
|
||||
void setPitch(float pitch);
|
||||
float getPitch() const { return pitch_; }
|
||||
|
||||
float getDuration() const;
|
||||
float getCursor() const;
|
||||
void setCursor(float seconds);
|
||||
|
||||
const std::string& getFilePath() const { return filePath_; }
|
||||
const std::string& getName() const { return name_; }
|
||||
|
||||
private:
|
||||
friend class AudioEngine;
|
||||
|
||||
Sound(const std::string& name, const std::string& filePath, Mix_Chunk* chunk);
|
||||
|
||||
std::string name_;
|
||||
std::string filePath_;
|
||||
Mix_Chunk* chunk_ = nullptr;
|
||||
int channel_ = -1; // SDL_mixer 分配的通道,-1 表示未播放
|
||||
float volume_ = 1.0f;
|
||||
float pitch_ = 1.0f;
|
||||
bool looping_ = false;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
// Easy2D v3.0 - 统一入口头文件
|
||||
// Extra2D - 统一入口头文件
|
||||
// 包含所有公共 API
|
||||
|
||||
// Core
|
||||
|
|
@ -23,18 +23,14 @@
|
|||
#include <extra2d/graphics/viewport_adapter.h>
|
||||
#include <extra2d/graphics/vram_manager.h>
|
||||
|
||||
#include <extra2d/graphics/texture_pool.h>
|
||||
|
||||
// Scene
|
||||
#include <extra2d/scene/node.h>
|
||||
#include <extra2d/scene/scene.h>
|
||||
#include <extra2d/scene/scene_manager.h>
|
||||
#include <extra2d/scene/shape_node.h>
|
||||
#include <extra2d/scene/sprite.h>
|
||||
#include <extra2d/scene/transition_box_scene.h>
|
||||
#include <extra2d/scene/transition_fade_scene.h>
|
||||
#include <extra2d/scene/transition_flip_scene.h>
|
||||
#include <extra2d/scene/transition_scale_scene.h>
|
||||
#include <extra2d/scene/transition_scene.h>
|
||||
#include <extra2d/scene/transition_slide_scene.h>
|
||||
|
||||
// Event
|
||||
#include <extra2d/event/event.h>
|
||||
|
|
@ -42,15 +38,7 @@
|
|||
#include <extra2d/event/event_queue.h>
|
||||
#include <extra2d/event/input_codes.h>
|
||||
|
||||
// Audio
|
||||
#include <extra2d/audio/audio_engine.h>
|
||||
#include <extra2d/audio/sound.h>
|
||||
|
||||
// Resource
|
||||
#include <extra2d/resource/resource_manager.h>
|
||||
|
||||
// Utils
|
||||
#include <extra2d/utils/data.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <extra2d/utils/random.h>
|
||||
#include <extra2d/utils/timer.h>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,569 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/graphics/texture.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// 前向声明
|
||||
class Scene;
|
||||
class RenderBackend;
|
||||
|
||||
// ============================================================================
|
||||
// 纹理加载选项
|
||||
// ============================================================================
|
||||
struct TextureLoadOptions {
|
||||
bool generateMipmaps = true; // 是否生成 mipmaps
|
||||
bool sRGB = true; // 是否使用 sRGB 色彩空间
|
||||
bool premultiplyAlpha = false; // 是否预乘 Alpha
|
||||
PixelFormat preferredFormat = PixelFormat::RGBA8; // 首选像素格式
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 纹理键 - 用于唯一标识纹理缓存条目
|
||||
// ============================================================================
|
||||
struct TextureKey {
|
||||
std::string path; // 纹理文件路径
|
||||
Rect region; // 纹理区域(用于纹理图集)
|
||||
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
TextureKey() = default;
|
||||
|
||||
/**
|
||||
* @brief 构造函数(仅路径)
|
||||
* @param p 纹理文件路径
|
||||
*/
|
||||
explicit TextureKey(const std::string& p) : path(p), region(Rect::Zero()) {}
|
||||
|
||||
/**
|
||||
* @brief 构造函数(路径 + 区域)
|
||||
* @param p 纹理文件路径
|
||||
* @param r 纹理区域
|
||||
*/
|
||||
TextureKey(const std::string& p, const Rect& r) : path(p), region(r) {}
|
||||
|
||||
/**
|
||||
* @brief 相等比较运算符
|
||||
* @param other 另一个 TextureKey
|
||||
* @return 是否相等
|
||||
*/
|
||||
bool operator==(const TextureKey& other) const {
|
||||
return path == other.path && region == other.region;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 不等比较运算符
|
||||
* @param other 另一个 TextureKey
|
||||
* @return 是否不等
|
||||
*/
|
||||
bool operator!=(const TextureKey& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// TextureKey 哈希函子
|
||||
// ============================================================================
|
||||
struct TextureKeyHash {
|
||||
/**
|
||||
* @brief 计算 TextureKey 的哈希值
|
||||
* @param key 纹理键
|
||||
* @return 哈希值
|
||||
*/
|
||||
size_t operator()(const TextureKey& key) const {
|
||||
size_t h1 = std::hash<std::string>{}(key.path);
|
||||
size_t h2 = std::hash<float>{}(key.region.origin.x);
|
||||
size_t h3 = std::hash<float>{}(key.region.origin.y);
|
||||
size_t h4 = std::hash<float>{}(key.region.size.width);
|
||||
size_t h5 = std::hash<float>{}(key.region.size.height);
|
||||
|
||||
// 组合哈希值
|
||||
size_t result = h1;
|
||||
result ^= h2 + 0x9e3779b9 + (result << 6) + (result >> 2);
|
||||
result ^= h3 + 0x9e3779b9 + (result << 6) + (result >> 2);
|
||||
result ^= h4 + 0x9e3779b9 + (result << 6) + (result >> 2);
|
||||
result ^= h5 + 0x9e3779b9 + (result << 6) + (result >> 2);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 纹理池条目
|
||||
// ============================================================================
|
||||
struct TexturePoolEntry {
|
||||
Ptr<Texture> texture; // 纹理对象
|
||||
mutable std::atomic<uint32_t> refCount; // 引用计数
|
||||
TextureKey key; // 纹理键
|
||||
size_t memorySize; // 内存占用(字节)
|
||||
mutable uint64_t lastAccessTime; // 最后访问时间戳
|
||||
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
TexturePoolEntry()
|
||||
: texture(nullptr)
|
||||
, refCount(0)
|
||||
, key()
|
||||
, memorySize(0)
|
||||
, lastAccessTime(0) {}
|
||||
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param tex 纹理对象
|
||||
* @param k 纹理键
|
||||
* @param memSize 内存占用
|
||||
*/
|
||||
TexturePoolEntry(Ptr<Texture> tex, const TextureKey& k, size_t memSize)
|
||||
: texture(tex)
|
||||
, refCount(1)
|
||||
, key(k)
|
||||
, memorySize(memSize)
|
||||
, lastAccessTime(getCurrentTime()) {}
|
||||
|
||||
/**
|
||||
* @brief 移动构造函数
|
||||
* @param other 另一个条目
|
||||
*/
|
||||
TexturePoolEntry(TexturePoolEntry&& other) noexcept
|
||||
: texture(std::move(other.texture))
|
||||
, refCount(other.refCount.load(std::memory_order_relaxed))
|
||||
, key(std::move(other.key))
|
||||
, memorySize(other.memorySize)
|
||||
, lastAccessTime(other.lastAccessTime) {}
|
||||
|
||||
/**
|
||||
* @brief 移动赋值运算符
|
||||
* @param other 另一个条目
|
||||
* @return 引用
|
||||
*/
|
||||
TexturePoolEntry& operator=(TexturePoolEntry&& other) noexcept {
|
||||
if (this != &other) {
|
||||
texture = std::move(other.texture);
|
||||
refCount.store(other.refCount.load(std::memory_order_relaxed), std::memory_order_relaxed);
|
||||
key = std::move(other.key);
|
||||
memorySize = other.memorySize;
|
||||
lastAccessTime = other.lastAccessTime;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// 禁止拷贝
|
||||
TexturePoolEntry(const TexturePoolEntry&) = delete;
|
||||
TexturePoolEntry& operator=(const TexturePoolEntry&) = delete;
|
||||
|
||||
/**
|
||||
* @brief 更新最后访问时间
|
||||
*/
|
||||
void touch() const { lastAccessTime = getCurrentTime(); }
|
||||
|
||||
/**
|
||||
* @brief 获取当前时间戳
|
||||
* @return 时间戳(毫秒)
|
||||
*/
|
||||
static uint64_t getCurrentTime() {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now.time_since_epoch());
|
||||
return static_cast<uint64_t>(duration.count());
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 纹理引用智能指针 - 自动管理纹理池引用计数
|
||||
// ============================================================================
|
||||
class TextureRef {
|
||||
public:
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
TextureRef() : texture_(nullptr), entry_(nullptr), mutex_(nullptr) {}
|
||||
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param texture 纹理对象
|
||||
* @param entry 纹理池条目
|
||||
* @param mutex 互斥锁
|
||||
*/
|
||||
TextureRef(Ptr<Texture> texture, TexturePoolEntry* entry, std::mutex* mutex)
|
||||
: texture_(texture), entry_(entry), mutex_(mutex) {}
|
||||
|
||||
/**
|
||||
* @brief 创建独立的纹理引用(不管理引用计数)
|
||||
* @param texture 纹理对象
|
||||
* @return 独立的纹理引用
|
||||
*/
|
||||
static TextureRef fromTexture(Ptr<Texture> texture) {
|
||||
return TextureRef(texture, nullptr, nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 拷贝构造函数
|
||||
* @param other 另一个 TextureRef
|
||||
*/
|
||||
TextureRef(const TextureRef& other)
|
||||
: texture_(other.texture_), entry_(other.entry_), mutex_(other.mutex_) {
|
||||
if (entry_ && entry_->refCount.load(std::memory_order_relaxed) > 0) {
|
||||
entry_->refCount.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 移动构造函数
|
||||
* @param other 另一个 TextureRef
|
||||
*/
|
||||
TextureRef(TextureRef&& other) noexcept
|
||||
: texture_(std::move(other.texture_))
|
||||
, entry_(other.entry_)
|
||||
, mutex_(other.mutex_) {
|
||||
other.entry_ = nullptr;
|
||||
other.mutex_ = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~TextureRef() { reset(); }
|
||||
|
||||
/**
|
||||
* @brief 拷贝赋值运算符
|
||||
* @param other 另一个 TextureRef
|
||||
* @return 引用
|
||||
*/
|
||||
TextureRef& operator=(const TextureRef& other) {
|
||||
if (this != &other) {
|
||||
reset();
|
||||
texture_ = other.texture_;
|
||||
entry_ = other.entry_;
|
||||
mutex_ = other.mutex_;
|
||||
if (entry_ && entry_->refCount.load(std::memory_order_relaxed) > 0) {
|
||||
entry_->refCount.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 移动赋值运算符
|
||||
* @param other 另一个 TextureRef
|
||||
* @return 引用
|
||||
*/
|
||||
TextureRef& operator=(TextureRef&& other) noexcept {
|
||||
if (this != &other) {
|
||||
reset();
|
||||
texture_ = std::move(other.texture_);
|
||||
entry_ = other.entry_;
|
||||
mutex_ = other.mutex_;
|
||||
other.entry_ = nullptr;
|
||||
other.mutex_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 重置引用
|
||||
*/
|
||||
void reset() {
|
||||
if (entry_ && mutex_) {
|
||||
std::lock_guard<std::mutex> lock(*mutex_);
|
||||
if (entry_->refCount.load(std::memory_order_relaxed) > 0) {
|
||||
entry_->refCount.fetch_sub(1, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
texture_.reset();
|
||||
entry_ = nullptr;
|
||||
mutex_ = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取纹理对象
|
||||
* @return 纹理对象指针
|
||||
*/
|
||||
Texture* get() const { return texture_.get(); }
|
||||
|
||||
/**
|
||||
* @brief 获取纹理对象(智能指针)
|
||||
* @return 纹理对象智能指针
|
||||
*/
|
||||
Ptr<Texture> getPtr() const { return texture_; }
|
||||
|
||||
/**
|
||||
* @brief 检查是否有效
|
||||
* @return 是否有效
|
||||
*/
|
||||
bool valid() const { return texture_ != nullptr; }
|
||||
|
||||
/**
|
||||
* @brief 布尔转换运算符
|
||||
*/
|
||||
explicit operator bool() const { return valid(); }
|
||||
|
||||
/**
|
||||
* @brief 箭头运算符
|
||||
*/
|
||||
Texture* operator->() const { return texture_.get(); }
|
||||
|
||||
/**
|
||||
* @brief 解引用运算符
|
||||
*/
|
||||
Texture& operator*() const { return *texture_; }
|
||||
|
||||
private:
|
||||
Ptr<Texture> texture_;
|
||||
TexturePoolEntry* entry_;
|
||||
std::mutex* mutex_;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 纹理池 - 纹理缓存和内存管理系统
|
||||
// 特性:
|
||||
// - 纹理缓存和复用
|
||||
// - 引用计数管理
|
||||
// - 内存使用限制
|
||||
// - LRU 淘汰策略
|
||||
// - 线程安全
|
||||
// ============================================================================
|
||||
class TexturePool {
|
||||
public:
|
||||
// ========================================================================
|
||||
// 统计信息
|
||||
// ========================================================================
|
||||
struct Stats {
|
||||
size_t textureCount = 0; // 纹理数量
|
||||
size_t memoryUsage = 0; // 内存使用量(字节)
|
||||
size_t maxMemoryUsage = 0; // 最大内存使用量
|
||||
size_t cacheHits = 0; // 缓存命中次数
|
||||
size_t cacheMisses = 0; // 缓存未命中次数
|
||||
size_t evictionCount = 0; // 淘汰次数
|
||||
};
|
||||
|
||||
// ========================================================================
|
||||
// 构造和析构
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
TexturePool();
|
||||
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param scene 场景指针
|
||||
* @param maxMemoryUsage 最大内存使用量(0 表示无限制)
|
||||
*/
|
||||
explicit TexturePool(Scene* scene, size_t maxMemoryUsage = 0);
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~TexturePool();
|
||||
|
||||
// 禁止拷贝
|
||||
TexturePool(const TexturePool&) = delete;
|
||||
TexturePool& operator=(const TexturePool&) = delete;
|
||||
|
||||
/**
|
||||
* @brief 初始化纹理池
|
||||
* @param scene 场景指针
|
||||
* @param maxMemoryUsage 最大内存使用量(0 表示无限制)
|
||||
*/
|
||||
void init(Scene* scene, size_t maxMemoryUsage = 0);
|
||||
|
||||
// ========================================================================
|
||||
// 纹理加载
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* @brief 从文件加载纹理
|
||||
* @param path 文件路径
|
||||
* @param options 加载选项
|
||||
* @return 纹理引用
|
||||
*/
|
||||
TextureRef load(const std::string& path,
|
||||
const TextureLoadOptions& options = TextureLoadOptions());
|
||||
|
||||
/**
|
||||
* @brief 从文件加载纹理区域
|
||||
* @param path 文件路径
|
||||
* @param region 纹理区域
|
||||
* @param options 加载选项
|
||||
* @return 纹理引用
|
||||
*/
|
||||
TextureRef load(const std::string& path, const Rect& region,
|
||||
const TextureLoadOptions& options = TextureLoadOptions());
|
||||
|
||||
/**
|
||||
* @brief 从内存加载纹理
|
||||
* @param data 像素数据
|
||||
* @param width 宽度
|
||||
* @param height 高度
|
||||
* @param channels 通道数
|
||||
* @param key 缓存键
|
||||
* @return 纹理引用
|
||||
*/
|
||||
TextureRef loadFromMemory(const uint8_t* data, int width, int height,
|
||||
int channels, const std::string& key);
|
||||
|
||||
/**
|
||||
* @brief 获取或加载纹理
|
||||
* @param path 文件路径
|
||||
* @param options 加载选项
|
||||
* @return 纹理引用
|
||||
*/
|
||||
TextureRef getOrLoad(const std::string& path,
|
||||
const TextureLoadOptions& options = TextureLoadOptions());
|
||||
|
||||
/**
|
||||
* @brief 获取或加载纹理区域
|
||||
* @param path 文件路径
|
||||
* @param region 纹理区域
|
||||
* @param options 加载选项
|
||||
* @return 纹理引用
|
||||
*/
|
||||
TextureRef getOrLoad(const std::string& path, const Rect& region,
|
||||
const TextureLoadOptions& options = TextureLoadOptions());
|
||||
|
||||
// ========================================================================
|
||||
// 引用计数管理
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* @brief 增加引用计数
|
||||
* @param key 纹理键
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool addRef(const TextureKey& key);
|
||||
|
||||
/**
|
||||
* @brief 减少引用计数
|
||||
* @param key 纹理键
|
||||
* @return 减少后的引用计数
|
||||
*/
|
||||
uint32_t release(const TextureKey& key);
|
||||
|
||||
/**
|
||||
* @brief 获取引用计数
|
||||
* @param key 纹理键
|
||||
* @return 引用计数
|
||||
*/
|
||||
uint32_t getRefCount(const TextureKey& key) const;
|
||||
|
||||
// ========================================================================
|
||||
// 缓存管理
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* @brief 检查纹理是否已缓存
|
||||
* @param key 纹理键
|
||||
* @return 是否已缓存
|
||||
*/
|
||||
bool isCached(const TextureKey& key) const;
|
||||
|
||||
/**
|
||||
* @brief 从缓存中移除纹理
|
||||
* @param key 纹理键
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool removeFromCache(const TextureKey& key);
|
||||
|
||||
/**
|
||||
* @brief 垃圾回收(移除引用计数为 0 的纹理)
|
||||
* @return 移除的纹理数量
|
||||
*/
|
||||
size_t collectGarbage();
|
||||
|
||||
/**
|
||||
* @brief 清空所有缓存
|
||||
*/
|
||||
void clear();
|
||||
|
||||
// ========================================================================
|
||||
// 内存管理
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取当前内存使用量
|
||||
* @return 内存使用量(字节)
|
||||
*/
|
||||
size_t getMemoryUsage() const;
|
||||
|
||||
/**
|
||||
* @brief 设置最大内存使用量
|
||||
* @param maxMemory 最大内存使用量(0 表示无限制)
|
||||
*/
|
||||
void setMaxMemoryUsage(size_t maxMemory);
|
||||
|
||||
/**
|
||||
* @brief 获取最大内存使用量
|
||||
* @return 最大内存使用量
|
||||
*/
|
||||
size_t getMaxMemoryUsage() const { return maxMemoryUsage_; }
|
||||
|
||||
/**
|
||||
* @brief 执行 LRU 淘汰
|
||||
* @param targetMemory 目标内存使用量
|
||||
* @return 淘汰的纹理数量
|
||||
*/
|
||||
size_t evictLRU(size_t targetMemory = 0);
|
||||
|
||||
// ========================================================================
|
||||
// 统计信息
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取统计信息
|
||||
* @return 统计信息
|
||||
*/
|
||||
Stats getStats() const;
|
||||
|
||||
/**
|
||||
* @brief 重置统计信息
|
||||
*/
|
||||
void resetStats();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 计算纹理内存大小
|
||||
* @param texture 纹理对象
|
||||
* @return 内存大小(字节)
|
||||
*/
|
||||
static size_t calculateTextureMemory(const Texture* texture);
|
||||
|
||||
/**
|
||||
* @brief 检查是否需要淘汰
|
||||
* @return 是否需要淘汰
|
||||
*/
|
||||
bool needsEviction() const;
|
||||
|
||||
/**
|
||||
* @brief 尝试自动淘汰
|
||||
*/
|
||||
void tryAutoEvict();
|
||||
|
||||
Scene* scene_; // 场景指针
|
||||
mutable std::mutex mutex_; // 互斥锁
|
||||
std::unordered_map<TextureKey, TexturePoolEntry, TextureKeyHash> cache_; // 纹理缓存
|
||||
|
||||
size_t maxMemoryUsage_; // 最大内存使用量
|
||||
size_t currentMemoryUsage_; // 当前内存使用量
|
||||
|
||||
// 统计信息
|
||||
mutable std::atomic<size_t> cacheHits_;
|
||||
mutable std::atomic<size_t> cacheMisses_;
|
||||
mutable std::atomic<size_t> evictionCount_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,356 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/audio/sound.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/graphics/alpha_mask.h>
|
||||
#include <extra2d/graphics/font.h>
|
||||
#include <extra2d/graphics/texture.h>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 资源管理器 - 统一管理纹理、字体、音效等资源
|
||||
// 支持异步加载和纹理压缩
|
||||
// ============================================================================
|
||||
|
||||
// 纹理格式枚举
|
||||
enum class TextureFormat {
|
||||
Auto = 0, // 自动选择最佳格式
|
||||
RGBA8, // 32位 RGBA
|
||||
RGB8, // 24位 RGB
|
||||
DXT1, // BC1/DXT1 压缩(1 bit alpha)
|
||||
DXT5, // BC3/DXT5 压缩(完整 alpha)
|
||||
ETC2, // ETC2 压缩(移动平台)
|
||||
ASTC4x4, // ASTC 4x4 压缩(高质量)
|
||||
ASTC8x8, // ASTC 8x8 压缩(高压缩率)
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 纹理LRU缓存项
|
||||
// ============================================================================
|
||||
struct TextureCacheEntry {
|
||||
Ptr<Texture> texture;
|
||||
size_t size = 0; // 纹理大小(字节)
|
||||
float lastAccessTime = 0.0f; // 最后访问时间
|
||||
uint32_t accessCount = 0; // 访问次数
|
||||
};
|
||||
|
||||
// 异步加载回调类型
|
||||
using TextureLoadCallback = std::function<void(Ptr<Texture>)>;
|
||||
|
||||
class ResourceManager {
|
||||
public:
|
||||
// ------------------------------------------------------------------------
|
||||
// 单例访问
|
||||
// ------------------------------------------------------------------------
|
||||
static ResourceManager &getInstance();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 纹理资源 - 同步加载
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// 加载纹理(带缓存)
|
||||
Ptr<Texture> loadTexture(const std::string &filepath);
|
||||
|
||||
/// 加载纹理(指定是否异步)
|
||||
Ptr<Texture> loadTexture(const std::string &filepath, bool async);
|
||||
|
||||
/// 加载纹理(完整参数:异步 + 压缩格式)
|
||||
Ptr<Texture> loadTexture(const std::string &filepath, bool async, TextureFormat format);
|
||||
|
||||
/// 异步加载纹理(带回调)
|
||||
void loadTextureAsync(const std::string &filepath, TextureLoadCallback callback);
|
||||
|
||||
/// 异步加载纹理(指定格式 + 回调)
|
||||
void loadTextureAsync(const std::string &filepath, TextureFormat format, TextureLoadCallback callback);
|
||||
|
||||
/// 加载纹理并生成Alpha遮罩(用于不规则形状图片)
|
||||
Ptr<Texture> loadTextureWithAlphaMask(const std::string &filepath);
|
||||
|
||||
/// 通过key获取已缓存的纹理
|
||||
Ptr<Texture> getTexture(const std::string &key) const;
|
||||
|
||||
/// 检查纹理是否已缓存
|
||||
bool hasTexture(const std::string &key) const;
|
||||
|
||||
/// 卸载指定纹理
|
||||
void unloadTexture(const std::string &key);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Alpha遮罩资源
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// 获取纹理的Alpha遮罩(如果已生成)
|
||||
const AlphaMask *getAlphaMask(const std::string &textureKey) const;
|
||||
|
||||
/// 为已加载的纹理生成Alpha遮罩
|
||||
bool generateAlphaMask(const std::string &textureKey);
|
||||
|
||||
/// 检查纹理是否有Alpha遮罩
|
||||
bool hasAlphaMask(const std::string &textureKey) const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 字体图集资源
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// 加载字体图集(带缓存)
|
||||
Ptr<FontAtlas> loadFont(const std::string &filepath, int fontSize,
|
||||
bool useSDF = false);
|
||||
|
||||
/// 通过key获取已缓存的字体图集
|
||||
Ptr<FontAtlas> getFont(const std::string &key) const;
|
||||
|
||||
/// 检查字体是否已缓存
|
||||
bool hasFont(const std::string &key) const;
|
||||
|
||||
/// 卸载指定字体
|
||||
void unloadFont(const std::string &key);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 音效资源
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// 加载音效(带缓存)
|
||||
Ptr<Sound> loadSound(const std::string &filepath);
|
||||
Ptr<Sound> loadSound(const std::string &name, const std::string &filepath);
|
||||
|
||||
/// 通过key获取已缓存的音效
|
||||
Ptr<Sound> getSound(const std::string &key) const;
|
||||
|
||||
/// 检查音效是否已缓存
|
||||
bool hasSound(const std::string &key) const;
|
||||
|
||||
/// 卸载指定音效
|
||||
void unloadSound(const std::string &key);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 文本文件资源
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// 加载文本文件(带缓存)
|
||||
/// @param filepath 文件路径,支持 romfs:/ 前缀
|
||||
/// @return 文件内容字符串,加载失败返回空字符串
|
||||
std::string loadTextFile(const std::string &filepath);
|
||||
|
||||
/// 加载文本文件(指定编码)
|
||||
/// @param filepath 文件路径
|
||||
/// @param encoding 文件编码(默认 UTF-8)
|
||||
/// @return 文件内容字符串
|
||||
std::string loadTextFile(const std::string &filepath, const std::string &encoding);
|
||||
|
||||
/// 通过key获取已缓存的文本内容
|
||||
std::string getTextFile(const std::string &key) const;
|
||||
|
||||
/// 检查文本文件是否已缓存
|
||||
bool hasTextFile(const std::string &key) const;
|
||||
|
||||
/// 卸载指定文本文件
|
||||
void unloadTextFile(const std::string &key);
|
||||
|
||||
/// 清理所有文本文件缓存
|
||||
void clearTextFileCache();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// JSON 文件资源
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// 加载并解析 JSON 文件
|
||||
/// @param filepath 文件路径,支持 romfs:/ 前缀
|
||||
/// @return JSON 字符串内容,加载或解析失败返回空字符串
|
||||
/// @note 返回的是原始 JSON 字符串,需要自行解析
|
||||
std::string loadJsonFile(const std::string &filepath);
|
||||
|
||||
/// 通过key获取已缓存的 JSON 内容
|
||||
std::string getJsonFile(const std::string &key) const;
|
||||
|
||||
/// 检查 JSON 文件是否已缓存
|
||||
bool hasJsonFile(const std::string &key) const;
|
||||
|
||||
/// 卸载指定 JSON 文件
|
||||
void unloadJsonFile(const std::string &key);
|
||||
|
||||
/// 清理所有 JSON 文件缓存
|
||||
void clearJsonFileCache();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 缓存清理
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// 清理所有失效的弱引用(自动清理已释放的资源)
|
||||
void purgeUnused();
|
||||
|
||||
/// 清理指定类型的所有缓存
|
||||
void clearTextureCache();
|
||||
void clearFontCache();
|
||||
void clearSoundCache();
|
||||
|
||||
/// 清理所有资源缓存
|
||||
void clearAllCaches();
|
||||
|
||||
/// 获取各类资源的缓存数量
|
||||
size_t getTextureCacheSize() const;
|
||||
size_t getFontCacheSize() const;
|
||||
size_t getSoundCacheSize() const;
|
||||
size_t getTextFileCacheSize() const;
|
||||
size_t getJsonFileCacheSize() const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// LRU 缓存管理
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// 设置纹理缓存参数
|
||||
void setTextureCache(size_t maxCacheSize, size_t maxTextureCount,
|
||||
float unloadInterval);
|
||||
|
||||
/// 获取当前缓存的总大小(字节)
|
||||
size_t getTextureCacheMemoryUsage() const;
|
||||
|
||||
/// 获取缓存命中率
|
||||
float getTextureCacheHitRate() const;
|
||||
|
||||
/// 打印缓存统计信息
|
||||
void printTextureCacheStats() const;
|
||||
|
||||
/// 更新缓存(在主循环中调用,用于自动清理)
|
||||
void update(float dt);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 异步加载控制
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// 初始化异步加载系统(可选,自动在首次异步加载时初始化)
|
||||
void initAsyncLoader();
|
||||
|
||||
/// 关闭异步加载系统
|
||||
void shutdownAsyncLoader();
|
||||
|
||||
/// 等待所有异步加载完成
|
||||
void waitForAsyncLoads();
|
||||
|
||||
/// 检查是否有正在进行的异步加载
|
||||
bool hasPendingAsyncLoads() const;
|
||||
|
||||
ResourceManager();
|
||||
~ResourceManager();
|
||||
ResourceManager(const ResourceManager &) = delete;
|
||||
ResourceManager &operator=(const ResourceManager &) = delete;
|
||||
|
||||
private:
|
||||
// 生成字体缓存key
|
||||
std::string makeFontKey(const std::string &filepath, int fontSize,
|
||||
bool useSDF) const;
|
||||
|
||||
// 内部加载实现
|
||||
Ptr<Texture> loadTextureInternal(const std::string &filepath, TextureFormat format);
|
||||
|
||||
// 选择最佳纹理格式
|
||||
TextureFormat selectBestFormat(TextureFormat requested) const;
|
||||
|
||||
// 压缩纹理数据
|
||||
std::vector<uint8_t> compressTexture(const uint8_t* data, int width, int height,
|
||||
int channels, TextureFormat format);
|
||||
|
||||
// 互斥锁保护缓存
|
||||
mutable std::mutex textureMutex_;
|
||||
mutable std::mutex fontMutex_;
|
||||
mutable std::mutex soundMutex_;
|
||||
mutable std::mutex textFileMutex_;
|
||||
mutable std::mutex jsonFileMutex_;
|
||||
|
||||
// 资源缓存 - 使用弱指针实现自动清理
|
||||
std::unordered_map<std::string, WeakPtr<FontAtlas>> fontCache_;
|
||||
std::unordered_map<std::string, WeakPtr<Sound>> soundCache_;
|
||||
|
||||
// 文本文件缓存 - 使用强引用(字符串值类型)
|
||||
std::unordered_map<std::string, std::string> textFileCache_;
|
||||
std::unordered_map<std::string, std::string> jsonFileCache_;
|
||||
|
||||
// ============================================================================
|
||||
// 纹理LRU缓存
|
||||
// ============================================================================
|
||||
|
||||
// LRU链表节点
|
||||
struct LRUNode {
|
||||
std::string key;
|
||||
uint32_t prev = 0; // 数组索引,0表示无效
|
||||
uint32_t next = 0; // 数组索引,0表示无效
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
// 纹理缓存配置
|
||||
size_t maxCacheSize_ = 64 * 1024 * 1024; // 最大缓存大小 (64MB)
|
||||
size_t maxTextureCount_ = 256; // 最大纹理数量
|
||||
float unloadInterval_ = 30.0f; // 自动清理间隔 (秒)
|
||||
|
||||
// 纹理缓存 - 使用强指针保持引用
|
||||
std::unordered_map<std::string, TextureCacheEntry> textureCache_;
|
||||
|
||||
// 侵入式LRU链表 - 使用数组索引代替指针,提高缓存局部性
|
||||
std::vector<LRUNode> lruNodes_;
|
||||
uint32_t lruHead_ = 0; // 最近使用
|
||||
uint32_t lruTail_ = 0; // 最久未使用
|
||||
uint32_t freeList_ = 0; // 空闲节点链表
|
||||
|
||||
// 统计
|
||||
size_t totalTextureSize_ = 0;
|
||||
uint64_t textureHitCount_ = 0;
|
||||
uint64_t textureMissCount_ = 0;
|
||||
float autoUnloadTimer_ = 0.0f;
|
||||
|
||||
// 异步加载相关
|
||||
struct AsyncLoadTask {
|
||||
std::string filepath;
|
||||
TextureFormat format;
|
||||
TextureLoadCallback callback;
|
||||
std::promise<Ptr<Texture>> promise;
|
||||
};
|
||||
|
||||
std::queue<AsyncLoadTask> asyncTaskQueue_;
|
||||
std::mutex asyncQueueMutex_;
|
||||
std::condition_variable asyncCondition_;
|
||||
std::unique_ptr<std::thread> asyncThread_;
|
||||
std::atomic<bool> asyncRunning_{false};
|
||||
std::atomic<int> pendingAsyncLoads_{0};
|
||||
|
||||
void asyncLoadLoop();
|
||||
|
||||
// ============================================================================
|
||||
// LRU 缓存内部方法
|
||||
// ============================================================================
|
||||
|
||||
/// 分配LRU节点
|
||||
uint32_t allocateLRUNode(const std::string &key);
|
||||
|
||||
/// 释放LRU节点
|
||||
void freeLRUNode(uint32_t index);
|
||||
|
||||
/// 将节点移到链表头部(最近使用)
|
||||
void moveToFront(uint32_t index);
|
||||
|
||||
/// 从链表中移除节点
|
||||
void removeFromList(uint32_t index);
|
||||
|
||||
/// 驱逐最久未使用的纹理
|
||||
std::string evictLRU();
|
||||
|
||||
/// 访问纹理(更新LRU位置)
|
||||
void touchTexture(const std::string &key);
|
||||
|
||||
/// 驱逐纹理直到满足大小限制
|
||||
void evictTexturesIfNeeded();
|
||||
|
||||
/// 计算纹理大小
|
||||
size_t calculateTextureSize(int width, int height, PixelFormat format) const;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/event/event_dispatcher.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -79,7 +78,7 @@ public:
|
|||
* @brief 设置颜色
|
||||
* @param color RGB颜色
|
||||
*/
|
||||
void setColor(const Color3B& color);
|
||||
void setColor(const Color3B &color);
|
||||
Color3B getColor() const { return color_; }
|
||||
|
||||
/**
|
||||
|
|
@ -183,58 +182,58 @@ protected:
|
|||
private:
|
||||
// ==========================================================================
|
||||
// 成员变量按类型大小降序排列,减少内存对齐填充
|
||||
// 64位系统对齐:std::string(32) > glm::mat4(64) > std::vector(24) >
|
||||
// 64位系统对齐:std::string(32) > glm::mat4(64) > std::vector(24) >
|
||||
// double(8) > float(4) > int(4) > bool(1)
|
||||
// ==========================================================================
|
||||
|
||||
// 1. 大块内存(64字节)
|
||||
mutable glm::mat4 localTransform_; // 64 bytes
|
||||
mutable glm::mat4 worldTransform_; // 64 bytes
|
||||
mutable glm::mat4 localTransform_; // 64 bytes
|
||||
mutable glm::mat4 worldTransform_; // 64 bytes
|
||||
|
||||
// 2. 字符串和容器(24-32字节)
|
||||
std::string name_; // 32 bytes
|
||||
std::vector<Ptr<Node>> children_; // 24 bytes
|
||||
std::string name_; // 32 bytes
|
||||
std::vector<Ptr<Node>> children_; // 24 bytes
|
||||
|
||||
// 3. 子节点索引(加速查找)
|
||||
std::unordered_map<std::string, WeakPtr<Node>> nameIndex_; // 56 bytes
|
||||
std::unordered_map<int, WeakPtr<Node>> tagIndex_; // 56 bytes
|
||||
|
||||
// 4. 事件分发器
|
||||
EventDispatcher eventDispatcher_; // 大小取决于实现
|
||||
EventDispatcher eventDispatcher_; // 大小取决于实现
|
||||
|
||||
// 5. 父节点引用
|
||||
WeakPtr<Node> parent_; // 16 bytes
|
||||
WeakPtr<Node> parent_; // 16 bytes
|
||||
|
||||
// 7. 变换属性(按访问频率分组)
|
||||
Vec2 position_ = Vec2::Zero(); // 8 bytes
|
||||
Vec2 scale_ = Vec2(1.0f, 1.0f); // 8 bytes
|
||||
Vec2 anchor_ = Vec2(0.5f, 0.5f); // 8 bytes
|
||||
Vec2 skew_ = Vec2::Zero(); // 8 bytes
|
||||
Vec2 position_ = Vec2::Zero(); // 8 bytes
|
||||
Vec2 scale_ = Vec2(1.0f, 1.0f); // 8 bytes
|
||||
Vec2 anchor_ = Vec2(0.5f, 0.5f); // 8 bytes
|
||||
Vec2 skew_ = Vec2::Zero(); // 8 bytes
|
||||
|
||||
// 8. 浮点属性
|
||||
float rotation_ = 0.0f; // 4 bytes
|
||||
float opacity_ = 1.0f; // 4 bytes
|
||||
float rotation_ = 0.0f; // 4 bytes
|
||||
float opacity_ = 1.0f; // 4 bytes
|
||||
|
||||
// 10. 颜色属性
|
||||
Color3B color_ = Color3B(255, 255, 255); // 3 bytes
|
||||
|
||||
// 11. 整数属性
|
||||
int zOrder_ = 0; // 4 bytes
|
||||
int tag_ = -1; // 4 bytes
|
||||
int zOrder_ = 0; // 4 bytes
|
||||
int tag_ = -1; // 4 bytes
|
||||
|
||||
// 12. 布尔属性
|
||||
bool flipX_ = false; // 1 byte
|
||||
bool flipY_ = false; // 1 byte
|
||||
bool flipX_ = false; // 1 byte
|
||||
bool flipY_ = false; // 1 byte
|
||||
|
||||
// 13. 场景指针
|
||||
Scene *scene_ = nullptr; // 8 bytes
|
||||
Scene *scene_ = nullptr; // 8 bytes
|
||||
|
||||
// 14. 布尔标志(打包在一起)
|
||||
mutable bool transformDirty_ = true; // 1 byte
|
||||
mutable bool worldTransformDirty_ = true; // 1 byte
|
||||
bool childrenOrderDirty_ = false; // 1 byte
|
||||
bool visible_ = true; // 1 byte
|
||||
bool running_ = false; // 1 byte
|
||||
mutable bool transformDirty_ = true; // 1 byte
|
||||
mutable bool worldTransformDirty_ = true; // 1 byte
|
||||
bool childrenOrderDirty_ = false; // 1 byte
|
||||
bool visible_ = true; // 1 byte
|
||||
bool running_ = false; // 1 byte
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/scene/scene.h>
|
||||
#include <extra2d/scene/transition_scene.h>
|
||||
|
||||
#include <functional>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
|
|
@ -11,87 +11,42 @@
|
|||
|
||||
namespace extra2d {
|
||||
|
||||
// 前向声明
|
||||
struct RenderCommand;
|
||||
class TransitionScene;
|
||||
|
||||
// ============================================================================
|
||||
// 场景管理器 - 管理场景的生命周期和切换
|
||||
// ============================================================================
|
||||
/**
|
||||
* @brief 场景管理器 - 管理场景的生命周期和切换
|
||||
*/
|
||||
class SceneManager {
|
||||
public:
|
||||
using TransitionCallback = std::function<void()>;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 单例访问
|
||||
// ------------------------------------------------------------------------
|
||||
static SceneManager &getInstance();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 场景栈操作
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
// 运行第一个场景
|
||||
void runWithScene(Ptr<Scene> scene);
|
||||
|
||||
// 替换当前场景
|
||||
void replaceScene(Ptr<Scene> scene);
|
||||
void replaceScene(Ptr<Scene> scene, TransitionType transition,
|
||||
float duration = 0.5f);
|
||||
|
||||
// 压入新场景(当前场景暂停)
|
||||
void pushScene(Ptr<Scene> scene);
|
||||
void pushScene(Ptr<Scene> scene, TransitionType transition,
|
||||
float duration = 0.5f);
|
||||
|
||||
// 弹出当前场景(恢复上一个场景)
|
||||
void popScene();
|
||||
void popScene(TransitionType transition, float duration = 0.5f);
|
||||
|
||||
// 弹出到根场景
|
||||
void popToRootScene();
|
||||
void popToRootScene(TransitionType transition, float duration = 0.5f);
|
||||
|
||||
// 弹出到指定场景
|
||||
void popToScene(const std::string &name);
|
||||
void popToScene(const std::string &name, TransitionType transition,
|
||||
float duration = 0.5f);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 获取场景
|
||||
// ------------------------------------------------------------------------
|
||||
Ptr<Scene> getCurrentScene() const;
|
||||
Ptr<Scene> getPreviousScene() const;
|
||||
Ptr<Scene> getRootScene() const;
|
||||
|
||||
// 通过名称获取场景
|
||||
Ptr<Scene> getSceneByName(const std::string &name) const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 查询
|
||||
// ------------------------------------------------------------------------
|
||||
size_t getSceneCount() const { return sceneStack_.size(); }
|
||||
bool isEmpty() const { return sceneStack_.empty(); }
|
||||
bool hasScene(const std::string &name) const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 更新和渲染
|
||||
// ------------------------------------------------------------------------
|
||||
void update(float dt);
|
||||
void render(RenderBackend &renderer);
|
||||
void collectRenderCommands(std::vector<RenderCommand> &commands);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 过渡控制
|
||||
// ------------------------------------------------------------------------
|
||||
bool isTransitioning() const { return isTransitioning_; }
|
||||
void setTransitionCallback(TransitionCallback callback) {
|
||||
transitionCallback_ = callback;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 清理
|
||||
// ------------------------------------------------------------------------
|
||||
void end();
|
||||
void purgeCachedScenes();
|
||||
|
||||
|
|
@ -101,33 +56,18 @@ public:
|
|||
SceneManager(const SceneManager &) = delete;
|
||||
SceneManager &operator=(const SceneManager &) = delete;
|
||||
|
||||
// 场景切换(供 Application 使用)
|
||||
void enterScene(Ptr<Scene> scene);
|
||||
void enterScene(Ptr<Scene> scene, Ptr<TransitionScene> transitionScene);
|
||||
|
||||
private:
|
||||
void doSceneSwitch();
|
||||
void startTransition(Ptr<Scene> from, Ptr<Scene> to, TransitionType type,
|
||||
float duration, Function<void()> stackAction);
|
||||
void finishTransition();
|
||||
void dispatchPointerEvents(Scene &scene);
|
||||
|
||||
// 创建过渡场景
|
||||
Ptr<TransitionScene> createTransitionScene(TransitionType type,
|
||||
float duration,
|
||||
Ptr<Scene> inScene);
|
||||
|
||||
std::stack<Ptr<Scene>> sceneStack_;
|
||||
std::unordered_map<std::string, Ptr<Scene>> namedScenes_;
|
||||
|
||||
// Transition state
|
||||
bool isTransitioning_ = false;
|
||||
TransitionType currentTransition_ = TransitionType::None;
|
||||
Ptr<TransitionScene> activeTransitionScene_;
|
||||
Function<void()> transitionStackAction_;
|
||||
TransitionCallback transitionCallback_;
|
||||
|
||||
// Next scene to switch to (queued during transition)
|
||||
Ptr<Scene> nextScene_;
|
||||
bool sendCleanupToScene_ = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/scene/transition_scene.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 方块/马赛克过渡场景
|
||||
// 实现原理:
|
||||
// 1. 将屏幕分成多个方块
|
||||
// 2. 方块逐个消失,显示新场景
|
||||
// ============================================================================
|
||||
class TransitionBoxScene : public TransitionScene {
|
||||
public:
|
||||
/**
|
||||
* @brief 创建方块过渡场景
|
||||
* @param duration 过渡持续时间(秒)
|
||||
* @param inScene 要进入的目标场景
|
||||
* @param divisions 方块分割数(默认为 8,表示 8x8 网格)
|
||||
*/
|
||||
TransitionBoxScene(float duration, Ptr<Scene> inScene, int divisions = 8);
|
||||
|
||||
static Ptr<TransitionBoxScene> create(float duration, Ptr<Scene> inScene,
|
||||
int divisions = 8);
|
||||
|
||||
protected:
|
||||
void onTransitionStart() override;
|
||||
void renderContent(RenderBackend &renderer) override;
|
||||
|
||||
private:
|
||||
int divisions_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/scene/transition_scene.h>
|
||||
#include <extra2d/scene/sprite.h>
|
||||
#include <extra2d/core/color.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 淡入淡出过渡场景
|
||||
// 实现原理:
|
||||
// 1. 创建一个纯色精灵作为遮罩层
|
||||
// 2. 第一阶段:遮罩从透明淡入到不透明(黑屏),同时显示旧场景
|
||||
// 3. 切换显示新场景
|
||||
// 4. 第二阶段:遮罩从不透明淡出到透明,显示新场景
|
||||
// ============================================================================
|
||||
class TransitionFadeScene : public TransitionScene {
|
||||
public:
|
||||
/**
|
||||
* @brief 创建淡入淡出过渡场景
|
||||
* @param duration 过渡持续时间(秒)
|
||||
* @param inScene 要进入的目标场景
|
||||
* @param color 遮罩颜色(默认为黑色)
|
||||
*/
|
||||
TransitionFadeScene(float duration, Ptr<Scene> inScene,
|
||||
const Color &color = Colors::Black);
|
||||
|
||||
static Ptr<TransitionFadeScene> create(float duration, Ptr<Scene> inScene,
|
||||
const Color &color = Colors::Black);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief 启动过渡动画
|
||||
* 创建遮罩层并运行动作序列
|
||||
*/
|
||||
void onTransitionStart() override;
|
||||
|
||||
/**
|
||||
* @brief 渲染内容
|
||||
* 根据进度控制新旧场景的显示
|
||||
*/
|
||||
void renderContent(RenderBackend &renderer) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 隐藏退出场景,显示进入场景
|
||||
*/
|
||||
void hideOutShowIn();
|
||||
|
||||
Color maskColor_; // 遮罩颜色
|
||||
bool hasSwitched_ = false; // 是否已经切换场景
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/scene/transition_scene.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 翻页过渡场景
|
||||
// 实现原理:
|
||||
// 1. 前半段:旧场景翻转消失
|
||||
// 2. 后半段:新场景翻转出现
|
||||
// ============================================================================
|
||||
class TransitionFlipScene : public TransitionScene {
|
||||
public:
|
||||
enum class Axis { Horizontal, Vertical };
|
||||
|
||||
/**
|
||||
* @brief 创建翻页过渡场景
|
||||
* @param duration 过渡持续时间(秒)
|
||||
* @param inScene 要进入的目标场景
|
||||
* @param axis 翻转轴(水平或垂直)
|
||||
*/
|
||||
TransitionFlipScene(float duration, Ptr<Scene> inScene,
|
||||
Axis axis = Axis::Horizontal);
|
||||
|
||||
static Ptr<TransitionFlipScene> create(float duration, Ptr<Scene> inScene,
|
||||
Axis axis = Axis::Horizontal);
|
||||
|
||||
protected:
|
||||
void onTransitionStart() override;
|
||||
void renderContent(RenderBackend &renderer) override;
|
||||
|
||||
private:
|
||||
Axis axis_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/scene/transition_scene.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 缩放过渡场景
|
||||
// 实现原理:
|
||||
// 1. 旧场景缩小消失
|
||||
// 2. 新场景放大出现
|
||||
// ============================================================================
|
||||
class TransitionScaleScene : public TransitionScene {
|
||||
public:
|
||||
/**
|
||||
* @brief 创建缩放过渡场景
|
||||
* @param duration 过渡持续时间(秒)
|
||||
* @param inScene 要进入的目标场景
|
||||
*/
|
||||
TransitionScaleScene(float duration, Ptr<Scene> inScene);
|
||||
|
||||
static Ptr<TransitionScaleScene> create(float duration, Ptr<Scene> inScene);
|
||||
|
||||
protected:
|
||||
void onTransitionStart() override;
|
||||
void renderContent(RenderBackend &renderer) override;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/scene/scene.h>
|
||||
#include <functional>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 过渡方向
|
||||
// ============================================================================
|
||||
enum class TransitionDirection { Left, Right, Up, Down };
|
||||
|
||||
// ============================================================================
|
||||
// 过渡效果类型
|
||||
// ============================================================================
|
||||
enum class TransitionType {
|
||||
None,
|
||||
Fade,
|
||||
SlideLeft,
|
||||
SlideRight,
|
||||
SlideUp,
|
||||
SlideDown,
|
||||
Scale,
|
||||
Flip,
|
||||
Box
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 过渡场景基类 - 继承自 Scene,作为中介场景管理过渡效果
|
||||
// 设计参考 Cocos2d-x 的 TransitionScene
|
||||
// ============================================================================
|
||||
class TransitionScene : public Scene {
|
||||
public:
|
||||
using FinishCallback = std::function<void()>;
|
||||
|
||||
/**
|
||||
* @brief 创建过渡场景
|
||||
* @param duration 过渡持续时间(秒)
|
||||
* @param inScene 要进入的目标场景
|
||||
*/
|
||||
TransitionScene(float duration, Ptr<Scene> inScene);
|
||||
~TransitionScene() override = default;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 场景管理
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取要进入的场景
|
||||
*/
|
||||
Ptr<Scene> getInScene() const { return inScene_; }
|
||||
|
||||
/**
|
||||
* @brief 获取要退出的场景
|
||||
*/
|
||||
Ptr<Scene> getOutScene() const { return outScene_; }
|
||||
|
||||
/**
|
||||
* @brief 设置要退出的场景(由 SceneManager 调用)
|
||||
*/
|
||||
void setOutScene(Ptr<Scene> outScene) { outScene_ = outScene; }
|
||||
|
||||
/**
|
||||
* @brief 设置过渡完成回调
|
||||
*/
|
||||
void setFinishCallback(FinishCallback callback) { finishCallback_ = callback; }
|
||||
|
||||
/**
|
||||
* @brief 获取过渡持续时间
|
||||
*/
|
||||
float getDuration() const { return duration_; }
|
||||
|
||||
/**
|
||||
* @brief 获取当前进度 [0, 1]
|
||||
*/
|
||||
float getProgress() const { return progress_; }
|
||||
|
||||
/**
|
||||
* @brief 是否已完成
|
||||
*/
|
||||
bool isFinished() const { return isFinished_; }
|
||||
|
||||
/**
|
||||
* @brief 完成过渡,通知 SceneManager 切换到目标场景
|
||||
*/
|
||||
void finish();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 渲染 - 在 TransitionScene 上渲染新旧两个子场景
|
||||
// ------------------------------------------------------------------------
|
||||
void renderContent(RenderBackend &renderer) override;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 生命周期
|
||||
// ------------------------------------------------------------------------
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief 子类实现具体的过渡逻辑
|
||||
* 在 onEnter 中设置动画,动画完成后调用 finish()
|
||||
*/
|
||||
virtual void onTransitionStart() = 0;
|
||||
|
||||
/**
|
||||
* @brief 绘制源场景(旧场景)
|
||||
*/
|
||||
virtual void drawOutScene(RenderBackend &renderer);
|
||||
|
||||
/**
|
||||
* @brief 绘制目标场景(新场景)
|
||||
*/
|
||||
virtual void drawInScene(RenderBackend &renderer);
|
||||
|
||||
float duration_;
|
||||
float elapsed_ = 0.0f;
|
||||
float progress_ = 0.0f;
|
||||
bool isFinished_ = false;
|
||||
|
||||
Ptr<Scene> inScene_; // 要进入的场景
|
||||
Ptr<Scene> outScene_; // 要退出的场景
|
||||
|
||||
FinishCallback finishCallback_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/scene/transition_scene.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 滑动过渡场景
|
||||
// 实现原理:
|
||||
// 1. 旧场景向指定方向滑出
|
||||
// 2. 新场景从相反方向滑入
|
||||
// ============================================================================
|
||||
class TransitionSlideScene : public TransitionScene {
|
||||
public:
|
||||
/**
|
||||
* @brief 创建滑动过渡场景
|
||||
* @param duration 过渡持续时间(秒)
|
||||
* @param inScene 要进入的目标场景
|
||||
* @param direction 滑动方向
|
||||
*/
|
||||
TransitionSlideScene(float duration, Ptr<Scene> inScene,
|
||||
TransitionDirection direction);
|
||||
|
||||
static Ptr<TransitionSlideScene> create(float duration, Ptr<Scene> inScene,
|
||||
TransitionDirection direction);
|
||||
|
||||
protected:
|
||||
void onTransitionStart() override;
|
||||
void renderContent(RenderBackend &renderer) override;
|
||||
|
||||
private:
|
||||
TransitionDirection direction_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,217 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 存档类型枚举
|
||||
// ============================================================================
|
||||
enum class SaveDataType {
|
||||
Account, // 用户存档(与特定用户关联)
|
||||
Common, // 公共存档(所有用户共享)
|
||||
Cache, // 缓存数据(可删除)
|
||||
Device, // 设备存档
|
||||
Temporary, // 临时数据
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 用户ID结构(封装 Switch AccountUid)
|
||||
// ============================================================================
|
||||
struct UserId {
|
||||
uint64_t uid[2] = {0, 0};
|
||||
|
||||
bool isValid() const { return uid[0] != 0 || uid[1] != 0; }
|
||||
bool operator==(const UserId &other) const {
|
||||
return uid[0] == other.uid[0] && uid[1] == other.uid[1];
|
||||
}
|
||||
bool operator!=(const UserId &other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// DataStore 类 - 数据持久化(支持 Switch 存档系统)
|
||||
// ============================================================================
|
||||
class DataStore {
|
||||
public:
|
||||
DataStore();
|
||||
~DataStore();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 文件操作
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// 加载 INI 文件
|
||||
bool load(const std::string &filename);
|
||||
|
||||
/// 保存到 INI 文件
|
||||
bool save(const std::string &filename);
|
||||
|
||||
/// 获取当前文件名
|
||||
const std::string &getFilename() const { return filename_; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Switch 存档系统支持
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 挂载 Switch 存档数据
|
||||
* @param type 存档类型
|
||||
* @param userId 用户ID(Account 类型需要)
|
||||
* @param mountName 挂载点名称(默认 "save")
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool mountSaveData(SaveDataType type = SaveDataType::Account,
|
||||
const UserId &userId = UserId(),
|
||||
const std::string &mountName = "save");
|
||||
|
||||
/**
|
||||
* @brief 卸载存档挂载
|
||||
* @param mountName 挂载点名称
|
||||
*/
|
||||
void unmountSaveData(const std::string &mountName = "save");
|
||||
|
||||
/**
|
||||
* @brief 提交存档更改(重要:修改后必须调用)
|
||||
* @param mountName 挂载点名称
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool commitSaveData(const std::string &mountName = "save");
|
||||
|
||||
/**
|
||||
* @brief 检查存档是否已挂载
|
||||
*/
|
||||
bool isSaveDataMounted() const { return saveDataMounted_; }
|
||||
|
||||
/**
|
||||
* @brief 获取挂载点路径
|
||||
*/
|
||||
std::string getSaveDataPath(const std::string &path = "") const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 用户账户管理
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取当前预选用户ID
|
||||
* @return 用户ID(无效时返回空ID)
|
||||
*/
|
||||
static UserId getCurrentUserId();
|
||||
|
||||
/**
|
||||
* @brief 设置默认用户ID
|
||||
*/
|
||||
void setDefaultUserId(const UserId &userId) { defaultUserId_ = userId; }
|
||||
|
||||
/**
|
||||
* @brief 获取默认用户ID
|
||||
*/
|
||||
UserId getDefaultUserId() const { return defaultUserId_; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 数据读写
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// 获取字符串值
|
||||
std::string getString(const std::string §ion, const std::string &key,
|
||||
const std::string &defaultValue = "");
|
||||
|
||||
/// 获取整数值
|
||||
int getInt(const std::string §ion, const std::string &key,
|
||||
int defaultValue = 0);
|
||||
|
||||
/// 获取浮点数值
|
||||
float getFloat(const std::string §ion, const std::string &key,
|
||||
float defaultValue = 0.0f);
|
||||
|
||||
/// 获取布尔值
|
||||
bool getBool(const std::string §ion, const std::string &key,
|
||||
bool defaultValue = false);
|
||||
|
||||
/// 设置字符串值
|
||||
void setString(const std::string §ion, const std::string &key,
|
||||
const std::string &value);
|
||||
|
||||
/// 设置整数值
|
||||
void setInt(const std::string §ion, const std::string &key, int value);
|
||||
|
||||
/// 设置浮点数值
|
||||
void setFloat(const std::string §ion, const std::string &key,
|
||||
float value);
|
||||
|
||||
/// 设置布尔值
|
||||
void setBool(const std::string §ion, const std::string &key, bool value);
|
||||
|
||||
/// 删除键
|
||||
void removeKey(const std::string §ion, const std::string &key);
|
||||
|
||||
/// 删除整个 section
|
||||
void removeSection(const std::string §ion);
|
||||
|
||||
/// 检查键是否存在
|
||||
bool hasKey(const std::string §ion, const std::string &key);
|
||||
|
||||
/// 检查 section 是否存在
|
||||
bool hasSection(const std::string §ion);
|
||||
|
||||
/// 清除所有数据
|
||||
void clear();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 事务支持
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 开始事务(批量操作,延迟写入)
|
||||
*/
|
||||
void beginTransaction();
|
||||
|
||||
/**
|
||||
* @brief 提交事务(写入文件)
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool commit();
|
||||
|
||||
/**
|
||||
* @brief 回滚事务(放弃更改)
|
||||
*/
|
||||
void rollback();
|
||||
|
||||
/**
|
||||
* @brief 检查是否在事务中
|
||||
*/
|
||||
bool isInTransaction() const { return inTransaction_; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 工具方法
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// 获取所有 section 名称
|
||||
std::vector<std::string> getAllSections() const;
|
||||
|
||||
/// 获取指定 section 的所有 key
|
||||
std::vector<std::string> getAllKeys(const std::string §ion) const;
|
||||
|
||||
/// 从存档加载(自动处理挂载路径)
|
||||
bool loadFromSave(const std::string &path);
|
||||
|
||||
/// 保存到存档(自动处理挂载路径和提交)
|
||||
bool saveToSave(const std::string &path);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
UniquePtr<Impl> impl_;
|
||||
std::string filename_;
|
||||
std::string mountName_;
|
||||
UserId defaultUserId_;
|
||||
bool saveDataMounted_ = false;
|
||||
bool inTransaction_ = false;
|
||||
bool dirty_ = false;
|
||||
|
||||
// 内部辅助方法
|
||||
bool internalSave(const std::string &filename);
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <sstream>
|
||||
|
|
@ -16,13 +15,13 @@ namespace extra2d {
|
|||
// 日志级别枚举 - 映射到 SDL_LogPriority
|
||||
// ============================================================================
|
||||
enum class LogLevel {
|
||||
Trace = SDL_LOG_PRIORITY_VERBOSE, // SDL 详细日志
|
||||
Debug = SDL_LOG_PRIORITY_DEBUG, // SDL 调试日志
|
||||
Info = SDL_LOG_PRIORITY_INFO, // SDL 信息日志
|
||||
Warn = SDL_LOG_PRIORITY_WARN, // SDL 警告日志
|
||||
Error = SDL_LOG_PRIORITY_ERROR, // SDL 错误日志
|
||||
Fatal = SDL_LOG_PRIORITY_CRITICAL, // SDL 严重日志
|
||||
Off = SDL_LOG_PRIORITY_CRITICAL + 1 // 关闭日志 (使用 Critical+1 作为关闭标记)
|
||||
Trace = SDL_LOG_PRIORITY_VERBOSE, // SDL 详细日志
|
||||
Debug = SDL_LOG_PRIORITY_DEBUG, // SDL 调试日志
|
||||
Info = SDL_LOG_PRIORITY_INFO, // SDL 信息日志
|
||||
Warn = SDL_LOG_PRIORITY_WARN, // SDL 警告日志
|
||||
Error = SDL_LOG_PRIORITY_ERROR, // SDL 错误日志
|
||||
Fatal = SDL_LOG_PRIORITY_CRITICAL, // SDL 严重日志
|
||||
Off = SDL_LOG_PRIORITY_CRITICAL + 1 // 关闭日志 (使用 Critical+1 作为关闭标记)
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
|
|
|
|||
|
|
@ -1,492 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 对象池 - 自动管理的高性能内存池
|
||||
// 特性:
|
||||
// - 自动内存对齐
|
||||
// - 侵入式空闲链表(零额外内存开销)
|
||||
// - 线程本地缓存(减少锁竞争)
|
||||
// - 自动容量管理(自动扩展/收缩)
|
||||
// - 自动预热
|
||||
// - 异常安全
|
||||
// ============================================================================
|
||||
|
||||
// 线程本地缓存配置
|
||||
struct PoolConfig {
|
||||
static constexpr size_t DEFAULT_BLOCK_SIZE = 64;
|
||||
static constexpr size_t THREAD_CACHE_SIZE = 16;
|
||||
static constexpr size_t SHRINK_THRESHOLD_MS = 30000;
|
||||
static constexpr double SHRINK_RATIO = 0.5;
|
||||
};
|
||||
|
||||
template <typename T, size_t BlockSize = PoolConfig::DEFAULT_BLOCK_SIZE>
|
||||
class ObjectPool {
|
||||
public:
|
||||
static_assert(std::is_default_constructible_v<T>, "T must be default constructible");
|
||||
static_assert(std::is_destructible_v<T>, "T must be destructible");
|
||||
static_assert(BlockSize > 0, "BlockSize must be greater than 0");
|
||||
static_assert(alignof(T) <= alignof(std::max_align_t),
|
||||
"Alignment requirement too high");
|
||||
|
||||
ObjectPool()
|
||||
: freeListHead_(nullptr)
|
||||
, blocks_()
|
||||
, allocatedCount_(0)
|
||||
, totalCapacity_(0)
|
||||
, isDestroyed_(false)
|
||||
, lastShrinkCheck_(0)
|
||||
, prewarmed_(false) {
|
||||
}
|
||||
|
||||
~ObjectPool() {
|
||||
clear();
|
||||
}
|
||||
|
||||
ObjectPool(const ObjectPool&) = delete;
|
||||
ObjectPool& operator=(const ObjectPool&) = delete;
|
||||
ObjectPool(ObjectPool&&) noexcept = delete;
|
||||
ObjectPool& operator=(ObjectPool&&) noexcept = delete;
|
||||
|
||||
/**
|
||||
* @brief 分配一个对象(自动预热、自动扩展)
|
||||
* @return 指向分配的对象的指针,失败返回 nullptr
|
||||
*/
|
||||
T* allocate() {
|
||||
auto& cache = getThreadCache();
|
||||
|
||||
if (T* obj = cache.pop()) {
|
||||
new (obj) T();
|
||||
allocatedCount_.fetch_add(1, std::memory_order_relaxed);
|
||||
return obj;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (isDestroyed_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!prewarmed_) {
|
||||
prewarmInternal();
|
||||
}
|
||||
|
||||
if (!freeListHead_) {
|
||||
growInternal();
|
||||
}
|
||||
|
||||
T* obj = popFreeList();
|
||||
if (obj) {
|
||||
new (obj) T();
|
||||
allocatedCount_.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 分配并构造一个对象(带参数)
|
||||
*/
|
||||
template <typename... Args>
|
||||
T* allocate(Args&&... args) {
|
||||
auto& cache = getThreadCache();
|
||||
|
||||
if (T* obj = cache.pop()) {
|
||||
new (obj) T(std::forward<Args>(args)...);
|
||||
allocatedCount_.fetch_add(1, std::memory_order_relaxed);
|
||||
return obj;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (isDestroyed_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!prewarmed_) {
|
||||
prewarmInternal();
|
||||
}
|
||||
|
||||
if (!freeListHead_) {
|
||||
growInternal();
|
||||
}
|
||||
|
||||
T* obj = popFreeList();
|
||||
if (obj) {
|
||||
new (obj) T(std::forward<Args>(args)...);
|
||||
allocatedCount_.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 回收一个对象(自动异常处理)
|
||||
* @param obj 要回收的对象指针
|
||||
* @return true 如果对象成功回收
|
||||
*/
|
||||
bool deallocate(T* obj) {
|
||||
if (!obj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
obj->~T();
|
||||
} catch (const std::exception& e) {
|
||||
Logger::log(LogLevel::Error, "ObjectPool: Exception in destructor: {}", e.what());
|
||||
} catch (...) {
|
||||
Logger::log(LogLevel::Error, "ObjectPool: Unknown exception in destructor");
|
||||
}
|
||||
|
||||
auto& cache = getThreadCache();
|
||||
if (cache.push(obj)) {
|
||||
allocatedCount_.fetch_sub(1, std::memory_order_relaxed);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (!isDestroyed_) {
|
||||
pushFreeList(obj);
|
||||
allocatedCount_.fetch_sub(1, std::memory_order_relaxed);
|
||||
tryAutoShrink();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取当前已分配的对象数量
|
||||
*/
|
||||
size_t allocatedCount() const {
|
||||
return allocatedCount_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取池中总的对象容量
|
||||
*/
|
||||
size_t capacity() const {
|
||||
return totalCapacity_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取内存使用量(字节)
|
||||
*/
|
||||
size_t memoryUsage() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return blocks_.size() * BlockSize * sizeof(T);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清空所有内存块
|
||||
*/
|
||||
void clear() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
isDestroyed_ = true;
|
||||
|
||||
for (auto& block : blocks_) {
|
||||
alignedFree(block);
|
||||
}
|
||||
blocks_.clear();
|
||||
freeListHead_ = nullptr;
|
||||
totalCapacity_.store(0, std::memory_order_relaxed);
|
||||
allocatedCount_.store(0, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
private:
|
||||
struct FreeNode {
|
||||
FreeNode* next;
|
||||
};
|
||||
|
||||
struct ThreadCache {
|
||||
T* objects[PoolConfig::THREAD_CACHE_SIZE];
|
||||
size_t count = 0;
|
||||
|
||||
T* pop() {
|
||||
if (count > 0) {
|
||||
return objects[--count];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool push(T* obj) {
|
||||
if (count < PoolConfig::THREAD_CACHE_SIZE) {
|
||||
objects[count++] = obj;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
count = 0;
|
||||
}
|
||||
};
|
||||
|
||||
static ThreadCache& getThreadCache() {
|
||||
thread_local ThreadCache cache;
|
||||
return cache;
|
||||
}
|
||||
|
||||
static constexpr size_t Alignment = alignof(T);
|
||||
static constexpr size_t AlignedSize = ((sizeof(T) + Alignment - 1) / Alignment) * Alignment;
|
||||
|
||||
static void* alignedAlloc(size_t size) {
|
||||
#ifdef _WIN32
|
||||
return _aligned_malloc(size, Alignment);
|
||||
#else
|
||||
return std::aligned_alloc(Alignment, size);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void alignedFree(void* ptr) {
|
||||
#ifdef _WIN32
|
||||
_aligned_free(ptr);
|
||||
#else
|
||||
std::free(ptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
void prewarmInternal() {
|
||||
if (!freeListHead_) {
|
||||
growInternal();
|
||||
}
|
||||
prewarmed_ = true;
|
||||
}
|
||||
|
||||
void growInternal() {
|
||||
size_t blockSize = AlignedSize > sizeof(FreeNode) ? AlignedSize : sizeof(FreeNode);
|
||||
size_t totalSize = blockSize * BlockSize;
|
||||
|
||||
void* block = alignedAlloc(totalSize);
|
||||
if (!block) {
|
||||
Logger::log(LogLevel::Error, "ObjectPool: Failed to allocate memory block");
|
||||
return;
|
||||
}
|
||||
|
||||
blocks_.push_back(block);
|
||||
totalCapacity_.fetch_add(BlockSize, std::memory_order_relaxed);
|
||||
|
||||
char* ptr = static_cast<char*>(block);
|
||||
for (size_t i = 0; i < BlockSize; ++i) {
|
||||
pushFreeList(reinterpret_cast<T*>(ptr + i * blockSize));
|
||||
}
|
||||
}
|
||||
|
||||
void pushFreeList(T* obj) {
|
||||
FreeNode* node = reinterpret_cast<FreeNode*>(obj);
|
||||
node->next = freeListHead_;
|
||||
freeListHead_ = node;
|
||||
}
|
||||
|
||||
T* popFreeList() {
|
||||
if (!freeListHead_) {
|
||||
return nullptr;
|
||||
}
|
||||
FreeNode* node = freeListHead_;
|
||||
freeListHead_ = freeListHead_->next;
|
||||
return reinterpret_cast<T*>(node);
|
||||
}
|
||||
|
||||
void tryAutoShrink() {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now.time_since_epoch()).count();
|
||||
|
||||
if (elapsed - lastShrinkCheck_ < PoolConfig::SHRINK_THRESHOLD_MS) {
|
||||
return;
|
||||
}
|
||||
lastShrinkCheck_ = elapsed;
|
||||
|
||||
size_t allocated = allocatedCount_.load(std::memory_order_relaxed);
|
||||
size_t capacity = totalCapacity_.load(std::memory_order_relaxed);
|
||||
|
||||
if (capacity > BlockSize &&
|
||||
static_cast<double>(allocated) / capacity < PoolConfig::SHRINK_RATIO) {
|
||||
shrinkInternal();
|
||||
}
|
||||
}
|
||||
|
||||
void shrinkInternal() {
|
||||
size_t toFree = 0;
|
||||
size_t freeCount = 0;
|
||||
|
||||
FreeNode* node = freeListHead_;
|
||||
while (node) {
|
||||
++freeCount;
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
if (freeCount < BlockSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t blocksToKeep = blocks_.size();
|
||||
if (allocatedCount_.load(std::memory_order_relaxed) > 0) {
|
||||
blocksToKeep = (allocatedCount_.load() + BlockSize - 1) / BlockSize;
|
||||
blocksToKeep = std::max(blocksToKeep, size_t(1));
|
||||
}
|
||||
|
||||
if (blocksToKeep >= blocks_.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t blocksToRemove = blocks_.size() - blocksToKeep;
|
||||
for (size_t i = 0; i < blocksToRemove; ++i) {
|
||||
if (blocks_.empty()) break;
|
||||
|
||||
void* block = blocks_.back();
|
||||
blocks_.pop_back();
|
||||
alignedFree(block);
|
||||
totalCapacity_.fetch_sub(BlockSize, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
rebuildFreeList();
|
||||
}
|
||||
|
||||
void rebuildFreeList() {
|
||||
freeListHead_ = nullptr;
|
||||
size_t blockSize = AlignedSize > sizeof(FreeNode) ? AlignedSize : sizeof(FreeNode);
|
||||
|
||||
for (void* block : blocks_) {
|
||||
char* ptr = static_cast<char*>(block);
|
||||
for (size_t i = 0; i < BlockSize; ++i) {
|
||||
pushFreeList(reinterpret_cast<T*>(ptr + i * blockSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutable std::mutex mutex_;
|
||||
FreeNode* freeListHead_;
|
||||
std::vector<void*> blocks_;
|
||||
std::atomic<size_t> allocatedCount_;
|
||||
std::atomic<size_t> totalCapacity_;
|
||||
bool isDestroyed_;
|
||||
uint64_t lastShrinkCheck_;
|
||||
bool prewarmed_;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 智能指针支持的内存池分配器
|
||||
// ============================================================================
|
||||
|
||||
template <typename T, size_t BlockSize = PoolConfig::DEFAULT_BLOCK_SIZE>
|
||||
class PooledAllocator {
|
||||
public:
|
||||
using PoolType = ObjectPool<T, BlockSize>;
|
||||
|
||||
PooledAllocator() : pool_(std::make_shared<PoolType>()) {}
|
||||
explicit PooledAllocator(std::shared_ptr<PoolType> pool) : pool_(pool) {}
|
||||
|
||||
/**
|
||||
* @brief 创建一个使用内存池的对象(自动管理)
|
||||
*/
|
||||
template <typename... Args>
|
||||
Ptr<T> makeShared(Args&&... args) {
|
||||
std::weak_ptr<PoolType> weakPool = pool_;
|
||||
T* obj = pool_->allocate(std::forward<Args>(args)...);
|
||||
if (!obj) {
|
||||
return nullptr;
|
||||
}
|
||||
return Ptr<T>(obj, Deleter{weakPool});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取底层内存池
|
||||
*/
|
||||
std::shared_ptr<PoolType> getPool() const {
|
||||
return pool_;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Deleter {
|
||||
std::weak_ptr<PoolType> pool;
|
||||
|
||||
void operator()(T* obj) const {
|
||||
if (auto sharedPool = pool.lock()) {
|
||||
if (!sharedPool->deallocate(obj)) {
|
||||
Logger::log(LogLevel::Warn, "PooledAllocator: Pool destroyed, memory leaked");
|
||||
}
|
||||
} else {
|
||||
Logger::log(LogLevel::Warn, "PooledAllocator: Pool expired during deallocation");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<PoolType> pool_;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 全局内存池管理器 - 自动管理所有池的生命周期
|
||||
// ============================================================================
|
||||
|
||||
class ObjectPoolManager {
|
||||
public:
|
||||
static ObjectPoolManager& getInstance() {
|
||||
static ObjectPoolManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取指定类型的内存池(自动管理)
|
||||
*/
|
||||
template <typename T, size_t BlockSize = PoolConfig::DEFAULT_BLOCK_SIZE>
|
||||
std::shared_ptr<ObjectPool<T, BlockSize>> getPool() {
|
||||
static auto pool = std::make_shared<ObjectPool<T, BlockSize>>();
|
||||
return pool;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建使用内存池的对象(自动管理)
|
||||
*/
|
||||
template <typename T, size_t BlockSize = PoolConfig::DEFAULT_BLOCK_SIZE, typename... Args>
|
||||
Ptr<T> makePooled(Args&&... args) {
|
||||
auto pool = getPool<T, BlockSize>();
|
||||
std::weak_ptr<ObjectPool<T, BlockSize>> weakPool = pool;
|
||||
T* obj = pool->allocate(std::forward<Args>(args)...);
|
||||
if (!obj) {
|
||||
return nullptr;
|
||||
}
|
||||
return Ptr<T>(obj, [weakPool](T* p) {
|
||||
if (auto sharedPool = weakPool.lock()) {
|
||||
if (!sharedPool->deallocate(p)) {
|
||||
Logger::log(LogLevel::Warn, "ObjectPoolManager: Pool destroyed during deallocation");
|
||||
}
|
||||
} else {
|
||||
Logger::log(LogLevel::Warn, "ObjectPoolManager: Pool expired");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectPoolManager() = default;
|
||||
~ObjectPoolManager() = default;
|
||||
ObjectPoolManager(const ObjectPoolManager&) = delete;
|
||||
ObjectPoolManager& operator=(const ObjectPoolManager&) = delete;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 内存池宏定义(便于使用)
|
||||
// ============================================================================
|
||||
|
||||
#define E2D_DECLARE_POOL(T, BlockSize) \
|
||||
static extra2d::ObjectPool<T, BlockSize>& getPool() { \
|
||||
static extra2d::ObjectPool<T, BlockSize> pool; \
|
||||
return pool; \
|
||||
}
|
||||
|
||||
#define E2D_MAKE_POOLED(T, ...) \
|
||||
extra2d::ObjectPoolManager::getInstance().makePooled<T>(__VA_ARGS__)
|
||||
|
||||
} // namespace extra2d
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,5 +1,4 @@
|
|||
#include <extra2d/app/application.h>
|
||||
#include <extra2d/audio/audio_engine.h>
|
||||
#include <extra2d/event/event_dispatcher.h>
|
||||
#include <extra2d/event/event_queue.h>
|
||||
#include <extra2d/graphics/camera.h>
|
||||
|
|
@ -8,13 +7,10 @@
|
|||
#include <extra2d/graphics/vram_manager.h>
|
||||
#include <extra2d/platform/input.h>
|
||||
#include <extra2d/platform/window.h>
|
||||
#include <extra2d/resource/resource_manager.h>
|
||||
#include <extra2d/scene/scene_manager.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <extra2d/utils/object_pool.h>
|
||||
#include <extra2d/utils/timer.h>
|
||||
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
|
|
@ -24,7 +20,9 @@
|
|||
|
||||
namespace extra2d {
|
||||
|
||||
// 获取当前时间(秒)
|
||||
/**
|
||||
* @brief 获取当前时间(秒)
|
||||
*/
|
||||
static double getTimeSeconds() {
|
||||
#ifdef __SWITCH__
|
||||
struct timespec ts;
|
||||
|
|
@ -32,7 +30,6 @@ static double getTimeSeconds() {
|
|||
return static_cast<double>(ts.tv_sec) +
|
||||
static_cast<double>(ts.tv_nsec) / 1000000000.0;
|
||||
#else
|
||||
// PC 平台使用 chrono
|
||||
using namespace std::chrono;
|
||||
auto now = steady_clock::now();
|
||||
auto duration = now.time_since_epoch();
|
||||
|
|
@ -55,7 +52,6 @@ bool Application::init(const AppConfig &config) {
|
|||
|
||||
config_ = config;
|
||||
|
||||
// 确定平台类型
|
||||
PlatformType platform = config_.platform;
|
||||
if (platform == PlatformType::Auto) {
|
||||
#ifdef __SWITCH__
|
||||
|
|
@ -67,9 +63,6 @@ bool Application::init(const AppConfig &config) {
|
|||
|
||||
if (platform == PlatformType::Switch) {
|
||||
#ifdef __SWITCH__
|
||||
// ========================================
|
||||
// 1. 初始化 RomFS 文件系统(Switch 平台)
|
||||
// ========================================
|
||||
Result rc;
|
||||
rc = romfsInit();
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
|
|
@ -79,9 +72,6 @@ bool Application::init(const AppConfig &config) {
|
|||
rc);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 2. 初始化 nxlink 调试输出(Switch 平台)
|
||||
// ========================================
|
||||
rc = socketInitializeDefault();
|
||||
if (R_FAILED(rc)) {
|
||||
E2D_LOG_WARN(
|
||||
|
|
@ -90,9 +80,6 @@ bool Application::init(const AppConfig &config) {
|
|||
#endif
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 3. 创建窗口(包含 SDL_Init + GLES 3.2 上下文创建)
|
||||
// ========================================
|
||||
window_ = makeUnique<Window>();
|
||||
WindowConfig winConfig;
|
||||
winConfig.title = config.title;
|
||||
|
|
@ -100,12 +87,11 @@ bool Application::init(const AppConfig &config) {
|
|||
winConfig.height = config.height;
|
||||
if (platform == PlatformType::Switch) {
|
||||
winConfig.fullscreen = true;
|
||||
winConfig.fullscreenDesktop = false; // Switch 使用固定分辨率全屏
|
||||
winConfig.fullscreenDesktop = false;
|
||||
winConfig.resizable = false;
|
||||
winConfig.enableCursors = false;
|
||||
winConfig.enableDpiScale = false;
|
||||
} else {
|
||||
// PC 平台默认窗口模式
|
||||
winConfig.fullscreen = config.fullscreen;
|
||||
winConfig.resizable = config.resizable;
|
||||
winConfig.enableCursors = config.enableCursors;
|
||||
|
|
@ -119,9 +105,6 @@ bool Application::init(const AppConfig &config) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 4. 初始化渲染器
|
||||
// ========================================
|
||||
renderer_ = RenderBackend::create(config.renderBackend);
|
||||
if (!renderer_ || !renderer_->init(window_.get())) {
|
||||
E2D_LOG_ERROR("Failed to initialize renderer");
|
||||
|
|
@ -129,18 +112,13 @@ bool Application::init(const AppConfig &config) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 5. 初始化其他子系统
|
||||
// ========================================
|
||||
sceneManager_ = makeUnique<SceneManager>();
|
||||
resourceManager_ = makeUnique<ResourceManager>();
|
||||
timerManager_ = makeUnique<TimerManager>();
|
||||
eventQueue_ = makeUnique<EventQueue>();
|
||||
eventDispatcher_ = makeUnique<EventDispatcher>();
|
||||
camera_ = makeUnique<Camera>(0, static_cast<float>(window_->getWidth()),
|
||||
static_cast<float>(window_->getHeight()), 0);
|
||||
|
||||
// 创建视口适配器
|
||||
viewportAdapter_ = makeUnique<ViewportAdapter>();
|
||||
ViewportConfig vpConfig;
|
||||
vpConfig.logicWidth = static_cast<float>(config.width);
|
||||
|
|
@ -148,16 +126,12 @@ bool Application::init(const AppConfig &config) {
|
|||
vpConfig.mode = ViewportMode::AspectRatio;
|
||||
viewportAdapter_->setConfig(vpConfig);
|
||||
|
||||
// 关联到各子系统
|
||||
camera_->setViewportAdapter(viewportAdapter_.get());
|
||||
input().setViewportAdapter(viewportAdapter_.get());
|
||||
|
||||
// 初始更新
|
||||
viewportAdapter_->update(window_->getWidth(), window_->getHeight());
|
||||
|
||||
// 窗口大小回调
|
||||
window_->setResizeCallback([this](int width, int height) {
|
||||
// 更新视口适配器
|
||||
if (viewportAdapter_) {
|
||||
viewportAdapter_->update(width, height);
|
||||
}
|
||||
|
|
@ -175,14 +149,6 @@ bool Application::init(const AppConfig &config) {
|
|||
}
|
||||
});
|
||||
|
||||
// 初始化音频引擎
|
||||
AudioEngine::getInstance().initialize();
|
||||
|
||||
// ========================================
|
||||
// 6. 预热对象池(自动管理)
|
||||
// ========================================
|
||||
prewarmObjectPools();
|
||||
|
||||
initialized_ = true;
|
||||
running_ = true;
|
||||
|
||||
|
|
@ -190,72 +156,36 @@ bool Application::init(const AppConfig &config) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void Application::prewarmObjectPools() {
|
||||
E2D_LOG_INFO("Prewarming object pools...");
|
||||
|
||||
auto &poolManager = ObjectPoolManager::getInstance();
|
||||
|
||||
// 预热常用类型的对象池
|
||||
// 这些池会在首次使用时自动预热,但提前预热可以避免运行时延迟
|
||||
|
||||
E2D_LOG_INFO("Object pools prewarmed successfully");
|
||||
}
|
||||
|
||||
void Application::shutdown() {
|
||||
if (!initialized_)
|
||||
return;
|
||||
|
||||
E2D_LOG_INFO("Shutting down application...");
|
||||
|
||||
// 打印 VRAM 统计
|
||||
VRAMManager::getInstance().printStats();
|
||||
|
||||
// 打印对象池内存统计
|
||||
E2D_LOG_INFO("Object pool memory usage: {} bytes (auto-managed)",
|
||||
ObjectPoolManager::getInstance().getPool<Node>()->memoryUsage());
|
||||
|
||||
// 先结束所有场景,确保 onExit() 被正确调用
|
||||
if (sceneManager_) {
|
||||
sceneManager_->end();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 1. 先清理所有持有 GPU 资源的子系统
|
||||
// 必须在渲染器关闭前释放纹理等资源
|
||||
// ========================================
|
||||
sceneManager_.reset(); // 场景持有纹理引用
|
||||
resourceManager_.reset(); // 纹理缓存持有 GPU 纹理
|
||||
viewportAdapter_.reset(); // 视口适配器
|
||||
camera_.reset(); // 相机可能持有渲染目标
|
||||
sceneManager_.reset();
|
||||
viewportAdapter_.reset();
|
||||
camera_.reset();
|
||||
|
||||
// ========================================
|
||||
// 2. 关闭音频(不依赖 GPU)
|
||||
// ========================================
|
||||
AudioEngine::getInstance().shutdown();
|
||||
|
||||
// ========================================
|
||||
// 3. 清理其他子系统
|
||||
// ========================================
|
||||
timerManager_.reset();
|
||||
eventQueue_.reset();
|
||||
eventDispatcher_.reset();
|
||||
|
||||
// ========================================
|
||||
// 4. 最后关闭渲染器和窗口
|
||||
// 必须在所有 GPU 资源释放后才能关闭 OpenGL 上下文
|
||||
// ========================================
|
||||
if (renderer_) {
|
||||
renderer_->shutdown();
|
||||
renderer_.reset();
|
||||
}
|
||||
|
||||
// 销毁窗口(包含 SDL_Quit,会销毁 OpenGL 上下文)
|
||||
if (window_) {
|
||||
window_->destroy();
|
||||
window_.reset();
|
||||
}
|
||||
|
||||
// Switch 平台清理
|
||||
PlatformType platform = config_.platform;
|
||||
if (platform == PlatformType::Auto) {
|
||||
#ifdef __SWITCH__
|
||||
|
|
@ -286,12 +216,10 @@ void Application::run() {
|
|||
lastFrameTime_ = getTimeSeconds();
|
||||
|
||||
#ifdef __SWITCH__
|
||||
// SDL2 on Switch 内部已处理 appletMainLoop
|
||||
while (running_ && !window_->shouldClose()) {
|
||||
mainLoop();
|
||||
}
|
||||
#else
|
||||
// PC 平台主循环
|
||||
while (running_ && !window_->shouldClose()) {
|
||||
mainLoop();
|
||||
}
|
||||
|
|
@ -319,14 +247,12 @@ void Application::resume() {
|
|||
}
|
||||
|
||||
void Application::mainLoop() {
|
||||
// 计算 delta time
|
||||
double currentTime = getTimeSeconds();
|
||||
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_);
|
||||
lastFrameTime_ = currentTime;
|
||||
|
||||
totalTime_ += deltaTime_;
|
||||
|
||||
// 计算 FPS
|
||||
frameCount_++;
|
||||
fpsTimer_ += deltaTime_;
|
||||
if (fpsTimer_ >= 1.0f) {
|
||||
|
|
@ -335,20 +261,16 @@ void Application::mainLoop() {
|
|||
fpsTimer_ -= 1.0f;
|
||||
}
|
||||
|
||||
// 处理窗口事件(SDL_PollEvent + 输入更新)
|
||||
window_->pollEvents();
|
||||
|
||||
// 处理事件队列
|
||||
if (eventDispatcher_ && eventQueue_) {
|
||||
eventDispatcher_->processQueue(*eventQueue_);
|
||||
}
|
||||
|
||||
// 更新
|
||||
if (!paused_) {
|
||||
update();
|
||||
}
|
||||
|
||||
// 渲染
|
||||
render();
|
||||
|
||||
if (!config_.vsync && config_.fpsLimit > 0) {
|
||||
|
|
@ -378,13 +300,11 @@ void Application::render() {
|
|||
return;
|
||||
}
|
||||
|
||||
// 应用视口适配器
|
||||
if (viewportAdapter_) {
|
||||
const auto &vp = viewportAdapter_->getViewport();
|
||||
renderer_->setViewport(static_cast<int>(vp.origin.x),
|
||||
static_cast<int>(vp.origin.y),
|
||||
static_cast<int>(vp.size.width),
|
||||
static_cast<int>(vp.size.height));
|
||||
renderer_->setViewport(
|
||||
static_cast<int>(vp.origin.x), static_cast<int>(vp.origin.y),
|
||||
static_cast<int>(vp.size.width), static_cast<int>(vp.size.height));
|
||||
} else {
|
||||
renderer_->setViewport(0, 0, window_->getWidth(), window_->getHeight());
|
||||
}
|
||||
|
|
@ -400,12 +320,8 @@ void Application::render() {
|
|||
|
||||
Input &Application::input() { return *window_->getInput(); }
|
||||
|
||||
AudioEngine &Application::audio() { return AudioEngine::getInstance(); }
|
||||
|
||||
SceneManager &Application::scenes() { return *sceneManager_; }
|
||||
|
||||
ResourceManager &Application::resources() { return *resourceManager_; }
|
||||
|
||||
TimerManager &Application::timers() { return *timerManager_; }
|
||||
|
||||
EventQueue &Application::eventQueue() { return *eventQueue_; }
|
||||
|
|
@ -416,14 +332,11 @@ Camera &Application::camera() { return *camera_; }
|
|||
|
||||
ViewportAdapter &Application::viewportAdapter() { return *viewportAdapter_; }
|
||||
|
||||
void Application::enterScene(Ptr<Scene> scene) { enterScene(scene, nullptr); }
|
||||
|
||||
void Application::enterScene(Ptr<Scene> scene,
|
||||
Ptr<class TransitionScene> transitionScene) {
|
||||
void Application::enterScene(Ptr<Scene> scene) {
|
||||
if (sceneManager_ && scene) {
|
||||
scene->setViewportSize(static_cast<float>(window_->getWidth()),
|
||||
static_cast<float>(window_->getHeight()));
|
||||
sceneManager_->enterScene(scene, transitionScene);
|
||||
sceneManager_->enterScene(scene);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,134 +0,0 @@
|
|||
#include "extra2d/audio/audio_engine.h"
|
||||
#include "extra2d/audio/sound.h"
|
||||
#include "extra2d/utils/logger.h"
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_mixer.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
AudioEngine &AudioEngine::getInstance() {
|
||||
static AudioEngine instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
AudioEngine::~AudioEngine() { shutdown(); }
|
||||
|
||||
bool AudioEngine::initialize() {
|
||||
if (initialized_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 初始化 SDL 音频子系统
|
||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
|
||||
E2D_LOG_ERROR("Failed to initialize SDL audio: {}", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 打开音频设备
|
||||
if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 4096) < 0) {
|
||||
E2D_LOG_ERROR("Failed to open audio: {}", Mix_GetError());
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 分配混音通道
|
||||
Mix_AllocateChannels(32);
|
||||
|
||||
initialized_ = true;
|
||||
E2D_LOG_INFO("AudioEngine initialized successfully (SDL2_mixer)");
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioEngine::shutdown() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
unloadAllSounds();
|
||||
|
||||
Mix_CloseAudio();
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
|
||||
initialized_ = false;
|
||||
E2D_LOG_INFO("AudioEngine shutdown");
|
||||
}
|
||||
|
||||
std::shared_ptr<Sound> AudioEngine::loadSound(const std::string &filePath) {
|
||||
return loadSound(filePath, filePath);
|
||||
}
|
||||
|
||||
std::shared_ptr<Sound> AudioEngine::loadSound(const std::string &name,
|
||||
const std::string &filePath) {
|
||||
if (!initialized_) {
|
||||
E2D_LOG_ERROR("AudioEngine not initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 检查是否已存在
|
||||
auto it = sounds_.find(name);
|
||||
if (it != sounds_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
Mix_Chunk *chunk = Mix_LoadWAV(filePath.c_str());
|
||||
if (!chunk) {
|
||||
E2D_LOG_ERROR("Failed to load sound: {} ({})", filePath, Mix_GetError());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto sound = std::shared_ptr<Sound>(new Sound(name, filePath, chunk));
|
||||
sounds_[name] = sound;
|
||||
|
||||
E2D_LOG_DEBUG("Loaded sound: {}", filePath);
|
||||
return sound;
|
||||
}
|
||||
|
||||
std::shared_ptr<Sound> AudioEngine::getSound(const std::string &name) {
|
||||
auto it = sounds_.find(name);
|
||||
if (it != sounds_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void AudioEngine::unloadSound(const std::string &name) {
|
||||
auto it = sounds_.find(name);
|
||||
if (it != sounds_.end()) {
|
||||
sounds_.erase(it);
|
||||
E2D_LOG_DEBUG("Unloaded sound: {}", name);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioEngine::unloadAllSounds() {
|
||||
stopAll();
|
||||
sounds_.clear();
|
||||
E2D_LOG_DEBUG("Unloaded all sounds");
|
||||
}
|
||||
|
||||
void AudioEngine::setMasterVolume(float volume) {
|
||||
masterVolume_ = volume;
|
||||
int mixVol = static_cast<int>(volume * MIX_MAX_VOLUME);
|
||||
Mix_Volume(-1, mixVol); // 所有通道
|
||||
Mix_VolumeMusic(mixVol); // 音乐
|
||||
}
|
||||
|
||||
float AudioEngine::getMasterVolume() const { return masterVolume_; }
|
||||
|
||||
void AudioEngine::pauseAll() {
|
||||
Mix_Pause(-1); // 暂停所有通道
|
||||
}
|
||||
|
||||
void AudioEngine::resumeAll() {
|
||||
Mix_Resume(-1); // 恢复所有通道
|
||||
}
|
||||
|
||||
void AudioEngine::stopAll() {
|
||||
for (auto &pair : sounds_) {
|
||||
if (pair.second) {
|
||||
pair.second->stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
#include <SDL2/SDL_mixer.h>
|
||||
#include <extra2d/audio/sound.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
Sound::Sound(const std::string &name, const std::string &filePath,
|
||||
Mix_Chunk *chunk)
|
||||
: name_(name), filePath_(filePath), chunk_(chunk) {}
|
||||
|
||||
Sound::~Sound() {
|
||||
if (channel_ >= 0) {
|
||||
Mix_HaltChannel(channel_);
|
||||
channel_ = -1;
|
||||
}
|
||||
if (chunk_) {
|
||||
Mix_FreeChunk(chunk_);
|
||||
chunk_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool Sound::play() {
|
||||
if (!chunk_) {
|
||||
E2D_LOG_WARN("Sound::play() failed: chunk is null for {}", name_);
|
||||
return false;
|
||||
}
|
||||
|
||||
int loops = looping_ ? -1 : 0;
|
||||
int newChannel = Mix_PlayChannel(-1, chunk_, loops);
|
||||
|
||||
if (newChannel < 0) {
|
||||
E2D_LOG_WARN("Sound::play() failed: no free channel for {} ({})", name_, Mix_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
channel_ = newChannel;
|
||||
|
||||
int mixVol = static_cast<int>(volume_ * MIX_MAX_VOLUME);
|
||||
Mix_Volume(channel_, mixVol);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Sound::pause() {
|
||||
if (channel_ >= 0) {
|
||||
Mix_Pause(channel_);
|
||||
}
|
||||
}
|
||||
|
||||
void Sound::resume() {
|
||||
if (channel_ >= 0) {
|
||||
Mix_Resume(channel_);
|
||||
}
|
||||
}
|
||||
|
||||
void Sound::stop() {
|
||||
if (channel_ >= 0) {
|
||||
Mix_HaltChannel(channel_);
|
||||
channel_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool Sound::isPlaying() const {
|
||||
if (channel_ < 0) {
|
||||
return false;
|
||||
}
|
||||
return Mix_Playing(channel_) && !Mix_Paused(channel_);
|
||||
}
|
||||
|
||||
bool Sound::isPaused() const {
|
||||
if (channel_ < 0) {
|
||||
return false;
|
||||
}
|
||||
return Mix_Paused(channel_) != 0;
|
||||
}
|
||||
|
||||
void Sound::setVolume(float volume) {
|
||||
volume_ = volume;
|
||||
if (channel_ >= 0) {
|
||||
int mixVol = static_cast<int>(volume * MIX_MAX_VOLUME);
|
||||
Mix_Volume(channel_, mixVol);
|
||||
}
|
||||
}
|
||||
|
||||
void Sound::setLooping(bool looping) {
|
||||
looping_ = looping;
|
||||
// SDL_mixer 的循环在播放时设置,运行中无法更改
|
||||
// 如果需要即时生效,需要重新播放
|
||||
}
|
||||
|
||||
void Sound::setPitch(float pitch) {
|
||||
pitch_ = pitch;
|
||||
// SDL2_mixer 不直接支持变速播放
|
||||
// 需要更高级的音频处理才能实现
|
||||
}
|
||||
|
||||
float Sound::getDuration() const {
|
||||
// SDL2_mixer 没有直接获取时长的 API
|
||||
// 返回 0 表示不支持
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float Sound::getCursor() const {
|
||||
// SDL2_mixer 没有直接获取播放位置的 API
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
void Sound::setCursor(float /*seconds*/) {
|
||||
// SDL2_mixer 不支持 seek 到特定位置 (对 chunks)
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,604 @@
|
|||
#include <extra2d/graphics/texture_pool.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <extra2d/scene/scene.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// TexturePool 实现
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
TexturePool::TexturePool()
|
||||
: scene_(nullptr)
|
||||
, maxMemoryUsage_(0)
|
||||
, currentMemoryUsage_(0)
|
||||
, cacheHits_(0)
|
||||
, cacheMisses_(0)
|
||||
, evictionCount_(0) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param scene 场景指针
|
||||
* @param maxMemoryUsage 最大内存使用量(0 表示无限制)
|
||||
*/
|
||||
TexturePool::TexturePool(Scene* scene, size_t maxMemoryUsage)
|
||||
: scene_(scene)
|
||||
, maxMemoryUsage_(maxMemoryUsage)
|
||||
, currentMemoryUsage_(0)
|
||||
, cacheHits_(0)
|
||||
, cacheMisses_(0)
|
||||
, evictionCount_(0) {
|
||||
E2D_LOG_INFO("TexturePool created with max memory: {} bytes", maxMemoryUsage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化纹理池
|
||||
* @param scene 场景指针
|
||||
* @param maxMemoryUsage 最大内存使用量(0 表示无限制)
|
||||
*/
|
||||
void TexturePool::init(Scene* scene, size_t maxMemoryUsage) {
|
||||
scene_ = scene;
|
||||
maxMemoryUsage_ = maxMemoryUsage;
|
||||
E2D_LOG_INFO("TexturePool initialized with max memory: {} bytes", maxMemoryUsage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
TexturePool::~TexturePool() {
|
||||
clear();
|
||||
E2D_LOG_INFO("TexturePool destroyed");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 纹理加载
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief 从文件加载纹理
|
||||
* @param path 文件路径
|
||||
* @param options 加载选项
|
||||
* @return 纹理引用
|
||||
*/
|
||||
TextureRef TexturePool::load(const std::string& path, const TextureLoadOptions& options) {
|
||||
return load(path, Rect::Zero(), options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从文件加载纹理区域
|
||||
* @param path 文件路径
|
||||
* @param region 纹理区域
|
||||
* @param options 加载选项
|
||||
* @return 纹理引用
|
||||
*/
|
||||
TextureRef TexturePool::load(const std::string& path, const Rect& region,
|
||||
const TextureLoadOptions& options) {
|
||||
TextureKey key(path, region);
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
// 检查缓存
|
||||
auto it = cache_.find(key);
|
||||
if (it != cache_.end()) {
|
||||
// 缓存命中
|
||||
it->second.touch();
|
||||
it->second.refCount.fetch_add(1, std::memory_order_relaxed);
|
||||
cacheHits_.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
E2D_LOG_DEBUG("Texture cache hit: {}", path);
|
||||
return TextureRef(it->second.texture, &it->second, &mutex_);
|
||||
}
|
||||
|
||||
// 缓存未命中
|
||||
cacheMisses_.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
// 获取渲染后端
|
||||
RenderBackend* backend = nullptr;
|
||||
if (scene_) {
|
||||
// 假设 Scene 有获取 RenderBackend 的方法
|
||||
// 这里需要根据实际接口调整
|
||||
backend = nullptr; // TODO: 从 Scene 获取 RenderBackend
|
||||
}
|
||||
|
||||
if (!backend) {
|
||||
E2D_LOG_ERROR("TexturePool: RenderBackend not available");
|
||||
return TextureRef();
|
||||
}
|
||||
|
||||
// 加载纹理
|
||||
Ptr<Texture> texture = backend->loadTexture(path);
|
||||
if (!texture) {
|
||||
E2D_LOG_ERROR("TexturePool: Failed to load texture: {}", path);
|
||||
return TextureRef();
|
||||
}
|
||||
|
||||
// 计算内存大小
|
||||
size_t memorySize = calculateTextureMemory(texture.get());
|
||||
|
||||
// 检查内存限制
|
||||
if (maxMemoryUsage_ > 0 && currentMemoryUsage_ + memorySize > maxMemoryUsage_) {
|
||||
// 尝试淘汰
|
||||
evictLRU(currentMemoryUsage_ + memorySize - maxMemoryUsage_);
|
||||
|
||||
// 再次检查
|
||||
if (currentMemoryUsage_ + memorySize > maxMemoryUsage_) {
|
||||
E2D_LOG_WARN("TexturePool: Memory limit exceeded, cannot load texture: {}", path);
|
||||
return TextureRef();
|
||||
}
|
||||
}
|
||||
|
||||
// 创建缓存条目
|
||||
auto result = cache_.emplace(key, TexturePoolEntry(nullptr, key, 0));
|
||||
if (result.second) {
|
||||
result.first->second.texture = texture;
|
||||
result.first->second.memorySize = memorySize;
|
||||
result.first->second.refCount.store(1, std::memory_order_relaxed);
|
||||
result.first->second.touch();
|
||||
currentMemoryUsage_ += memorySize;
|
||||
E2D_LOG_INFO("TexturePool: Loaded texture: {} ({} bytes)", path, memorySize);
|
||||
return TextureRef(texture, &result.first->second, &mutex_);
|
||||
}
|
||||
|
||||
return TextureRef();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从内存加载纹理
|
||||
* @param data 像素数据
|
||||
* @param width 宽度
|
||||
* @param height 高度
|
||||
* @param channels 通道数
|
||||
* @param key 缓存键
|
||||
* @return 纹理引用
|
||||
*/
|
||||
TextureRef TexturePool::loadFromMemory(const uint8_t* data, int width, int height,
|
||||
int channels, const std::string& key) {
|
||||
TextureKey textureKey(key);
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
// 检查缓存
|
||||
auto it = cache_.find(textureKey);
|
||||
if (it != cache_.end()) {
|
||||
it->second.touch();
|
||||
it->second.refCount.fetch_add(1, std::memory_order_relaxed);
|
||||
cacheHits_.fetch_add(1, std::memory_order_relaxed);
|
||||
return TextureRef(it->second.texture, &it->second, &mutex_);
|
||||
}
|
||||
|
||||
cacheMisses_.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
// 获取渲染后端
|
||||
RenderBackend* backend = nullptr;
|
||||
if (scene_) {
|
||||
backend = nullptr; // TODO: 从 Scene 获取 RenderBackend
|
||||
}
|
||||
|
||||
if (!backend) {
|
||||
E2D_LOG_ERROR("TexturePool: RenderBackend not available");
|
||||
return TextureRef();
|
||||
}
|
||||
|
||||
// 创建纹理
|
||||
Ptr<Texture> texture = backend->createTexture(width, height, data, channels);
|
||||
if (!texture) {
|
||||
E2D_LOG_ERROR("TexturePool: Failed to create texture from memory");
|
||||
return TextureRef();
|
||||
}
|
||||
|
||||
// 计算内存大小
|
||||
size_t memorySize = calculateTextureMemory(texture.get());
|
||||
|
||||
// 检查内存限制
|
||||
if (maxMemoryUsage_ > 0 && currentMemoryUsage_ + memorySize > maxMemoryUsage_) {
|
||||
evictLRU(currentMemoryUsage_ + memorySize - maxMemoryUsage_);
|
||||
|
||||
if (currentMemoryUsage_ + memorySize > maxMemoryUsage_) {
|
||||
E2D_LOG_WARN("TexturePool: Memory limit exceeded");
|
||||
return TextureRef();
|
||||
}
|
||||
}
|
||||
|
||||
// 创建缓存条目
|
||||
auto result = cache_.emplace(textureKey, TexturePoolEntry(nullptr, textureKey, 0));
|
||||
if (result.second) {
|
||||
result.first->second.texture = texture;
|
||||
result.first->second.memorySize = memorySize;
|
||||
result.first->second.refCount.store(1, std::memory_order_relaxed);
|
||||
result.first->second.touch();
|
||||
currentMemoryUsage_ += memorySize;
|
||||
E2D_LOG_INFO("TexturePool: Created texture from memory ({} bytes)", memorySize);
|
||||
return TextureRef(texture, &result.first->second, &mutex_);
|
||||
}
|
||||
|
||||
return TextureRef();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取或加载纹理
|
||||
* @param path 文件路径
|
||||
* @param options 加载选项
|
||||
* @return 纹理引用
|
||||
*/
|
||||
TextureRef TexturePool::getOrLoad(const std::string& path, const TextureLoadOptions& options) {
|
||||
return getOrLoad(path, Rect::Zero(), options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取或加载纹理区域
|
||||
* @param path 文件路径
|
||||
* @param region 纹理区域
|
||||
* @param options 加载选项
|
||||
* @return 纹理引用
|
||||
*/
|
||||
TextureRef TexturePool::getOrLoad(const std::string& path, const Rect& region,
|
||||
const TextureLoadOptions& options) {
|
||||
TextureKey key(path, region);
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
// 检查缓存
|
||||
auto it = cache_.find(key);
|
||||
if (it != cache_.end()) {
|
||||
it->second.touch();
|
||||
it->second.refCount.fetch_add(1, std::memory_order_relaxed);
|
||||
cacheHits_.fetch_add(1, std::memory_order_relaxed);
|
||||
return TextureRef(it->second.texture, &it->second, &mutex_);
|
||||
}
|
||||
|
||||
// 释放锁后调用 load
|
||||
// 注意:这里需要重新设计以避免死锁
|
||||
// 简化处理:直接在这里加载
|
||||
|
||||
cacheMisses_.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
RenderBackend* backend = nullptr;
|
||||
if (scene_) {
|
||||
backend = nullptr; // TODO: 从 Scene 获取 RenderBackend
|
||||
}
|
||||
|
||||
if (!backend) {
|
||||
E2D_LOG_ERROR("TexturePool: RenderBackend not available");
|
||||
return TextureRef();
|
||||
}
|
||||
|
||||
Ptr<Texture> texture = backend->loadTexture(path);
|
||||
if (!texture) {
|
||||
E2D_LOG_ERROR("TexturePool: Failed to load texture: {}", path);
|
||||
return TextureRef();
|
||||
}
|
||||
|
||||
size_t memorySize = calculateTextureMemory(texture.get());
|
||||
|
||||
if (maxMemoryUsage_ > 0 && currentMemoryUsage_ + memorySize > maxMemoryUsage_) {
|
||||
evictLRU(currentMemoryUsage_ + memorySize - maxMemoryUsage_);
|
||||
|
||||
if (currentMemoryUsage_ + memorySize > maxMemoryUsage_) {
|
||||
E2D_LOG_WARN("TexturePool: Memory limit exceeded");
|
||||
return TextureRef();
|
||||
}
|
||||
}
|
||||
|
||||
auto result = cache_.emplace(key, TexturePoolEntry(nullptr, key, 0));
|
||||
if (result.second) {
|
||||
result.first->second.texture = texture;
|
||||
result.first->second.memorySize = memorySize;
|
||||
result.first->second.refCount.store(1, std::memory_order_relaxed);
|
||||
result.first->second.touch();
|
||||
currentMemoryUsage_ += memorySize;
|
||||
return TextureRef(texture, &result.first->second, &mutex_);
|
||||
}
|
||||
|
||||
return TextureRef();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 引用计数管理
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief 增加引用计数
|
||||
* @param key 纹理键
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool TexturePool::addRef(const TextureKey& key) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
auto it = cache_.find(key);
|
||||
if (it != cache_.end()) {
|
||||
it->second.touch();
|
||||
it->second.refCount.fetch_add(1, std::memory_order_relaxed);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 减少引用计数
|
||||
* @param key 纹理键
|
||||
* @return 减少后的引用计数
|
||||
*/
|
||||
uint32_t TexturePool::release(const TextureKey& key) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
auto it = cache_.find(key);
|
||||
if (it != cache_.end()) {
|
||||
uint32_t count = it->second.refCount.fetch_sub(1, std::memory_order_relaxed);
|
||||
return count > 0 ? count - 1 : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取引用计数
|
||||
* @param key 纹理键
|
||||
* @return 引用计数
|
||||
*/
|
||||
uint32_t TexturePool::getRefCount(const TextureKey& key) const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
auto it = cache_.find(key);
|
||||
if (it != cache_.end()) {
|
||||
return it->second.refCount.load(std::memory_order_relaxed);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 缓存管理
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief 检查纹理是否已缓存
|
||||
* @param key 纹理键
|
||||
* @return 是否已缓存
|
||||
*/
|
||||
bool TexturePool::isCached(const TextureKey& key) const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return cache_.find(key) != cache_.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从缓存中移除纹理
|
||||
* @param key 纹理键
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool TexturePool::removeFromCache(const TextureKey& key) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
auto it = cache_.find(key);
|
||||
if (it != cache_.end()) {
|
||||
currentMemoryUsage_ -= it->second.memorySize;
|
||||
cache_.erase(it);
|
||||
E2D_LOG_DEBUG("TexturePool: Removed texture from cache");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 垃圾回收(移除引用计数为 0 的纹理)
|
||||
* @return 移除的纹理数量
|
||||
*/
|
||||
size_t TexturePool::collectGarbage() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
size_t removed = 0;
|
||||
for (auto it = cache_.begin(); it != cache_.end(); ) {
|
||||
if (it->second.refCount.load(std::memory_order_relaxed) == 0) {
|
||||
currentMemoryUsage_ -= it->second.memorySize;
|
||||
it = cache_.erase(it);
|
||||
++removed;
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
if (removed > 0) {
|
||||
E2D_LOG_INFO("TexturePool: Garbage collected {} textures", removed);
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清空所有缓存
|
||||
*/
|
||||
void TexturePool::clear() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
cache_.clear();
|
||||
currentMemoryUsage_ = 0;
|
||||
|
||||
E2D_LOG_INFO("TexturePool: Cleared all textures");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 内存管理
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取当前内存使用量
|
||||
* @return 内存使用量(字节)
|
||||
*/
|
||||
size_t TexturePool::getMemoryUsage() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return currentMemoryUsage_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置最大内存使用量
|
||||
* @param maxMemory 最大内存使用量(0 表示无限制)
|
||||
*/
|
||||
void TexturePool::setMaxMemoryUsage(size_t maxMemory) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
maxMemoryUsage_ = maxMemory;
|
||||
|
||||
// 如果当前内存超过新的限制,执行淘汰
|
||||
if (maxMemoryUsage_ > 0 && currentMemoryUsage_ > maxMemoryUsage_) {
|
||||
evictLRU(maxMemoryUsage_);
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("TexturePool: Max memory set to {} bytes", maxMemory);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 执行 LRU 淘汰
|
||||
* @param targetMemory 目标内存使用量
|
||||
* @return 淘汰的纹理数量
|
||||
*/
|
||||
size_t TexturePool::evictLRU(size_t targetMemory) {
|
||||
// 注意:调用者应该已持有锁
|
||||
|
||||
if (cache_.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 收集所有条目并按最后访问时间排序
|
||||
std::vector<std::pair<TextureKey, uint64_t>> entries;
|
||||
entries.reserve(cache_.size());
|
||||
|
||||
for (const auto& pair : cache_) {
|
||||
// 只淘汰引用计数为 0 的纹理
|
||||
if (pair.second.refCount.load(std::memory_order_relaxed) == 0) {
|
||||
entries.emplace_back(pair.first, pair.second.lastAccessTime);
|
||||
}
|
||||
}
|
||||
|
||||
// 按访问时间升序排序(最旧的在前)
|
||||
std::sort(entries.begin(), entries.end(),
|
||||
[](const auto& a, const auto& b) { return a.second < b.second; });
|
||||
|
||||
size_t evicted = 0;
|
||||
size_t target = targetMemory > 0 ? targetMemory : 0;
|
||||
|
||||
for (const auto& entry : entries) {
|
||||
if (targetMemory > 0 && currentMemoryUsage_ <= target) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto it = cache_.find(entry.first);
|
||||
if (it != cache_.end()) {
|
||||
currentMemoryUsage_ -= it->second.memorySize;
|
||||
cache_.erase(it);
|
||||
++evicted;
|
||||
}
|
||||
}
|
||||
|
||||
if (evicted > 0) {
|
||||
evictionCount_.fetch_add(evicted, std::memory_order_relaxed);
|
||||
E2D_LOG_INFO("TexturePool: LRU evicted {} textures", evicted);
|
||||
}
|
||||
|
||||
return evicted;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 统计信息
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取统计信息
|
||||
* @return 统计信息
|
||||
*/
|
||||
TexturePool::Stats TexturePool::getStats() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
Stats stats;
|
||||
stats.textureCount = cache_.size();
|
||||
stats.memoryUsage = currentMemoryUsage_;
|
||||
stats.maxMemoryUsage = maxMemoryUsage_;
|
||||
stats.cacheHits = cacheHits_.load(std::memory_order_relaxed);
|
||||
stats.cacheMisses = cacheMisses_.load(std::memory_order_relaxed);
|
||||
stats.evictionCount = evictionCount_.load(std::memory_order_relaxed);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 重置统计信息
|
||||
*/
|
||||
void TexturePool::resetStats() {
|
||||
cacheHits_.store(0, std::memory_order_relaxed);
|
||||
cacheMisses_.store(0, std::memory_order_relaxed);
|
||||
evictionCount_.store(0, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 私有方法
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief 计算纹理内存大小
|
||||
* @param texture 纹理对象
|
||||
* @return 内存大小(字节)
|
||||
*/
|
||||
size_t TexturePool::calculateTextureMemory(const Texture* texture) {
|
||||
if (!texture) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int width = texture->getWidth();
|
||||
int height = texture->getHeight();
|
||||
int channels = texture->getChannels();
|
||||
|
||||
// 基础内存计算
|
||||
size_t baseSize = static_cast<size_t>(width) * height * channels;
|
||||
|
||||
// 根据像素格式调整
|
||||
PixelFormat format = texture->getFormat();
|
||||
switch (format) {
|
||||
case PixelFormat::RGB16F:
|
||||
case PixelFormat::RGBA16F:
|
||||
baseSize *= 2; // 半精度浮点
|
||||
break;
|
||||
case PixelFormat::RGB32F:
|
||||
case PixelFormat::RGBA32F:
|
||||
baseSize *= 4; // 全精度浮点
|
||||
break;
|
||||
case PixelFormat::Depth16:
|
||||
baseSize = static_cast<size_t>(width) * height * 2;
|
||||
break;
|
||||
case PixelFormat::Depth24:
|
||||
case PixelFormat::Depth24Stencil8:
|
||||
baseSize = static_cast<size_t>(width) * height * 4;
|
||||
break;
|
||||
case PixelFormat::Depth32F:
|
||||
baseSize = static_cast<size_t>(width) * height * 4;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// 考虑 Mipmaps(大约增加 33% 内存)
|
||||
// 注意:这里假设生成了 mipmaps,实际应该根据 TextureLoadOptions 判断
|
||||
// baseSize = baseSize * 4 / 3;
|
||||
|
||||
return baseSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查是否需要淘汰
|
||||
* @return 是否需要淘汰
|
||||
*/
|
||||
bool TexturePool::needsEviction() const {
|
||||
return maxMemoryUsage_ > 0 && currentMemoryUsage_ > maxMemoryUsage_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 尝试自动淘汰
|
||||
*/
|
||||
void TexturePool::tryAutoEvict() {
|
||||
if (needsEviction()) {
|
||||
evictLRU(maxMemoryUsage_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -4,18 +4,15 @@
|
|||
#include <extra2d/graphics/render_command.h>
|
||||
#include <extra2d/platform/input.h>
|
||||
#include <extra2d/scene/scene_manager.h>
|
||||
#include <extra2d/scene/transition_box_scene.h>
|
||||
#include <extra2d/scene/transition_fade_scene.h>
|
||||
#include <extra2d/scene/transition_flip_scene.h>
|
||||
#include <extra2d/scene/transition_scale_scene.h>
|
||||
#include <extra2d/scene/transition_scene.h>
|
||||
#include <extra2d/scene/transition_slide_scene.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief 命中测试 - 从节点树中找到最上层的可交互节点
|
||||
*/
|
||||
Node *hitTestTopmost(const Ptr<Node> &node, const Vec2 &worldPos) {
|
||||
if (!node || !node->isVisible()) {
|
||||
return nullptr;
|
||||
|
|
@ -45,6 +42,9 @@ Node *hitTestTopmost(const Ptr<Node> &node, const Vec2 &worldPos) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 向节点分发事件
|
||||
*/
|
||||
void dispatchToNode(Node *node, Event &event) {
|
||||
if (!node) {
|
||||
return;
|
||||
|
|
@ -84,13 +84,11 @@ void SceneManager::replaceScene(Ptr<Scene> scene) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Pop current scene
|
||||
auto oldScene = sceneStack_.top();
|
||||
oldScene->onExit();
|
||||
oldScene->onDetachFromScene();
|
||||
sceneStack_.pop();
|
||||
|
||||
// Push new scene
|
||||
scene->onEnter();
|
||||
scene->onAttachToScene(scene.get());
|
||||
sceneStack_.push(scene);
|
||||
|
|
@ -108,161 +106,20 @@ void SceneManager::enterScene(Ptr<Scene> scene) {
|
|||
}
|
||||
}
|
||||
|
||||
void SceneManager::enterScene(Ptr<Scene> scene,
|
||||
Ptr<TransitionScene> transitionScene) {
|
||||
if (!scene || isTransitioning_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果没有过渡场景,使用无过渡切换
|
||||
if (!transitionScene) {
|
||||
enterScene(scene);
|
||||
return;
|
||||
}
|
||||
|
||||
auto current = getCurrentScene();
|
||||
if (!current) {
|
||||
enterScene(scene);
|
||||
return;
|
||||
}
|
||||
|
||||
// 在过渡开始前,发送 UIHoverExit 给当前悬停的节点,重置按钮状态
|
||||
if (hoverTarget_) {
|
||||
Event evt;
|
||||
evt.type = EventType::UIHoverExit;
|
||||
evt.data = CustomEvent{0, hoverTarget_};
|
||||
dispatchToNode(hoverTarget_, evt);
|
||||
hoverTarget_ = nullptr;
|
||||
}
|
||||
captureTarget_ = nullptr;
|
||||
hasLastPointerWorld_ = false;
|
||||
|
||||
// 设置过渡场景
|
||||
transitionScene->setOutScene(current);
|
||||
transitionScene->setFinishCallback([this]() { finishTransition(); });
|
||||
|
||||
// 暂停当前场景
|
||||
current->pause();
|
||||
|
||||
// 推入过渡场景(作为中介场景)
|
||||
transitionScene->onEnter();
|
||||
transitionScene->onAttachToScene(transitionScene.get());
|
||||
sceneStack_.push(transitionScene);
|
||||
|
||||
isTransitioning_ = true;
|
||||
activeTransitionScene_ = transitionScene;
|
||||
transitionStackAction_ = [this, transitionScene]() {
|
||||
// 退出旧场景
|
||||
auto outScene = transitionScene->getOutScene();
|
||||
if (!sceneStack_.empty() && outScene) {
|
||||
// 过渡场景已经在栈顶,弹出它
|
||||
if (sceneStack_.top().get() == transitionScene.get()) {
|
||||
sceneStack_.pop();
|
||||
}
|
||||
outScene->onExit();
|
||||
outScene->onDetachFromScene();
|
||||
}
|
||||
|
||||
// 推入新场景
|
||||
auto inScene = transitionScene->getInScene();
|
||||
if (inScene) {
|
||||
inScene->onAttachToScene(inScene.get());
|
||||
sceneStack_.push(inScene);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void SceneManager::replaceScene(Ptr<Scene> scene, TransitionType transition,
|
||||
float duration) {
|
||||
if (!scene || isTransitioning_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sceneStack_.empty()) {
|
||||
runWithScene(scene);
|
||||
return;
|
||||
}
|
||||
|
||||
auto oldScene = sceneStack_.top();
|
||||
|
||||
startTransition(oldScene, scene, transition, duration, [this]() {
|
||||
// 过渡完成后,退出旧场景并从堆栈中移除
|
||||
if (!sceneStack_.empty() && activeTransitionScene_) {
|
||||
// 弹出过渡场景
|
||||
if (sceneStack_.top().get() == activeTransitionScene_.get()) {
|
||||
sceneStack_.pop();
|
||||
}
|
||||
|
||||
// 退出旧场景
|
||||
auto outScene = activeTransitionScene_->getOutScene();
|
||||
if (outScene) {
|
||||
outScene->onExit();
|
||||
outScene->onDetachFromScene();
|
||||
}
|
||||
}
|
||||
|
||||
// 将新场景推入堆栈
|
||||
if (activeTransitionScene_) {
|
||||
auto inScene = activeTransitionScene_->getInScene();
|
||||
if (inScene) {
|
||||
inScene->onAttachToScene(inScene.get());
|
||||
sceneStack_.push(inScene);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SceneManager::pushScene(Ptr<Scene> scene) {
|
||||
if (!scene || isTransitioning_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pause current scene
|
||||
if (!sceneStack_.empty()) {
|
||||
sceneStack_.top()->pause();
|
||||
}
|
||||
|
||||
// Push new scene
|
||||
scene->onEnter();
|
||||
scene->onAttachToScene(scene.get());
|
||||
sceneStack_.push(scene);
|
||||
}
|
||||
|
||||
void SceneManager::pushScene(Ptr<Scene> scene, TransitionType transition,
|
||||
float duration) {
|
||||
if (!scene || isTransitioning_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sceneStack_.empty()) {
|
||||
runWithScene(scene);
|
||||
return;
|
||||
}
|
||||
|
||||
// 暂停当前场景
|
||||
sceneStack_.top()->pause();
|
||||
|
||||
auto currentScene = sceneStack_.top();
|
||||
|
||||
startTransition(currentScene, scene, transition, duration, [this]() {
|
||||
// 过渡完成后,将新场景推入堆栈
|
||||
if (!sceneStack_.empty() && activeTransitionScene_) {
|
||||
// 弹出过渡场景
|
||||
if (sceneStack_.top().get() == activeTransitionScene_.get()) {
|
||||
sceneStack_.pop();
|
||||
}
|
||||
}
|
||||
|
||||
if (activeTransitionScene_) {
|
||||
auto inScene = activeTransitionScene_->getInScene();
|
||||
if (inScene) {
|
||||
inScene->onAttachToScene(inScene.get());
|
||||
sceneStack_.push(inScene);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SceneManager::popScene() {
|
||||
if (sceneStack_.size() <= 1 || isTransitioning_) {
|
||||
return;
|
||||
|
|
@ -273,52 +130,16 @@ void SceneManager::popScene() {
|
|||
current->onDetachFromScene();
|
||||
sceneStack_.pop();
|
||||
|
||||
// Resume previous scene
|
||||
if (!sceneStack_.empty()) {
|
||||
sceneStack_.top()->resume();
|
||||
}
|
||||
}
|
||||
|
||||
void SceneManager::popScene(TransitionType transition, float duration) {
|
||||
if (sceneStack_.size() <= 1 || isTransitioning_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto current = sceneStack_.top();
|
||||
auto previous = getPreviousScene();
|
||||
|
||||
startTransition(current, previous, transition, duration, [this]() {
|
||||
// 过渡完成后,退出当前场景并从堆栈中移除
|
||||
if (!sceneStack_.empty() && activeTransitionScene_) {
|
||||
// 弹出过渡场景
|
||||
if (sceneStack_.top().get() == activeTransitionScene_.get()) {
|
||||
sceneStack_.pop();
|
||||
}
|
||||
|
||||
// 退出当前场景
|
||||
auto outScene = activeTransitionScene_->getOutScene();
|
||||
if (outScene) {
|
||||
outScene->onExit();
|
||||
outScene->onDetachFromScene();
|
||||
}
|
||||
}
|
||||
|
||||
// 恢复前一个场景
|
||||
if (activeTransitionScene_) {
|
||||
auto inScene = activeTransitionScene_->getInScene();
|
||||
if (inScene && !sceneStack_.empty() && sceneStack_.top() == inScene) {
|
||||
inScene->resume();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SceneManager::popToRootScene() {
|
||||
if (sceneStack_.size() <= 1 || isTransitioning_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Exit all scenes except root
|
||||
while (sceneStack_.size() > 1) {
|
||||
auto scene = sceneStack_.top();
|
||||
scene->onExit();
|
||||
|
|
@ -326,40 +147,14 @@ void SceneManager::popToRootScene() {
|
|||
sceneStack_.pop();
|
||||
}
|
||||
|
||||
// Resume root
|
||||
sceneStack_.top()->resume();
|
||||
}
|
||||
|
||||
void SceneManager::popToRootScene(TransitionType transition, float duration) {
|
||||
if (sceneStack_.size() <= 1 || isTransitioning_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto root = getRootScene();
|
||||
auto current = sceneStack_.top();
|
||||
|
||||
startTransition(current, root, transition, duration, [this, root]() {
|
||||
// 退出所有场景直到根场景
|
||||
while (!sceneStack_.empty() && sceneStack_.top().get() != root.get()) {
|
||||
auto scene = sceneStack_.top();
|
||||
scene->onExit();
|
||||
scene->onDetachFromScene();
|
||||
sceneStack_.pop();
|
||||
}
|
||||
|
||||
// 恢复根场景
|
||||
if (!sceneStack_.empty() && sceneStack_.top().get() == root.get()) {
|
||||
root->resume();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SceneManager::popToScene(const std::string &name) {
|
||||
if (isTransitioning_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find target scene in stack
|
||||
std::stack<Ptr<Scene>> tempStack;
|
||||
Ptr<Scene> target = nullptr;
|
||||
|
||||
|
|
@ -379,33 +174,6 @@ void SceneManager::popToScene(const std::string &name) {
|
|||
}
|
||||
}
|
||||
|
||||
void SceneManager::popToScene(const std::string &name,
|
||||
TransitionType transition, float duration) {
|
||||
if (isTransitioning_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto target = getSceneByName(name);
|
||||
if (target && target != sceneStack_.top()) {
|
||||
auto current = sceneStack_.top();
|
||||
|
||||
startTransition(current, target, transition, duration, [this, target]() {
|
||||
// 退出所有场景直到目标场景
|
||||
while (!sceneStack_.empty() && sceneStack_.top() != target) {
|
||||
auto scene = sceneStack_.top();
|
||||
scene->onExit();
|
||||
scene->onDetachFromScene();
|
||||
sceneStack_.pop();
|
||||
}
|
||||
|
||||
// 恢复目标场景
|
||||
if (!sceneStack_.empty() && sceneStack_.top() == target) {
|
||||
target->resume();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ptr<Scene> SceneManager::getCurrentScene() const {
|
||||
if (sceneStack_.empty()) {
|
||||
return nullptr;
|
||||
|
|
@ -418,7 +186,6 @@ Ptr<Scene> SceneManager::getPreviousScene() const {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
// Copy stack to access second top
|
||||
auto tempStack = sceneStack_;
|
||||
tempStack.pop();
|
||||
return tempStack.top();
|
||||
|
|
@ -429,7 +196,6 @@ Ptr<Scene> SceneManager::getRootScene() const {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
// Copy stack to access bottom
|
||||
auto tempStack = sceneStack_;
|
||||
Ptr<Scene> root;
|
||||
while (!tempStack.empty()) {
|
||||
|
|
@ -445,7 +211,6 @@ Ptr<Scene> SceneManager::getSceneByName(const std::string &name) const {
|
|||
return it->second;
|
||||
}
|
||||
|
||||
// Search in stack
|
||||
auto tempStack = sceneStack_;
|
||||
while (!tempStack.empty()) {
|
||||
auto scene = tempStack.top();
|
||||
|
|
@ -464,7 +229,6 @@ bool SceneManager::hasScene(const std::string &name) const {
|
|||
|
||||
void SceneManager::update(float dt) {
|
||||
if (isTransitioning_) {
|
||||
// 过渡场景在栈顶,正常更新即可
|
||||
hoverTarget_ = nullptr;
|
||||
captureTarget_ = nullptr;
|
||||
hasLastPointerWorld_ = false;
|
||||
|
|
@ -518,114 +282,6 @@ void SceneManager::end() {
|
|||
|
||||
void SceneManager::purgeCachedScenes() { namedScenes_.clear(); }
|
||||
|
||||
void SceneManager::startTransition(Ptr<Scene> from, Ptr<Scene> to,
|
||||
TransitionType type, float duration,
|
||||
Function<void()> stackAction) {
|
||||
if (!from || !to) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建过渡场景
|
||||
auto transitionScene = createTransitionScene(type, duration, to);
|
||||
if (!transitionScene) {
|
||||
// 回退到无过渡切换
|
||||
replaceScene(to);
|
||||
return;
|
||||
}
|
||||
|
||||
// 在过渡开始前,发送 UIHoverExit 给当前悬停的节点,重置按钮状态
|
||||
if (hoverTarget_) {
|
||||
Event evt;
|
||||
evt.type = EventType::UIHoverExit;
|
||||
evt.data = CustomEvent{0, hoverTarget_};
|
||||
dispatchToNode(hoverTarget_, evt);
|
||||
hoverTarget_ = nullptr;
|
||||
}
|
||||
captureTarget_ = nullptr;
|
||||
hasLastPointerWorld_ = false;
|
||||
|
||||
// 设置过渡场景
|
||||
transitionScene->setOutScene(from);
|
||||
transitionScene->setFinishCallback([this]() { finishTransition(); });
|
||||
|
||||
// 暂停当前场景
|
||||
from->pause();
|
||||
|
||||
// 推入过渡场景(作为中介场景)
|
||||
transitionScene->onEnter();
|
||||
transitionScene->onAttachToScene(transitionScene.get());
|
||||
sceneStack_.push(transitionScene);
|
||||
|
||||
isTransitioning_ = true;
|
||||
currentTransition_ = type;
|
||||
activeTransitionScene_ = transitionScene;
|
||||
transitionStackAction_ = std::move(stackAction);
|
||||
}
|
||||
|
||||
Ptr<TransitionScene> SceneManager::createTransitionScene(TransitionType type,
|
||||
float duration,
|
||||
Ptr<Scene> inScene) {
|
||||
if (!inScene) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case TransitionType::Fade:
|
||||
return TransitionFadeScene::create(duration, inScene);
|
||||
case TransitionType::SlideLeft:
|
||||
return TransitionSlideScene::create(duration, inScene,
|
||||
TransitionDirection::Left);
|
||||
case TransitionType::SlideRight:
|
||||
return TransitionSlideScene::create(duration, inScene,
|
||||
TransitionDirection::Right);
|
||||
case TransitionType::SlideUp:
|
||||
return TransitionSlideScene::create(duration, inScene,
|
||||
TransitionDirection::Up);
|
||||
case TransitionType::SlideDown:
|
||||
return TransitionSlideScene::create(duration, inScene,
|
||||
TransitionDirection::Down);
|
||||
case TransitionType::Scale:
|
||||
return TransitionScaleScene::create(duration, inScene);
|
||||
case TransitionType::Flip:
|
||||
return TransitionFlipScene::create(duration, inScene);
|
||||
case TransitionType::Box:
|
||||
return TransitionBoxScene::create(duration, inScene);
|
||||
default:
|
||||
// 默认使用淡入淡出
|
||||
return TransitionFadeScene::create(duration, inScene);
|
||||
}
|
||||
}
|
||||
|
||||
void SceneManager::finishTransition() {
|
||||
// 先保存当前悬停的节点,然后在 transitionStackAction_ 之后发送 UIHoverExit
|
||||
Node *lastHoverTarget = hoverTarget_;
|
||||
|
||||
isTransitioning_ = false;
|
||||
hoverTarget_ = nullptr;
|
||||
captureTarget_ = nullptr;
|
||||
hasLastPointerWorld_ = false;
|
||||
|
||||
if (transitionStackAction_) {
|
||||
transitionStackAction_();
|
||||
}
|
||||
|
||||
// 在 transitionStackAction_ 之后发送 UIHoverExit,确保旧场景仍然有效
|
||||
if (lastHoverTarget) {
|
||||
Event evt;
|
||||
evt.type = EventType::UIHoverExit;
|
||||
evt.data = CustomEvent{0, lastHoverTarget};
|
||||
dispatchToNode(lastHoverTarget, evt);
|
||||
}
|
||||
|
||||
activeTransitionScene_.reset();
|
||||
transitionStackAction_ = nullptr;
|
||||
|
||||
if (transitionCallback_) {
|
||||
transitionCallback_();
|
||||
transitionCallback_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void SceneManager::dispatchPointerEvents(Scene &scene) {
|
||||
auto &input = Application::instance().input();
|
||||
Vec2 screenPos = input.getMousePosition();
|
||||
|
|
|
|||
|
|
@ -19,9 +19,7 @@ void Sprite::setTexture(Ptr<Texture> texture) {
|
|||
}
|
||||
}
|
||||
|
||||
void Sprite::setTextureRect(const Rect &rect) {
|
||||
textureRect_ = rect;
|
||||
}
|
||||
void Sprite::setTextureRect(const Rect &rect) { textureRect_ = rect; }
|
||||
|
||||
void Sprite::setColor(const Color &color) { color_ = color; }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,72 +0,0 @@
|
|||
#include <extra2d/scene/transition_box_scene.h>
|
||||
#include <extra2d/app/application.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <extra2d/core/color.h>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
TransitionBoxScene::TransitionBoxScene(float duration, Ptr<Scene> inScene,
|
||||
int divisions)
|
||||
: TransitionScene(duration, inScene), divisions_(divisions) {}
|
||||
|
||||
Ptr<TransitionBoxScene> TransitionBoxScene::create(float duration,
|
||||
Ptr<Scene> inScene,
|
||||
int divisions) {
|
||||
return makePtr<TransitionBoxScene>(duration, inScene, divisions);
|
||||
}
|
||||
|
||||
void TransitionBoxScene::onTransitionStart() {
|
||||
// 方块过渡不需要特殊的初始化
|
||||
}
|
||||
|
||||
void TransitionBoxScene::renderContent(RenderBackend &renderer) {
|
||||
// 获取窗口大小
|
||||
auto &app = Application::instance();
|
||||
float windowWidth = static_cast<float>(app.window().getWidth());
|
||||
float windowHeight = static_cast<float>(app.window().getHeight());
|
||||
|
||||
// 更新进度
|
||||
elapsed_ += 1.0f / 60.0f;
|
||||
progress_ = duration_ > 0.0f ? std::min(1.0f, elapsed_ / duration_) : 1.0f;
|
||||
|
||||
// 先渲染新场景
|
||||
if (inScene_) {
|
||||
inScene_->renderContent(renderer);
|
||||
} else if (outScene_) {
|
||||
outScene_->renderContent(renderer);
|
||||
}
|
||||
|
||||
// 设置视口为整个窗口
|
||||
renderer.setViewport(0, 0, static_cast<int>(windowWidth),
|
||||
static_cast<int>(windowHeight));
|
||||
|
||||
// 计算要显示的方块数量
|
||||
int div = std::max(1, divisions_);
|
||||
int total = div * div;
|
||||
int visible = std::clamp(static_cast<int>(total * progress_), 0, total);
|
||||
|
||||
float cellW = windowWidth / static_cast<float>(div);
|
||||
float cellH = windowHeight / static_cast<float>(div);
|
||||
|
||||
// 设置正交投影
|
||||
glm::mat4 overlayVP =
|
||||
glm::ortho(0.0f, windowWidth, windowHeight, 0.0f, -1.0f, 1.0f);
|
||||
renderer.setViewProjection(overlayVP);
|
||||
|
||||
// 绘制剩余的方块(作为遮罩)
|
||||
for (int idx = visible; idx < total; ++idx) {
|
||||
int x = idx % div;
|
||||
int y = idx / div;
|
||||
renderer.fillRect(Rect(x * cellW, y * cellH, cellW + 1.0f, cellH + 1.0f),
|
||||
Colors::Black);
|
||||
}
|
||||
|
||||
// 检查是否完成
|
||||
if (progress_ >= 1.0f && !isFinished_) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
#include <extra2d/scene/transition_fade_scene.h>
|
||||
#include <extra2d/app/application.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <extra2d/graphics/render_target.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
TransitionFadeScene::TransitionFadeScene(float duration, Ptr<Scene> inScene,
|
||||
const Color &color)
|
||||
: TransitionScene(duration, inScene), maskColor_(color) {}
|
||||
|
||||
Ptr<TransitionFadeScene> TransitionFadeScene::create(float duration,
|
||||
Ptr<Scene> inScene,
|
||||
const Color &color) {
|
||||
return makePtr<TransitionFadeScene>(duration, inScene, color);
|
||||
}
|
||||
|
||||
void TransitionFadeScene::onTransitionStart() {
|
||||
E2D_LOG_DEBUG("TransitionFadeScene::onTransitionStart - 启动淡入淡出过渡");
|
||||
|
||||
// 使用一个定时器来更新进度
|
||||
// 由于我们没有直接的动作系统集成到 Scene,使用简单的 update 逻辑
|
||||
// 实际进度更新由 SceneManager 的 update 驱动
|
||||
}
|
||||
|
||||
void TransitionFadeScene::renderContent(RenderBackend &renderer) {
|
||||
// 获取窗口大小
|
||||
auto &app = Application::instance();
|
||||
float windowWidth = static_cast<float>(app.window().getWidth());
|
||||
float windowHeight = static_cast<float>(app.window().getHeight());
|
||||
|
||||
// 计算当前进度
|
||||
elapsed_ += 1.0f / 60.0f; // 假设 60fps,实际应该由 update 传递 dt
|
||||
progress_ = duration_ > 0.0f ? std::min(1.0f, elapsed_ / duration_) : 1.0f;
|
||||
|
||||
// 检查是否需要切换场景(进度过半时)
|
||||
if (!hasSwitched_ && progress_ >= 0.5f) {
|
||||
hideOutShowIn();
|
||||
}
|
||||
|
||||
// 根据进度渲染
|
||||
if (progress_ < 0.5f) {
|
||||
// 第一阶段:显示旧场景
|
||||
drawOutScene(renderer);
|
||||
} else {
|
||||
// 第二阶段:显示新场景
|
||||
drawInScene(renderer);
|
||||
}
|
||||
|
||||
// 绘制遮罩层
|
||||
// 计算遮罩透明度
|
||||
float maskAlpha;
|
||||
if (progress_ < 0.5f) {
|
||||
// 前半段:从透明到不透明
|
||||
maskAlpha = progress_ * 2.0f; // 0 -> 1
|
||||
} else {
|
||||
// 后半段:从不透明到透明
|
||||
maskAlpha = (1.0f - progress_) * 2.0f; // 1 -> 0
|
||||
}
|
||||
|
||||
// 设置视口为整个窗口
|
||||
renderer.setViewport(0, 0, static_cast<int>(windowWidth),
|
||||
static_cast<int>(windowHeight));
|
||||
|
||||
// 设置正交投影
|
||||
glm::mat4 overlayVP =
|
||||
glm::ortho(0.0f, windowWidth, windowHeight, 0.0f, -1.0f, 1.0f);
|
||||
renderer.setViewProjection(overlayVP);
|
||||
|
||||
// 绘制遮罩
|
||||
Color maskColor = maskColor_;
|
||||
maskColor.a = maskAlpha;
|
||||
renderer.fillRect(Rect(0.0f, 0.0f, windowWidth, windowHeight), maskColor);
|
||||
|
||||
// 检查是否完成
|
||||
if (progress_ >= 1.0f && !isFinished_) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
void TransitionFadeScene::hideOutShowIn() {
|
||||
hasSwitched_ = true;
|
||||
E2D_LOG_DEBUG("TransitionFadeScene::hideOutShowIn - 切换场景显示");
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
#include <extra2d/scene/transition_flip_scene.h>
|
||||
#include <extra2d/graphics/camera.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
TransitionFlipScene::TransitionFlipScene(float duration, Ptr<Scene> inScene,
|
||||
Axis axis)
|
||||
: TransitionScene(duration, inScene), axis_(axis) {}
|
||||
|
||||
Ptr<TransitionFlipScene> TransitionFlipScene::create(float duration,
|
||||
Ptr<Scene> inScene,
|
||||
Axis axis) {
|
||||
return makePtr<TransitionFlipScene>(duration, inScene, axis);
|
||||
}
|
||||
|
||||
void TransitionFlipScene::onTransitionStart() {
|
||||
// 翻页过渡不需要特殊的初始化
|
||||
}
|
||||
|
||||
void TransitionFlipScene::renderContent(RenderBackend &renderer) {
|
||||
// 更新进度
|
||||
elapsed_ += 1.0f / 60.0f;
|
||||
progress_ = duration_ > 0.0f ? std::min(1.0f, elapsed_ / duration_) : 1.0f;
|
||||
|
||||
// 缓动函数
|
||||
float easeProgress = progress_ < 0.5f ? 2.0f * progress_ * progress_
|
||||
: -1.0f + (4.0f - 2.0f * progress_) * progress_;
|
||||
|
||||
float angle = easeProgress * PI_F; // 180度翻转
|
||||
|
||||
if (progress_ < 0.5f) {
|
||||
// 前半段:翻转源场景
|
||||
if (outScene_) {
|
||||
float currentAngle = angle;
|
||||
|
||||
Camera *camera = outScene_->getActiveCamera();
|
||||
float originalRotation = camera ? camera->getRotation() : 0.0f;
|
||||
|
||||
if (axis_ == Axis::Horizontal) {
|
||||
// 水平轴翻转 - 模拟绕X轴旋转
|
||||
if (camera) {
|
||||
camera->setRotation(originalRotation + currentAngle * RAD_TO_DEG);
|
||||
}
|
||||
} else {
|
||||
// 垂直轴翻转 - 模拟绕Y轴旋转
|
||||
if (camera) {
|
||||
camera->setRotation(originalRotation - currentAngle * RAD_TO_DEG);
|
||||
}
|
||||
}
|
||||
|
||||
outScene_->renderContent(renderer);
|
||||
|
||||
if (camera) {
|
||||
camera->setRotation(originalRotation);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 后半段:翻转目标场景
|
||||
if (inScene_) {
|
||||
float currentAngle = angle - PI_F;
|
||||
|
||||
Camera *camera = inScene_->getActiveCamera();
|
||||
float originalRotation = camera ? camera->getRotation() : 0.0f;
|
||||
|
||||
if (axis_ == Axis::Horizontal) {
|
||||
if (camera) {
|
||||
camera->setRotation(originalRotation + currentAngle * RAD_TO_DEG);
|
||||
}
|
||||
} else {
|
||||
if (camera) {
|
||||
camera->setRotation(originalRotation - currentAngle * RAD_TO_DEG);
|
||||
}
|
||||
}
|
||||
|
||||
inScene_->renderContent(renderer);
|
||||
|
||||
if (camera) {
|
||||
camera->setRotation(originalRotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否完成
|
||||
if (progress_ >= 1.0f && !isFinished_) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
#include <extra2d/scene/transition_scale_scene.h>
|
||||
#include <extra2d/graphics/camera.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
TransitionScaleScene::TransitionScaleScene(float duration, Ptr<Scene> inScene)
|
||||
: TransitionScene(duration, inScene) {}
|
||||
|
||||
Ptr<TransitionScaleScene> TransitionScaleScene::create(float duration,
|
||||
Ptr<Scene> inScene) {
|
||||
return makePtr<TransitionScaleScene>(duration, inScene);
|
||||
}
|
||||
|
||||
void TransitionScaleScene::onTransitionStart() {
|
||||
// 缩放过渡不需要特殊的初始化
|
||||
}
|
||||
|
||||
void TransitionScaleScene::renderContent(RenderBackend &renderer) {
|
||||
// 更新进度
|
||||
elapsed_ += 1.0f / 60.0f;
|
||||
progress_ = duration_ > 0.0f ? std::min(1.0f, elapsed_ / duration_) : 1.0f;
|
||||
|
||||
// 缓动函数
|
||||
float easeProgress = progress_ < 0.5f ? 2.0f * progress_ * progress_
|
||||
: -1.0f + (4.0f - 2.0f * progress_) * progress_;
|
||||
|
||||
// 源场景:缩小消失
|
||||
if (outScene_) {
|
||||
float scale = std::max(0.01f, 1.0f - easeProgress);
|
||||
|
||||
Camera *camera = outScene_->getActiveCamera();
|
||||
float originalZoom = camera ? camera->getZoom() : 1.0f;
|
||||
|
||||
if (camera) {
|
||||
camera->setZoom(originalZoom * scale);
|
||||
}
|
||||
|
||||
outScene_->renderContent(renderer);
|
||||
|
||||
if (camera) {
|
||||
camera->setZoom(originalZoom);
|
||||
}
|
||||
}
|
||||
|
||||
// 目标场景:放大出现
|
||||
if (inScene_) {
|
||||
float scale = std::max(0.01f, easeProgress);
|
||||
|
||||
Camera *camera = inScene_->getActiveCamera();
|
||||
float originalZoom = camera ? camera->getZoom() : 1.0f;
|
||||
|
||||
if (camera) {
|
||||
camera->setZoom(originalZoom * scale);
|
||||
}
|
||||
|
||||
inScene_->renderContent(renderer);
|
||||
|
||||
if (camera) {
|
||||
camera->setZoom(originalZoom);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否完成
|
||||
if (progress_ >= 1.0f && !isFinished_) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
#include <extra2d/scene/transition_scene.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
TransitionScene::TransitionScene(float duration, Ptr<Scene> inScene)
|
||||
: duration_(duration), inScene_(inScene) {}
|
||||
|
||||
void TransitionScene::onEnter() {
|
||||
// 调用基类的 onEnter
|
||||
Scene::onEnter();
|
||||
|
||||
// 调用退出场景的 onExitTransitionDidStart
|
||||
if (outScene_) {
|
||||
outScene_->onExitTransitionDidStart();
|
||||
}
|
||||
|
||||
// 调用进入场景的 onEnter
|
||||
if (inScene_) {
|
||||
inScene_->onEnter();
|
||||
inScene_->onAttachToScene(inScene_.get());
|
||||
}
|
||||
|
||||
// 启动过渡
|
||||
onTransitionStart();
|
||||
}
|
||||
|
||||
void TransitionScene::onExit() {
|
||||
// 调用退出场景的 onExit
|
||||
if (outScene_) {
|
||||
outScene_->onExit();
|
||||
outScene_->onDetachFromScene();
|
||||
}
|
||||
|
||||
// 调用进入场景的 onEnterTransitionDidFinish
|
||||
if (inScene_) {
|
||||
inScene_->onEnterTransitionDidFinish();
|
||||
}
|
||||
|
||||
// 调用基类的 onExit
|
||||
Scene::onExit();
|
||||
}
|
||||
|
||||
void TransitionScene::finish() {
|
||||
if (isFinished_) {
|
||||
return;
|
||||
}
|
||||
|
||||
isFinished_ = true;
|
||||
|
||||
E2D_LOG_DEBUG("TransitionScene::finish - 过渡完成,切换到目标场景");
|
||||
|
||||
// 调用完成回调,通知 SceneManager 进行场景切换
|
||||
if (finishCallback_) {
|
||||
finishCallback_();
|
||||
}
|
||||
}
|
||||
|
||||
void TransitionScene::renderContent(RenderBackend &renderer) {
|
||||
// 在 TransitionScene 上渲染新旧两个子场景
|
||||
// 子类可以重写此方法来控制渲染顺序和效果
|
||||
|
||||
// 默认先渲染退出场景,再渲染进入场景(在上方)
|
||||
drawOutScene(renderer);
|
||||
drawInScene(renderer);
|
||||
}
|
||||
|
||||
void TransitionScene::drawOutScene(RenderBackend &renderer) {
|
||||
if (outScene_) {
|
||||
outScene_->renderContent(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
void TransitionScene::drawInScene(RenderBackend &renderer) {
|
||||
if (inScene_) {
|
||||
inScene_->renderContent(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
#include <extra2d/scene/transition_slide_scene.h>
|
||||
#include <extra2d/graphics/camera.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
TransitionSlideScene::TransitionSlideScene(float duration, Ptr<Scene> inScene,
|
||||
TransitionDirection direction)
|
||||
: TransitionScene(duration, inScene), direction_(direction) {}
|
||||
|
||||
Ptr<TransitionSlideScene> TransitionSlideScene::create(
|
||||
float duration, Ptr<Scene> inScene, TransitionDirection direction) {
|
||||
return makePtr<TransitionSlideScene>(duration, inScene, direction);
|
||||
}
|
||||
|
||||
void TransitionSlideScene::onTransitionStart() {
|
||||
// 滑动过渡不需要特殊的初始化
|
||||
}
|
||||
|
||||
void TransitionSlideScene::renderContent(RenderBackend &renderer) {
|
||||
// 获取视口尺寸
|
||||
float screenWidth = 800.0f;
|
||||
float screenHeight = 600.0f;
|
||||
|
||||
if (outScene_) {
|
||||
Size viewportSize = outScene_->getViewportSize();
|
||||
if (viewportSize.width > 0 && viewportSize.height > 0) {
|
||||
screenWidth = viewportSize.width;
|
||||
screenHeight = viewportSize.height;
|
||||
}
|
||||
} else if (inScene_) {
|
||||
Size viewportSize = inScene_->getViewportSize();
|
||||
if (viewportSize.width > 0 && viewportSize.height > 0) {
|
||||
screenWidth = viewportSize.width;
|
||||
screenHeight = viewportSize.height;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新进度
|
||||
elapsed_ += 1.0f / 60.0f;
|
||||
progress_ = duration_ > 0.0f ? std::min(1.0f, elapsed_ / duration_) : 1.0f;
|
||||
|
||||
// 缓动函数
|
||||
float easeProgress = progress_ < 0.5f ? 2.0f * progress_ * progress_
|
||||
: -1.0f + (4.0f - 2.0f * progress_) * progress_;
|
||||
|
||||
// 渲染退出场景(滑出)
|
||||
if (outScene_) {
|
||||
float offsetX = 0.0f;
|
||||
float offsetY = 0.0f;
|
||||
|
||||
switch (direction_) {
|
||||
case TransitionDirection::Left:
|
||||
offsetX = -screenWidth * easeProgress;
|
||||
break;
|
||||
case TransitionDirection::Right:
|
||||
offsetX = screenWidth * easeProgress;
|
||||
break;
|
||||
case TransitionDirection::Up:
|
||||
offsetY = -screenHeight * easeProgress;
|
||||
break;
|
||||
case TransitionDirection::Down:
|
||||
offsetY = screenHeight * easeProgress;
|
||||
break;
|
||||
}
|
||||
|
||||
Camera *camera = outScene_->getActiveCamera();
|
||||
Vec2 originalPos = camera ? camera->getPosition() : Vec2::Zero();
|
||||
|
||||
if (camera) {
|
||||
camera->setPosition(originalPos.x + offsetX, originalPos.y + offsetY);
|
||||
}
|
||||
|
||||
outScene_->renderContent(renderer);
|
||||
|
||||
if (camera) {
|
||||
camera->setPosition(originalPos);
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染进入场景(滑入)
|
||||
if (inScene_) {
|
||||
float offsetX = 0.0f;
|
||||
float offsetY = 0.0f;
|
||||
|
||||
switch (direction_) {
|
||||
case TransitionDirection::Left:
|
||||
offsetX = screenWidth * (1.0f - easeProgress);
|
||||
break;
|
||||
case TransitionDirection::Right:
|
||||
offsetX = -screenWidth * (1.0f - easeProgress);
|
||||
break;
|
||||
case TransitionDirection::Up:
|
||||
offsetY = screenHeight * (1.0f - easeProgress);
|
||||
break;
|
||||
case TransitionDirection::Down:
|
||||
offsetY = -screenHeight * (1.0f - easeProgress);
|
||||
break;
|
||||
}
|
||||
|
||||
Camera *camera = inScene_->getActiveCamera();
|
||||
Vec2 originalPos = camera ? camera->getPosition() : Vec2::Zero();
|
||||
|
||||
if (camera) {
|
||||
camera->setPosition(originalPos.x + offsetX, originalPos.y + offsetY);
|
||||
}
|
||||
|
||||
inScene_->renderContent(renderer);
|
||||
|
||||
if (camera) {
|
||||
camera->setPosition(originalPos);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否完成
|
||||
if (progress_ >= 1.0f && !isFinished_) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,444 +0,0 @@
|
|||
#include <extra2d/utils/data.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <simpleini/SimpleIni.h>
|
||||
|
||||
// Switch 平台特定头文件
|
||||
#ifdef __SWITCH__
|
||||
#include <switch.h>
|
||||
#include <switch/services/fs.h>
|
||||
#endif
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class DataStore::Impl {
|
||||
public:
|
||||
CSimpleIniA ini;
|
||||
};
|
||||
|
||||
DataStore::DataStore() : impl_(makeUnique<Impl>()) {}
|
||||
|
||||
DataStore::~DataStore() {
|
||||
// 如果在事务中,尝试提交
|
||||
if (inTransaction_ && dirty_) {
|
||||
commit();
|
||||
}
|
||||
// 如果存档已挂载,卸载
|
||||
if (saveDataMounted_) {
|
||||
unmountSaveData(mountName_);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 文件操作
|
||||
// ============================================================================
|
||||
|
||||
bool DataStore::load(const std::string &filename) {
|
||||
filename_ = filename;
|
||||
SI_Error rc = impl_->ini.LoadFile(filename.c_str());
|
||||
dirty_ = false;
|
||||
return rc >= 0;
|
||||
}
|
||||
|
||||
bool DataStore::save(const std::string &filename) {
|
||||
// 如果在事务中,只标记为脏,不实际写入
|
||||
if (inTransaction_) {
|
||||
dirty_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string &targetFile = filename.empty() ? filename_ : filename;
|
||||
if (targetFile.empty()) {
|
||||
E2D_LOG_ERROR("DataStore::save: 没有指定文件名");
|
||||
return false;
|
||||
}
|
||||
|
||||
return internalSave(targetFile);
|
||||
}
|
||||
|
||||
bool DataStore::internalSave(const std::string &filename) {
|
||||
SI_Error rc = impl_->ini.SaveFile(filename.c_str());
|
||||
if (rc < 0) {
|
||||
E2D_LOG_ERROR("DataStore::save: 保存文件失败: {}", filename);
|
||||
return false;
|
||||
}
|
||||
dirty_ = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Switch 存档系统支持
|
||||
// ============================================================================
|
||||
|
||||
#ifdef __SWITCH__
|
||||
|
||||
bool DataStore::mountSaveData(SaveDataType type, const UserId &userId,
|
||||
const std::string &mountName) {
|
||||
// 如果已经挂载,先卸载
|
||||
if (saveDataMounted_) {
|
||||
unmountSaveData(mountName_);
|
||||
}
|
||||
|
||||
Result rc = 0;
|
||||
AccountUid uid = {userId.uid[0], userId.uid[1]};
|
||||
|
||||
// 如果没有提供用户ID,尝试获取当前用户
|
||||
if (type == SaveDataType::Account && !userId.isValid()) {
|
||||
UserId currentUid = getCurrentUserId();
|
||||
uid.uid[0] = currentUid.uid[0];
|
||||
uid.uid[1] = currentUid.uid[1];
|
||||
if (uid.uid[0] == 0 && uid.uid[1] == 0) {
|
||||
E2D_LOG_ERROR("DataStore::mountSaveData: 无法获取当前用户ID");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 使用 fsdevMountSaveData 挂载
|
||||
// 注意:这里使用当前应用程序ID (0 表示当前应用)
|
||||
u64 applicationId = 0;
|
||||
rc = fsdevMountSaveData(mountName.c_str(), applicationId, uid);
|
||||
|
||||
if (R_FAILED(rc)) {
|
||||
E2D_LOG_ERROR("DataStore::mountSaveData: 挂载失败: 0x{:X}", rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
mountName_ = mountName;
|
||||
saveDataMounted_ = true;
|
||||
defaultUserId_ = UserId{uid.uid[0], uid.uid[1]};
|
||||
|
||||
E2D_LOG_INFO("DataStore::mountSaveData: 成功挂载存档: {}", mountName);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DataStore::unmountSaveData(const std::string &mountName) {
|
||||
if (!saveDataMounted_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 先提交更改
|
||||
if (dirty_) {
|
||||
commitSaveData(mountName_);
|
||||
}
|
||||
|
||||
fsdevUnmountDevice(mountName.c_str());
|
||||
saveDataMounted_ = false;
|
||||
mountName_.clear();
|
||||
|
||||
E2D_LOG_INFO("DataStore::unmountSaveData: 已卸载存档");
|
||||
}
|
||||
|
||||
bool DataStore::commitSaveData(const std::string &mountName) {
|
||||
if (!saveDataMounted_) {
|
||||
E2D_LOG_WARN("DataStore::commitSaveData: 存档未挂载");
|
||||
return false;
|
||||
}
|
||||
|
||||
Result rc = fsdevCommitDevice(mountName.c_str());
|
||||
if (R_FAILED(rc)) {
|
||||
E2D_LOG_ERROR("DataStore::commitSaveData: 提交失败: 0x{:X}", rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
E2D_LOG_DEBUG("DataStore::commitSaveData: 提交成功");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string DataStore::getSaveDataPath(const std::string &path) const {
|
||||
if (!saveDataMounted_) {
|
||||
return path;
|
||||
}
|
||||
return mountName_ + ":/" + path;
|
||||
}
|
||||
|
||||
UserId DataStore::getCurrentUserId() {
|
||||
UserId result;
|
||||
|
||||
Result rc = accountInitialize(AccountServiceType_Application);
|
||||
if (R_FAILED(rc)) {
|
||||
E2D_LOG_ERROR("DataStore::getCurrentUserId: accountInitialize 失败: 0x{:X}",
|
||||
rc);
|
||||
return result;
|
||||
}
|
||||
|
||||
AccountUid uid;
|
||||
rc = accountGetPreselectedUser(&uid);
|
||||
accountExit();
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
result.uid[0] = uid.uid[0];
|
||||
result.uid[1] = uid.uid[1];
|
||||
E2D_LOG_DEBUG("DataStore::getCurrentUserId: 获取成功: 0x{:X}{:X}",
|
||||
result.uid[1], result.uid[0]);
|
||||
} else {
|
||||
E2D_LOG_ERROR("DataStore::getCurrentUserId: 获取失败: 0x{:X}", rc);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// 非 Switch 平台的存根实现
|
||||
|
||||
bool DataStore::mountSaveData(SaveDataType type, const UserId &userId,
|
||||
const std::string &mountName) {
|
||||
(void)type;
|
||||
(void)userId;
|
||||
(void)mountName;
|
||||
E2D_LOG_WARN("DataStore::mountSaveData: 非 Switch 平台,存档功能不可用");
|
||||
return false;
|
||||
}
|
||||
|
||||
void DataStore::unmountSaveData(const std::string &mountName) {
|
||||
(void)mountName;
|
||||
saveDataMounted_ = false;
|
||||
}
|
||||
|
||||
bool DataStore::commitSaveData(const std::string &mountName) {
|
||||
(void)mountName;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string DataStore::getSaveDataPath(const std::string &path) const {
|
||||
return path;
|
||||
}
|
||||
|
||||
UserId DataStore::getCurrentUserId() {
|
||||
return UserId();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// ============================================================================
|
||||
// 数据读写
|
||||
// ============================================================================
|
||||
|
||||
std::string DataStore::getString(const std::string §ion,
|
||||
const std::string &key,
|
||||
const std::string &defaultValue) {
|
||||
const char *value =
|
||||
impl_->ini.GetValue(section.c_str(), key.c_str(), defaultValue.c_str());
|
||||
return value ? value : defaultValue;
|
||||
}
|
||||
|
||||
int DataStore::getInt(const std::string §ion, const std::string &key,
|
||||
int defaultValue) {
|
||||
return static_cast<int>(
|
||||
impl_->ini.GetLongValue(section.c_str(), key.c_str(), defaultValue));
|
||||
}
|
||||
|
||||
float DataStore::getFloat(const std::string §ion, const std::string &key,
|
||||
float defaultValue) {
|
||||
const char *value =
|
||||
impl_->ini.GetValue(section.c_str(), key.c_str(), nullptr);
|
||||
if (value) {
|
||||
try {
|
||||
return std::stof(value);
|
||||
} catch (...) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
bool DataStore::getBool(const std::string §ion, const std::string &key,
|
||||
bool defaultValue) {
|
||||
return impl_->ini.GetBoolValue(section.c_str(), key.c_str(), defaultValue);
|
||||
}
|
||||
|
||||
void DataStore::setString(const std::string §ion, const std::string &key,
|
||||
const std::string &value) {
|
||||
impl_->ini.SetValue(section.c_str(), key.c_str(), value.c_str());
|
||||
dirty_ = true;
|
||||
|
||||
// 不在事务中时自动保存
|
||||
if (!inTransaction_ && !filename_.empty()) {
|
||||
save("");
|
||||
}
|
||||
}
|
||||
|
||||
void DataStore::setInt(const std::string §ion, const std::string &key,
|
||||
int value) {
|
||||
impl_->ini.SetLongValue(section.c_str(), key.c_str(), value);
|
||||
dirty_ = true;
|
||||
|
||||
if (!inTransaction_ && !filename_.empty()) {
|
||||
save("");
|
||||
}
|
||||
}
|
||||
|
||||
void DataStore::setFloat(const std::string §ion, const std::string &key,
|
||||
float value) {
|
||||
impl_->ini.SetValue(section.c_str(), key.c_str(),
|
||||
std::to_string(value).c_str());
|
||||
dirty_ = true;
|
||||
|
||||
if (!inTransaction_ && !filename_.empty()) {
|
||||
save("");
|
||||
}
|
||||
}
|
||||
|
||||
void DataStore::setBool(const std::string §ion, const std::string &key,
|
||||
bool value) {
|
||||
impl_->ini.SetBoolValue(section.c_str(), key.c_str(), value);
|
||||
dirty_ = true;
|
||||
|
||||
if (!inTransaction_ && !filename_.empty()) {
|
||||
save("");
|
||||
}
|
||||
}
|
||||
|
||||
void DataStore::removeKey(const std::string §ion, const std::string &key) {
|
||||
impl_->ini.Delete(section.c_str(), key.c_str());
|
||||
dirty_ = true;
|
||||
|
||||
if (!inTransaction_ && !filename_.empty()) {
|
||||
save("");
|
||||
}
|
||||
}
|
||||
|
||||
void DataStore::removeSection(const std::string §ion) {
|
||||
impl_->ini.Delete(section.c_str(), nullptr);
|
||||
dirty_ = true;
|
||||
|
||||
if (!inTransaction_ && !filename_.empty()) {
|
||||
save("");
|
||||
}
|
||||
}
|
||||
|
||||
bool DataStore::hasKey(const std::string §ion, const std::string &key) {
|
||||
return impl_->ini.GetValue(section.c_str(), key.c_str(), nullptr) != nullptr;
|
||||
}
|
||||
|
||||
bool DataStore::hasSection(const std::string §ion) {
|
||||
return impl_->ini.GetSection(section.c_str()) != nullptr;
|
||||
}
|
||||
|
||||
void DataStore::clear() {
|
||||
impl_->ini.Reset();
|
||||
dirty_ = true;
|
||||
|
||||
if (!inTransaction_ && !filename_.empty()) {
|
||||
save("");
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 事务支持
|
||||
// ============================================================================
|
||||
|
||||
void DataStore::beginTransaction() {
|
||||
if (inTransaction_) {
|
||||
E2D_LOG_WARN("DataStore::beginTransaction: 已经处于事务中");
|
||||
return;
|
||||
}
|
||||
|
||||
inTransaction_ = true;
|
||||
dirty_ = false;
|
||||
|
||||
E2D_LOG_DEBUG("DataStore::beginTransaction: 事务开始");
|
||||
}
|
||||
|
||||
bool DataStore::commit() {
|
||||
if (!inTransaction_) {
|
||||
E2D_LOG_WARN("DataStore::commit: 不在事务中");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果有文件名,写入文件
|
||||
bool result = true;
|
||||
if (!filename_.empty() && dirty_) {
|
||||
result = internalSave(filename_);
|
||||
|
||||
// 如果挂载了存档,提交更改
|
||||
if (result && saveDataMounted_) {
|
||||
result = commitSaveData(mountName_);
|
||||
}
|
||||
}
|
||||
|
||||
inTransaction_ = false;
|
||||
|
||||
E2D_LOG_DEBUG("DataStore::commit: 事务提交 {}", result ? "成功" : "失败");
|
||||
return result;
|
||||
}
|
||||
|
||||
void DataStore::rollback() {
|
||||
if (!inTransaction_) {
|
||||
E2D_LOG_WARN("DataStore::rollback: 不在事务中");
|
||||
return;
|
||||
}
|
||||
|
||||
// 重新加载文件来恢复数据
|
||||
if (!filename_.empty()) {
|
||||
impl_->ini.Reset();
|
||||
SI_Error rc = impl_->ini.LoadFile(filename_.c_str());
|
||||
if (rc < 0) {
|
||||
E2D_LOG_ERROR("DataStore::rollback: 重新加载文件失败: {}", filename_);
|
||||
}
|
||||
} else {
|
||||
// 如果没有文件名,清空数据
|
||||
impl_->ini.Reset();
|
||||
}
|
||||
|
||||
inTransaction_ = false;
|
||||
dirty_ = false;
|
||||
|
||||
E2D_LOG_DEBUG("DataStore::rollback: 事务已回滚");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 工具方法
|
||||
// ============================================================================
|
||||
|
||||
std::vector<std::string> DataStore::getAllSections() const {
|
||||
std::vector<std::string> sections;
|
||||
CSimpleIniA::TNamesDepend sectionList;
|
||||
impl_->ini.GetAllSections(sectionList);
|
||||
|
||||
for (const auto §ion : sectionList) {
|
||||
sections.emplace_back(section.pItem);
|
||||
}
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
std::vector<std::string> DataStore::getAllKeys(const std::string §ion) const {
|
||||
std::vector<std::string> keys;
|
||||
CSimpleIniA::TNamesDepend keyList;
|
||||
impl_->ini.GetAllKeys(section.c_str(), keyList);
|
||||
|
||||
for (const auto &key : keyList) {
|
||||
keys.emplace_back(key.pItem);
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
bool DataStore::loadFromSave(const std::string &path) {
|
||||
if (!saveDataMounted_) {
|
||||
E2D_LOG_ERROR("DataStore::loadFromSave: 存档未挂载");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string fullPath = getSaveDataPath(path);
|
||||
return load(fullPath);
|
||||
}
|
||||
|
||||
bool DataStore::saveToSave(const std::string &path) {
|
||||
if (!saveDataMounted_) {
|
||||
E2D_LOG_ERROR("DataStore::saveToSave: 存档未挂载");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string fullPath = getSaveDataPath(path);
|
||||
bool result = save(fullPath);
|
||||
|
||||
// 自动提交
|
||||
if (result) {
|
||||
result = commitSaveData(mountName_);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
#include <extra2d/utils/object_pool.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ObjectPoolManager 单例实现
|
||||
// 所有对象池通过静态局部变量自动管理生命周期
|
||||
// 程序退出时自动清理,无需手动调用 cleanup
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
#include <extra2d/extra2d.h>
|
||||
|
||||
using namespace extra2d;
|
||||
|
||||
/**
|
||||
* @brief Hello World 场景类
|
||||
* 显示简单的精灵和形状
|
||||
*/
|
||||
class HelloWorldScene : public Scene {
|
||||
public:
|
||||
/**
|
||||
* @brief 场景进入时调用
|
||||
*/
|
||||
void onEnter() override {
|
||||
E2D_LOG_INFO("HelloWorldScene::onEnter - 进入场景");
|
||||
|
||||
setBackgroundColor(Color(0.1f, 0.1f, 0.3f, 1.0f));
|
||||
|
||||
auto center = ShapeNode::createFilledCircle(
|
||||
Vec2(640.0f, 360.0f), 50.0f, Color(1.0f, 1.0f, 1.0f, 1.0f), 32);
|
||||
center->setName("center_circle");
|
||||
addChild(center);
|
||||
|
||||
auto rect = ShapeNode::createFilledRect(
|
||||
Rect(100.0f, 100.0f, 200.0f, 100.0f), Color(1.0f, 0.5f, 0.5f, 1.0f));
|
||||
rect->setName("red_rect");
|
||||
addChild(rect);
|
||||
|
||||
auto line = ShapeNode::createLine(Vec2(500.0f, 500.0f), Vec2(780.0f, 500.0f),
|
||||
Color(0.0f, 1.0f, 1.0f, 1.0f), 3.0f);
|
||||
line->setName("cyan_line");
|
||||
addChild(line);
|
||||
|
||||
auto triangle = ShapeNode::createFilledTriangle(
|
||||
Vec2(900.0f, 200.0f), Vec2(1000.0f, 350.0f), Vec2(800.0f, 350.0f),
|
||||
Color(0.5f, 1.0f, 0.5f, 1.0f));
|
||||
triangle->setName("green_triangle");
|
||||
addChild(triangle);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 每帧更新时调用
|
||||
* @param dt 时间间隔(秒)
|
||||
*/
|
||||
void onUpdate(float dt) override {
|
||||
Scene::onUpdate(dt);
|
||||
|
||||
auto &input = Application::instance().input();
|
||||
|
||||
if (input.isButtonPressed(GamepadButton::Start)) {
|
||||
E2D_LOG_INFO("退出应用 (START 按钮)");
|
||||
Application::instance().quit();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 程序入口
|
||||
*/
|
||||
int main(int argc, char **argv) {
|
||||
Logger::init();
|
||||
Logger::setLevel(LogLevel::Debug);
|
||||
|
||||
E2D_LOG_INFO("========================");
|
||||
E2D_LOG_INFO("Easy2D Hello World Demo");
|
||||
E2D_LOG_INFO("========================");
|
||||
|
||||
auto &app = Application::instance();
|
||||
|
||||
AppConfig config;
|
||||
config.title = "Easy2D - Hello World";
|
||||
config.width = 1280;
|
||||
config.height = 720;
|
||||
config.vsync = true;
|
||||
config.fpsLimit = 60;
|
||||
|
||||
if (!app.init(config)) {
|
||||
E2D_LOG_ERROR("应用初始化失败!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
app.enterScene(makePtr<HelloWorldScene>());
|
||||
|
||||
E2D_LOG_INFO("开始主循环...");
|
||||
|
||||
app.run();
|
||||
|
||||
E2D_LOG_INFO("应用结束");
|
||||
|
||||
return 0;
|
||||
}
|
||||
Binary file not shown.
|
|
@ -1,76 +0,0 @@
|
|||
-- ==============================================
|
||||
-- Hello World 示例 - Xmake 构建脚本
|
||||
-- 支持平台: MinGW (Windows), Nintendo Switch
|
||||
-- ==============================================
|
||||
|
||||
-- 获取当前脚本所在目录(示例根目录)
|
||||
local example_dir = os.scriptdir()
|
||||
|
||||
-- 可执行文件目标
|
||||
target("hello_world")
|
||||
set_kind("binary")
|
||||
add_files("main.cpp")
|
||||
add_includedirs("../../Extra2D/include")
|
||||
add_deps("extra2d")
|
||||
|
||||
-- 使用与主项目相同的平台配置
|
||||
if is_plat("switch") then
|
||||
set_targetdir("../../build/examples/hello_world")
|
||||
|
||||
-- 构建后生成 NRO 文件
|
||||
after_build(function (target)
|
||||
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
|
||||
local elf_file = target:targetfile()
|
||||
local output_dir = path.directory(elf_file)
|
||||
local nacp_file = path.join(output_dir, "hello_world.nacp")
|
||||
local nro_file = path.join(output_dir, "hello_world.nro")
|
||||
local nacptool = path.join(devkitPro, "tools/bin/nacptool.exe")
|
||||
local elf2nro = path.join(devkitPro, "tools/bin/elf2nro.exe")
|
||||
|
||||
if os.isfile(nacptool) and os.isfile(elf2nro) then
|
||||
os.vrunv(nacptool, {"--create", "Hello World", "Extra2D Team", "1.0.0", nacp_file})
|
||||
local romfs = path.join(example_dir, "romfs")
|
||||
if os.isdir(romfs) then
|
||||
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file, "--romfsdir=" .. romfs})
|
||||
else
|
||||
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file})
|
||||
end
|
||||
print("Generated NRO: " .. nro_file)
|
||||
end
|
||||
end)
|
||||
|
||||
-- 打包时将 NRO 文件复制到 package 目录
|
||||
after_package(function (target)
|
||||
local nro_file = path.join(target:targetdir(), "hello_world.nro")
|
||||
local package_dir = target:packagedir()
|
||||
if os.isfile(nro_file) and package_dir then
|
||||
os.cp(nro_file, package_dir)
|
||||
print("Copied NRO to package: " .. package_dir)
|
||||
end
|
||||
end)
|
||||
|
||||
elseif is_plat("mingw") then
|
||||
set_targetdir("../../build/examples/hello_world")
|
||||
add_ldflags("-mwindows", {force = true})
|
||||
|
||||
-- 复制资源到输出目录
|
||||
after_build(function (target)
|
||||
local romfs = path.join(example_dir, "romfs")
|
||||
if os.isdir(romfs) then
|
||||
local target_dir = path.directory(target:targetfile())
|
||||
local assets_dir = path.join(target_dir, "assets")
|
||||
|
||||
-- 创建 assets 目录
|
||||
if not os.isdir(assets_dir) then
|
||||
os.mkdir(assets_dir)
|
||||
end
|
||||
|
||||
-- 复制所有资源文件(包括子目录)
|
||||
os.cp(path.join(romfs, "assets/**"), assets_dir)
|
||||
print("Copied assets from " .. romfs .. " to " .. assets_dir)
|
||||
else
|
||||
print("Warning: romfs directory not found at " .. romfs)
|
||||
end
|
||||
end)
|
||||
end
|
||||
target_end()
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<defs>
|
||||
<!-- 主渐变 -->
|
||||
<linearGradient id="mainGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#E60012;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#FF4757;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#00C3E3;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
|
||||
<!-- 蓝色渐变 -->
|
||||
<linearGradient id="blueGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#00C3E3;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#0099CC;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
|
||||
<!-- 阴影 -->
|
||||
<filter id="shadow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feDropShadow dx="4" dy="8" stdDeviation="12" flood-color="rgba(0,0,0,0.25)"/>
|
||||
</filter>
|
||||
|
||||
<!-- 内阴影 -->
|
||||
<filter id="innerShadow">
|
||||
<feOffset dx="0" dy="2"/>
|
||||
<feGaussianBlur stdDeviation="3" result="offset-blur"/>
|
||||
<feComposite operator="out" in="SourceGraphic" in2="offset-blur" result="inverse"/>
|
||||
<feFlood flood-color="rgba(0,0,0,0.2)" result="color"/>
|
||||
<feComposite operator="in" in="color" in2="inverse" result="shadow"/>
|
||||
<feComposite operator="over" in="shadow" in2="SourceGraphic"/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- 背景 - 六边形风格 -->
|
||||
<g transform="translate(256, 256)" filter="url(#shadow)">
|
||||
<!-- 主体六边形 -->
|
||||
<path d="M-200 -100 L-200 100 L0 200 L200 100 L200 -100 L0 -200 Z"
|
||||
fill="url(#mainGradient)" />
|
||||
|
||||
<!-- 内部装饰线条 - 左侧红 -->
|
||||
<path d="M-180 -90 L-180 90 L-60 150 L-60 -150 Z"
|
||||
fill="#E60012" opacity="0.3"/>
|
||||
|
||||
<!-- 内部装饰线条 - 右侧蓝 -->
|
||||
<path d="M60 -150 L60 150 L180 90 L180 -90 Z"
|
||||
fill="#00C3E3" opacity="0.3"/>
|
||||
|
||||
<!-- 中心白色区域 -->
|
||||
<path d="M-120 -60 L-120 60 L0 120 L120 60 L120 -60 L0 -120 Z"
|
||||
fill="white" opacity="0.95"/>
|
||||
|
||||
<!-- 边框线条 -->
|
||||
<path d="M-200 -100 L-200 100 L0 200 L200 100 L200 -100 L0 -200 Z"
|
||||
fill="none" stroke="rgba(255,255,255,0.3)" stroke-width="4"/>
|
||||
</g>
|
||||
|
||||
<!-- 文字 E2D -->
|
||||
<g transform="translate(256, 270)">
|
||||
<!-- E -->
|
||||
<text x="-85" y="25"
|
||||
font-family="Arial Black, Arial, sans-serif"
|
||||
font-size="130"
|
||||
font-weight="900"
|
||||
fill="#2D3436"
|
||||
style="letter-spacing: -5px;">E</text>
|
||||
|
||||
<!-- 2 -->
|
||||
<text x="5" y="25"
|
||||
font-family="Arial Black, Arial, sans-serif"
|
||||
font-size="110"
|
||||
font-weight="900"
|
||||
fill="#E60012">2</text>
|
||||
|
||||
<!-- D -->
|
||||
<text x="75" y="25"
|
||||
font-family="Arial Black, Arial, sans-serif"
|
||||
font-size="110"
|
||||
font-weight="900"
|
||||
fill="#00C3E3">D</text>
|
||||
</g>
|
||||
|
||||
<!-- 顶部高光 -->
|
||||
<g transform="translate(256, 256)" opacity="0.4">
|
||||
<path d="M-180 -90 L0 -180 L180 -90 L160 -80 L0 -160 L-160 -80 Z"
|
||||
fill="white"/>
|
||||
</g>
|
||||
|
||||
<!-- 像素装饰 - 左下角 -->
|
||||
<g transform="translate(80, 400)" opacity="0.6">
|
||||
<rect x="0" y="0" width="16" height="16" fill="#E60012" rx="2"/>
|
||||
<rect x="20" y="0" width="16" height="16" fill="#E60012" rx="2"/>
|
||||
<rect x="40" y="0" width="16" height="16" fill="#E60012" rx="2"/>
|
||||
</g>
|
||||
|
||||
<!-- 像素装饰 - 右下角 -->
|
||||
<g transform="translate(376, 400)" opacity="0.6">
|
||||
<rect x="0" y="0" width="16" height="16" fill="#00C3E3" rx="2"/>
|
||||
<rect x="20" y="0" width="16" height="16" fill="#00C3E3" rx="2"/>
|
||||
<rect x="40" y="0" width="16" height="16" fill="#00C3E3" rx="2"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.6 KiB |
|
|
@ -1,88 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 300">
|
||||
<defs>
|
||||
<!-- 主渐变 -->
|
||||
<linearGradient id="mainGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#E60012;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#FF4757;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#00C3E3;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
|
||||
<!-- 阴影 -->
|
||||
<filter id="shadow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feDropShadow dx="3" dy="6" stdDeviation="8" flood-color="rgba(0,0,0,0.3)"/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- Logo 图标 - 六边形 -->
|
||||
<g transform="translate(100, 150)" filter="url(#shadow)">
|
||||
<!-- 主体六边形 -->
|
||||
<path d="M-60 -35 L-60 35 L0 70 L60 35 L60 -35 L0 -70 Z"
|
||||
fill="url(#mainGradient)" />
|
||||
|
||||
<!-- 内部白色区域 -->
|
||||
<path d="M-40 -23 L-40 23 L0 46 L40 23 L40 -23 L0 -46 Z"
|
||||
fill="white" opacity="0.95"/>
|
||||
|
||||
<!-- 边框 -->
|
||||
<path d="M-60 -35 L-60 35 L0 70 L60 35 L60 -35 L0 -70 Z"
|
||||
fill="none" stroke="rgba(255,255,255,0.4)" stroke-width="2"/>
|
||||
|
||||
<!-- 文字 E -->
|
||||
<text x="-22" y="18"
|
||||
font-family="Arial Black, Arial, sans-serif"
|
||||
font-size="45"
|
||||
font-weight="900"
|
||||
fill="#2D3436">E</text>
|
||||
|
||||
<!-- 小 2 -->
|
||||
<text x="8" y="12"
|
||||
font-family="Arial Black, Arial, sans-serif"
|
||||
font-size="24"
|
||||
font-weight="900"
|
||||
fill="#E60012">2</text>
|
||||
|
||||
<!-- 小 D -->
|
||||
<text x="8" y="32"
|
||||
font-family="Arial Black, Arial, sans-serif"
|
||||
font-size="20"
|
||||
font-weight="900"
|
||||
fill="#00C3E3">D</text>
|
||||
</g>
|
||||
|
||||
<!-- Extra2D 文字 -->
|
||||
<g transform="translate(200, 145)">
|
||||
<!-- Extra -->
|
||||
<text x="0" y="20"
|
||||
font-family="Arial, sans-serif"
|
||||
font-size="70"
|
||||
font-weight="800"
|
||||
fill="white">Extra</text>
|
||||
|
||||
<!-- 2 - 红色 -->
|
||||
<text x="215" y="20"
|
||||
font-family="Arial, sans-serif"
|
||||
font-size="70"
|
||||
font-weight="800"
|
||||
fill="#E60012">2</text>
|
||||
|
||||
<!-- D - 蓝色 -->
|
||||
<text x="265" y="20"
|
||||
font-family="Arial, sans-serif"
|
||||
font-size="70"
|
||||
font-weight="800"
|
||||
fill="#00C3E3">D</text>
|
||||
</g>
|
||||
|
||||
<!-- 副标题 -->
|
||||
<g transform="translate(200, 190)">
|
||||
<text x="0" y="0"
|
||||
font-family="Arial, sans-serif"
|
||||
font-size="18"
|
||||
font-weight="500"
|
||||
fill="#888"
|
||||
letter-spacing="4">SWITCH GAME ENGINE</text>
|
||||
</g>
|
||||
|
||||
<!-- 装饰线 -->
|
||||
<line x1="200" y1="165" x2="720" y2="165" stroke="rgba(255,255,255,0.1)" stroke-width="1"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.7 KiB |
|
|
@ -1,88 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 300">
|
||||
<defs>
|
||||
<!-- 主渐变 -->
|
||||
<linearGradient id="mainGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#E60012;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#FF4757;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#00C3E3;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
|
||||
<!-- 阴影 -->
|
||||
<filter id="shadow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feDropShadow dx="3" dy="6" stdDeviation="8" flood-color="rgba(0,0,0,0.2)"/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- Logo 图标 - 六边形 -->
|
||||
<g transform="translate(100, 150)" filter="url(#shadow)">
|
||||
<!-- 主体六边形 -->
|
||||
<path d="M-60 -35 L-60 35 L0 70 L60 35 L60 -35 L0 -70 Z"
|
||||
fill="url(#mainGradient)" />
|
||||
|
||||
<!-- 内部白色区域 -->
|
||||
<path d="M-40 -23 L-40 23 L0 46 L40 23 L40 -23 L0 -46 Z"
|
||||
fill="white" opacity="0.95"/>
|
||||
|
||||
<!-- 边框 -->
|
||||
<path d="M-60 -35 L-60 35 L0 70 L60 35 L60 -35 L0 -70 Z"
|
||||
fill="none" stroke="rgba(255,255,255,0.4)" stroke-width="2"/>
|
||||
|
||||
<!-- 文字 E -->
|
||||
<text x="-22" y="18"
|
||||
font-family="Arial Black, Arial, sans-serif"
|
||||
font-size="45"
|
||||
font-weight="900"
|
||||
fill="#2D3436">E</text>
|
||||
|
||||
<!-- 小 2 -->
|
||||
<text x="8" y="12"
|
||||
font-family="Arial Black, Arial, sans-serif"
|
||||
font-size="24"
|
||||
font-weight="900"
|
||||
fill="#E60012">2</text>
|
||||
|
||||
<!-- 小 D -->
|
||||
<text x="8" y="32"
|
||||
font-family="Arial Black, Arial, sans-serif"
|
||||
font-size="20"
|
||||
font-weight="900"
|
||||
fill="#00C3E3">D</text>
|
||||
</g>
|
||||
|
||||
<!-- Extra2D 文字 -->
|
||||
<g transform="translate(200, 145)">
|
||||
<!-- Extra - 深色 -->
|
||||
<text x="0" y="20"
|
||||
font-family="Arial, sans-serif"
|
||||
font-size="70"
|
||||
font-weight="800"
|
||||
fill="#2D3436">Extra</text>
|
||||
|
||||
<!-- 2 - 红色 -->
|
||||
<text x="215" y="20"
|
||||
font-family="Arial, sans-serif"
|
||||
font-size="70"
|
||||
font-weight="800"
|
||||
fill="#E60012">2</text>
|
||||
|
||||
<!-- D - 蓝色 -->
|
||||
<text x="265" y="20"
|
||||
font-family="Arial, sans-serif"
|
||||
font-size="70"
|
||||
font-weight="800"
|
||||
fill="#00C3E3">D</text>
|
||||
</g>
|
||||
|
||||
<!-- 副标题 -->
|
||||
<g transform="translate(200, 190)">
|
||||
<text x="0" y="0"
|
||||
font-family="Arial, sans-serif"
|
||||
font-size="18"
|
||||
font-weight="500"
|
||||
fill="#636E72"
|
||||
letter-spacing="4">SWITCH GAME ENGINE</text>
|
||||
</g>
|
||||
|
||||
<!-- 装饰线 -->
|
||||
<line x1="200" y1="165" x2="720" y2="165" stroke="rgba(0,0,0,0.1)" stroke-width="1"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.7 KiB |
|
|
@ -1,362 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Extra2D Logo Preview - Geometric Design</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
|
||||
min-height: 100vh;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
text-align: center;
|
||||
color: #888;
|
||||
margin-bottom: 50px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.preview-section {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
color: white;
|
||||
font-size: 1.3rem;
|
||||
margin-bottom: 20px;
|
||||
padding-left: 15px;
|
||||
border-left: 4px solid #E60012;
|
||||
}
|
||||
|
||||
.preview-box {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 20px;
|
||||
padding: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 300px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.preview-box.light {
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #e4e8ec 100%);
|
||||
}
|
||||
|
||||
.preview-box.dark {
|
||||
background: linear-gradient(135deg, #2d3436 0%, #1a1a2e 100%);
|
||||
}
|
||||
|
||||
.icon-preview {
|
||||
width: 220px;
|
||||
height: 220px;
|
||||
}
|
||||
|
||||
.text-preview {
|
||||
width: 100%;
|
||||
max-width: 700px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.color-info {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 30px;
|
||||
margin-top: 30px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.color-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.color-dot {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(255,255,255,0.3);
|
||||
}
|
||||
|
||||
.features {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 15px;
|
||||
padding: 25px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.feature-card h3 {
|
||||
color: white;
|
||||
margin-bottom: 10px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.feature-card p {
|
||||
color: #888;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.size-preview {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.size-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.size-label {
|
||||
color: #888;
|
||||
font-size: 0.9rem;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.reference-logos {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 40px;
|
||||
margin-top: 30px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ref-item {
|
||||
text-align: center;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.ref-item img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin-bottom: 10px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Extra2D Logo Design</h1>
|
||||
<p class="subtitle">Geometric Style - Nintendo Switch Game Engine</p>
|
||||
|
||||
<!-- 主图标预览 -->
|
||||
<div class="preview-section">
|
||||
<h2 class="section-title">主图标 (App Icon) - 六边形设计</h2>
|
||||
<div class="preview-box dark">
|
||||
<svg class="icon-preview" viewBox="0 0 512 512">
|
||||
<defs>
|
||||
<linearGradient id="mainGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#E60012;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#FF4757;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#00C3E3;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<filter id="shadow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feDropShadow dx="4" dy="8" stdDeviation="12" flood-color="rgba(0,0,0,0.25)"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<g transform="translate(256, 256)" filter="url(#shadow)">
|
||||
<path d="M-200 -100 L-200 100 L0 200 L200 100 L200 -100 L0 -200 Z" fill="url(#mainGradient)" />
|
||||
<path d="M-180 -90 L-180 90 L-60 150 L-60 -150 Z" fill="#E60012" opacity="0.3"/>
|
||||
<path d="M60 -150 L60 150 L180 90 L180 -90 Z" fill="#00C3E3" opacity="0.3"/>
|
||||
<path d="M-120 -60 L-120 60 L0 120 L120 60 L120 -60 L0 -120 Z" fill="white" opacity="0.95"/>
|
||||
<path d="M-200 -100 L-200 100 L0 200 L200 100 L200 -100 L0 -200 Z" fill="none" stroke="rgba(255,255,255,0.3)" stroke-width="4"/>
|
||||
</g>
|
||||
<g transform="translate(256, 270)">
|
||||
<text x="-85" y="25" font-family="Arial Black, Arial, sans-serif" font-size="130" font-weight="900" fill="#2D3436" style="letter-spacing: -5px;">E</text>
|
||||
<text x="5" y="25" font-family="Arial Black, Arial, sans-serif" font-size="110" font-weight="900" fill="#E60012">2</text>
|
||||
<text x="75" y="25" font-family="Arial Black, Arial, sans-serif" font-size="110" font-weight="900" fill="#00C3E3">D</text>
|
||||
</g>
|
||||
<g transform="translate(256, 256)" opacity="0.4">
|
||||
<path d="M-180 -90 L0 -180 L180 -90 L160 -80 L0 -160 L-160 -80 Z" fill="white"/>
|
||||
</g>
|
||||
<g transform="translate(80, 400)" opacity="0.6">
|
||||
<rect x="0" y="0" width="16" height="16" fill="#E60012" rx="2"/>
|
||||
<rect x="20" y="0" width="16" height="16" fill="#E60012" rx="2"/>
|
||||
<rect x="40" y="0" width="16" height="16" fill="#E60012" rx="2"/>
|
||||
</g>
|
||||
<g transform="translate(376, 400)" opacity="0.6">
|
||||
<rect x="0" y="0" width="16" height="16" fill="#00C3E3" rx="2"/>
|
||||
<rect x="20" y="0" width="16" height="16" fill="#00C3E3" rx="2"/>
|
||||
<rect x="40" y="0" width="16" height="16" fill="#00C3E3" rx="2"/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="size-preview" style="margin-top: 30px;">
|
||||
<div class="size-item">
|
||||
<svg width="64" height="64" viewBox="0 0 512 512">
|
||||
<g transform="translate(256, 256)">
|
||||
<path d="M-200 -100 L-200 100 L0 200 L200 100 L200 -100 L0 -200 Z" fill="url(#mainGradient)" />
|
||||
<path d="M-120 -60 L-120 60 L0 120 L120 60 L120 -60 L0 -120 Z" fill="white" opacity="0.95"/>
|
||||
</g>
|
||||
<text x="256" y="280" text-anchor="middle" font-family="Arial Black, Arial, sans-serif" font-size="130" font-weight="900" fill="#2D3436">E</text>
|
||||
</svg>
|
||||
<div class="size-label">64x64</div>
|
||||
</div>
|
||||
<div class="size-item">
|
||||
<svg width="128" height="128" viewBox="0 0 512 512">
|
||||
<g transform="translate(256, 256)">
|
||||
<path d="M-200 -100 L-200 100 L0 200 L200 100 L200 -100 L0 -200 Z" fill="url(#mainGradient)" />
|
||||
<path d="M-120 -60 L-120 60 L0 120 L120 60 L120 -60 L0 -120 Z" fill="white" opacity="0.95"/>
|
||||
</g>
|
||||
<text x="256" y="280" text-anchor="middle" font-family="Arial Black, Arial, sans-serif" font-size="130" font-weight="900" fill="#2D3436">E</text>
|
||||
</svg>
|
||||
<div class="size-label">128x128</div>
|
||||
</div>
|
||||
<div class="size-item">
|
||||
<svg width="256" height="256" viewBox="0 0 512 512">
|
||||
<g transform="translate(256, 256)">
|
||||
<path d="M-200 -100 L-200 100 L0 200 L200 100 L200 -100 L0 -200 Z" fill="url(#mainGradient)" />
|
||||
<path d="M-120 -60 L-120 60 L0 120 L120 60 L120 -60 L0 -120 Z" fill="white" opacity="0.95"/>
|
||||
</g>
|
||||
<text x="256" y="280" text-anchor="middle" font-family="Arial Black, Arial, sans-serif" font-size="130" font-weight="900" fill="#2D3436">E</text>
|
||||
</svg>
|
||||
<div class="size-label">256x256</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 文字 Logo 预览 -->
|
||||
<div class="preview-section">
|
||||
<h2 class="section-title">文字 Logo - 深色背景</h2>
|
||||
<div class="preview-box dark">
|
||||
<svg class="text-preview" viewBox="0 0 800 300">
|
||||
<defs>
|
||||
<linearGradient id="textGradient1" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#E60012;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#FF4757;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#00C3E3;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<filter id="textShadow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feDropShadow dx="3" dy="6" stdDeviation="8" flood-color="rgba(0,0,0,0.3)"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<g transform="translate(100, 150)" filter="url(#textShadow)">
|
||||
<path d="M-60 -35 L-60 35 L0 70 L60 35 L60 -35 L0 -70 Z" fill="url(#textGradient1)" />
|
||||
<path d="M-40 -23 L-40 23 L0 46 L40 23 L40 -23 L0 -46 Z" fill="white" opacity="0.95"/>
|
||||
<path d="M-60 -35 L-60 35 L0 70 L60 35 L60 -35 L0 -70 Z" fill="none" stroke="rgba(255,255,255,0.4)" stroke-width="2"/>
|
||||
<text x="-22" y="18" font-family="Arial Black, Arial, sans-serif" font-size="45" font-weight="900" fill="#2D3436">E</text>
|
||||
<text x="8" y="12" font-family="Arial Black, Arial, sans-serif" font-size="24" font-weight="900" fill="#E60012">2</text>
|
||||
<text x="8" y="32" font-family="Arial Black, Arial, sans-serif" font-size="20" font-weight="900" fill="#00C3E3">D</text>
|
||||
</g>
|
||||
<g transform="translate(200, 145)">
|
||||
<text x="0" y="20" font-family="Arial, sans-serif" font-size="70" font-weight="800" fill="white">Extra</text>
|
||||
<text x="215" y="20" font-family="Arial, sans-serif" font-size="70" font-weight="800" fill="#E60012">2</text>
|
||||
<text x="265" y="20" font-family="Arial, sans-serif" font-size="70" font-weight="800" fill="#00C3E3">D</text>
|
||||
</g>
|
||||
<g transform="translate(200, 190)">
|
||||
<text x="0" y="0" font-family="Arial, sans-serif" font-size="18" font-weight="500" fill="#888" letter-spacing="4">SWITCH GAME ENGINE</text>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="preview-section">
|
||||
<h2 class="section-title">文字 Logo - 浅色背景</h2>
|
||||
<div class="preview-box light">
|
||||
<svg class="text-preview" viewBox="0 0 800 300">
|
||||
<defs>
|
||||
<linearGradient id="textGradient2" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#E60012;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#FF4757;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#00C3E3;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g transform="translate(100, 150)" filter="url(#shadow)">
|
||||
<path d="M-60 -35 L-60 35 L0 70 L60 35 L60 -35 L0 -70 Z" fill="url(#textGradient2)" />
|
||||
<path d="M-40 -23 L-40 23 L0 46 L40 23 L40 -23 L0 -46 Z" fill="white" opacity="0.95"/>
|
||||
<path d="M-60 -35 L-60 35 L0 70 L60 35 L60 -35 L0 -70 Z" fill="none" stroke="rgba(255,255,255,0.4)" stroke-width="2"/>
|
||||
<text x="-22" y="18" font-family="Arial Black, Arial, sans-serif" font-size="45" font-weight="900" fill="#2D3436">E</text>
|
||||
<text x="8" y="12" font-family="Arial Black, Arial, sans-serif" font-size="24" font-weight="900" fill="#E60012">2</text>
|
||||
<text x="8" y="32" font-family="Arial Black, Arial, sans-serif" font-size="20" font-weight="900" fill="#00C3E3">D</text>
|
||||
</g>
|
||||
<g transform="translate(200, 145)">
|
||||
<text x="0" y="20" font-family="Arial, sans-serif" font-size="70" font-weight="800" fill="#2D3436">Extra</text>
|
||||
<text x="215" y="20" font-family="Arial, sans-serif" font-size="70" font-weight="800" fill="#E60012">2</text>
|
||||
<text x="265" y="20" font-family="Arial, sans-serif" font-size="70" font-weight="800" fill="#00C3E3">D</text>
|
||||
</g>
|
||||
<g transform="translate(200, 190)">
|
||||
<text x="0" y="0" font-family="Arial, sans-serif" font-size="18" font-weight="500" fill="#636E72" letter-spacing="4">SWITCH GAME ENGINE</text>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 配色方案 -->
|
||||
<div class="preview-section">
|
||||
<h2 class="section-title">配色方案</h2>
|
||||
<div class="color-info">
|
||||
<div class="color-item">
|
||||
<div class="color-dot" style="background: #E60012;"></div>
|
||||
<span>Nintendo Red #E60012</span>
|
||||
</div>
|
||||
<div class="color-item">
|
||||
<div class="color-dot" style="background: #FF4757;"></div>
|
||||
<span>Coral #FF4757</span>
|
||||
</div>
|
||||
<div class="color-item">
|
||||
<div class="color-dot" style="background: #00C3E3;"></div>
|
||||
<span>Nintendo Blue #00C3E3</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 设计特点 -->
|
||||
<div class="features">
|
||||
<div class="feature-card">
|
||||
<h3>🔷 几何风格</h3>
|
||||
<p>采用六边形设计,类似 Godot、Cocos2d 等流行引擎的几何 logo 风格</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>🎮 Switch 配色</h3>
|
||||
<p>红蓝渐变配色致敬 Nintendo Switch,左红右蓝的视觉分割</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>✨ 现代扁平</h3>
|
||||
<p>扁平化设计配合微妙阴影,符合现代游戏引擎的审美趋势</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>🎯 清晰识别</h3>
|
||||
<p>E2D 字母组合清晰醒目,在各种尺寸下都能保持辨识度</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,782 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Extra2D 项目脚手架工具
|
||||
用于快速创建新的 Extra2D 游戏项目
|
||||
|
||||
用法:
|
||||
python create_project.py <项目名称> [选项]
|
||||
|
||||
示例:
|
||||
python create_project.py my_game
|
||||
python create_project.py my_game --path ./games
|
||||
python create_project.py my_game --author "Your Name"
|
||||
|
||||
项目结构:
|
||||
my_game/
|
||||
├── src/
|
||||
│ └── main.cpp
|
||||
├── romfs/
|
||||
│ └── assets/
|
||||
├── xmake.lua
|
||||
├── README.md
|
||||
└── Extra2D/ # 引擎源码(自动克隆)
|
||||
├── Extra2D/
|
||||
├── xmake/
|
||||
└── ...
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import subprocess
|
||||
import platform
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
ENGINE_REPO = "https://github.com/ChestnutYueyue/Extra2D.git"
|
||||
DEVKITPRO_URL = "https://github.com/devkitPro/installer/releases/download/v3.0.3/devkitProUpdater-3.0.3.exe"
|
||||
MINGW_URL = "https://github.com/brechtsanders/winlibs_mingw/releases/download/16.0.0-snapshot20251026posix-14.0.0-ucrt-r1/winlibs-i686-posix-dwarf-gcc-16.0.0-snapshot20251026-mingw-w64ucrt-14.0.0-r1.zip"
|
||||
|
||||
|
||||
class DevToolsChecker:
|
||||
"""开发工具检测器"""
|
||||
|
||||
def __init__(self):
|
||||
self.is_windows = platform.system() == "Windows"
|
||||
|
||||
def check_git(self) -> bool:
|
||||
"""检查 Git 是否安装"""
|
||||
return shutil.which("git") is not None
|
||||
|
||||
def check_xmake(self) -> bool:
|
||||
"""检查 xmake 是否安装"""
|
||||
return shutil.which("xmake") is not None
|
||||
|
||||
def check_mingw(self) -> bool:
|
||||
"""检查 MinGW 是否安装"""
|
||||
if shutil.which("gcc"):
|
||||
result = subprocess.run(
|
||||
["gcc", "-dumpmachine"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
creationflags=subprocess.CREATE_NO_WINDOW if self.is_windows else 0
|
||||
)
|
||||
return "mingw" in result.stdout.lower()
|
||||
return False
|
||||
|
||||
def check_devkitpro(self) -> bool:
|
||||
"""检查 devkitPro 是否安装"""
|
||||
devkitpro = os.environ.get("DEVKITPRO", "C:/devkitPro")
|
||||
devkita64 = os.path.join(devkitpro, "devkitA64")
|
||||
return os.path.isdir(devkita64)
|
||||
|
||||
def get_missing_tools(self) -> dict:
|
||||
"""获取缺失的工具列表"""
|
||||
missing = {}
|
||||
|
||||
if not self.check_git():
|
||||
missing["git"] = "Git 版本控制工具"
|
||||
|
||||
if not self.check_xmake():
|
||||
missing["xmake"] = "xmake 构建工具"
|
||||
|
||||
return missing
|
||||
|
||||
def get_missing_dev_tools(self) -> dict:
|
||||
"""获取缺失的开发工具链"""
|
||||
missing = {}
|
||||
|
||||
if not self.check_mingw():
|
||||
missing["mingw"] = MINGW_URL
|
||||
|
||||
if not self.check_devkitpro():
|
||||
missing["devkitpro"] = DEVKITPRO_URL
|
||||
|
||||
return missing
|
||||
|
||||
def print_tool_status(self):
|
||||
"""打印工具状态"""
|
||||
print("\n========================================")
|
||||
print("开发环境检测")
|
||||
print("========================================")
|
||||
|
||||
tools = [
|
||||
("Git", self.check_git()),
|
||||
("xmake", self.check_xmake()),
|
||||
("MinGW (Windows开发)", self.check_mingw()),
|
||||
("devkitPro (Switch开发)", self.check_devkitpro()),
|
||||
]
|
||||
|
||||
for name, installed in tools:
|
||||
status = "✓ 已安装" if installed else "✗ 未安装"
|
||||
print(f" {name}: {status}")
|
||||
|
||||
print("========================================\n")
|
||||
|
||||
|
||||
class ProjectCreator:
|
||||
"""Extra2D 项目创建器"""
|
||||
|
||||
def __init__(self, project_name: str, output_path: str = None, author: str = "Extra2D Team"):
|
||||
self.project_name = project_name
|
||||
self.author = author
|
||||
self.output_path = Path(output_path) if output_path else Path.cwd()
|
||||
self.project_path = self.output_path / project_name
|
||||
self.engine_path = self.project_path / "Extra2D"
|
||||
|
||||
def create(self) -> bool:
|
||||
"""创建项目"""
|
||||
print(f"\n正在创建 Extra2D 项目: {self.project_name}")
|
||||
print(f"项目路径: {self.project_path}")
|
||||
|
||||
if self.project_path.exists():
|
||||
print(f"错误: 项目目录已存在: {self.project_path}")
|
||||
return False
|
||||
|
||||
try:
|
||||
self._create_directories()
|
||||
self._copy_system_font()
|
||||
self._create_main_cpp()
|
||||
self._create_xmake_lua()
|
||||
self._create_gitignore()
|
||||
self._create_readme()
|
||||
|
||||
print(f"\n✓ 项目创建成功!")
|
||||
|
||||
if self._clone_engine():
|
||||
self._print_next_steps()
|
||||
else:
|
||||
print("\n请手动克隆引擎源码:")
|
||||
print(f" cd {self.project_path}")
|
||||
print(f" git clone {ENGINE_REPO} Extra2D")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"创建项目时出错: {e}")
|
||||
return False
|
||||
|
||||
def _create_directories(self):
|
||||
"""创建项目目录结构"""
|
||||
print("创建目录结构...")
|
||||
self.project_path.mkdir(parents=True, exist_ok=True)
|
||||
(self.project_path / "romfs" / "assets" / "images").mkdir(parents=True, exist_ok=True)
|
||||
(self.project_path / "romfs" / "assets" / "audio").mkdir(parents=True, exist_ok=True)
|
||||
(self.project_path / "src").mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def _copy_system_font(self):
|
||||
"""复制系统字体到项目 assets 目录"""
|
||||
print("复制系统字体...")
|
||||
font_dest = self.project_path / "romfs" / "assets" / "font.ttf"
|
||||
|
||||
if platform.system() == "Windows":
|
||||
font_sources = [
|
||||
Path("C:/Windows/Fonts/msyh.ttc"), # 微软雅黑
|
||||
Path("C:/Windows/Fonts/msyh.ttf"), # 微软雅黑 (部分系统)
|
||||
Path("C:/Windows/Fonts/simhei.ttf"), # 黑体
|
||||
Path("C:/Windows/Fonts/simsun.ttc"), # 宋体
|
||||
]
|
||||
else:
|
||||
font_sources = [
|
||||
Path("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"),
|
||||
Path("/usr/share/fonts/TTF/DejaVuSans.ttf"),
|
||||
Path("/System/Library/Fonts/PingFang.ttc"), # macOS
|
||||
]
|
||||
|
||||
for font_src in font_sources:
|
||||
if font_src.exists():
|
||||
try:
|
||||
shutil.copy2(str(font_src), str(font_dest))
|
||||
print(f"✓ 已复制字体: {font_src.name}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"✗ 复制字体失败: {e}")
|
||||
continue
|
||||
|
||||
print("⚠ 未找到系统字体,请手动添加字体文件到 romfs/assets/font.ttf")
|
||||
return False
|
||||
|
||||
def _clone_engine(self) -> bool:
|
||||
"""克隆引擎源码到项目目录内的 Extra2D 子目录"""
|
||||
print(f"\n正在克隆 Extra2D 引擎源码...")
|
||||
print(f"仓库地址: {ENGINE_REPO}")
|
||||
print(f"目标路径: {self.engine_path}")
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "clone", ENGINE_REPO, str(self.engine_path)],
|
||||
cwd=str(self.project_path),
|
||||
creationflags=subprocess.CREATE_NO_WINDOW if platform.system() == "Windows" else 0
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
print("✓ 引擎源码克隆成功!")
|
||||
return True
|
||||
else:
|
||||
print("✗ 引擎源码克隆失败!")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"✗ 克隆过程中出错: {e}")
|
||||
return False
|
||||
|
||||
def _create_main_cpp(self):
|
||||
"""创建 main.cpp 文件(放在 src 目录下)"""
|
||||
print("创建 src/main.cpp...")
|
||||
content = f'''#include <extra2d/extra2d.h>
|
||||
|
||||
using namespace extra2d;
|
||||
|
||||
// ============================================================================
|
||||
// 主场景
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief 主游戏场景
|
||||
*/
|
||||
class MainScene : public Scene {{
|
||||
public:
|
||||
/**
|
||||
* @brief 场景进入时调用
|
||||
*/
|
||||
void onEnter() override {{
|
||||
E2D_LOG_INFO("MainScene::onEnter - 进入场景");
|
||||
|
||||
// 设置背景颜色
|
||||
setBackgroundColor(Color(0.1f, 0.1f, 0.2f, 1.0f));
|
||||
|
||||
// 加载字体(请确保 assets 目录下有 font.ttf 文件)
|
||||
auto &resources = Application::instance().resources();
|
||||
font_ = resources.loadFont("assets/font.ttf", 32, true);
|
||||
|
||||
if (!font_) {{
|
||||
E2D_LOG_ERROR("字体加载失败!请确保 assets 目录下有 font.ttf 文件");
|
||||
return;
|
||||
}}
|
||||
|
||||
// 创建标题文本
|
||||
auto title = Text::create("{self.project_name}", font_);
|
||||
title->setCoordinateSpace(CoordinateSpace::Screen);
|
||||
title->setScreenPosition(640.0f, 200.0f);
|
||||
title->setAnchor(0.5f, 0.5f);
|
||||
title->setTextColor(Color(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
addChild(title);
|
||||
|
||||
// 创建提示文本
|
||||
auto hint = Text::create("按 START 退出", font_);
|
||||
hint->setCoordinateSpace(CoordinateSpace::Screen);
|
||||
hint->setScreenPosition(640.0f, 650.0f);
|
||||
hint->setAnchor(0.5f, 0.5f);
|
||||
hint->setTextColor(Color(0.7f, 0.7f, 0.7f, 1.0f));
|
||||
addChild(hint);
|
||||
}}
|
||||
|
||||
/**
|
||||
* @brief 每帧更新时调用
|
||||
* @param dt 时间间隔(秒)
|
||||
*/
|
||||
void onUpdate(float dt) override {{
|
||||
Scene::onUpdate(dt);
|
||||
|
||||
// 检查退出按键
|
||||
auto &input = Application::instance().input();
|
||||
if (input.isButtonPressed(GamepadButton::Start)) {{
|
||||
E2D_LOG_INFO("退出应用");
|
||||
Application::instance().quit();
|
||||
}}
|
||||
}}
|
||||
|
||||
private:
|
||||
Ptr<FontAtlas> font_;
|
||||
}};
|
||||
|
||||
// ============================================================================
|
||||
// 程序入口
|
||||
// ============================================================================
|
||||
|
||||
int main(int argc, char **argv) {{
|
||||
// 初始化日志系统
|
||||
Logger::init();
|
||||
Logger::setLevel(LogLevel::Debug);
|
||||
|
||||
E2D_LOG_INFO("========================");
|
||||
E2D_LOG_INFO("{self.project_name}");
|
||||
E2D_LOG_INFO("========================");
|
||||
|
||||
// 获取应用实例
|
||||
auto &app = Application::instance();
|
||||
|
||||
// 配置应用
|
||||
AppConfig config;
|
||||
config.title = "{self.project_name}";
|
||||
config.width = 1280;
|
||||
config.height = 720;
|
||||
config.vsync = true;
|
||||
config.fpsLimit = 60;
|
||||
|
||||
// 初始化应用
|
||||
if (!app.init(config)) {{
|
||||
E2D_LOG_ERROR("应用初始化失败!");
|
||||
return -1;
|
||||
}}
|
||||
|
||||
// 进入主场景
|
||||
app.enterScene(makePtr<MainScene>());
|
||||
|
||||
E2D_LOG_INFO("开始主循环...");
|
||||
|
||||
// 运行应用
|
||||
app.run();
|
||||
|
||||
E2D_LOG_INFO("应用结束");
|
||||
|
||||
return 0;
|
||||
}}
|
||||
'''
|
||||
with open(self.project_path / "src" / "main.cpp", "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
|
||||
def _create_xmake_lua(self):
|
||||
"""创建 xmake.lua 文件(使用项目内的引擎源码)"""
|
||||
print("创建 xmake.lua...")
|
||||
content = f'''-- ==============================================
|
||||
-- {self.project_name} - Extra2D 游戏项目
|
||||
-- 构建系统: Xmake
|
||||
-- 支持平台: MinGW (Windows), Nintendo Switch
|
||||
-- ==============================================
|
||||
|
||||
-- 项目元信息
|
||||
set_project("{self.project_name}")
|
||||
set_version("1.0.0")
|
||||
set_license("MIT")
|
||||
|
||||
-- 语言设置
|
||||
set_languages("c++17")
|
||||
set_encodings("utf-8")
|
||||
|
||||
-- 构建模式
|
||||
add_rules("mode.debug", "mode.release")
|
||||
|
||||
-- ==============================================
|
||||
-- 平台检测与配置
|
||||
-- ==============================================
|
||||
|
||||
local host_plat = os.host()
|
||||
local target_plat = get_config("plat") or host_plat
|
||||
local supported_plats = {{mingw = true, switch = true}}
|
||||
|
||||
if not supported_plats[target_plat] then
|
||||
if host_plat == "windows" then
|
||||
target_plat = "mingw"
|
||||
else
|
||||
error("Unsupported platform: " .. target_plat .. ". Supported platforms: mingw, switch")
|
||||
end
|
||||
end
|
||||
|
||||
set_plat(target_plat)
|
||||
|
||||
if target_plat == "switch" then
|
||||
set_arch("arm64")
|
||||
elseif target_plat == "mingw" then
|
||||
set_arch("x86_64")
|
||||
end
|
||||
|
||||
-- ==============================================
|
||||
-- 加载工具链配置
|
||||
-- ==============================================
|
||||
|
||||
-- 引擎目录(克隆的仓库目录)
|
||||
local engine_repo = "Extra2D"
|
||||
-- 引擎源码目录(仓库内的 Extra2D 子目录)
|
||||
local engine_src = path.join(engine_repo, "Extra2D")
|
||||
|
||||
if target_plat == "switch" then
|
||||
includes(path.join(engine_repo, "xmake/toolchains/switch.lua"))
|
||||
set_toolchains("switch")
|
||||
elseif target_plat == "mingw" then
|
||||
set_toolchains("mingw")
|
||||
end
|
||||
|
||||
-- ==============================================
|
||||
-- 添加依赖包 (MinGW)
|
||||
-- ==============================================
|
||||
|
||||
if target_plat == "mingw" then
|
||||
add_requires("glm", "libsdl2", "libsdl2_mixer")
|
||||
end
|
||||
|
||||
-- ==============================================
|
||||
-- 引擎库定义
|
||||
-- ==============================================
|
||||
|
||||
target("extra2d")
|
||||
set_kind("static")
|
||||
|
||||
-- 引擎源文件
|
||||
add_files(path.join(engine_src, "src/**.cpp"))
|
||||
add_files(path.join(engine_src, "src/glad/glad.c"))
|
||||
|
||||
-- 头文件路径
|
||||
add_includedirs(path.join(engine_src, "include"), {{public = true}})
|
||||
add_includedirs(path.join(engine_src, "include/extra2d/platform"), {{public = true}})
|
||||
|
||||
-- 平台配置
|
||||
if target_plat == "switch" then
|
||||
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
|
||||
add_includedirs(devkitPro .. "/portlibs/switch/include", {{public = true}})
|
||||
add_linkdirs(devkitPro .. "/portlibs/switch/lib")
|
||||
add_syslinks("SDL2_mixer", "SDL2", "opusfile", "opus", "vorbisidec", "ogg",
|
||||
"modplug", "mpg123", "FLAC", "GLESv2", "EGL", "glapi", "drm_nouveau",
|
||||
{{public = true}})
|
||||
elseif target_plat == "mingw" then
|
||||
add_packages("glm", "libsdl2", "libsdl2_mixer", {{public = true}})
|
||||
add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi", {{public = true}})
|
||||
end
|
||||
|
||||
-- 编译器标志
|
||||
add_cxflags("-Wall", "-Wextra", {{force = true}})
|
||||
add_cxflags("-Wno-unused-variable", "-Wno-unused-function", "-Wno-unused-parameter", {{force = true}})
|
||||
add_cxflags("-Wno-strict-aliasing", "-Wno-implicit-fallthrough", {{force = true}})
|
||||
add_cxflags("-Wno-missing-field-initializers", {{force = true}})
|
||||
add_cxxflags("-Wno-deprecated-copy", "-Wno-class-memaccess", {{force = true}})
|
||||
|
||||
if is_mode("debug") then
|
||||
add_defines("E2D_DEBUG", "_DEBUG", {{public = true}})
|
||||
add_cxxflags("-O0", "-g", {{force = true}})
|
||||
else
|
||||
add_defines("NDEBUG", {{public = true}})
|
||||
add_cxxflags("-O2", {{force = true}})
|
||||
end
|
||||
target_end()
|
||||
|
||||
-- ==============================================
|
||||
-- 目标定义
|
||||
-- ==============================================
|
||||
|
||||
target("{self.project_name}")
|
||||
set_kind("binary")
|
||||
add_files("src/**.cpp")
|
||||
add_deps("extra2d")
|
||||
|
||||
-- Nintendo Switch 平台配置
|
||||
if is_plat("switch") then
|
||||
set_targetdir("build/switch")
|
||||
|
||||
-- 构建后生成 NRO 文件
|
||||
after_build(function (target)
|
||||
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
|
||||
local elf_file = target:targetfile()
|
||||
local output_dir = path.directory(elf_file)
|
||||
local nacp_file = path.join(output_dir, "{self.project_name}.nacp")
|
||||
local nro_file = path.join(output_dir, "{self.project_name}.nro")
|
||||
local nacptool = path.join(devkitPro, "tools/bin/nacptool.exe")
|
||||
local elf2nro = path.join(devkitPro, "tools/bin/elf2nro.exe")
|
||||
|
||||
if os.isfile(nacptool) and os.isfile(elf2nro) then
|
||||
os.vrunv(nacptool, {{"--create", "{self.project_name}", "{self.author}", "1.0.0", nacp_file}})
|
||||
local project_dir = os.scriptdir()
|
||||
local romfs = path.join(project_dir, "romfs")
|
||||
if os.isdir(romfs) then
|
||||
os.vrunv(elf2nro, {{elf_file, nro_file, "--nacp=" .. nacp_file, "--romfsdir=" .. romfs}})
|
||||
else
|
||||
os.vrunv(elf2nro, {{elf_file, nro_file, "--nacp=" .. nacp_file}})
|
||||
end
|
||||
print("Generated NRO: " .. nro_file)
|
||||
end
|
||||
end)
|
||||
|
||||
-- 打包时将 NRO 文件复制到 package 目录
|
||||
after_package(function (target)
|
||||
local nro_file = path.join(target:targetdir(), "{self.project_name}.nro")
|
||||
local package_dir = target:packagedir()
|
||||
if os.isfile(nro_file) and package_dir then
|
||||
os.cp(nro_file, package_dir)
|
||||
print("Copied NRO to package: " .. package_dir)
|
||||
end
|
||||
end)
|
||||
|
||||
-- Windows 平台配置
|
||||
elseif is_plat("mingw", "windows") then
|
||||
set_targetdir("build/windows")
|
||||
add_ldflags("-mwindows", {{force = true}})
|
||||
|
||||
-- 复制资源到输出目录
|
||||
after_build(function (target)
|
||||
local project_dir = os.scriptdir()
|
||||
local romfs = path.join(project_dir, "romfs")
|
||||
if os.isdir(romfs) then
|
||||
local target_dir = path.directory(target:targetfile())
|
||||
local assets_dir = path.join(target_dir, "assets")
|
||||
|
||||
if not os.isdir(assets_dir) then
|
||||
os.mkdir(assets_dir)
|
||||
end
|
||||
|
||||
os.cp(path.join(romfs, "assets/**"), assets_dir)
|
||||
print("Copied assets to: " .. assets_dir)
|
||||
end
|
||||
end)
|
||||
end
|
||||
target_end()
|
||||
|
||||
-- ==============================================
|
||||
-- 项目信息输出
|
||||
-- ==============================================
|
||||
|
||||
print("========================================")
|
||||
print("{self.project_name} Build Configuration")
|
||||
print("========================================")
|
||||
print("Platform: " .. target_plat)
|
||||
print("Architecture: " .. (get_config("arch") or "auto"))
|
||||
print("Mode: " .. (is_mode("debug") and "debug" or "release"))
|
||||
print("========================================")
|
||||
'''
|
||||
with open(self.project_path / "xmake.lua", "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
|
||||
def _create_gitignore(self):
|
||||
"""创建 .gitignore 文件"""
|
||||
print("创建 .gitignore...")
|
||||
content = '''# 构建产物
|
||||
.build/
|
||||
build/
|
||||
.xmake/
|
||||
|
||||
# IDE 配置
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# 操作系统文件
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# 临时文件
|
||||
*.tmp
|
||||
*.temp
|
||||
*.log
|
||||
'''
|
||||
with open(self.project_path / ".gitignore", "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
|
||||
def _create_readme(self):
|
||||
"""创建 README.md 文件"""
|
||||
print("创建 README.md...")
|
||||
content = f'''# {self.project_name}
|
||||
|
||||
使用 Extra2D 游戏引擎开发的游戏项目。
|
||||
|
||||
## 支持平台
|
||||
|
||||
- MinGW (Windows)
|
||||
- Nintendo Switch
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
{self.project_name}/
|
||||
├── src/ # 源代码目录
|
||||
│ └── main.cpp # 主程序入口
|
||||
├── romfs/ # 资源文件目录
|
||||
│ └── assets/
|
||||
│ ├── images/ # 图片资源
|
||||
│ └── audio/ # 音频资源
|
||||
├── Extra2D/ # 引擎源码
|
||||
├── xmake.lua # 构建配置
|
||||
└── README.md # 项目说明
|
||||
```
|
||||
|
||||
## 构建说明
|
||||
|
||||
### 前置要求
|
||||
|
||||
- [xmake](https://xmake.io/) 构建工具
|
||||
- MinGW-w64 工具链 (Windows) 或 devkitPro (Switch)
|
||||
|
||||
### 构建步骤
|
||||
|
||||
#### Windows 平台
|
||||
|
||||
```bash
|
||||
# 配置项目
|
||||
xmake config -p mingw
|
||||
|
||||
# 构建项目
|
||||
xmake build
|
||||
|
||||
# 运行项目
|
||||
xmake run {self.project_name}
|
||||
```
|
||||
|
||||
#### Nintendo Switch 平台
|
||||
|
||||
```bash
|
||||
# 配置项目
|
||||
xmake config -p switch
|
||||
|
||||
# 构建项目
|
||||
xmake build
|
||||
|
||||
# 生成的 NRO 文件位于 build/switch/ 目录
|
||||
```
|
||||
|
||||
## 资源文件
|
||||
|
||||
请将游戏所需的资源文件放入 `romfs/assets/` 目录:
|
||||
|
||||
- `images/` - 图片资源(PNG, JPG 等)
|
||||
- `audio/` - 音频资源(WAV, OGG 等)
|
||||
- `font.ttf` - 字体文件(需要手动添加)
|
||||
|
||||
## 作者
|
||||
|
||||
{self.author}
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License
|
||||
'''
|
||||
with open(self.project_path / "README.md", "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
|
||||
def _print_next_steps(self):
|
||||
"""打印后续步骤"""
|
||||
print(f"\n后续步骤:")
|
||||
print(f" 1. cd {self.project_path}")
|
||||
print(f" 2. xmake config -p mingw")
|
||||
print(f" 3. xmake build")
|
||||
print(f" 4. xmake run {self.project_name}")
|
||||
|
||||
|
||||
def prompt_download_tools(dev_tools: dict):
|
||||
"""
|
||||
提示用户下载缺失的开发工具链
|
||||
|
||||
@param dev_tools: 缺失的开发工具链
|
||||
"""
|
||||
if not dev_tools:
|
||||
return
|
||||
|
||||
print("\n========================================")
|
||||
print("检测到以下开发工具链未安装:")
|
||||
print("========================================")
|
||||
|
||||
if "mingw" in dev_tools:
|
||||
print(" - MinGW-w64 (Windows 开发)")
|
||||
if "devkitpro" in dev_tools:
|
||||
print(" - devkitPro (Switch 开发)")
|
||||
|
||||
print("\n是否需要下载这些工具?")
|
||||
print(" 1. 下载 MinGW-w64 (Windows 开发)")
|
||||
print(" 2. 下载 devkitPro (Switch 开发)")
|
||||
print(" 3. 下载全部")
|
||||
print(" 4. 跳过下载")
|
||||
|
||||
try:
|
||||
choice = input("\n请选择 (1-4): ").strip()
|
||||
|
||||
import webbrowser
|
||||
|
||||
if choice == "1" and "mingw" in dev_tools:
|
||||
print(f"\n正在打开 MinGW 下载页面...")
|
||||
print(f"下载地址: {MINGW_URL}")
|
||||
webbrowser.open(MINGW_URL)
|
||||
elif choice == "2" and "devkitpro" in dev_tools:
|
||||
print(f"\n正在打开 devkitPro 下载页面...")
|
||||
print(f"下载地址: {DEVKITPRO_URL}")
|
||||
webbrowser.open(DEVKITPRO_URL)
|
||||
elif choice == "3":
|
||||
if "mingw" in dev_tools:
|
||||
print(f"\n正在打开 MinGW 下载页面...")
|
||||
print(f"下载地址: {MINGW_URL}")
|
||||
webbrowser.open(MINGW_URL)
|
||||
if "devkitpro" in dev_tools:
|
||||
print(f"\n正在打开 devkitPro 下载页面...")
|
||||
print(f"下载地址: {DEVKITPRO_URL}")
|
||||
webbrowser.open(DEVKITPRO_URL)
|
||||
else:
|
||||
print("\n已跳过下载。")
|
||||
|
||||
if dev_tools:
|
||||
print("\n安装提示:")
|
||||
if "mingw" in dev_tools:
|
||||
print(" MinGW: 解压后将 bin 目录添加到系统 PATH 环境变量")
|
||||
if "devkitpro" in dev_tools:
|
||||
print(" devkitPro: 运行安装程序,按提示完成安装")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n已取消。")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Extra2D 项目脚手架工具",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
示例:
|
||||
python create_project.py my_game
|
||||
python create_project.py my_game --path ./games
|
||||
python create_project.py my_game --author "Your Name"
|
||||
|
||||
项目结构:
|
||||
my_game/
|
||||
├── src/
|
||||
│ └── main.cpp
|
||||
├── romfs/
|
||||
│ └── assets/
|
||||
├── xmake.lua
|
||||
├── README.md
|
||||
└── Extra2D/ # 引擎源码(自动克隆)
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"name",
|
||||
help="项目名称(只能包含字母、数字和下划线)"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--path", "-p",
|
||||
help="项目创建路径(默认为当前目录)"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--author", "-a",
|
||||
default="Extra2D Team",
|
||||
help="项目作者(默认: Extra2D Team)"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
checker = DevToolsChecker()
|
||||
checker.print_tool_status()
|
||||
|
||||
missing_tools = checker.get_missing_tools()
|
||||
if missing_tools:
|
||||
print("错误: 缺少必要的工具:")
|
||||
for tool, desc in missing_tools.items():
|
||||
print(f" - {desc}")
|
||||
print("\n请先安装这些工具后再创建项目。")
|
||||
sys.exit(1)
|
||||
|
||||
dev_tools = checker.get_missing_dev_tools()
|
||||
if dev_tools:
|
||||
prompt_download_tools(dev_tools)
|
||||
|
||||
if not args.name.replace("_", "").isalnum():
|
||||
print("错误: 项目名称只能包含字母、数字和下划线")
|
||||
sys.exit(1)
|
||||
|
||||
creator = ProjectCreator(
|
||||
project_name=args.name,
|
||||
output_path=args.path,
|
||||
author=args.author
|
||||
)
|
||||
|
||||
if not creator.create():
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
14
xmake.lua
14
xmake.lua
|
|
@ -20,12 +20,6 @@ add_rules("mode.debug", "mode.release")
|
|||
-- 构建选项
|
||||
-- ==============================================
|
||||
|
||||
option("examples")
|
||||
set_default(false)
|
||||
set_showmenu(true)
|
||||
set_description("Build example programs")
|
||||
option_end()
|
||||
|
||||
option("debug_logs")
|
||||
set_default(false)
|
||||
set_showmenu(true)
|
||||
|
|
@ -72,7 +66,7 @@ end
|
|||
-- ==============================================
|
||||
|
||||
if target_plat == "mingw" then
|
||||
add_requires("glm", "libsdl2", "libsdl2_mixer")
|
||||
add_requires("glm", "libsdl2")
|
||||
end
|
||||
|
||||
-- ==============================================
|
||||
|
|
@ -85,11 +79,6 @@ includes("xmake/engine.lua")
|
|||
-- 定义引擎库
|
||||
define_extra2d_engine()
|
||||
|
||||
-- 示例程序目标(作为子项目)
|
||||
if is_config("examples","true") then
|
||||
includes("examples/hello_world", {rootdir = "examples/hello_world"})
|
||||
end
|
||||
|
||||
-- ==============================================
|
||||
-- 项目信息输出
|
||||
-- ==============================================
|
||||
|
|
@ -100,5 +89,4 @@ print("========================================")
|
|||
print("Platform: " .. target_plat)
|
||||
print("Architecture: " .. (get_config("arch") or "auto"))
|
||||
print("Mode: " .. (is_mode("debug") and "debug" or "release"))
|
||||
print("Examples: " .. (has_config("examples") and "enabled" or "disabled"))
|
||||
print("========================================")
|
||||
|
|
|
|||
|
|
@ -27,11 +27,10 @@ function define_extra2d_engine()
|
|||
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
|
||||
add_includedirs(devkitPro .. "/portlibs/switch/include", {public = true})
|
||||
add_linkdirs(devkitPro .. "/portlibs/switch/lib")
|
||||
add_syslinks("SDL2_mixer", "SDL2", "opusfile", "opus", "vorbisidec", "ogg",
|
||||
"modplug", "mpg123", "FLAC", "GLESv2", "EGL", "glapi", "drm_nouveau",
|
||||
add_syslinks("SDL2", "GLESv2", "EGL", "glapi", "drm_nouveau",
|
||||
{public = true})
|
||||
elseif plat == "mingw" then
|
||||
add_packages("glm", "libsdl2", "libsdl2_mixer", {public = true})
|
||||
add_packages("glm", "libsdl2", {public = true})
|
||||
add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi", {public = true})
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue