refactor: 移除音频系统、过渡场景和示例代码

- 删除音频引擎相关代码,包括AudioEngine和Sound类
- 移除所有过渡场景实现及基类
- 清理示例程序代码
- 简化构建配置,移除SDL2_mixer依赖
- 优化代码结构,移除不必要的资源管理代码
This commit is contained in:
ChestnutYueyue 2026-02-14 22:37:09 +08:00
parent 93d07e547f
commit c6c90a7374
44 changed files with 1239 additions and 35185 deletions

View File

@ -6,21 +6,14 @@
namespace extra2d { namespace extra2d {
// 前向声明
class Input; class Input;
class AudioEngine;
class SceneManager; class SceneManager;
class ResourceManager;
class TimerManager; class TimerManager;
class EventQueue; class EventQueue;
class EventDispatcher; class EventDispatcher;
class Camera; class Camera;
class ViewportAdapter; class ViewportAdapter;
// ============================================================================
// Application 配置
// ============================================================================
enum class PlatformType { Auto = 0, PC, Switch }; enum class PlatformType { Auto = 0, PC, Switch };
struct AppConfig { struct AppConfig {
@ -34,71 +27,46 @@ struct AppConfig {
BackendType renderBackend = BackendType::OpenGL; BackendType renderBackend = BackendType::OpenGL;
int msaaSamples = 0; int msaaSamples = 0;
PlatformType platform = PlatformType::Auto; PlatformType platform = PlatformType::Auto;
// 窗口高级配置 bool enableCursors = true;
bool enableCursors = true; // 是否启用光标 bool enableDpiScale = false;
bool enableDpiScale = false; // 是否启用DPI缩放
}; };
// ============================================================================ /**
// Application 单例 - 应用主控 * @brief Application -
// ============================================================================ */
class Application { class Application {
public: public:
// Meyer's 单例
static Application &instance(); static Application &instance();
// 禁止拷贝
Application(const Application &) = delete; Application(const Application &) = delete;
Application &operator=(const Application &) = delete; Application &operator=(const Application &) = delete;
// ------------------------------------------------------------------------
// 生命周期
// ------------------------------------------------------------------------
bool init(const AppConfig &config); bool init(const AppConfig &config);
void shutdown(); void shutdown();
void run(); void run();
void quit(); void quit();
// ------------------------------------------------------------------------
// 状态控制
// ------------------------------------------------------------------------
void pause(); void pause();
void resume(); void resume();
bool isPaused() const { return paused_; } bool isPaused() const { return paused_; }
bool isRunning() const { return running_; } bool isRunning() const { return running_; }
// ------------------------------------------------------------------------
// 子系统访问
// ------------------------------------------------------------------------
Window &window() { return *window_; } Window &window() { return *window_; }
RenderBackend &renderer() { return *renderer_; } RenderBackend &renderer() { return *renderer_; }
Input &input(); Input &input();
AudioEngine &audio();
SceneManager &scenes(); SceneManager &scenes();
ResourceManager &resources();
TimerManager &timers(); TimerManager &timers();
EventQueue &eventQueue(); EventQueue &eventQueue();
EventDispatcher &eventDispatcher(); EventDispatcher &eventDispatcher();
Camera &camera(); Camera &camera();
/**
* @brief
* @return
*/
ViewportAdapter &viewportAdapter(); ViewportAdapter &viewportAdapter();
// ------------------------------------------------------------------------
// 便捷方法
// ------------------------------------------------------------------------
void enterScene(Ptr<class Scene> scene); void enterScene(Ptr<class Scene> scene);
void enterScene(Ptr<class Scene> scene,
Ptr<class TransitionScene> transitionScene);
float deltaTime() const { return deltaTime_; } float deltaTime() const { return deltaTime_; }
float totalTime() const { return totalTime_; } float totalTime() const { return totalTime_; }
int fps() const { return currentFps_; } int fps() const { return currentFps_; }
// 获取配置
const AppConfig &getConfig() const { return config_; } const AppConfig &getConfig() const { return config_; }
private: private:
@ -108,29 +76,23 @@ private:
void mainLoop(); void mainLoop();
void update(); void update();
void render(); void render();
void prewarmObjectPools();
// 配置
AppConfig config_; AppConfig config_;
// 子系统
UniquePtr<Window> window_; UniquePtr<Window> window_;
UniquePtr<RenderBackend> renderer_; UniquePtr<RenderBackend> renderer_;
UniquePtr<SceneManager> sceneManager_; UniquePtr<SceneManager> sceneManager_;
UniquePtr<ResourceManager> resourceManager_;
UniquePtr<TimerManager> timerManager_; UniquePtr<TimerManager> timerManager_;
UniquePtr<EventQueue> eventQueue_; UniquePtr<EventQueue> eventQueue_;
UniquePtr<EventDispatcher> eventDispatcher_; UniquePtr<EventDispatcher> eventDispatcher_;
UniquePtr<Camera> camera_; UniquePtr<Camera> camera_;
UniquePtr<ViewportAdapter> viewportAdapter_; UniquePtr<ViewportAdapter> viewportAdapter_;
// 状态
bool initialized_ = false; bool initialized_ = false;
bool running_ = false; bool running_ = false;
bool paused_ = false; bool paused_ = false;
bool shouldQuit_ = false; bool shouldQuit_ = false;
// 时间
float deltaTime_ = 0.0f; float deltaTime_ = 0.0f;
float totalTime_ = 0.0f; float totalTime_ = 0.0f;
double lastFrameTime_ = 0.0; double lastFrameTime_ = 0.0;

View File

@ -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

View File

@ -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

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
// Easy2D v3.0 - 统一入口头文件 // Extra2D - 统一入口头文件
// 包含所有公共 API // 包含所有公共 API
// Core // Core
@ -23,18 +23,14 @@
#include <extra2d/graphics/viewport_adapter.h> #include <extra2d/graphics/viewport_adapter.h>
#include <extra2d/graphics/vram_manager.h> #include <extra2d/graphics/vram_manager.h>
#include <extra2d/graphics/texture_pool.h>
// Scene // Scene
#include <extra2d/scene/node.h> #include <extra2d/scene/node.h>
#include <extra2d/scene/scene.h> #include <extra2d/scene/scene.h>
#include <extra2d/scene/scene_manager.h> #include <extra2d/scene/scene_manager.h>
#include <extra2d/scene/shape_node.h> #include <extra2d/scene/shape_node.h>
#include <extra2d/scene/sprite.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 // Event
#include <extra2d/event/event.h> #include <extra2d/event/event.h>
@ -42,15 +38,7 @@
#include <extra2d/event/event_queue.h> #include <extra2d/event/event_queue.h>
#include <extra2d/event/input_codes.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 // Utils
#include <extra2d/utils/data.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
#include <extra2d/utils/random.h> #include <extra2d/utils/random.h>
#include <extra2d/utils/timer.h> #include <extra2d/utils/timer.h>

View File

@ -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

View File

@ -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

View File

@ -1,12 +1,11 @@
#pragma once #pragma once
#include <algorithm>
#include <extra2d/core/color.h> #include <extra2d/core/color.h>
#include <extra2d/core/math_types.h> #include <extra2d/core/math_types.h>
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <extra2d/event/event_dispatcher.h> #include <extra2d/event/event_dispatcher.h>
#include <extra2d/graphics/render_backend.h> #include <extra2d/graphics/render_backend.h>
#include <functional> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
@ -79,7 +78,7 @@ public:
* @brief * @brief
* @param color RGB颜色 * @param color RGB颜色
*/ */
void setColor(const Color3B& color); void setColor(const Color3B &color);
Color3B getColor() const { return color_; } Color3B getColor() const { return color_; }
/** /**
@ -183,58 +182,58 @@ protected:
private: 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) // double(8) > float(4) > int(4) > bool(1)
// ========================================================================== // ==========================================================================
// 1. 大块内存64字节 // 1. 大块内存64字节
mutable glm::mat4 localTransform_; // 64 bytes mutable glm::mat4 localTransform_; // 64 bytes
mutable glm::mat4 worldTransform_; // 64 bytes mutable glm::mat4 worldTransform_; // 64 bytes
// 2. 字符串和容器24-32字节 // 2. 字符串和容器24-32字节
std::string name_; // 32 bytes std::string name_; // 32 bytes
std::vector<Ptr<Node>> children_; // 24 bytes std::vector<Ptr<Node>> children_; // 24 bytes
// 3. 子节点索引(加速查找) // 3. 子节点索引(加速查找)
std::unordered_map<std::string, WeakPtr<Node>> nameIndex_; // 56 bytes std::unordered_map<std::string, WeakPtr<Node>> nameIndex_; // 56 bytes
std::unordered_map<int, WeakPtr<Node>> tagIndex_; // 56 bytes std::unordered_map<int, WeakPtr<Node>> tagIndex_; // 56 bytes
// 4. 事件分发器 // 4. 事件分发器
EventDispatcher eventDispatcher_; // 大小取决于实现 EventDispatcher eventDispatcher_; // 大小取决于实现
// 5. 父节点引用 // 5. 父节点引用
WeakPtr<Node> parent_; // 16 bytes WeakPtr<Node> parent_; // 16 bytes
// 7. 变换属性(按访问频率分组) // 7. 变换属性(按访问频率分组)
Vec2 position_ = Vec2::Zero(); // 8 bytes Vec2 position_ = Vec2::Zero(); // 8 bytes
Vec2 scale_ = Vec2(1.0f, 1.0f); // 8 bytes Vec2 scale_ = Vec2(1.0f, 1.0f); // 8 bytes
Vec2 anchor_ = Vec2(0.5f, 0.5f); // 8 bytes Vec2 anchor_ = Vec2(0.5f, 0.5f); // 8 bytes
Vec2 skew_ = Vec2::Zero(); // 8 bytes Vec2 skew_ = Vec2::Zero(); // 8 bytes
// 8. 浮点属性 // 8. 浮点属性
float rotation_ = 0.0f; // 4 bytes float rotation_ = 0.0f; // 4 bytes
float opacity_ = 1.0f; // 4 bytes float opacity_ = 1.0f; // 4 bytes
// 10. 颜色属性 // 10. 颜色属性
Color3B color_ = Color3B(255, 255, 255); // 3 bytes Color3B color_ = Color3B(255, 255, 255); // 3 bytes
// 11. 整数属性 // 11. 整数属性
int zOrder_ = 0; // 4 bytes int zOrder_ = 0; // 4 bytes
int tag_ = -1; // 4 bytes int tag_ = -1; // 4 bytes
// 12. 布尔属性 // 12. 布尔属性
bool flipX_ = false; // 1 byte bool flipX_ = false; // 1 byte
bool flipY_ = false; // 1 byte bool flipY_ = false; // 1 byte
// 13. 场景指针 // 13. 场景指针
Scene *scene_ = nullptr; // 8 bytes Scene *scene_ = nullptr; // 8 bytes
// 14. 布尔标志(打包在一起) // 14. 布尔标志(打包在一起)
mutable bool transformDirty_ = true; // 1 byte mutable bool transformDirty_ = true; // 1 byte
mutable bool worldTransformDirty_ = true; // 1 byte mutable bool worldTransformDirty_ = true; // 1 byte
bool childrenOrderDirty_ = false; // 1 byte bool childrenOrderDirty_ = false; // 1 byte
bool visible_ = true; // 1 byte bool visible_ = true; // 1 byte
bool running_ = false; // 1 byte bool running_ = false; // 1 byte
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -2,7 +2,7 @@
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <extra2d/scene/scene.h> #include <extra2d/scene/scene.h>
#include <extra2d/scene/transition_scene.h>
#include <functional> #include <functional>
#include <stack> #include <stack>
#include <string> #include <string>
@ -11,87 +11,42 @@
namespace extra2d { namespace extra2d {
// 前向声明
struct RenderCommand; struct RenderCommand;
class TransitionScene;
// ============================================================================ /**
// 场景管理器 - 管理场景的生命周期和切换 * @brief -
// ============================================================================ */
class SceneManager { class SceneManager {
public: public:
using TransitionCallback = std::function<void()>; using TransitionCallback = std::function<void()>;
// ------------------------------------------------------------------------
// 单例访问
// ------------------------------------------------------------------------
static SceneManager &getInstance(); static SceneManager &getInstance();
// ------------------------------------------------------------------------
// 场景栈操作
// ------------------------------------------------------------------------
// 运行第一个场景
void runWithScene(Ptr<Scene> scene); void runWithScene(Ptr<Scene> scene);
// 替换当前场景
void replaceScene(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);
void pushScene(Ptr<Scene> scene, TransitionType transition,
float duration = 0.5f);
// 弹出当前场景(恢复上一个场景)
void popScene(); void popScene();
void popScene(TransitionType transition, float duration = 0.5f);
// 弹出到根场景
void popToRootScene(); void popToRootScene();
void popToRootScene(TransitionType transition, float duration = 0.5f);
// 弹出到指定场景
void popToScene(const std::string &name); void popToScene(const std::string &name);
void popToScene(const std::string &name, TransitionType transition,
float duration = 0.5f);
// ------------------------------------------------------------------------
// 获取场景
// ------------------------------------------------------------------------
Ptr<Scene> getCurrentScene() const; Ptr<Scene> getCurrentScene() const;
Ptr<Scene> getPreviousScene() const; Ptr<Scene> getPreviousScene() const;
Ptr<Scene> getRootScene() const; Ptr<Scene> getRootScene() const;
// 通过名称获取场景
Ptr<Scene> getSceneByName(const std::string &name) const; Ptr<Scene> getSceneByName(const std::string &name) const;
// ------------------------------------------------------------------------
// 查询
// ------------------------------------------------------------------------
size_t getSceneCount() const { return sceneStack_.size(); } size_t getSceneCount() const { return sceneStack_.size(); }
bool isEmpty() const { return sceneStack_.empty(); } bool isEmpty() const { return sceneStack_.empty(); }
bool hasScene(const std::string &name) const; bool hasScene(const std::string &name) const;
// ------------------------------------------------------------------------
// 更新和渲染
// ------------------------------------------------------------------------
void update(float dt); void update(float dt);
void render(RenderBackend &renderer); void render(RenderBackend &renderer);
void collectRenderCommands(std::vector<RenderCommand> &commands); void collectRenderCommands(std::vector<RenderCommand> &commands);
// ------------------------------------------------------------------------
// 过渡控制
// ------------------------------------------------------------------------
bool isTransitioning() const { return isTransitioning_; } bool isTransitioning() const { return isTransitioning_; }
void setTransitionCallback(TransitionCallback callback) { void setTransitionCallback(TransitionCallback callback) {
transitionCallback_ = callback; transitionCallback_ = callback;
} }
// ------------------------------------------------------------------------
// 清理
// ------------------------------------------------------------------------
void end(); void end();
void purgeCachedScenes(); void purgeCachedScenes();
@ -101,33 +56,18 @@ public:
SceneManager(const SceneManager &) = delete; SceneManager(const SceneManager &) = delete;
SceneManager &operator=(const SceneManager &) = delete; SceneManager &operator=(const SceneManager &) = delete;
// 场景切换(供 Application 使用)
void enterScene(Ptr<Scene> scene); void enterScene(Ptr<Scene> scene);
void enterScene(Ptr<Scene> scene, Ptr<TransitionScene> transitionScene);
private: private:
void doSceneSwitch(); void doSceneSwitch();
void startTransition(Ptr<Scene> from, Ptr<Scene> to, TransitionType type,
float duration, Function<void()> stackAction);
void finishTransition();
void dispatchPointerEvents(Scene &scene); void dispatchPointerEvents(Scene &scene);
// 创建过渡场景
Ptr<TransitionScene> createTransitionScene(TransitionType type,
float duration,
Ptr<Scene> inScene);
std::stack<Ptr<Scene>> sceneStack_; std::stack<Ptr<Scene>> sceneStack_;
std::unordered_map<std::string, Ptr<Scene>> namedScenes_; std::unordered_map<std::string, Ptr<Scene>> namedScenes_;
// Transition state
bool isTransitioning_ = false; bool isTransitioning_ = false;
TransitionType currentTransition_ = TransitionType::None;
Ptr<TransitionScene> activeTransitionScene_;
Function<void()> transitionStackAction_;
TransitionCallback transitionCallback_; TransitionCallback transitionCallback_;
// Next scene to switch to (queued during transition)
Ptr<Scene> nextScene_; Ptr<Scene> nextScene_;
bool sendCleanupToScene_ = false; bool sendCleanupToScene_ = false;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 IDAccount
* @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 IDID
*/
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 &section, const std::string &key,
const std::string &defaultValue = "");
/// 获取整数值
int getInt(const std::string &section, const std::string &key,
int defaultValue = 0);
/// 获取浮点数值
float getFloat(const std::string &section, const std::string &key,
float defaultValue = 0.0f);
/// 获取布尔值
bool getBool(const std::string &section, const std::string &key,
bool defaultValue = false);
/// 设置字符串值
void setString(const std::string &section, const std::string &key,
const std::string &value);
/// 设置整数值
void setInt(const std::string &section, const std::string &key, int value);
/// 设置浮点数值
void setFloat(const std::string &section, const std::string &key,
float value);
/// 设置布尔值
void setBool(const std::string &section, const std::string &key, bool value);
/// 删除键
void removeKey(const std::string &section, const std::string &key);
/// 删除整个 section
void removeSection(const std::string &section);
/// 检查键是否存在
bool hasKey(const std::string &section, const std::string &key);
/// 检查 section 是否存在
bool hasSection(const std::string &section);
/// 清除所有数据
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 &section) 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

View File

@ -1,6 +1,5 @@
#pragma once #pragma once
#include <cstdarg>
#include <cstdio> #include <cstdio>
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <sstream> #include <sstream>
@ -16,13 +15,13 @@ namespace extra2d {
// 日志级别枚举 - 映射到 SDL_LogPriority // 日志级别枚举 - 映射到 SDL_LogPriority
// ============================================================================ // ============================================================================
enum class LogLevel { enum class LogLevel {
Trace = SDL_LOG_PRIORITY_VERBOSE, // SDL 详细日志 Trace = SDL_LOG_PRIORITY_VERBOSE, // SDL 详细日志
Debug = SDL_LOG_PRIORITY_DEBUG, // SDL 调试日志 Debug = SDL_LOG_PRIORITY_DEBUG, // SDL 调试日志
Info = SDL_LOG_PRIORITY_INFO, // SDL 信息日志 Info = SDL_LOG_PRIORITY_INFO, // SDL 信息日志
Warn = SDL_LOG_PRIORITY_WARN, // SDL 警告日志 Warn = SDL_LOG_PRIORITY_WARN, // SDL 警告日志
Error = SDL_LOG_PRIORITY_ERROR, // SDL 错误日志 Error = SDL_LOG_PRIORITY_ERROR, // SDL 错误日志
Fatal = SDL_LOG_PRIORITY_CRITICAL, // SDL 严重日志 Fatal = SDL_LOG_PRIORITY_CRITICAL, // SDL 严重日志
Off = SDL_LOG_PRIORITY_CRITICAL + 1 // 关闭日志 (使用 Critical+1 作为关闭标记) Off = SDL_LOG_PRIORITY_CRITICAL + 1 // 关闭日志 (使用 Critical+1 作为关闭标记)
}; };
// ============================================================================ // ============================================================================

View File

@ -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

View File

@ -1,5 +1,4 @@
#include <extra2d/app/application.h> #include <extra2d/app/application.h>
#include <extra2d/audio/audio_engine.h>
#include <extra2d/event/event_dispatcher.h> #include <extra2d/event/event_dispatcher.h>
#include <extra2d/event/event_queue.h> #include <extra2d/event/event_queue.h>
#include <extra2d/graphics/camera.h> #include <extra2d/graphics/camera.h>
@ -8,13 +7,10 @@
#include <extra2d/graphics/vram_manager.h> #include <extra2d/graphics/vram_manager.h>
#include <extra2d/platform/input.h> #include <extra2d/platform/input.h>
#include <extra2d/platform/window.h> #include <extra2d/platform/window.h>
#include <extra2d/resource/resource_manager.h>
#include <extra2d/scene/scene_manager.h> #include <extra2d/scene/scene_manager.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
#include <extra2d/utils/object_pool.h>
#include <extra2d/utils/timer.h> #include <extra2d/utils/timer.h>
#include <chrono> #include <chrono>
#include <thread> #include <thread>
@ -24,7 +20,9 @@
namespace extra2d { namespace extra2d {
// 获取当前时间(秒) /**
* @brief
*/
static double getTimeSeconds() { static double getTimeSeconds() {
#ifdef __SWITCH__ #ifdef __SWITCH__
struct timespec ts; struct timespec ts;
@ -32,7 +30,6 @@ static double getTimeSeconds() {
return static_cast<double>(ts.tv_sec) + return static_cast<double>(ts.tv_sec) +
static_cast<double>(ts.tv_nsec) / 1000000000.0; static_cast<double>(ts.tv_nsec) / 1000000000.0;
#else #else
// PC 平台使用 chrono
using namespace std::chrono; using namespace std::chrono;
auto now = steady_clock::now(); auto now = steady_clock::now();
auto duration = now.time_since_epoch(); auto duration = now.time_since_epoch();
@ -55,7 +52,6 @@ bool Application::init(const AppConfig &config) {
config_ = config; config_ = config;
// 确定平台类型
PlatformType platform = config_.platform; PlatformType platform = config_.platform;
if (platform == PlatformType::Auto) { if (platform == PlatformType::Auto) {
#ifdef __SWITCH__ #ifdef __SWITCH__
@ -67,9 +63,6 @@ bool Application::init(const AppConfig &config) {
if (platform == PlatformType::Switch) { if (platform == PlatformType::Switch) {
#ifdef __SWITCH__ #ifdef __SWITCH__
// ========================================
// 1. 初始化 RomFS 文件系统Switch 平台)
// ========================================
Result rc; Result rc;
rc = romfsInit(); rc = romfsInit();
if (R_SUCCEEDED(rc)) { if (R_SUCCEEDED(rc)) {
@ -79,9 +72,6 @@ bool Application::init(const AppConfig &config) {
rc); rc);
} }
// ========================================
// 2. 初始化 nxlink 调试输出Switch 平台)
// ========================================
rc = socketInitializeDefault(); rc = socketInitializeDefault();
if (R_FAILED(rc)) { if (R_FAILED(rc)) {
E2D_LOG_WARN( E2D_LOG_WARN(
@ -90,9 +80,6 @@ bool Application::init(const AppConfig &config) {
#endif #endif
} }
// ========================================
// 3. 创建窗口(包含 SDL_Init + GLES 3.2 上下文创建)
// ========================================
window_ = makeUnique<Window>(); window_ = makeUnique<Window>();
WindowConfig winConfig; WindowConfig winConfig;
winConfig.title = config.title; winConfig.title = config.title;
@ -100,12 +87,11 @@ bool Application::init(const AppConfig &config) {
winConfig.height = config.height; winConfig.height = config.height;
if (platform == PlatformType::Switch) { if (platform == PlatformType::Switch) {
winConfig.fullscreen = true; winConfig.fullscreen = true;
winConfig.fullscreenDesktop = false; // Switch 使用固定分辨率全屏 winConfig.fullscreenDesktop = false;
winConfig.resizable = false; winConfig.resizable = false;
winConfig.enableCursors = false; winConfig.enableCursors = false;
winConfig.enableDpiScale = false; winConfig.enableDpiScale = false;
} else { } else {
// PC 平台默认窗口模式
winConfig.fullscreen = config.fullscreen; winConfig.fullscreen = config.fullscreen;
winConfig.resizable = config.resizable; winConfig.resizable = config.resizable;
winConfig.enableCursors = config.enableCursors; winConfig.enableCursors = config.enableCursors;
@ -119,9 +105,6 @@ bool Application::init(const AppConfig &config) {
return false; return false;
} }
// ========================================
// 4. 初始化渲染器
// ========================================
renderer_ = RenderBackend::create(config.renderBackend); renderer_ = RenderBackend::create(config.renderBackend);
if (!renderer_ || !renderer_->init(window_.get())) { if (!renderer_ || !renderer_->init(window_.get())) {
E2D_LOG_ERROR("Failed to initialize renderer"); E2D_LOG_ERROR("Failed to initialize renderer");
@ -129,18 +112,13 @@ bool Application::init(const AppConfig &config) {
return false; return false;
} }
// ========================================
// 5. 初始化其他子系统
// ========================================
sceneManager_ = makeUnique<SceneManager>(); sceneManager_ = makeUnique<SceneManager>();
resourceManager_ = makeUnique<ResourceManager>();
timerManager_ = makeUnique<TimerManager>(); timerManager_ = makeUnique<TimerManager>();
eventQueue_ = makeUnique<EventQueue>(); eventQueue_ = makeUnique<EventQueue>();
eventDispatcher_ = makeUnique<EventDispatcher>(); eventDispatcher_ = makeUnique<EventDispatcher>();
camera_ = makeUnique<Camera>(0, static_cast<float>(window_->getWidth()), camera_ = makeUnique<Camera>(0, static_cast<float>(window_->getWidth()),
static_cast<float>(window_->getHeight()), 0); static_cast<float>(window_->getHeight()), 0);
// 创建视口适配器
viewportAdapter_ = makeUnique<ViewportAdapter>(); viewportAdapter_ = makeUnique<ViewportAdapter>();
ViewportConfig vpConfig; ViewportConfig vpConfig;
vpConfig.logicWidth = static_cast<float>(config.width); vpConfig.logicWidth = static_cast<float>(config.width);
@ -148,16 +126,12 @@ bool Application::init(const AppConfig &config) {
vpConfig.mode = ViewportMode::AspectRatio; vpConfig.mode = ViewportMode::AspectRatio;
viewportAdapter_->setConfig(vpConfig); viewportAdapter_->setConfig(vpConfig);
// 关联到各子系统
camera_->setViewportAdapter(viewportAdapter_.get()); camera_->setViewportAdapter(viewportAdapter_.get());
input().setViewportAdapter(viewportAdapter_.get()); input().setViewportAdapter(viewportAdapter_.get());
// 初始更新
viewportAdapter_->update(window_->getWidth(), window_->getHeight()); viewportAdapter_->update(window_->getWidth(), window_->getHeight());
// 窗口大小回调
window_->setResizeCallback([this](int width, int height) { window_->setResizeCallback([this](int width, int height) {
// 更新视口适配器
if (viewportAdapter_) { if (viewportAdapter_) {
viewportAdapter_->update(width, height); viewportAdapter_->update(width, height);
} }
@ -175,14 +149,6 @@ bool Application::init(const AppConfig &config) {
} }
}); });
// 初始化音频引擎
AudioEngine::getInstance().initialize();
// ========================================
// 6. 预热对象池(自动管理)
// ========================================
prewarmObjectPools();
initialized_ = true; initialized_ = true;
running_ = true; running_ = true;
@ -190,72 +156,36 @@ bool Application::init(const AppConfig &config) {
return true; 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() { void Application::shutdown() {
if (!initialized_) if (!initialized_)
return; return;
E2D_LOG_INFO("Shutting down application..."); E2D_LOG_INFO("Shutting down application...");
// 打印 VRAM 统计
VRAMManager::getInstance().printStats(); VRAMManager::getInstance().printStats();
// 打印对象池内存统计
E2D_LOG_INFO("Object pool memory usage: {} bytes (auto-managed)",
ObjectPoolManager::getInstance().getPool<Node>()->memoryUsage());
// 先结束所有场景,确保 onExit() 被正确调用
if (sceneManager_) { if (sceneManager_) {
sceneManager_->end(); sceneManager_->end();
} }
// ======================================== sceneManager_.reset();
// 1. 先清理所有持有 GPU 资源的子系统 viewportAdapter_.reset();
// 必须在渲染器关闭前释放纹理等资源 camera_.reset();
// ========================================
sceneManager_.reset(); // 场景持有纹理引用
resourceManager_.reset(); // 纹理缓存持有 GPU 纹理
viewportAdapter_.reset(); // 视口适配器
camera_.reset(); // 相机可能持有渲染目标
// ========================================
// 2. 关闭音频(不依赖 GPU
// ========================================
AudioEngine::getInstance().shutdown();
// ========================================
// 3. 清理其他子系统
// ========================================
timerManager_.reset(); timerManager_.reset();
eventQueue_.reset(); eventQueue_.reset();
eventDispatcher_.reset(); eventDispatcher_.reset();
// ========================================
// 4. 最后关闭渲染器和窗口
// 必须在所有 GPU 资源释放后才能关闭 OpenGL 上下文
// ========================================
if (renderer_) { if (renderer_) {
renderer_->shutdown(); renderer_->shutdown();
renderer_.reset(); renderer_.reset();
} }
// 销毁窗口(包含 SDL_Quit会销毁 OpenGL 上下文)
if (window_) { if (window_) {
window_->destroy(); window_->destroy();
window_.reset(); window_.reset();
} }
// Switch 平台清理
PlatformType platform = config_.platform; PlatformType platform = config_.platform;
if (platform == PlatformType::Auto) { if (platform == PlatformType::Auto) {
#ifdef __SWITCH__ #ifdef __SWITCH__
@ -286,12 +216,10 @@ void Application::run() {
lastFrameTime_ = getTimeSeconds(); lastFrameTime_ = getTimeSeconds();
#ifdef __SWITCH__ #ifdef __SWITCH__
// SDL2 on Switch 内部已处理 appletMainLoop
while (running_ && !window_->shouldClose()) { while (running_ && !window_->shouldClose()) {
mainLoop(); mainLoop();
} }
#else #else
// PC 平台主循环
while (running_ && !window_->shouldClose()) { while (running_ && !window_->shouldClose()) {
mainLoop(); mainLoop();
} }
@ -319,14 +247,12 @@ void Application::resume() {
} }
void Application::mainLoop() { void Application::mainLoop() {
// 计算 delta time
double currentTime = getTimeSeconds(); double currentTime = getTimeSeconds();
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_); deltaTime_ = static_cast<float>(currentTime - lastFrameTime_);
lastFrameTime_ = currentTime; lastFrameTime_ = currentTime;
totalTime_ += deltaTime_; totalTime_ += deltaTime_;
// 计算 FPS
frameCount_++; frameCount_++;
fpsTimer_ += deltaTime_; fpsTimer_ += deltaTime_;
if (fpsTimer_ >= 1.0f) { if (fpsTimer_ >= 1.0f) {
@ -335,20 +261,16 @@ void Application::mainLoop() {
fpsTimer_ -= 1.0f; fpsTimer_ -= 1.0f;
} }
// 处理窗口事件SDL_PollEvent + 输入更新)
window_->pollEvents(); window_->pollEvents();
// 处理事件队列
if (eventDispatcher_ && eventQueue_) { if (eventDispatcher_ && eventQueue_) {
eventDispatcher_->processQueue(*eventQueue_); eventDispatcher_->processQueue(*eventQueue_);
} }
// 更新
if (!paused_) { if (!paused_) {
update(); update();
} }
// 渲染
render(); render();
if (!config_.vsync && config_.fpsLimit > 0) { if (!config_.vsync && config_.fpsLimit > 0) {
@ -378,13 +300,11 @@ void Application::render() {
return; return;
} }
// 应用视口适配器
if (viewportAdapter_) { if (viewportAdapter_) {
const auto &vp = viewportAdapter_->getViewport(); const auto &vp = viewportAdapter_->getViewport();
renderer_->setViewport(static_cast<int>(vp.origin.x), renderer_->setViewport(
static_cast<int>(vp.origin.y), static_cast<int>(vp.origin.x), static_cast<int>(vp.origin.y),
static_cast<int>(vp.size.width), static_cast<int>(vp.size.width), static_cast<int>(vp.size.height));
static_cast<int>(vp.size.height));
} else { } else {
renderer_->setViewport(0, 0, window_->getWidth(), window_->getHeight()); renderer_->setViewport(0, 0, window_->getWidth(), window_->getHeight());
} }
@ -400,12 +320,8 @@ void Application::render() {
Input &Application::input() { return *window_->getInput(); } Input &Application::input() { return *window_->getInput(); }
AudioEngine &Application::audio() { return AudioEngine::getInstance(); }
SceneManager &Application::scenes() { return *sceneManager_; } SceneManager &Application::scenes() { return *sceneManager_; }
ResourceManager &Application::resources() { return *resourceManager_; }
TimerManager &Application::timers() { return *timerManager_; } TimerManager &Application::timers() { return *timerManager_; }
EventQueue &Application::eventQueue() { return *eventQueue_; } EventQueue &Application::eventQueue() { return *eventQueue_; }
@ -416,14 +332,11 @@ Camera &Application::camera() { return *camera_; }
ViewportAdapter &Application::viewportAdapter() { return *viewportAdapter_; } ViewportAdapter &Application::viewportAdapter() { return *viewportAdapter_; }
void Application::enterScene(Ptr<Scene> scene) { enterScene(scene, nullptr); } void Application::enterScene(Ptr<Scene> scene) {
void Application::enterScene(Ptr<Scene> scene,
Ptr<class TransitionScene> transitionScene) {
if (sceneManager_ && scene) { if (sceneManager_ && scene) {
scene->setViewportSize(static_cast<float>(window_->getWidth()), scene->setViewportSize(static_cast<float>(window_->getWidth()),
static_cast<float>(window_->getHeight())); static_cast<float>(window_->getHeight()));
sceneManager_->enterScene(scene, transitionScene); sceneManager_->enterScene(scene);
} }
} }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -4,18 +4,15 @@
#include <extra2d/graphics/render_command.h> #include <extra2d/graphics/render_command.h>
#include <extra2d/platform/input.h> #include <extra2d/platform/input.h>
#include <extra2d/scene/scene_manager.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> #include <extra2d/utils/logger.h>
namespace extra2d { namespace extra2d {
namespace { namespace {
/**
* @brief -
*/
Node *hitTestTopmost(const Ptr<Node> &node, const Vec2 &worldPos) { Node *hitTestTopmost(const Ptr<Node> &node, const Vec2 &worldPos) {
if (!node || !node->isVisible()) { if (!node || !node->isVisible()) {
return nullptr; return nullptr;
@ -45,6 +42,9 @@ Node *hitTestTopmost(const Ptr<Node> &node, const Vec2 &worldPos) {
return nullptr; return nullptr;
} }
/**
* @brief
*/
void dispatchToNode(Node *node, Event &event) { void dispatchToNode(Node *node, Event &event) {
if (!node) { if (!node) {
return; return;
@ -84,13 +84,11 @@ void SceneManager::replaceScene(Ptr<Scene> scene) {
return; return;
} }
// Pop current scene
auto oldScene = sceneStack_.top(); auto oldScene = sceneStack_.top();
oldScene->onExit(); oldScene->onExit();
oldScene->onDetachFromScene(); oldScene->onDetachFromScene();
sceneStack_.pop(); sceneStack_.pop();
// Push new scene
scene->onEnter(); scene->onEnter();
scene->onAttachToScene(scene.get()); scene->onAttachToScene(scene.get());
sceneStack_.push(scene); 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) { void SceneManager::pushScene(Ptr<Scene> scene) {
if (!scene || isTransitioning_) { if (!scene || isTransitioning_) {
return; return;
} }
// Pause current scene
if (!sceneStack_.empty()) { if (!sceneStack_.empty()) {
sceneStack_.top()->pause(); sceneStack_.top()->pause();
} }
// Push new scene
scene->onEnter(); scene->onEnter();
scene->onAttachToScene(scene.get()); scene->onAttachToScene(scene.get());
sceneStack_.push(scene); 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() { void SceneManager::popScene() {
if (sceneStack_.size() <= 1 || isTransitioning_) { if (sceneStack_.size() <= 1 || isTransitioning_) {
return; return;
@ -273,52 +130,16 @@ void SceneManager::popScene() {
current->onDetachFromScene(); current->onDetachFromScene();
sceneStack_.pop(); sceneStack_.pop();
// Resume previous scene
if (!sceneStack_.empty()) { if (!sceneStack_.empty()) {
sceneStack_.top()->resume(); 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() { void SceneManager::popToRootScene() {
if (sceneStack_.size() <= 1 || isTransitioning_) { if (sceneStack_.size() <= 1 || isTransitioning_) {
return; return;
} }
// Exit all scenes except root
while (sceneStack_.size() > 1) { while (sceneStack_.size() > 1) {
auto scene = sceneStack_.top(); auto scene = sceneStack_.top();
scene->onExit(); scene->onExit();
@ -326,40 +147,14 @@ void SceneManager::popToRootScene() {
sceneStack_.pop(); sceneStack_.pop();
} }
// Resume root
sceneStack_.top()->resume(); 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) { void SceneManager::popToScene(const std::string &name) {
if (isTransitioning_) { if (isTransitioning_) {
return; return;
} }
// Find target scene in stack
std::stack<Ptr<Scene>> tempStack; std::stack<Ptr<Scene>> tempStack;
Ptr<Scene> target = nullptr; 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 { Ptr<Scene> SceneManager::getCurrentScene() const {
if (sceneStack_.empty()) { if (sceneStack_.empty()) {
return nullptr; return nullptr;
@ -418,7 +186,6 @@ Ptr<Scene> SceneManager::getPreviousScene() const {
return nullptr; return nullptr;
} }
// Copy stack to access second top
auto tempStack = sceneStack_; auto tempStack = sceneStack_;
tempStack.pop(); tempStack.pop();
return tempStack.top(); return tempStack.top();
@ -429,7 +196,6 @@ Ptr<Scene> SceneManager::getRootScene() const {
return nullptr; return nullptr;
} }
// Copy stack to access bottom
auto tempStack = sceneStack_; auto tempStack = sceneStack_;
Ptr<Scene> root; Ptr<Scene> root;
while (!tempStack.empty()) { while (!tempStack.empty()) {
@ -445,7 +211,6 @@ Ptr<Scene> SceneManager::getSceneByName(const std::string &name) const {
return it->second; return it->second;
} }
// Search in stack
auto tempStack = sceneStack_; auto tempStack = sceneStack_;
while (!tempStack.empty()) { while (!tempStack.empty()) {
auto scene = tempStack.top(); auto scene = tempStack.top();
@ -464,7 +229,6 @@ bool SceneManager::hasScene(const std::string &name) const {
void SceneManager::update(float dt) { void SceneManager::update(float dt) {
if (isTransitioning_) { if (isTransitioning_) {
// 过渡场景在栈顶,正常更新即可
hoverTarget_ = nullptr; hoverTarget_ = nullptr;
captureTarget_ = nullptr; captureTarget_ = nullptr;
hasLastPointerWorld_ = false; hasLastPointerWorld_ = false;
@ -518,114 +282,6 @@ void SceneManager::end() {
void SceneManager::purgeCachedScenes() { namedScenes_.clear(); } 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) { void SceneManager::dispatchPointerEvents(Scene &scene) {
auto &input = Application::instance().input(); auto &input = Application::instance().input();
Vec2 screenPos = input.getMousePosition(); Vec2 screenPos = input.getMousePosition();

View File

@ -19,9 +19,7 @@ void Sprite::setTexture(Ptr<Texture> texture) {
} }
} }
void Sprite::setTextureRect(const Rect &rect) { void Sprite::setTextureRect(const Rect &rect) { textureRect_ = rect; }
textureRect_ = rect;
}
void Sprite::setColor(const Color &color) { color_ = color; } void Sprite::setColor(const Color &color) { color_ = color; }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 &section,
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 &section, 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 &section, 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 &section, const std::string &key,
bool defaultValue) {
return impl_->ini.GetBoolValue(section.c_str(), key.c_str(), defaultValue);
}
void DataStore::setString(const std::string &section, 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 &section, 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 &section, 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 &section, 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 &section, 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 &section) {
impl_->ini.Delete(section.c_str(), nullptr);
dirty_ = true;
if (!inTransaction_ && !filename_.empty()) {
save("");
}
}
bool DataStore::hasKey(const std::string &section, const std::string &key) {
return impl_->ini.GetValue(section.c_str(), key.c_str(), nullptr) != nullptr;
}
bool DataStore::hasSection(const std::string &section) {
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 &section : sectionList) {
sections.emplace_back(section.pItem);
}
return sections;
}
std::vector<std::string> DataStore::getAllKeys(const std::string &section) 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

View File

@ -1,9 +0,0 @@
#include <extra2d/utils/object_pool.h>
namespace extra2d {
// ObjectPoolManager 单例实现
// 所有对象池通过静态局部变量自动管理生命周期
// 程序退出时自动清理,无需手动调用 cleanup
} // namespace extra2d

View File

@ -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;
}

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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()

View File

@ -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") option("debug_logs")
set_default(false) set_default(false)
set_showmenu(true) set_showmenu(true)
@ -72,7 +66,7 @@ end
-- ============================================== -- ==============================================
if target_plat == "mingw" then if target_plat == "mingw" then
add_requires("glm", "libsdl2", "libsdl2_mixer") add_requires("glm", "libsdl2")
end end
-- ============================================== -- ==============================================
@ -85,11 +79,6 @@ includes("xmake/engine.lua")
-- 定义引擎库 -- 定义引擎库
define_extra2d_engine() 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("Platform: " .. target_plat)
print("Architecture: " .. (get_config("arch") or "auto")) print("Architecture: " .. (get_config("arch") or "auto"))
print("Mode: " .. (is_mode("debug") and "debug" or "release")) print("Mode: " .. (is_mode("debug") and "debug" or "release"))
print("Examples: " .. (has_config("examples") and "enabled" or "disabled"))
print("========================================") print("========================================")

View File

@ -27,11 +27,10 @@ function define_extra2d_engine()
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro" local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
add_includedirs(devkitPro .. "/portlibs/switch/include", {public = true}) add_includedirs(devkitPro .. "/portlibs/switch/include", {public = true})
add_linkdirs(devkitPro .. "/portlibs/switch/lib") add_linkdirs(devkitPro .. "/portlibs/switch/lib")
add_syslinks("SDL2_mixer", "SDL2", "opusfile", "opus", "vorbisidec", "ogg", add_syslinks("SDL2", "GLESv2", "EGL", "glapi", "drm_nouveau",
"modplug", "mpg123", "FLAC", "GLESv2", "EGL", "glapi", "drm_nouveau",
{public = true}) {public = true})
elseif plat == "mingw" then 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}) add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi", {public = true})
end end