Initial engine repository

This commit is contained in:
2026-06-08 19:58:35 +08:00
commit 7a925c3736
137 changed files with 162421 additions and 0 deletions
+220
View File
@@ -0,0 +1,220 @@
#pragma once
#include <frostbite2D/base/RefObject.h>
#include <frostbite2D/types/type_alias.h>
#include <frostbite2D/types/type_math.h>
#include <frostbite2D/types/uuid.h>
#include <frostbite2D/utils/intrusive_list.hpp>
#include <frostbite2D/event/event.h>
#include <frostbite2D/event/key_event.h>
#include <frostbite2D/event/mouse_event.h>
#include <frostbite2D/event/touch_event.h>
#include <frostbite2D/event/joystick_event.h>
#include <frostbite2D/event/window_event.h>
#include <string>
#include <vector>
#include <functional>
#include <unordered_map>
namespace frostbite2D {
class Actor;
class Scene;
using ActorList = IntrusiveList<RefPtr<Actor>>;
class Actor : public RefObject, protected IntrusiveListValue<RefPtr<Actor>> {
public:
using IntrusiveListValue<RefPtr<Actor>>::GetNext;
using IntrusiveListValue<RefPtr<Actor>>::GetPrev;
Actor();
virtual ~Actor();
virtual void Update(float deltaTime);
virtual void Render();
virtual void OnUpdate(float deltaTime) {}
virtual void OnAdded(Actor* parent) {}
void AddChild(RefPtr<Actor> child);
void RemoveChild(RefPtr<Actor> child);
void RemoveAllChildren();
Actor* GetParent() const { return parent_; }
Scene* GetScene() const { return scene_; }
const std::string& GetName() const { return name_; }
void SetName(const std::string& name) { name_ = name; }
const UUID& GetUUID() const { return uuid_; }
std::string GetUUIDString() const { return uuid_.toString(); }
/**
* @brief 获取锚点在父坐标系中的位置
*/
const Vec2& GetPosition() const { return position_; }
/**
* @brief 设置锚点在父坐标系中的位置
*/
void SetPosition(const Vec2& pos);
void SetPosition(float x, float y);
float GetRotation() const { return rotation_; }
void SetRotation(float rotation);
const Vec2& GetScale() const { return scale_; }
void SetScale(const Vec2& scale);
void SetScale(float scale);
const Vec2& GetSkew() const { return skew_; }
void SetSkew(const Vec2& skew);
void SetSkew(float skewX, float skewY);
const Transform2D& GetLocalTransform() const;
const Transform2D& GetWorldTransform() const;
const Vec2& GetSize() const { return size_; }
void SetSize(const Vec2& size);
void SetSize(float width, float height);
/**
* @brief 获取归一化锚点,(0, 0) 为左上角,(0.5, 0.5) 为中心
*/
const Vec2& GetAnchor() const { return anchor_; }
/**
* @brief 设置归一化锚点,不会自动补偿 position
*/
void SetAnchor(const Vec2& anchor);
void SetAnchor(float x, float y);
/**
* @brief 获取未旋转矩形左上角在父坐标系中的位置
*/
Vec2 GetTopLeftPosition() const;
/**
* @brief 设置未旋转矩形左上角在父坐标系中的位置
*/
void SetTopLeftPosition(const Vec2& pos);
void SetTopLeftPosition(float x, float y);
Vec2 LocalToWorld(const Vec2& point) const;
Vec2 WorldToLocal(const Vec2& point) const;
Rect GetWorldBounds() const;
bool ContainsWorldPoint(const Vec2& point) const;
bool ContainsWorldPoint(float x, float y) const;
bool IsVisible() const { return visible_; }
void SetVisible(bool visible) { visible_ = visible; }
int GetZOrder() const { return zOrder_; }
void SetZOrder(int zOrder);
float GetOpacity() const { return opacity_; }
void SetOpacity(float opacity);
float GetWorldOpacity() const;
ActorList& GetChildren() { return children_; }
const ActorList& GetChildren() const { return children_; }
bool IsEventReceiveEnabled() const { return eventReceiveEnabled_; }
void EnableEventReceive() { eventReceiveEnabled_ = true; }
void DisableEventReceive() { eventReceiveEnabled_ = false; }
int32 GetEventPriority() const { return eventPriority_; }
void SetEventPriority(int32 priority) { eventPriority_ = priority; }
bool IsEventInterceptEnabled() const { return eventInterceptEnabled_; }
void EnableEventIntercept() { eventInterceptEnabled_ = true; }
void DisableEventIntercept() { eventInterceptEnabled_ = false; }
enum class UpdatePhase {
BeforeChildren,
AfterChildren
};
using UpdateCallback = Function<void(Actor&, float)>;
using EventCallback = std::function<bool(const Event&)>;
uint32 AddUpdateListener(UpdateCallback callback,
UpdatePhase phase = UpdatePhase::BeforeChildren);
bool RemoveUpdateListener(uint32 listenerId);
void ClearUpdateListeners();
uint32 AddEventListener(EventType type, EventCallback callback);
bool RemoveEventListener(uint32 listenerId);
void ClearEventListeners();
virtual bool OnEvent(const Event& event);
bool DispatchEvent(const Event& event);
bool BubbleEvent(const Event& event);
protected:
void SetParent(Actor* parent) { parent_ = parent; }
void SetScene(Scene* scene) { scene_ = scene; }
void insertChildByZOrder(RefPtr<Actor> child);
void UpdateChildren(float deltaTime);
void RenderChildren();
virtual void OnTransformChanged() {};
private:
void markChildrenOrderDirty();
void reorderChildrenByZOrder();
Actor* parent_;
Scene* scene_;
ActorList children_;
UUID uuid_;
std::string name_;
Vec2 position_;
float rotation_;
Vec2 scale_ = Vec2(1.0f, 1.0f);
Vec2 skew_;
Vec2 size_;
Vec2 anchor_;
bool visible_;
int zOrder_;
float opacity_;
mutable Transform2D localTransform_;
mutable Transform2D worldTransform_;
mutable bool transformDirty_ = true;
bool eventReceiveEnabled_ = false;
int32 eventPriority_ = 0;
bool eventInterceptEnabled_ = false;
struct UpdateListener {
uint32 id;
UpdatePhase phase;
UpdateCallback callback;
bool active = true;
};
std::vector<UpdateListener> updateListeners_;
uint32 nextUpdateListenerId_ = 1;
bool iteratingUpdateListeners_ = false;
bool updateListenersDirty_ = false;
bool iteratingChildren_ = false;
bool childrenOrderDirty_ = false;
struct EventListener {
uint32 id;
EventType type;
EventCallback callback;
};
std::vector<EventListener> eventListeners_;
uint32 nextListenerId_ = 1;
void markTransformDirty();
void updateLocalTransform() const;
void updateWorldTransform() const;
void executeUpdateListeners(UpdatePhase phase, float deltaTime);
void compactUpdateListeners();
friend class Scene;
};
}
@@ -0,0 +1,64 @@
#pragma once
#include <frostbite2D/2d/sprite.h>
#include <frostbite2D/graphics/camera.h>
#include <frostbite2D/graphics/render_texture.h>
#include <functional>
namespace frostbite2D {
/// @brief 离屏画布节点。
///
/// 子节点通过 `AddCanvasChild()` 挂到内部画布树,只会在重绘到离屏纹理时参与渲染,
/// 不会像普通场景子节点那样直接显示到屏幕。
class CanvasActor : public Sprite {
public:
CanvasActor();
~CanvasActor() override = default;
bool Init(int width, int height);
bool SetCanvasSize(int width, int height);
Vec2 GetCanvasSize() const {
return Vec2(static_cast<float>(canvasWidth_), static_cast<float>(canvasHeight_));
}
void SetClearColor(const Color& color);
const Color& GetClearColor() const { return clearColor_; }
void SetDirty();
bool IsDirty() const { return dirty_; }
bool Redraw();
void SetCustomDrawCallback(std::function<void()> callback);
void ClearCustomDrawCallback();
void AddCanvasChild(RefPtr<Actor> child);
void RemoveCanvasChild(RefPtr<Actor> child);
void RemoveAllCanvasChildren();
Actor* GetCanvasRoot() const { return canvasRoot_.Get(); }
ActorList& GetCanvasChildren() { return canvasRoot_->GetChildren(); }
const ActorList& GetCanvasChildren() const { return canvasRoot_->GetChildren(); }
Ptr<Texture> GetOutputTexture() const;
Ptr<RenderTexture> GetRenderTexture() const { return renderTexture_; }
bool IsCanvasReady() const { return renderTexture_ && renderTexture_->IsValid(); }
Camera& GetCanvasCamera() { return canvasCamera_; }
const Camera& GetCanvasCamera() const { return canvasCamera_; }
void OnUpdate(float deltaTime) override;
void Render() override;
private:
bool redrawInternal();
void syncCanvasResources();
RefPtr<Actor> canvasRoot_;
Ptr<RenderTexture> renderTexture_ = nullptr;
Camera canvasCamera_;
Color clearColor_ = Colors::Transparent;
int canvasWidth_ = 0;
int canvasHeight_ = 0;
bool dirty_ = true;
std::function<void()> customDrawCallback_;
};
} // namespace frostbite2D
@@ -0,0 +1,72 @@
#pragma once
#include <frostbite2D/2d/actor.h>
#include <frostbite2D/graphics/texture.h>
#include <frostbite2D/graphics/shader.h>
#include <frostbite2D/graphics/types.h>
#include <string>
namespace frostbite2D {
class Sprite : public Actor {
public:
Sprite();
explicit Sprite(Ptr<Texture> texture);
virtual ~Sprite();
static Ptr<Sprite> createFromFile(const std::string& path);
static Ptr<Sprite> createFromMemory(const uint8* data, int width, int height, int channels);
static Ptr<Sprite> createFromNpk(const std::string& imgPath, size_t frameIndex = 0);
void Render() override;
void SetTexture(Ptr<Texture> texture);
Ptr<Texture> GetTexture() const { return texture_; }
void SetShader(const std::string& shaderName);
void SetDefaultShader();
const std::string& GetShaderName() const { return shaderName_; }
bool HasCustomShader() const { return !shaderName_.empty(); }
void SetSourceRect(const Rect& rect);
const Rect& GetSourceRect() const { return srcRect_; }
void SetColor(const Color& color);
void SetColor(float r, float g, float b, float a = 1.0f);
const Color& GetColor() const { return color_; }
void SetFlippedX(bool flipped);
void SetFlippedY(bool flipped);
bool IsFlippedX() const { return flippedX_; }
bool IsFlippedY() const { return flippedY_; }
void SetBlendMode(BlendMode mode);
BlendMode GetBlendMode() const { return blendMode_; }
void SetSizeToTexture();
const Vec2& GetOffset() const { return offset_; }
void SetOffset(const Vec2& offset);
void SetOffset(float x, float y);
protected:
virtual void ConfigureShader(Shader* shader) const;
void updateTransform();
Shader* getActiveShader() const;
Quad createQuad() const;
private:
Ptr<Texture> texture_;
std::string shaderName_;
Color color_ = Color(1.0f, 1.0f, 1.0f, 1.0f);
Rect srcRect_;
bool flippedX_ = false;
bool flippedY_ = false;
BlendMode blendMode_ = BlendMode::Normal;
Vec2 offset_ = Vec2::Zero();
Transform2D transform_;
static constexpr const char* DEFAULT_SHADER = "sprite";
};
}
@@ -0,0 +1,49 @@
#pragma once
#include <frostbite2D/2d/sprite.h>
#include <frostbite2D/types/type_color.h>
#include <frostbite2D/types/type_math.h>
#include <string>
namespace frostbite2D {
class TextSprite : public Sprite {
public:
enum class Align {
Left,
Center,
Right
};
TextSprite();
~TextSprite();
static Ptr<TextSprite> create();
static Ptr<TextSprite> create(const std::string& text, const std::string& fontName);
void SetText(const std::string& text);
const std::string& GetText() const { return text_; }
void SetFont(const std::string& fontName);
const std::string& GetFontName() const { return fontName_; }
void SetTextColor(const Color& color);
void SetTextColor(float r, float g, float b, float a = 1.0f);
const Color& GetTextColor() const { return textColor_; }
void SetAlign(Align align);
Align GetAlign() const { return align_; }
void RenderText();
Vec2 GetTextSize() const;
private:
void updateTexture();
std::string text_;
std::string fontName_;
Color textColor_ = Colors::White;
Align align_ = Align::Left;
};
}
@@ -0,0 +1,94 @@
#pragma once
#include <frostbite2D/2d/actor.h>
#include <frostbite2D/2d/sprite.h>
#include <frostbite2D/animation/animation_data.h>
#include <functional>
#include <string>
#include <vector>
#include <unordered_map>
namespace frostbite2D {
class Sprite;
class Animation : public Actor {
public:
struct ReplaceData {
int param1 = 0;
int param2 = 0;
ReplaceData() = default;
ReplaceData(int p1, int p2) : param1(p1), param2(p2) {}
};
public:
Animation();
explicit Animation(const std::string& aniPath);
Animation(const std::string& aniPath,
std::function<std::string(std::string, ReplaceData)> additionalOptions,
ReplaceData data);
~Animation();
void Init(const std::string& aniPath);
void OnUpdate(float deltaTime) override;
void OnAdded(Actor* parent) override;
void SetVisible(bool visible);
public:
void FlushFrame(int index);
void Reset();
animation::AniFrame GetCurrentFrameInfo() const;
void SetFrameIndex(int index);
void SetDirection(int direction);
void InterpolationLogic();
Vec2 GetMaxSize() const;
bool GetStaticLocalBounds(Rect& outBounds) const;
bool GetStaticLocalBounds(int direction, Rect& outBounds) const;
uint64 GetRenderSignature() const;
bool IsUsable() const { return usable_; }
void SetUsable(bool usable) { usable_ = usable; }
int GetCurrentFrameIndex() const { return currentFrameIndex_; }
int GetTotalFrameCount() const { return totalFrameCount_; }
public:
bool usable_ = true;
int currentFrameIndex_ = 0;
int totalFrameCount_ = 0;
float currentFrameTime_ = 0.0f;
Ptr<Sprite> currentFrame_ = nullptr;
float nextFrameDelay_ = 9999999.0f;
bool isLooping_ = true;
std::function<void(int)> changeFrameCallback_;
std::function<void()> endCallback_;
std::vector<animation::AniFrame> frames_;
std::vector<Ptr<Sprite>> spriteFrames_;
std::vector<Vec2> spriteFrameOffsets_;
std::unordered_map<std::string, animation::AniFlag> animationFlag_;
std::string type_ = "normal";
std::string aniPath_;
std::function<std::string(std::string, ReplaceData)> additionalOptions_;
ReplaceData additionalOptionsData_;
Vec2 maxSize_ = Vec2::Zero();
int direction_ = 1;
std::vector<animation::AniFrame> interpolationData_;
private:
void ApplyFramePresentation(const Vec2& framePos,
const Vec2& imageRate,
float rotation,
BlendMode blendMode);
};
} // namespace frostbite2D
@@ -0,0 +1,102 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <frostbite2D/types/type_math.h>
#include <variant>
#include <vector>
#include <string>
#include <unordered_map>
#include <optional>
namespace frostbite2D {
class BinaryReader;
namespace animation {
// ---------------------------------------------------------------------------
// 类型定义
// ---------------------------------------------------------------------------
using AniFlag = std::variant<
int,
float,
Vec2,
std::string,
std::vector<int>,
std::vector<float>>;
// ---------------------------------------------------------------------------
// 数据结构
// ---------------------------------------------------------------------------
struct AniFrame {
std::string imgPath; // 图片路径
int imgIndex = 0; // 图片索引
Vec2 imgPos; // 图片位置
std::vector<std::vector<int>> attackBox; // 攻击框 [x, y, w, h, type, param]
std::vector<std::vector<int>> damageBox; // 受击框 [x, y, w, h, type, param]
std::unordered_map<std::string, AniFlag> flag; // 帧特效数据
int delay = 0; // 延迟(毫秒)
};
struct AniInfo {
std::vector<std::string> imgList; // 图片列表
std::vector<AniFrame> frames; // 帧列表
std::unordered_map<std::string, AniFlag> flag; // 动画特效数据
};
struct AlsAniInfo {
std::string path; // 路径
std::vector<int> layer; // 图层 [zOrder, subLayer]
};
struct AlsInfo {
std::unordered_map<std::string, AlsAniInfo> aniList; // ALS 动画列表
};
// ---------------------------------------------------------------------------
// 工具函数
// ---------------------------------------------------------------------------
std::string getAniFlag(int type);
std::string getAniEffectType(int type);
std::string getAniFlipType(int type);
std::string getAniDamageType(int type);
// ---------------------------------------------------------------------------
// 解析函数
// ---------------------------------------------------------------------------
/**
* @brief 解析 .ani 二进制数据
* @param data 二进制数据
* @param size 数据大小
* @return 解析后的动画信息
*/
AniInfo parseAniInfo(const char* data, size_t size);
/**
* @brief 从 PVF 路径解析 .ani 文件
* @param path PVF 中的文件路径(如 "character/player/attack.ani"
* @return 解析后的动画信息,解析失败返回 std::nullopt
*/
std::optional<AniInfo> parseAniFromPvf(const std::string& path);
/**
* @brief 解析 .als 文本数据
* @param data 文本数据
* @return 解析后的 ALS 信息
*/
AlsInfo parseAlsInfo(const std::string& data);
/**
* @brief 从 PVF 路径解析 .als 文件
* @param path PVF 中的文件路径
* @return 解析后的 ALS 信息,解析失败返回 std::nullopt
*/
std::optional<AlsInfo> parseAlsFromPvf(const std::string& path);
} // namespace animation
} // namespace frostbite2D
@@ -0,0 +1,63 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <string>
namespace frostbite2D {
/**
* @brief 音频系统配置
*/
struct AudioConfig {
int frequency = 44100;
uint16_t format = 0x8010;
int channels = 2;
int chunksize = 4096;
int maxSoundChannels = 32;
};
/**
* @brief 音频系统(单例)
*
* 负责初始化和管理整个音频系统,包括音量控制和声道管理。
*/
class AudioSystem {
public:
static AudioSystem& get();
AudioSystem(const AudioSystem&) = delete;
AudioSystem& operator=(const AudioSystem&) = delete;
bool init(const AudioConfig& config = AudioConfig());
void shutdown();
bool isInitialized() const { return initialized_; }
void setMasterVolume(float volume);
float getMasterVolume() const;
void setSoundVolume(float volume);
float getSoundVolume() const;
void setMusicVolume(float volume);
float getMusicVolume() const;
void pauseAllSounds();
void resumeAllSounds();
void stopAllSounds();
int getMaxChannels() const { return maxChannels_; }
private:
AudioSystem() = default;
// ~AudioSystem() 在 shutdown() 中手动调用销毁
void updateVolumes();
bool initialized_ = false;
float masterVolume_ = 1.0f;
float soundVolume_ = 1.0f;
float musicVolume_ = 1.0f;
int maxChannels_ = 32;
};
}
@@ -0,0 +1,52 @@
#pragma once
#include <frostbite2D/base/RefObject.h>
#include <frostbite2D/types/type_alias.h>
#include <string>
#include <SDL2/SDL_mixer.h>
namespace frostbite2D {
/**
* @brief 音乐类
*
* 用于加载和播放长音频文件(背景音乐),支持从文件或内存加载。
* 支持 WAV、OGG、MP3 等格式。
*/
class Music : public RefObject {
public:
static Ptr<Music> loadFromPath(const std::string& path);
static Ptr<Music> loadFromFile(const std::string& path);
static Ptr<Music> loadFromMemory(const uint8* data, size_t size);
static Ptr<Music> loadFromNpk(const std::string& audioPath);
static bool isPathPlaying(const std::string& path);
static std::string getCurrentPlayingPath();
~Music();
bool play(int loops = -1);
bool fadeIn(int ms, int loops = -1);
void pause();
void resume();
void stop();
bool fadeOut(int ms);
void setVolume(float volume);
float getVolume() const;
bool isPlaying() const;
bool isPaused() const;
const std::string& getPath() const { return path_; }
private:
Music(Mix_Music* music, const std::string& path = "");
Music(Mix_Music* music, std::vector<uint8> data, const std::string& path = "");
Mix_Music* music_ = nullptr;
std::string path_;
std::vector<uint8> data_;
float volume_ = 1.0f;
};
}
@@ -0,0 +1,50 @@
#pragma once
#include <frostbite2D/base/RefObject.h>
#include <frostbite2D/types/type_alias.h>
#include <string>
#include <SDL2/SDL_mixer.h>
namespace frostbite2D {
/**
* @brief 音效类
*
* 用于加载和播放短音频文件,支持从文件或内存加载。
* 支持 WAV、OGG 等格式。
*/
class Sound : public RefObject {
public:
static Ptr<Sound> loadFromPath(const std::string& path);
static Ptr<Sound> loadFromFile(const std::string& path);
static Ptr<Sound> loadFromMemory(const uint8* data, size_t size);
static Ptr<Sound> loadFromNpk(const std::string& audioPath);
~Sound();
int play(int loops = 0, int channel = -1);
int playLoop(int channel = -1);
void setVolume(float volume);
float getVolume() const;
int getLastChannel() const;
static void stop(int channel);
static void stopAll();
static bool isPlaying(int channel);
static void pause(int channel);
static void resume(int channel);
static void pauseAll();
static void resumeAll();
const std::string& getPath() const { return path_; }
private:
Sound(Mix_Chunk* chunk, const std::string& path = "");
Mix_Chunk* chunk_ = nullptr;
std::string path_;
float volume_ = 1.0f;
int lastChannel_ = -1;
};
}
@@ -0,0 +1,14 @@
#pragma once
namespace frostbite2D {
class Noncopyable {
protected:
Noncopyable() = default;
private:
Noncopyable(const Noncopyable&) = delete;
Noncopyable& operator=(const Noncopyable&) = delete;
};
}
@@ -0,0 +1,24 @@
#pragma once
#include <atomic>
#include <cstdint>
#include <frostbite2D/base/Noncopyable.h>
namespace frostbite2D {
class RefObject : protected Noncopyable {
public:
void Retain();
void Release();
uint32_t GetRefCount() const;
virtual ~RefObject();
protected:
RefObject();
private:
std::atomic<uint32_t> ref_count_;
};
}
@@ -0,0 +1,39 @@
#pragma once
#include <frostbite2D/base/RefObject.h>
#include <frostbite2D/core/RefBasePtr.hpp>
namespace frostbite2D {
struct DefaultRefPtrPolicy {
void Retain(RefObject* ptr) {
if (ptr) {
ptr->Retain();
}
}
void Release(RefObject* ptr) {
if (ptr) {
ptr->Release();
}
}
};
template <typename T>
using RefPtr = RefBasePtr<T, DefaultRefPtrPolicy>;
template <typename T, typename... Args>
RefPtr<T> MakePtr(Args&&... args) {
static_assert(std::is_base_of<RefObject, T>::value,
"T must be derived from RefObject");
return RefPtr<T>(new T(std::forward<Args>(args)...));
}
template <typename T>
RefPtr<T> MakePtr(T* ptr) {
static_assert(std::is_base_of<RefObject, T>::value,
"T must be derived from RefObject");
return RefPtr<T>(ptr);
}
}
@@ -0,0 +1,4 @@
#pragma once
#include <frostbite2D/base/RefObject.h>
#include <frostbite2D/base/RefPtr.h>
@@ -0,0 +1,39 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <cstddef>
#include <memory>
#include <map>
namespace frostbite2D {
class Actor;
class UUID;
class ObjectRegistry {
public:
static ObjectRegistry& get();
ObjectRegistry(const ObjectRegistry&) = delete;
ObjectRegistry& operator=(const ObjectRegistry&) = delete;
void registerObject(const UUID& uuid, Actor* actor);
void unregisterObject(const UUID& uuid);
Actor* getObject(const UUID& uuid);
Actor* getObject(const std::string& uuidStr);
bool hasObject(const UUID& uuid) const;
bool hasObject(const std::string& uuidStr) const;
size_t count() const;
void clear();
private:
ObjectRegistry() = default;
~ObjectRegistry() = default;
std::map<std::string, Actor*> registry_;
};
} // namespace frostbite2D
@@ -0,0 +1,178 @@
#pragma once
#include <utility>
#include <type_traits>
namespace frostbite2D {
template <typename T, typename RefPolicy>
class RefBasePtr : protected RefPolicy {
public:
using value_type = T;
using pointer_type = T*;
using const_pointer_type = const T*;
using reference_type = T&;
using const_reference_type = const T&;
RefBasePtr() noexcept : ptr_(nullptr) {}
RefBasePtr(std::nullptr_t) noexcept : ptr_(nullptr) {}
RefBasePtr(pointer_type p) : ptr_(p) {
RefPolicy::Retain(ptr_);
}
RefBasePtr(const RefBasePtr& other) : ptr_(other.ptr_) {
RefPolicy::Retain(ptr_);
}
RefBasePtr(RefBasePtr&& other) noexcept : ptr_(nullptr) {
Swap(other);
}
~RefBasePtr() {
Tidy();
}
template <typename U,
typename std::enable_if<std::is_convertible<U*, T*>::value, int>::type = 0>
RefBasePtr(const RefBasePtr<U, RefPolicy>& other) {
ptr_ = dynamic_cast<pointer_type>(other.Get());
RefPolicy::Retain(ptr_);
}
pointer_type Get() const noexcept { return ptr_; }
pointer_type* GetAddressOfAndRelease() {
Tidy();
return &ptr_;
}
void Reset(pointer_type ptr = nullptr) {
if (ptr) {
RefBasePtr(ptr).Swap(*this);
} else {
Tidy();
}
}
void Swap(RefBasePtr& other) noexcept {
std::swap(ptr_, other.ptr_);
}
pointer_type operator->() { return ptr_; }
const_pointer_type operator->() const { return ptr_; }
reference_type operator*() { return *ptr_; }
const_reference_type operator*() const { return *ptr_; }
pointer_type* operator&() { return this->GetAddressOfAndRelease(); }
operator bool() const noexcept { return ptr_ != nullptr; }
bool operator!() const noexcept { return ptr_ == nullptr; }
RefBasePtr& operator=(const RefBasePtr& other) {
if (other.ptr_ != ptr_) {
RefBasePtr(other).Swap(*this);
}
return *this;
}
RefBasePtr& operator=(RefBasePtr&& other) noexcept {
if (other.ptr_ != ptr_) {
other.Swap(*this);
}
return *this;
}
RefBasePtr& operator=(pointer_type p) {
if (p != ptr_) {
RefBasePtr(p).Swap(*this);
}
return *this;
}
template <typename U,
typename std::enable_if<std::is_convertible<U*, T*>::value, int>::type = 0>
RefBasePtr& operator=(const RefBasePtr<U, RefPolicy>& other) {
if (other.Get() != ptr_) {
RefBasePtr(dynamic_cast<pointer_type>(other.Get())).Swap(*this);
}
return *this;
}
RefBasePtr& operator=(std::nullptr_t) {
Tidy();
return *this;
}
private:
void Tidy() {
RefPolicy::Release(ptr_);
ptr_ = nullptr;
}
pointer_type ptr_;
};
template <class T, class U, class RefPolicy>
bool operator==(const RefBasePtr<T, RefPolicy>& lhs,
const RefBasePtr<U, RefPolicy>& rhs) noexcept {
return lhs.Get() == rhs.Get();
}
template <class T, class RefPolicy>
bool operator==(const RefBasePtr<T, RefPolicy>& lhs, T* rhs) noexcept {
return lhs.Get() == rhs;
}
template <class T, class RefPolicy>
bool operator==(T* lhs, const RefBasePtr<T, RefPolicy>& rhs) noexcept {
return lhs == rhs.Get();
}
template <class T, class RefPolicy>
bool operator==(const RefBasePtr<T, RefPolicy>& lhs, std::nullptr_t) noexcept {
return !static_cast<bool>(lhs);
}
template <class T, class RefPolicy>
bool operator==(std::nullptr_t, const RefBasePtr<T, RefPolicy>& rhs) noexcept {
return !static_cast<bool>(rhs);
}
template <class T, class U, class RefPolicy>
bool operator!=(const RefBasePtr<T, RefPolicy>& lhs,
const RefBasePtr<U, RefPolicy>& rhs) noexcept {
return !(lhs == rhs);
}
template <class T, class RefPolicy>
bool operator!=(const RefBasePtr<T, RefPolicy>& lhs, T* rhs) noexcept {
return lhs.Get() != rhs;
}
template <class T, class RefPolicy>
bool operator!=(T* lhs, const RefBasePtr<T, RefPolicy>& rhs) noexcept {
return lhs != rhs.Get();
}
template <class T, class RefPolicy>
bool operator!=(const RefBasePtr<T, RefPolicy>& lhs, std::nullptr_t) noexcept {
return static_cast<bool>(lhs);
}
template <class T, class RefPolicy>
bool operator!=(std::nullptr_t, const RefBasePtr<T, RefPolicy>& rhs) noexcept {
return static_cast<bool>(rhs);
}
template <class T, class U, class RefPolicy>
bool operator<(const RefBasePtr<T, RefPolicy>& lhs,
const RefBasePtr<U, RefPolicy>& rhs) noexcept {
return lhs.Get() < rhs.Get();
}
template <class T, class RefPolicy>
void swap(RefBasePtr<T, RefPolicy>& lhs, RefBasePtr<T, RefPolicy>& rhs) noexcept {
lhs.Swap(rhs);
}
}
@@ -0,0 +1,272 @@
#pragma once
#include <frostbite2D/core/window.h>
#include <frostbite2D/graphics/camera.h>
#include <frostbite2D/graphics/render_style.h>
#include <frostbite2D/graphics/render_resolution.h>
#include <frostbite2D/types/type_alias.h>
#include <frostbite2D/event/event.h>
#include <string>
#include <memory>
namespace frostbite2D {
/**
* @brief 应用配置结构体
* 仅包含应用级别的配置项,模块配置由各模块自行管理
*/
struct AppConfig {
/**
* @brief 应用名称
*/
std::string appName = "frostbite2D App";
/**
* @brief 应用版本号
*/
std::string appVersion = "1.0.0";
/**
* @brief 组织名称
*/
std::string organization = "frostbite";
/**
* @brief 窗口配置
*/
WindowConfig windowConfig;
/**
* @brief 目标平台
*/
PlatformType targetPlatform = PlatformType::Auto;
/**
* @brief 是否启用虚拟分辨率
*/
bool useVirtualResolution = false;
/**
* @brief 虚拟分辨率宽度
*/
int virtualWidth = 0;
/**
* @brief 虚拟分辨率高度
*/
int virtualHeight = 0;
/**
* @brief 虚拟分辨率缩放模式
*/
ResolutionScaleMode resolutionMode = ResolutionScaleMode::Fit;
bool useSeparateUIVirtualResolution = false;
int uiVirtualWidth = 0;
int uiVirtualHeight = 0;
ResolutionScaleMode uiResolutionMode = ResolutionScaleMode::Fit;
/**
* @brief 默认 2D 渲染风格预设
* 启动时作为世界/UI 渲染的基础风格,Scene 可按需覆盖
*/
RenderStyleProfileId renderStyleProfile = RenderStyleProfileId::Hybrid2D;
/**
* @brief 最大帧率,0 表示不限制
*/
int maxFps = 0;
/**
* @brief 创建默认配置
* @return 默认的应用配置实例
*/
static AppConfig createDefault() {
AppConfig config;
config.appName = "Frostbite2D App";
config.appVersion = "1.0.0";
config.organization = "frostbite";
config.targetPlatform = PlatformType::Auto;
config.windowConfig = WindowConfig();
config.renderStyleProfile = RenderStyleProfileId::Hybrid2D;
config.useSeparateUIVirtualResolution = false;
config.uiVirtualWidth = 0;
config.uiVirtualHeight = 0;
config.uiResolutionMode = ResolutionScaleMode::Fit;
config.maxFps = 0;
return config;
}
};
/**
* @brief 应用程序类
*/
class Application {
public:
using StartCallback = Function<void()>;
/**
* @brief 获取单例实例
* @return 应用程序实例引用
*/
static Application& get();
Application(const Application&) = delete;
Application& operator=(const Application&) = delete;
/**
* @brief 使用默认配置初始化
* @return 初始化成功返回 true
*/
bool init();
/**
* @brief 使用指定配置初始化
* @param config 应用配置
* @return 初始化成功返回 true
*/
bool init(const AppConfig& config);
/**
* @brief 关闭应用程序
*/
void shutdown();
/**
* @brief 运行主循环
* @param callback 主循环开始前执行一次的启动回调
*/
void run(StartCallback callback);
/**
* @brief 请求退出
*/
void quit();
/**
* @brief 暂停应用程序
*/
void pause();
/**
* @brief 恢复应用程序
*/
void resume();
/**
* @brief 检查是否暂停
* @return 暂停状态返回 true
*/
bool isPaused() const { return paused_; }
/**
* @brief 检查是否运行中
* @return 运行中返回 true
*/
bool isRunning() const { return running_; }
/**
* @brief 获取总运行时间
* @return 总运行时间(秒)
*/
float totalTime() const { return totalTime_; }
/**
* @brief 获取当前帧率
* @return 帧率
*/
int fps() const { return currentFps_; }
/**
* @brief 设置最大帧率
* @param maxFps 0 表示不限制,>0 表示帧率上限
*/
void setMaxFps(int maxFps);
/**
* @brief 获取最大帧率设置
* @return 0 表示不限制,>0 表示帧率上限
*/
int maxFps() const { return maxFps_; }
void setTextInputEnabled(bool enabled);
bool isTextInputEnabled() const { return textInputEnabled_; }
void requestTextInput();
void releaseTextInput();
void setImmEnabled(bool enabled);
bool isImmEnabled() const;
/**
* @brief 获取应用配置
* @return 应用配置常量引用
*/
const AppConfig& getConfig() const;
/**
* @brief 获取窗口
* @return 窗口指针
*/
Window* getWindow() const { return window_; }
/**
* @brief 获取渲染器
* @return 渲染器指针
*/
class Renderer* getRenderer() const { return renderer_; }
/**
* @brief 获取 delta time
* @return 帧间隔时间(秒)
*/
float deltaTime() const { return deltaTime_; }
private:
Application() = default;
~Application();
/**
* @brief 初始化核心模块
* @return 初始化成功返回 true
*/
bool initCoreModules();
/**
* @brief 主循环
*/
void mainLoop();
/**
* @brief 更新
*/
void update();
/**
* @brief 渲染
*/
void render();
std::unique_ptr<Event> convertSDLEvent(const SDL_Event& sdlEvent);
void dispatchEvent(const Event& event);
Window* window_ = nullptr;
class Renderer* renderer_ = nullptr;
Camera* camera_ = nullptr;
AppConfig config_;
bool initialized_ = false;
bool running_ = false;
bool paused_ = false;
bool shouldQuit_ = false;
bool textInputEnabled_ = false;
bool immEnabled_ = false;
int textInputRequestCount_ = 0;
float deltaTime_ = 0.0f;
float totalTime_ = 0.0f;
double lastFrameTime_ = 0.0;
int frameCount_ = 0;
float fpsTimer_ = 0.0f;
int currentFps_ = 0;
int maxFps_ = 0;
double targetFrameDuration_ = 0.0;
const float fpsUpdateInterval_ = 1.0f;
};
}
@@ -0,0 +1,142 @@
#pragma once
#include <atomic>
#include <condition_variable>
#include <exception>
#include <future>
#include <memory>
#include <mutex>
#include <queue>
#include <thread>
#include <type_traits>
#include <utility>
#include <vector>
#include <frostbite2D/types/type_alias.h>
namespace frostbite2D {
struct TaskSystemConfig {
uint32 workerCount = 0;
uint32 maxMainThreadCallbacksPerFrame = 0;
uint32 threadStackSize = 0;
};
class TaskSystem {
public:
static TaskSystem& get();
TaskSystem(const TaskSystem&) = delete;
TaskSystem& operator=(const TaskSystem&) = delete;
bool init(const TaskSystemConfig& config = {});
void shutdown();
bool isInitialized() const { return initialized_; }
bool isMainThread() const;
size_t workerCount() const;
uint32 maxMainThreadCallbacksPerFrame() const;
template <typename F>
void post(F&& task) {
enqueueWorkerTask(Function<void()>(std::forward<F>(task)));
}
template <typename F>
auto submit(F&& task) -> std::future<std::invoke_result_t<F>> {
using Result = std::invoke_result_t<F>;
auto packagedTask =
std::make_shared<std::packaged_task<Result()>>(std::forward<F>(task));
std::future<Result> future = packagedTask->get_future();
enqueueWorkerTask([packagedTask]() mutable { (*packagedTask)(); });
return future;
}
template <typename F>
void postToMainThread(F&& callback) {
enqueueMainThreadTask(Function<void()>(std::forward<F>(callback)));
}
template <typename Task, typename OnComplete>
void submitThen(Task&& task, OnComplete&& onComplete) {
using Result = std::invoke_result_t<Task>;
using Storage = std::decay_t<Result>;
auto taskFn = std::make_shared<std::decay_t<Task>>(std::forward<Task>(task));
auto completeFn =
std::make_shared<std::decay_t<OnComplete>>(std::forward<OnComplete>(onComplete));
enqueueWorkerTask([this, taskFn, completeFn]() mutable {
try {
if constexpr (std::is_void_v<Result>) {
std::invoke(*taskFn);
enqueueMainThreadTask([completeFn]() mutable { std::invoke(*completeFn); });
} else {
auto result = std::make_shared<Storage>(std::invoke(*taskFn));
enqueueMainThreadTask([completeFn, result]() mutable {
std::invoke(*completeFn, std::move(*result));
});
}
} catch (...) {
logUnhandledTaskException(std::current_exception());
}
});
}
template <typename Task, typename OnComplete, typename OnError>
void submitThen(Task&& task, OnComplete&& onComplete, OnError&& onError) {
using Result = std::invoke_result_t<Task>;
using Storage = std::decay_t<Result>;
auto taskFn = std::make_shared<std::decay_t<Task>>(std::forward<Task>(task));
auto completeFn =
std::make_shared<std::decay_t<OnComplete>>(std::forward<OnComplete>(onComplete));
auto errorFn =
std::make_shared<std::decay_t<OnError>>(std::forward<OnError>(onError));
enqueueWorkerTask([this, taskFn, completeFn, errorFn]() mutable {
try {
if constexpr (std::is_void_v<Result>) {
std::invoke(*taskFn);
enqueueMainThreadTask([completeFn]() mutable { std::invoke(*completeFn); });
} else {
auto result = std::make_shared<Storage>(std::invoke(*taskFn));
enqueueMainThreadTask([completeFn, result]() mutable {
std::invoke(*completeFn, std::move(*result));
});
}
} catch (...) {
auto error = std::make_shared<std::exception_ptr>(std::current_exception());
enqueueMainThreadTask([errorFn, error]() mutable { std::invoke(*errorFn, *error); });
}
});
}
void drainMainThreadTasks(uint32 maxTasks = 0);
private:
TaskSystem() = default;
~TaskSystem();
void enqueueWorkerTask(Function<void()> task);
void enqueueMainThreadTask(Function<void()> task);
size_t resolveWorkerCount(uint32 requestedCount) const;
static void logUnhandledTaskException(std::exception_ptr error);
TaskSystemConfig config_;
std::vector<std::thread> workers_;
std::queue<Function<void()>> workerTasks_;
std::queue<Function<void()>> mainThreadTasks_;
mutable std::mutex workerMutex_;
mutable std::mutex mainThreadMutex_;
std::condition_variable workerCondition_;
std::atomic<bool> initialized_ = false;
std::atomic<bool> acceptingTasks_ = false;
std::thread::id mainThreadId_;
};
} // namespace frostbite2D
@@ -0,0 +1,137 @@
#pragma once
#include <SDL2/SDL.h>
#include <frostbite2D/types/type_alias.h>
#include <frostbite2D/types/type_math.h>
#include <functional>
#include <string>
namespace frostbite2D {
enum class CursorType {
Arrow,
TextInput,
Hand,
SizeAll,
SizeWE,
SizeNS,
SizeNESW,
SizeNWSE,
No,
};
struct Resolution {
uint32_t width = 0;
uint32_t height = 0;
uint32_t refresh_rate = 0;
Resolution() = default;
Resolution(uint32_t width, uint32_t height, uint32_t refresh_rate)
: width(width), height(height), refresh_rate(refresh_rate) {}
};
struct Icon {
Icon() = default;
Icon(std::string file_path) : file_path(file_path) {}
std::string file_path;
#if defined(_WIN32)
uint32_t resource_id = 0;
Icon(uint32_t resource_id) : resource_id(resource_id) {}
#endif
};
struct WindowConfig {
uint32_t width = 640;
uint32_t height = 480;
std::string title = "frostbite2D Game";
Icon icon;
bool resizable = false;
bool fullscreen = false;
bool borderless = false;
bool decorated = true;
int multisamples = 0;
bool centered = true;
bool vsync = true;
bool showCursor = true;
};
class Window {
public:
Window() = default;
~Window() = default;
virtual bool create(const WindowConfig& cfg);
virtual void destroy();
virtual void poll();
virtual void swap();
virtual void close();
virtual void setTitle(const std::string& title);
virtual void setSize(int w, int h);
virtual void setPos(int x, int y);
virtual void setFullscreen(bool fs);
virtual void setVSync(bool vsync);
virtual void setVisible(bool visible);
virtual int width() const;
virtual int height() const;
virtual int drawableWidth() const;
virtual int drawableHeight() const;
virtual Size size() const;
virtual Vec2 pos() const;
virtual bool fullscreen() const;
virtual bool vsync() const;
virtual bool focused() const;
virtual bool minimized() const;
virtual void setImmEnabled(bool enabled);
virtual bool isImmEnabled() const;
virtual float scaleX() const;
virtual float scaleY() const;
virtual bool refreshMetrics(bool emitResize = false);
virtual void setCursor(CursorType cursor);
virtual void showCursor(bool show);
virtual void lockCursor(bool lock);
using ResizeCb = std::function<void(int, int)>;
using CloseCb = std::function<void()>;
using FocusCb = std::function<void(bool)>;
virtual void onResize(ResizeCb cb);
virtual void onClose(CloseCb cb);
virtual void onFocus(FocusCb cb);
virtual void* native() const;
SDL_Window* sdlWindow() const { return sdlWindow_; }
SDL_GLContext glContext() const { return glContext_; }
private:
SDL_Window* sdlWindow_ = nullptr;
SDL_GLContext glContext_ = nullptr;
int width_ = 1280;
int height_ = 720;
int drawableWidth_ = 1280;
int drawableHeight_ = 720;
bool fullscreen_ = false;
bool vsync_ = true;
bool focused_ = true;
bool minimized_ = false;
bool shouldClose_ = false;
float scaleX_ = 1.0f;
float scaleY_ = 1.0f;
bool cursorVisible_ = true;
bool cursorLocked_ = false;
bool immEnabled_ = false;
bool immDisabledByApp_ = false;
void* immRestoreContext_ = nullptr;
ResizeCb resizeCb_;
CloseCb closeCb_;
FocusCb focusCb_;
};
} // namespace frostbite2D
@@ -0,0 +1,63 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
namespace frostbite2D {
class Actor;
enum class EventType : int32 {
None = 0,
WindowClose,
WindowResize,
WindowFocus,
WindowMinimize,
WindowMaximize,
WindowRestore,
KeyDown,
KeyUp,
KeyText,
MouseMove,
MouseButtonDown,
MouseButtonUp,
MouseWheel,
TouchDown,
TouchUp,
TouchMove,
JoystickAxis,
JoystickBall,
JoystickHat,
JoystickButtonDown,
JoystickButtonUp,
Custom
};
class Event {
public:
virtual ~Event() = default;
virtual EventType getType() const = 0;
bool isHandled() const { return handled_; }
void setHandled(bool handled) { handled_ = handled; }
bool isPropagationStopped() const { return propagationStopped_; }
void stopPropagation() { propagationStopped_ = true; }
Actor* getSource() const { return source_; }
void setSource(Actor* actor) { source_ = actor; }
uint32 getTimestamp() const { return timestamp_; }
protected:
Event() = default;
Event(uint32 timestamp)
: timestamp_(timestamp) {}
bool handled_ = false;
bool propagationStopped_ = false;
Actor* source_ = nullptr;
uint32 timestamp_ = 0;
};
}
@@ -0,0 +1,112 @@
#pragma once
#include <frostbite2D/event/event.h>
#include <SDL2/SDL_joystick.h>
namespace frostbite2D {
class JoystickEvent : public Event {
public:
JoystickEvent(EventType type, uint32 timestamp, int32 deviceId)
: Event(timestamp)
, type_(type)
, deviceId_(deviceId) {}
EventType getType() const override {
return type_;
}
int32 getDeviceId() const { return deviceId_; }
protected:
EventType type_;
int32 deviceId_;
};
class JoystickAxisEvent : public JoystickEvent {
public:
JoystickAxisEvent(uint32 timestamp, int32 deviceId, uint8 axis, int16 value)
: JoystickEvent(EventType::JoystickAxis, timestamp, deviceId)
, axis_(axis)
, value_(value) {}
uint8 getAxis() const { return axis_; }
int16 getValue() const { return value_; }
float getNormalizedValue() const {
return static_cast<float>(value_) / 32767.0f;
}
private:
uint8 axis_;
int16 value_;
};
class JoystickBallEvent : public JoystickEvent {
public:
JoystickBallEvent(uint32 timestamp, int32 deviceId, uint8 ball, int16 dx, int16 dy)
: JoystickEvent(EventType::JoystickBall, timestamp, deviceId)
, ball_(ball)
, dx_(dx)
, dy_(dy) {}
uint8 getBall() const { return ball_; }
int16 getDX() const { return dx_; }
int16 getDY() const { return dy_; }
private:
uint8 ball_;
int16 dx_;
int16 dy_;
};
class JoystickHatEvent : public JoystickEvent {
public:
JoystickHatEvent(uint32 timestamp, int32 deviceId, uint8 hat, uint8 value)
: JoystickEvent(EventType::JoystickHat, timestamp, deviceId)
, hat_(hat)
, value_(value) {}
uint8 getHat() const { return hat_; }
uint8 getValue() const { return value_; }
bool isCentered() const { return value_ == SDL_HAT_CENTERED; }
bool isUp() const { return (value_ & SDL_HAT_UP) != 0; }
bool isRight() const { return (value_ & SDL_HAT_RIGHT) != 0; }
bool isDown() const { return (value_ & SDL_HAT_DOWN) != 0; }
bool isLeft() const { return (value_ & SDL_HAT_LEFT) != 0; }
private:
uint8 hat_;
uint8 value_;
};
class JoystickButtonEvent : public JoystickEvent {
public:
JoystickButtonEvent(EventType type, uint32 timestamp, int32 deviceId, uint8 button)
: JoystickEvent(type, timestamp, deviceId)
, button_(button) {}
EventType getType() const override {
return type_;
}
uint8 getButton() const { return button_; }
private:
uint8 button_;
};
class JoystickButtonDownEvent : public JoystickButtonEvent {
public:
JoystickButtonDownEvent(uint32 timestamp, int32 deviceId, uint8 button)
: JoystickButtonEvent(EventType::JoystickButtonDown, timestamp, deviceId, button) {}
};
class JoystickButtonUpEvent : public JoystickButtonEvent {
public:
JoystickButtonUpEvent(uint32 timestamp, int32 deviceId, uint8 button)
: JoystickButtonEvent(EventType::JoystickButtonUp, timestamp, deviceId, button) {}
};
}
@@ -0,0 +1,208 @@
#pragma once
#include <frostbite2D/event/event.h>
#include <SDL2/SDL_keyboard.h>
#include <string>
namespace frostbite2D {
enum class KeyCode {
Unknown = SDLK_UNKNOWN,
Return = SDLK_RETURN,
Escape = SDLK_ESCAPE,
Backspace = SDLK_BACKSPACE,
Tab = SDLK_TAB,
Space = SDLK_SPACE,
Exclaim = SDLK_EXCLAIM,
Quotedbl = SDLK_QUOTEDBL,
Hash = SDLK_HASH,
Percent = SDLK_PERCENT,
Dollar = SDLK_DOLLAR,
Ampersand = SDLK_AMPERSAND,
Quote = SDLK_QUOTE,
LeftParen = SDLK_LEFTPAREN,
RightParen = SDLK_RIGHTPAREN,
Asterisk = SDLK_ASTERISK,
Plus = SDLK_PLUS,
Comma = SDLK_COMMA,
Minus = SDLK_MINUS,
Period = SDLK_PERIOD,
Slash = SDLK_SLASH,
Num0 = SDLK_0,
Num1 = SDLK_1,
Num2 = SDLK_2,
Num3 = SDLK_3,
Num4 = SDLK_4,
Num5 = SDLK_5,
Num6 = SDLK_6,
Num7 = SDLK_7,
Num8 = SDLK_8,
Num9 = SDLK_9,
Colon = SDLK_COLON,
Semicolon = SDLK_SEMICOLON,
Less = SDLK_LESS,
Equals = SDLK_EQUALS,
Greater = SDLK_GREATER,
Question = SDLK_QUESTION,
At = SDLK_AT,
LeftBracket = SDLK_LEFTBRACKET,
Backslash = SDLK_BACKSLASH,
RightBracket = SDLK_RIGHTBRACKET,
Caret = SDLK_CARET,
Underscore = SDLK_UNDERSCORE,
Backquote = SDLK_BACKQUOTE,
a = SDLK_a,
b = SDLK_b,
c = SDLK_c,
d = SDLK_d,
e = SDLK_e,
f = SDLK_f,
g = SDLK_g,
h = SDLK_h,
i = SDLK_i,
j = SDLK_j,
k = SDLK_k,
l = SDLK_l,
m = SDLK_m,
n = SDLK_n,
o = SDLK_o,
p = SDLK_p,
q = SDLK_q,
r = SDLK_r,
s = SDLK_s,
t = SDLK_t,
u = SDLK_u,
v = SDLK_v,
w = SDLK_w,
x = SDLK_x,
y = SDLK_y,
z = SDLK_z,
CapsLock = SDLK_CAPSLOCK,
F1 = SDLK_F1,
F2 = SDLK_F2,
F3 = SDLK_F3,
F4 = SDLK_F4,
F5 = SDLK_F5,
F6 = SDLK_F6,
F7 = SDLK_F7,
F8 = SDLK_F8,
F9 = SDLK_F9,
F10 = SDLK_F10,
F11 = SDLK_F11,
F12 = SDLK_F12,
PrintScreen = SDLK_PRINTSCREEN,
ScrollLock = SDLK_SCROLLLOCK,
Pause = SDLK_PAUSE,
Insert = SDLK_INSERT,
Home = SDLK_HOME,
PageUp = SDLK_PAGEUP,
Delete = SDLK_DELETE,
End = SDLK_END,
PageDown = SDLK_PAGEDOWN,
Right = SDLK_RIGHT,
Left = SDLK_LEFT,
Down = SDLK_DOWN,
Up = SDLK_UP,
Numpad0 = SDLK_KP_0,
Numpad1 = SDLK_KP_1,
Numpad2 = SDLK_KP_2,
Numpad3 = SDLK_KP_3,
Numpad4 = SDLK_KP_4,
Numpad5 = SDLK_KP_5,
Numpad6 = SDLK_KP_6,
Numpad7 = SDLK_KP_7,
Numpad8 = SDLK_KP_8,
Numpad9 = SDLK_KP_9,
NumpadDecimal = SDLK_KP_DECIMAL,
NumpadDivide = SDLK_KP_DIVIDE,
NumpadMultiply = SDLK_KP_MULTIPLY,
NumpadMinus = SDLK_KP_MINUS,
NumpadPlus = SDLK_KP_PLUS,
NumpadEnter = SDLK_KP_ENTER,
NumpadEquals = SDLK_KP_EQUALS,
LShift = SDLK_LSHIFT,
RShift = SDLK_RSHIFT,
LCtrl = SDLK_LCTRL,
RCtrl = SDLK_RCTRL,
LAlt = SDLK_LALT,
RAlt = SDLK_RALT,
LGui = SDLK_LGUI,
RGui = SDLK_RGUI
};
enum class KeyMod {
None = KMOD_NONE,
LShift = KMOD_LSHIFT,
RShift = KMOD_RSHIFT,
Shift = KMOD_SHIFT,
LCtrl = KMOD_LCTRL,
RCtrl = KMOD_RCTRL,
Ctrl = KMOD_CTRL,
LAlt = KMOD_LALT,
RAlt = KMOD_RALT,
Alt = KMOD_ALT,
LGui = KMOD_LGUI,
RGui = KMOD_RGUI,
Gui = KMOD_GUI,
Num = KMOD_NUM,
Caps = KMOD_CAPS
};
class KeyEvent : public Event {
public:
KeyEvent(uint32 timestamp, KeyCode keyCode, KeyMod modifiers, bool repeat)
: Event(timestamp)
, keyCode_(keyCode)
, modifiers_(modifiers)
, repeat_(repeat) {}
EventType getType() const override {
return EventType::KeyDown;
}
KeyCode getKeyCode() const { return keyCode_; }
KeyMod getModifiers() const { return modifiers_; }
bool isRepeat() const { return repeat_; }
private:
KeyCode keyCode_;
KeyMod modifiers_;
bool repeat_;
};
class KeyUpEvent : public Event {
public:
KeyUpEvent(uint32 timestamp, KeyCode keyCode, KeyMod modifiers)
: Event(timestamp)
, keyCode_(keyCode)
, modifiers_(modifiers) {}
EventType getType() const override {
return EventType::KeyUp;
}
KeyCode getKeyCode() const { return keyCode_; }
KeyMod getModifiers() const { return modifiers_; }
private:
KeyCode keyCode_;
KeyMod modifiers_;
};
class KeyTextEvent : public Event {
public:
KeyTextEvent(uint32 timestamp, const std::string& text)
: Event(timestamp)
, text_(text) {}
EventType getType() const override {
return EventType::KeyText;
}
const std::string& getText() const { return text_; }
private:
std::string text_;
};
}
@@ -0,0 +1,108 @@
#pragma once
#include <frostbite2D/event/event.h>
#include <frostbite2D/types/type_math.h>
#include <SDL2/SDL_mouse.h>
namespace frostbite2D {
enum class MouseButton {
Left = SDL_BUTTON_LEFT,
Right = SDL_BUTTON_RIGHT,
Middle = SDL_BUTTON_MIDDLE,
X1 = SDL_BUTTON_X1,
X2 = SDL_BUTTON_X2
};
class MouseEvent : public Event {
public:
MouseEvent(EventType type, uint32 timestamp, const Vec2& position)
: Event(timestamp)
, type_(type)
, position_(position) {}
virtual ~MouseEvent() = default;
EventType getType() const override { return type_; }
const Vec2& getPosition() const { return position_; }
int32 getX() const { return static_cast<int32>(position_.x); }
int32 getY() const { return static_cast<int32>(position_.y); }
protected:
EventType type_;
Vec2 position_;
};
class MouseMoveEvent : public Event {
public:
MouseMoveEvent(uint32 timestamp, const Vec2& position, const Vec2& relative)
: Event(timestamp)
, position_(position)
, relative_(relative) {}
EventType getType() const override {
return EventType::MouseMove;
}
const Vec2& getPosition() const { return position_; }
const Vec2& getRelative() const { return relative_; }
int32 getX() const { return static_cast<int32>(position_.x); }
int32 getY() const { return static_cast<int32>(position_.y); }
private:
Vec2 position_;
Vec2 relative_;
};
class MouseButtonEvent : public Event {
public:
MouseButtonEvent(uint32 timestamp, MouseButton button, const Vec2& position, bool down)
: Event(timestamp)
, button_(button)
, position_(position)
, down_(down) {}
EventType getType() const override {
return down_ ? EventType::MouseButtonDown : EventType::MouseButtonUp;
}
MouseButton getButton() const { return button_; }
const Vec2& getPosition() const { return position_; }
bool isDown() const { return down_; }
int32 getX() const { return static_cast<int32>(position_.x); }
int32 getY() const { return static_cast<int32>(position_.y); }
private:
MouseButton button_;
Vec2 position_;
bool down_;
};
class MouseWheelEvent : public Event {
public:
MouseWheelEvent(uint32 timestamp, const Vec2& position, const Vec2& direction, bool flipped)
: Event(timestamp)
, position_(position)
, direction_(direction)
, flipped_(flipped) {}
EventType getType() const override {
return EventType::MouseWheel;
}
const Vec2& getPosition() const { return position_; }
const Vec2& getDirection() const { return direction_; }
bool isFlipped() const { return flipped_; }
int32 getX() const { return static_cast<int32>(direction_.x); }
int32 getY() const { return static_cast<int32>(direction_.y); }
private:
Vec2 position_;
Vec2 direction_;
bool flipped_;
};
}
@@ -0,0 +1,64 @@
#pragma once
#include <frostbite2D/event/event.h>
#include <frostbite2D/types/type_math.h>
#include <SDL2/SDL_touch.h>
namespace frostbite2D {
class TouchEvent : public Event {
public:
TouchEvent(EventType type, uint32 timestamp, int64 touchId, int64 fingerId,
const Vec2& position, const Vec2& delta, float pressure)
: Event(timestamp)
, type_(type)
, touchId_(touchId)
, fingerId_(fingerId)
, position_(position)
, delta_(delta)
, pressure_(pressure) {}
EventType getType() const override {
return type_;
}
int64 getTouchId() const { return touchId_; }
int64 getFingerId() const { return fingerId_; }
const Vec2& getPosition() const { return position_; }
const Vec2& getDelta() const { return delta_; }
float getPressure() const { return pressure_; }
private:
EventType type_;
int64 touchId_;
int64 fingerId_;
Vec2 position_;
Vec2 delta_;
float pressure_;
};
class TouchDownEvent : public TouchEvent {
public:
TouchDownEvent(uint32 timestamp, int64 touchId, int64 fingerId,
const Vec2& position, float pressure)
: TouchEvent(EventType::TouchDown, timestamp, touchId, fingerId,
position, Vec2(0, 0), pressure) {}
};
class TouchUpEvent : public TouchEvent {
public:
TouchUpEvent(uint32 timestamp, int64 touchId, int64 fingerId,
const Vec2& position, float pressure)
: TouchEvent(EventType::TouchUp, timestamp, touchId, fingerId,
position, Vec2(0, 0), pressure) {}
};
class TouchMoveEvent : public TouchEvent {
public:
TouchMoveEvent(uint32 timestamp, int64 touchId, int64 fingerId,
const Vec2& position, const Vec2& delta, float pressure)
: TouchEvent(EventType::TouchMove, timestamp, touchId, fingerId,
position, delta, pressure) {}
};
}
@@ -0,0 +1,77 @@
#pragma once
#include <frostbite2D/event/event.h>
#include <SDL2/SDL_events.h>
namespace frostbite2D {
class WindowEvent : public Event {
public:
WindowEvent(EventType type, uint32 timestamp, int32 windowId)
: Event(timestamp)
, type_(type)
, windowId_(windowId) {}
EventType getType() const override {
return type_;
}
int32 getWindowId() const { return windowId_; }
protected:
EventType type_;
int32 windowId_;
};
class WindowCloseEvent : public WindowEvent {
public:
WindowCloseEvent(uint32 timestamp, int32 windowId)
: WindowEvent(EventType::WindowClose, timestamp, windowId) {}
};
class WindowResizeEvent : public WindowEvent {
public:
WindowResizeEvent(uint32 timestamp, int32 windowId, int32 width, int32 height)
: WindowEvent(EventType::WindowResize, timestamp, windowId)
, width_(width)
, height_(height) {}
int32 getWidth() const { return width_; }
int32 getHeight() const { return height_; }
private:
int32 width_;
int32 height_;
};
class WindowFocusEvent : public WindowEvent {
public:
WindowFocusEvent(uint32 timestamp, int32 windowId, bool focused)
: WindowEvent(EventType::WindowFocus, timestamp, windowId)
, focused_(focused) {}
bool isFocused() const { return focused_; }
private:
bool focused_;
};
class WindowMinimizeEvent : public WindowEvent {
public:
WindowMinimizeEvent(uint32 timestamp, int32 windowId)
: WindowEvent(EventType::WindowMinimize, timestamp, windowId) {}
};
class WindowMaximizeEvent : public WindowEvent {
public:
WindowMaximizeEvent(uint32 timestamp, int32 windowId)
: WindowEvent(EventType::WindowMaximize, timestamp, windowId) {}
};
class WindowRestoreEvent : public WindowEvent {
public:
WindowRestoreEvent(uint32 timestamp, int32 windowId)
: WindowEvent(EventType::WindowRestore, timestamp, windowId) {}
};
}
@@ -0,0 +1,68 @@
#pragma once
#include <frostbite2D/graphics/types.h>
#include <frostbite2D/graphics/texture.h>
#include <frostbite2D/graphics/shader.h>
#include <frostbite2D/types/type_alias.h>
namespace frostbite2D {
struct BatchKey {
uint32_t shaderID;
uint32_t textureID;
uint32_t blendMode;
bool operator<(const BatchKey& other) const {
if (shaderID != other.shaderID) return shaderID < other.shaderID;
if (textureID != other.textureID) return textureID < other.textureID;
return blendMode < other.blendMode;
}
};
class Batch {
public:
static constexpr int MAX_QUADS = 2048;
static constexpr int MAX_VERTICES = MAX_QUADS * 4;
static constexpr int MAX_INDICES = MAX_QUADS * 6;
Batch();
~Batch();
bool init();
void shutdown();
void begin();
void end();
void submitQuad(const Quad& quad, const Transform2D& transform,
Ptr<Texture> texture, Shader* shader,
BlendMode blendMode = BlendMode::Normal);
void flush();
void flushIfNeeded(const BatchKey& newKey, Shader* shader, Ptr<Texture> texture);
uint32 drawCallCount() const { return drawCallCount_; }
private:
void setupMesh();
void flushCurrentBatch();
struct BatchData {
std::vector<Vertex> vertices;
std::vector<uint16> indices;
BatchKey key;
Shader* shader = nullptr;
Ptr<Texture> texture;
};
uint32_t vao_ = 0;
uint32_t vbo_ = 0;
uint32_t ibo_ = 0;
BatchData currentBatch_;
std::vector<BatchData> batches_;
bool begun_ = false;
uint32 drawCallCount_ = 0;
};
}
@@ -0,0 +1,43 @@
#pragma once
#include <frostbite2D/types/type_math.h>
namespace frostbite2D {
class Camera {
public:
Camera();
void setPosition(const Vec2& pos);
void setZoom(float zoom);
void setViewport(int width, int height);
void setFlipY(bool flip) { flipY_ = flip; } // Debug Y-axis flip.
void setPixelSnapEnabled(bool enabled) { pixelSnapEnabled_ = enabled; }
const Vec2& getPosition() const { return position_; }
Vec2 getRenderPosition() const;
Vec2 snapWorldPosition(const Vec2& position) const;
float getZoom() const { return zoom_; }
int getViewportWidth() const { return viewportWidth_; }
int getViewportHeight() const { return viewportHeight_; }
float getVisibleWidth() const;
float getVisibleHeight() const;
bool isPixelSnapEnabled() const { return pixelSnapEnabled_; }
void lookAt(const Vec2& target);
void move(const Vec2& delta);
void zoomAt(float factor, const Vec2& screenPos);
glm::mat4 getViewMatrix() const;
glm::mat4 getProjectionMatrix() const;
private:
Vec2 position_;
float zoom_ = 1.0f;
int viewportWidth_ = 1280;
int viewportHeight_ = 720;
bool flipY_ = false; // Debug Y-axis flip.
bool pixelSnapEnabled_ = false;
};
} // namespace frostbite2D
@@ -0,0 +1,43 @@
#pragma once
#include <SDL2/SDL_ttf.h>
#include <frostbite2D/types/type_alias.h>
#include <optional>
#include <string>
#include <unordered_map>
namespace frostbite2D {
class FontManager {
public:
static FontManager& get();
bool init();
void shutdown();
bool registerFont(const std::string& name, const std::string& path, int fontSize);
TTF_Font* getFont(const std::string& name);
bool hasFont(const std::string& name) const;
void unregisterFont(const std::string& name);
void unloadAll();
struct FontInfo {
std::string name;
std::string path;
int size = 0;
};
std::optional<FontInfo> getFontInfo(const std::string& name) const;
FontManager(const FontManager&) = delete;
FontManager& operator=(const FontManager&) = delete;
private:
FontManager() = default;
~FontManager();
std::unordered_map<std::string, TTF_Font*> fonts_;
std::unordered_map<std::string, FontInfo> fontInfos_;
};
}
@@ -0,0 +1,46 @@
#pragma once
#include <frostbite2D/types/type_math.h>
namespace frostbite2D {
/**
* @brief 虚拟分辨率缩放模式
*/
enum class ResolutionScaleMode {
Disabled, ///< 不保持纵横比,直接按窗口宽高分别缩放
Fit, ///< 保持纵横比并完整显示,按较小缩放因子适配
FitHeight, ///< 以高度优先适配,宽度不足时回退为 Fit
};
struct RenderResolutionState {
int windowWidth = 1280;
int windowHeight = 720;
int drawableWidth = 1280;
int drawableHeight = 720;
int windowViewportX = 0;
int windowViewportY = 0;
int windowViewportWidth = 1280;
int windowViewportHeight = 720;
int viewportX = 0;
int viewportY = 0;
int viewportWidth = 1280;
int viewportHeight = 720;
float contentScaleX = 1.0f;
float contentScaleY = 1.0f;
float scaleX = 1.0f;
float scaleY = 1.0f;
Rect getWindowViewportRect() const {
return Rect(static_cast<float>(windowViewportX),
static_cast<float>(windowViewportY),
static_cast<float>(windowViewportWidth),
static_cast<float>(windowViewportHeight));
}
};
} // namespace frostbite2D
@@ -0,0 +1,72 @@
#pragma once
namespace frostbite2D {
/**
* @brief 2D 渲染风格预设 ID
*/
enum class RenderStyleProfileId {
PixelArt2D, ///< 世界与 UI 都按像素风策略渲染
Smooth2D, ///< 世界与 UI 都按平滑 2D 策略渲染
Hybrid2D ///< 世界走像素风,UI 走平滑策略
};
/**
* @brief 渲染风格作用层级
*/
enum class RenderStyleLayerRole {
World, ///< 世界层,如地图、角色、场景特效
UI ///< UI 层,如 HUD、菜单、文字
};
/**
* @brief 单个层级的渲染风格开关集合
*/
struct RenderStyleLayerSettings {
bool cameraPixelSnap = false; ///< 是否让相机位置吸附到像素网格
bool vertexPixelSnap = false; ///< 是否让世界顶点在提交前做像素对齐
bool pixelArtSampling = false; ///< 是否优先使用像素风采样(如 GL_NEAREST
bool shrinkSubTextureUVs = false; ///< 是否对子纹理 UV 做向内收缩,减轻图集边缘闪烁
};
/**
* @brief 一套完整的 2D 渲染风格设置
*/
struct RenderStyleSettings {
RenderStyleLayerSettings world; ///< 世界层使用的渲染风格
RenderStyleLayerSettings ui; ///< UI 层使用的渲染风格
/**
* @brief 根据预设 ID 生成对应的渲染风格
*/
static RenderStyleSettings FromProfile(RenderStyleProfileId profile) {
RenderStyleSettings settings;
switch (profile) {
case RenderStyleProfileId::PixelArt2D:
settings.world = {true, true, true, true};
settings.ui = {true, true, true, true};
break;
case RenderStyleProfileId::Smooth2D:
settings.world = {false, false, false, false};
settings.ui = {false, false, false, false};
break;
case RenderStyleProfileId::Hybrid2D:
default:
settings.world = {true, true, true, true};
settings.ui = {false, false, false, false};
break;
}
return settings;
}
/**
* @brief 获取指定层级对应的风格设置
*/
const RenderStyleLayerSettings& layer(RenderStyleLayerRole role) const {
return role == RenderStyleLayerRole::UI ? ui : world;
}
};
} // namespace frostbite2D
@@ -0,0 +1,34 @@
#pragma once
#include <frostbite2D/base/RefObject.h>
#include <frostbite2D/graphics/texture.h>
namespace frostbite2D {
class Renderer;
/// @brief 2D 离屏渲染目标,封装颜色纹理和对应的 framebuffer。
class RenderTexture : public RefObject {
public:
RenderTexture() = default;
~RenderTexture() override;
bool Init(int width, int height);
bool Resize(int width, int height);
void Reset();
bool IsValid() const { return framebufferID_ != 0 && texture_ != nullptr; }
int GetWidth() const { return width_; }
int GetHeight() const { return height_; }
Ptr<Texture> GetTexture() const { return texture_; }
private:
Ptr<Texture> texture_ = nullptr;
uint32 framebufferID_ = 0;
int width_ = 0;
int height_ = 0;
friend class Renderer;
};
} // namespace frostbite2D
@@ -0,0 +1,179 @@
#pragma once
#include <frostbite2D/graphics/batch.h>
#include <frostbite2D/graphics/camera.h>
#include <frostbite2D/graphics/render_resolution.h>
#include <frostbite2D/graphics/render_style.h>
#include <frostbite2D/graphics/shader.h>
#include <frostbite2D/graphics/shader_manager.h>
#include <frostbite2D/graphics/texture.h>
#include <frostbite2D/graphics/types.h>
#include <string>
#include <vector>
namespace frostbite2D {
class RenderTexture;
class Renderer {
public:
static Renderer& get();
bool init();
void shutdown();
void beginFrame();
void endFrame();
void flush();
bool beginRenderToTexture(RenderTexture& target, Camera* camera,
const Color& clearColor = Color(0.0f, 0.0f, 0.0f, 0.0f),
bool clear = true);
bool endRenderToTexture();
void setViewport(int x, int y, int width, int height);
void setWindowSize(int width, int height, float contentScaleX = 1.0f,
float contentScaleY = 1.0f);
void setVirtualResolutionEnabled(bool enabled);
void setVirtualResolution(int width, int height);
void setResolutionScaleMode(ResolutionScaleMode mode);
void setSeparateUIVirtualResolutionEnabled(bool enabled);
void setUIVirtualResolution(int width, int height);
void setUIResolutionScaleMode(ResolutionScaleMode mode);
void setClearColor(float r, float g, float b, float a = 1.0f);
void setClearColor(const Color& color);
void clear(uint32_t flags);
/**
* @brief 设置应用默认渲染风格预设
*/
void setDefaultRenderStyleProfile(RenderStyleProfileId profile);
/**
* @brief 获取应用默认渲染风格预设
*/
RenderStyleProfileId getDefaultRenderStyleProfile() const {
return defaultRenderStyleProfile_;
}
/**
* @brief 切换当前正在使用的渲染风格上下文
*/
void setActiveRenderStyleProfile(RenderStyleProfileId profile,
RenderStyleLayerRole role);
/**
* @brief 将指定风格作用到某个相机
*/
void applyRenderStyleToCamera(Camera* camera, RenderStyleProfileId profile,
RenderStyleLayerRole role) const;
void setCamera(Camera* camera);
Camera* getCamera() { return camera_; }
int getVirtualWidth() const;
int getVirtualHeight() const;
int getUIVirtualWidth() const;
int getUIVirtualHeight() const;
Rect getViewportRect() const;
Rect getUIViewportRect() const;
const RenderResolutionState& getResolutionState() const {
return resolutionState_;
}
const RenderResolutionState& getUIResolutionState() const {
return uiResolutionState_;
}
bool isInitialized() const { return initialized_; }
bool isFrameActive() const { return frameActive_; }
bool isRenderingToTexture() const { return !renderTargetStack_.empty(); }
void setCullingEnabled(bool enabled) { cullingEnabled_ = enabled; }
bool isCullingEnabled() const { return cullingEnabled_; }
void applyWorldViewport() const;
void applyUIViewport() const;
Vec2 screenToVirtual(const Vec2& screenPos) const;
Vec2 screenToUIVirtual(const Vec2& screenPos) const;
Vec2 virtualToScreen(const Vec2& virtualPos) const;
void drawQuad(const Vec2& pos, const Size& size, float cr = 1.0f,
float cg = 1.0f, float cb = 1.0f, float ca = 1.0f);
void drawQuad(const Vec2& pos, const Size& size, Ptr<Texture> texture);
void drawQuad(const Rect& rect, const Color& color);
void drawSprite(const Vec2& pos, const Size& size, Ptr<Texture> texture);
void drawSprite(const Vec2& pos, const Rect& srcRect, const Vec2& texSize,
Ptr<Texture> texture,
const Color& color = Color(1, 1, 1, 1));
void setupBlendMode(BlendMode mode);
/**
* @brief 获取当前激活的层级风格设置
*/
const RenderStyleLayerSettings& getActiveRenderStyle() const {
return activeRenderStyle_;
}
/**
* @brief 当前风格是否要求顶点像素对齐
*/
bool shouldSnapVertices() const { return activeRenderStyle_.vertexPixelSnap; }
/**
* @brief 当前风格是否优先使用像素风采样
*/
bool shouldUsePixelArtSampling() const {
return activeRenderStyle_.pixelArtSampling;
}
/**
* @brief 当前风格是否需要对子纹理 UV 做向内收缩
*/
bool shouldShrinkSubTextureUVs() const {
return activeRenderStyle_.shrinkSubTextureUVs;
}
ShaderManager& getShaderManager() { return shaderManager_; }
Batch& getBatch() { return batch_; }
private:
Renderer();
void updateUniforms();
void recalculateResolutionState();
void recalculateSingleResolutionState(RenderResolutionState& state,
bool useVirtualResolution,
int virtualWidth, int virtualHeight,
ResolutionScaleMode mode) const;
void syncCameraViewport();
struct RenderTargetState {
uint32 framebuffer = 0;
int viewportX = 0;
int viewportY = 0;
int viewportWidth = 1;
int viewportHeight = 1;
float clearColor[4] = {0.0f, 0.0f, 0.0f, 0.0f};
Camera* camera = nullptr;
bool startedStandaloneBatch = false;
bool cullingEnabled = true;
};
ShaderManager shaderManager_;
Batch batch_;
Camera* camera_ = nullptr;
RenderStyleProfileId defaultRenderStyleProfile_ = RenderStyleProfileId::Hybrid2D;
RenderStyleProfileId activeRenderStyleProfile_ = RenderStyleProfileId::Hybrid2D;
RenderStyleLayerRole activeRenderStyleRole_ = RenderStyleLayerRole::World;
RenderStyleLayerSettings activeRenderStyle_;
uint32_t clearColor_[4] = {0, 0, 0, 255};
bool useVirtualResolution_ = false;
int virtualWidth_ = 1280;
int virtualHeight_ = 720;
ResolutionScaleMode resolutionMode_ = ResolutionScaleMode::Fit;
bool useSeparateUIVirtualResolution_ = false;
int uiVirtualWidth_ = 0;
int uiVirtualHeight_ = 0;
ResolutionScaleMode uiResolutionMode_ = ResolutionScaleMode::Fit;
RenderResolutionState resolutionState_;
RenderResolutionState uiResolutionState_;
bool initialized_ = false;
bool frameActive_ = false;
bool cullingEnabled_ = true;
std::vector<RenderTargetState> renderTargetStack_;
Renderer(const Renderer&) = delete;
Renderer& operator=(const Renderer&) = delete;
};
} // namespace frostbite2D
@@ -0,0 +1,49 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <frostbite2D/types/type_math.h>
#include <frostbite2D/base/RefObject.h>
#include <string>
#include <unordered_map>
namespace frostbite2D {
class Shader : public RefObject {
public:
Shader() = default;
~Shader();
void use();
void unuse();
bool isValid() const { return programID_ != 0; }
uint32_t getID() const { return programID_; }
const std::string& getName() const { return name_; }
void setInt(const std::string& name, int value);
void setFloat(const std::string& name, float value);
void setVec2(const std::string& name, const Vec2& value);
void setVec3(const std::string& name, const glm::vec3& value);
void setVec4(const std::string& name, const glm::vec4& value);
void setMat3(const std::string& name, const glm::mat3& value);
void setMat4(const std::string& name, const glm::mat4& value);
void setTexture(const std::string& name, int slot);
private:
Shader(const std::string& name, uint32_t programID);
friend class ShaderManager;
bool compile(uint32_t type, const std::string& source);
bool link(uint32_t vertexID, uint32_t fragmentID);
int getUniformLocation(const std::string& name);
uint32_t programID_ = 0;
std::string name_;
std::unordered_map<std::string, int> uniformLocations_;
Shader(const Shader&) = delete;
Shader& operator=(const Shader&) = delete;
};
}
@@ -0,0 +1,42 @@
#pragma once
#include <frostbite2D/graphics/shader.h>
#include <frostbite2D/types/type_alias.h>
#include <string>
#include <unordered_map>
namespace frostbite2D {
class Renderer;
class ShaderManager {
public:
static ShaderManager& get();
bool init(const std::string& shadersDir);
void shutdown();
Shader* getShader(const std::string& name);
bool hasShader(const std::string& name) const;
const std::unordered_map<std::string, Ptr<Shader>>& getLoadedShaders() const {
return shaders_;
}
~ShaderManager() = default;
ShaderManager() = default;
private:
std::unordered_map<std::string, Ptr<Shader>> shaders_;
std::string shadersDir_;
bool loadShadersFromConfig(const std::string& configPath);
bool loadShader(const std::string& name, const std::string& vertPath,
const std::string& fragPath);
ShaderManager(const ShaderManager&) = delete;
ShaderManager& operator=(const ShaderManager&) = delete;
};
}
@@ -0,0 +1,56 @@
#pragma once
#include <frostbite2D/graphics/types.h>
#include <frostbite2D/types/type_alias.h>
#include <frostbite2D/base/RefObject.h>
#include <string>
#include <memory>
namespace frostbite2D {
enum class TextureSampling {
Linear,
PixelArt
};
class Texture : public RefObject {
public:
static Ptr<Texture> loadFromFile(const std::string& path);
static Ptr<Texture> createFromMemory(const uint8* data, int width, int height, int channels);
static Ptr<Texture> createEmpty(int width, int height);
~Texture();
void bind(uint32_t slot = 0, bool preferPixelArtSampling = false);
void unbind();
void setWrapMode(uint32_t wrapS, uint32_t wrapT);
void setFilterMode(uint32_t minFilter, uint32_t magFilter);
void setSampling(TextureSampling sampling);
TextureSampling getSampling() const { return sampling_; }
int getWidth() const { return width_; }
int getHeight() const { return height_; }
uint32_t getID() const { return textureID_; }
const std::string& getPath() const { return path_; }
private:
Texture(int width, int height, uint32_t id);
void applyFilter(uint32_t minFilter, uint32_t magFilter);
void applySampling(bool preferPixelArtSampling);
uint32_t textureID_ = 0;
int width_ = 0;
int height_ = 0;
int channels_ = 0;
std::string path_;
TextureSampling sampling_ = TextureSampling::Linear;
uint32_t appliedMinFilter_ = 0;
uint32_t appliedMagFilter_ = 0;
Texture() = default;
Texture(const Texture&) = delete;
Texture& operator=(const Texture&) = delete;
};
}
@@ -0,0 +1,111 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <frostbite2D/types/type_math.h>
#include <frostbite2D/types/type_color.h>
#include <cstdint>
namespace frostbite2D {
enum class BlendMode {
None,
Normal,
Additive,
Screen,
Premultiplied,
Subtractive,
Multiply
};
enum class PrimitiveType {
Triangles,
TriangleStrip,
TriangleFan,
Quads
};
struct Vertex {
Vec2 position;
Vec2 texCoord;
float r, g, b, a;
Vertex() = default;
Vertex(const Vec2& pos, const Vec2& uv, float cr, float cg, float cb, float ca)
: position(pos), texCoord(uv), r(cr), g(cg), b(cb), a(ca) {}
};
struct Quad {
Vertex vertices[4];
Quad() = default;
static Quad create(const Rect& rect, float cr = 1.0f, float cg = 1.0f,
float cb = 1.0f, float ca = 1.0f) {
Quad q;
Vec2 bl(rect.left(), rect.bottom());
Vec2 br(rect.right(), rect.bottom());
Vec2 tl(rect.left(), rect.top());
Vec2 tr(rect.right(), rect.top());
q.vertices[0] = Vertex(bl, Vec2(0, 0), cr, cg, cb, ca);
q.vertices[1] = Vertex(br, Vec2(1, 0), cr, cg, cb, ca);
q.vertices[2] = Vertex(tl, Vec2(0, 1), cr, cg, cb, ca);
q.vertices[3] = Vertex(tr, Vec2(1, 1), cr, cg, cb, ca);
return q;
}
static Quad createTextured(const Rect& destRect, const Rect& srcRect,
const Vec2& texSize, float cr = 1.0f,
float cg = 1.0f, float cb = 1.0f, float ca = 1.0f,
bool shrinkSubTextureUVs = false) {
Quad q;
Vec2 bl(destRect.left(), destRect.bottom());
Vec2 br(destRect.right(), destRect.bottom());
Vec2 tl(destRect.left(), destRect.top());
Vec2 tr(destRect.right(), destRect.top());
auto resolveUvX = [&srcRect, &texSize, shrinkSubTextureUVs](bool useRightEdge) {
float pixelInset = 0.0f;
if (!shrinkSubTextureUVs) {
return (useRightEdge ? srcRect.right() : srcRect.left()) / texSize.x;
}
if (useRightEdge && srcRect.right() < texSize.x) {
pixelInset = -0.5f;
} else if (!useRightEdge && srcRect.left() > 0.0f) {
pixelInset = 0.5f;
}
return (useRightEdge ? srcRect.right() : srcRect.left()) / texSize.x +
pixelInset / texSize.x;
};
auto resolveUvY = [&srcRect, &texSize, shrinkSubTextureUVs](bool useBottomEdge) {
float pixelInset = 0.0f;
if (!shrinkSubTextureUVs) {
return (useBottomEdge ? srcRect.bottom() : srcRect.top()) / texSize.y;
}
if (useBottomEdge && srcRect.bottom() < texSize.y) {
pixelInset = -0.5f;
} else if (!useBottomEdge && srcRect.top() > 0.0f) {
pixelInset = 0.5f;
}
return (useBottomEdge ? srcRect.bottom() : srcRect.top()) / texSize.y +
pixelInset / texSize.y;
};
Vec2 uvBL(resolveUvX(false), resolveUvY(true));
Vec2 uvBR(resolveUvX(true), resolveUvY(true));
Vec2 uvTL(resolveUvX(false), resolveUvY(false));
Vec2 uvTR(resolveUvX(true), resolveUvY(false));
q.vertices[0] = Vertex(bl, uvBL, cr, cg, cb, ca);
q.vertices[1] = Vertex(br, uvBR, cr, cg, cb, ca);
q.vertices[2] = Vertex(tl, uvTL, cr, cg, cb, ca);
q.vertices[3] = Vertex(tr, uvTR, cr, cg, cb, ca);
return q;
}
};
}
@@ -0,0 +1,18 @@
#pragma once
#ifdef __SWITCH__
#include <switch.h>
#include <sys/socket.h>
namespace frostbite2D {
/**
* @brief Switch平台相关函数
*/
void switchInit();
void switchShutdown();
} // namespace frostbite2D
#endif
@@ -0,0 +1,13 @@
#pragma once
#ifdef _WIN32
#include <windows.h>
namespace frostbite2D {
void windowsInit();
} // namespace frostbite2D
#endif
@@ -0,0 +1,397 @@
#pragma once
#include <filesystem>
#include <frostbite2D/types/type_alias.h>
#include <optional>
#include <string>
#include <vector>
namespace frostbite2D {
namespace fs = std::filesystem;
/**
* @brief 文件信息结构体
*/
struct FileInfo {
std::string name; ///< 文件名(包含扩展名)
std::string extension; ///< 文件扩展名(包含点,如 ".txt"
std::string fullPath; ///< 完整路径
uint64 size = 0; ///< 文件大小(字节)
bool isDirectory = false; ///< 是否为目录
bool isRegularFile = false; ///< 是否为普通文件
bool exists = false; ///< 是否存在
};
/**
* @brief 资源管理类(单例)
*
* 提供文件读写、目录操作、路径处理等功能。
* 支持设置工作目录,所有相对路径将基于工作目录解析。
* 支持 UTF-8 编码的中文路径。
*
* @example
* auto& asset = Asset::get();
* asset.setWorkingDirectory("D:/游戏/资源");
* std::string content;
* asset.readTextFile("配置/设置.json", content);
*/
class Asset {
public:
/**
* @brief 获取单例实例
* @return 资源管理器实例引用
*/
static Asset &get();
Asset(const Asset &) = delete;
Asset &operator=(const Asset &) = delete;
// ---------------------------------------------------------------------------
// 文件读写
// ---------------------------------------------------------------------------
/**
* @brief 读取文本文件
* @param path 文件路径(相对或绝对路径)
* @param outContent 输出文件内容
* @return 读取成功返回 true
*/
bool readTextFile(const std::string &path, std::string &outContent);
/**
* @brief 写入文本文件
* @param path 文件路径
* @param content 要写入的内容
* @param append 是否追加模式,默认覆盖
* @return 写入成功返回 true
*/
bool writeTextFile(const std::string &path, const std::string &content,
bool append = false);
/**
* @brief 读取二进制文件
* @param path 文件路径
* @param outData 输出字节数组
* @return 读取成功返回 true
*/
bool readBinaryFile(const std::string &path, std::vector<uint8> &outData);
/**
* @brief 写入二进制文件
* @param path 文件路径
* @param data 要写入的字节数组
* @param append 是否追加模式,默认覆盖
* @return 写入成功返回 true
*/
bool writeBinaryFile(const std::string &path, const std::vector<uint8> &data,
bool append = false);
/**
* @brief 读取文本文件到字符串(便捷方法)
* @param path 文件路径
* @return 文件内容,失败返回 std::nullopt
*/
std::optional<std::string> readFileToString(const std::string &path);
/**
* @brief 读取二进制文件到字节数组(便捷方法)
* @param path 文件路径
* @return 字节数组,失败返回 std::nullopt
*/
std::optional<std::vector<uint8>> readFileToBytes(const std::string &path);
// ---------------------------------------------------------------------------
// 文件/目录检查
// ---------------------------------------------------------------------------
/**
* @brief 检查路径是否存在
* @param path 路径
* @return 存在返回 true
*/
bool exists(const std::string &path) const;
/**
* @brief 检查路径是否为目录
* @param path 路径
* @return 是目录返回 true
*/
bool isDirectory(const std::string &path) const;
/**
* @brief 检查路径是否为普通文件
* @param path 路径
* @return 是普通文件返回 true
*/
bool isRegularFile(const std::string &path) const;
/**
* @brief 检查文件或目录是否为空
* @param path 路径
* @return 为空返回 true
*/
bool isEmpty(const std::string &path) const;
// ---------------------------------------------------------------------------
// 目录操作
// ---------------------------------------------------------------------------
/**
* @brief 创建单层目录
* @param path 目录路径
* @return 创建成功返回 true
*/
bool createDirectory(const std::string &path);
/**
* @brief 递归创建多层目录
* @param path 目录路径
* @return 创建成功返回 true
*/
bool createDirectories(const std::string &path);
/**
* @brief 删除文件
* @param path 文件路径
* @return 删除成功返回 true
*/
bool removeFile(const std::string &path);
/**
* @brief 删除目录及其所有内容
* @param path 目录路径
* @return 删除成功返回 true
*/
bool removeDirectory(const std::string &path);
// ---------------------------------------------------------------------------
// 文件操作
// ---------------------------------------------------------------------------
/**
* @brief 复制文件
* @param from 源文件路径
* @param to 目标文件路径
* @param overwrite 是否覆盖已存在的文件
* @return 复制成功返回 true
*/
bool copyFile(const std::string &from, const std::string &to,
bool overwrite = false);
/**
* @brief 移动文件
* @param from 源文件路径
* @param to 目标文件路径
* @return 移动成功返回 true
*/
bool moveFile(const std::string &from, const std::string &to);
/**
* @brief 重命名文件或目录
* @param oldPath 原路径
* @param newPath 新路径
* @return 重命名成功返回 true
*/
bool rename(const std::string &oldPath, const std::string &newPath);
// ---------------------------------------------------------------------------
// 目录遍历
// ---------------------------------------------------------------------------
/**
* @brief 列出目录中的所有文件
* @param directoryPath 目录路径
* @param recursive 是否递归遍历子目录
* @return 文件路径列表
*/
std::vector<std::string> listFiles(const std::string &directoryPath,
bool recursive = false);
/**
* @brief 列出目录中的所有子目录
* @param directoryPath 目录路径
* @param recursive 是否递归遍历子目录
* @return 子目录路径列表
*/
std::vector<std::string> listDirectories(const std::string &directoryPath,
bool recursive = false);
/**
* @brief 列出目录中的所有文件和子目录
* @param directoryPath 目录路径
* @param recursive 是否递归遍历子目录
* @return 所有项目路径列表
*/
std::vector<std::string> listAll(const std::string &directoryPath,
bool recursive = false);
/**
* @brief 按扩展名列出文件
* @param directoryPath 目录路径
* @param extension 文件扩展名(如 ".png"
* @param recursive 是否递归遍历子目录
* @return 匹配的文件路径列表
*/
std::vector<std::string>
listFilesWithExtension(const std::string &directoryPath,
const std::string &extension, bool recursive = false);
// ---------------------------------------------------------------------------
// 文件信息
// ---------------------------------------------------------------------------
/**
* @brief 获取文件详细信息
* @param path 文件路径
* @return 文件信息结构体
*/
FileInfo getFileInfo(const std::string &path);
/**
* @brief 获取文件大小
* @param path 文件路径
* @return 文件大小(字节)
*/
uint64 getFileSize(const std::string &path) const;
// ---------------------------------------------------------------------------
// 路径处理
// ---------------------------------------------------------------------------
/**
* @brief 获取文件名(包含扩展名)
* @param path 文件路径
* @return 文件名
*/
std::string getFileName(const std::string &path) const;
/**
* @brief 获取文件名(不含扩展名)
* @param path 文件路径
* @return 文件名
*/
std::string getFileNameWithoutExtension(const std::string &path) const;
/**
* @brief 获取文件扩展名
* @param path 文件路径
* @return 扩展名(包含点,如 ".txt"
*/
std::string getExtension(const std::string &path) const;
/**
* @brief 获取父目录路径
* @param path 文件路径
* @return 父目录路径
*/
std::string getParentPath(const std::string &path) const;
/**
* @brief 获取绝对路径
* @param path 相对或绝对路径
* @return 绝对路径
*/
std::string getAbsolutePath(const std::string &path) const;
/**
* @brief 获取规范路径(解析符号链接和相对路径)
* @param path 路径
* @return 规范化路径
*/
std::string getCanonicalPath(const std::string &path) const;
/**
* @brief 合并两个路径
* @param left 左侧路径
* @param right 右侧路径
* @return 合并后的路径
*/
std::string combinePath(const std::string &left,
const std::string &right) const;
/**
* @brief 规范化路径(转换为系统首选格式)
* @param path 路径
* @return 规范化路径
*/
std::string normalizePath(const std::string &path) const;
// ---------------------------------------------------------------------------
// 工作目录
// ---------------------------------------------------------------------------
/**
* @brief 获取当前工作目录
* @return 当前工作目录路径
*/
std::string getCurrentPath() const;
/**
* @brief 设置当前工作目录
* @param path 目录路径
* @return 设置成功返回 true
*/
bool setCurrentPath(const std::string &path);
/**
* @brief 设置资源工作目录
*
* 设置后,所有相对路径操作都将基于此目录。
*
* @param path 工作目录路径
*/
void setWorkingDirectory(const std::string &path);
/**
* @brief 获取资源工作目录
* @return 工作目录路径
*/
const std::string &getWorkingDirectory() const;
/**
* @brief 解析相对路径为完整路径
* @param relativePath 相对路径
* @return 完整路径
*/
std::string resolvePath(const std::string &relativePath) const;
// ---------------------------------------------------------------------------
// 资源根目录
// ---------------------------------------------------------------------------
/**
* @brief 设置资源根目录
* @param root 资源根目录路径
*/
void setAssetRoot(const std::string &root);
/**
* @brief 获取资源根目录
* @return 资源根目录路径
*/
const std::string &getAssetRoot() const;
/**
* @brief 解析资源相对路径
*
* 将相对路径解析为:工作目录/资源根目录/相对路径
*
* @param relativePath 相对路径
* @return 完整路径
*/
std::string resolveAssetPath(const std::string &relativePath) const;
private:
Asset() = default;
~Asset() = default;
fs::path toPath(const std::string &path) const;
std::string fromPath(const fs::path &path) const;
std::string resolveFullPath(const std::string &path) const;
std::string workingDirectory_;
std::string assetRoot_;
};
} // namespace frostbite2D
@@ -0,0 +1,353 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <optional>
#include <random>
#include <string>
#include <unordered_map>
#include <vector>
namespace frostbite2D {
/**
* @brief 音频项类型枚举
*/
enum class AudioEntryType {
None, ///< invalid
Effect, ///< one-shot sound effect
Ambient, ///< looping ambient track
Music, ///< background music
Random ///< random sound group
};
/**
* @brief 音效配置
*/
struct AudioEffect {
std::string id; ///< 音效 ID
std::string file; ///< 文件路径
std::string subgroup; ///< 子组(可选)
int32 loopDelay = -1; ///< 循环延迟(-1 表示未设置)
int32 loopDelayRange = -1;///< 循环延迟范围(-1 表示未设置)
};
/**
* @brief 音乐配置
*/
struct AudioMusic {
std::string id; ///< 音乐 ID
std::string file; ///< 文件路径
int32 loopDelay = -1; ///< 循环延迟(-1 表示未设置)
};
/**
* @brief ?????????
*/
struct AudioAmbient {
std::string id; ///< ?????ID
std::string file; ///< ??????
int32 loopDelay = -1; ///< ????????1 ?????????
int32 loopDelayRange = -1;///< ???????????1 ?????????
};
/**
* @brief 随机音效项
*/
struct RandomItem {
std::string tag; ///< 音效标签(ID
int32 probability = 0; ///< 概率权重
};
/**
* @brief 随机音效组
*/
struct AudioRandom {
std::string id; ///< 随机组 ID
std::vector<RandomItem> items; ///< 随机项列表
};
/**
* @brief 统一的音频条目(包含类型和数据)
*/
struct AudioEntry {
AudioEntryType type = AudioEntryType::None; ///< 条目类型
const AudioEffect* effect = nullptr; ///< 音效指针(type == Effect 时有效)
const AudioAmbient* ambient = nullptr;///< ???????????ype == Ambient ??????
const AudioMusic* music = nullptr; ///< 音乐指针(type == Music 时有效)
const AudioRandom* random = nullptr; ///< 随机组指针(type == Random 时有效)
/// @brief 判断是否有效
bool isValid() const { return type != AudioEntryType::None; }
/// @brief 判断是否为音效
bool isEffect() const { return type == AudioEntryType::Effect; }
/// @brief ????????????
bool isAmbient() const { return type == AudioEntryType::Ambient; }
/// @brief 判断是否为音乐
bool isMusic() const { return type == AudioEntryType::Music; }
/// @brief 判断是否为随机组
bool isRandom() const { return type == AudioEntryType::Random; }
/// @brief 安全获取文件路径
std::string getFilePath() const {
if (type == AudioEntryType::Effect && effect)
return effect->file;
if (type == AudioEntryType::Ambient && ambient)
return ambient->file;
if (type == AudioEntryType::Music && music)
return music->file;
return "";
}
/// @brief 安全获取子组
std::string getSubgroup() const {
if (type == AudioEntryType::Effect && effect)
return effect->subgroup;
return "";
}
};
/**
* @brief 音频数据库(单例)
*
* 用于解析和访问 audio.xml 文件中的音频配置。
* 提供统一的查询接口,自动判断音频类型。
*
* @example
* auto& audioDB = AudioDatabase::get();
* if (audioDB.loadFromFile("audio.xml")) {
* // 统一查询
* if (auto entry = audioDB.get("GN_FINAL_SHOT_01")) {
* // 使用 entry
* }
*
* // 获取文件路径
* if (auto filePath = audioDB.getFilePath("GN_FINAL_SHOT_01")) {
* // 使用 *filePath
* }
*
* // 获取音效(自动处理随机组)
* if (auto soundId = audioDB.getSound("R_GN_FINAL_SHOT")) {
* // 使用 *soundId
* }
* }
*/
class AudioDatabase {
public:
/**
* @brief 获取单例实例
* @return 音频数据库实例引用
*/
static AudioDatabase& get();
AudioDatabase(const AudioDatabase&) = delete;
AudioDatabase& operator=(const AudioDatabase&) = delete;
// ---------------------------------------------------------------------------
// 加载和解析
// ---------------------------------------------------------------------------
/**
* @brief 从文件加载音频数据库
* @param path XML 文件路径
* @return 加载成功返回 true
*/
bool loadFromFile(const std::string& path);
/**
* @brief 从字符串加载音频数据库
* @param xmlContent XML 内容字符串
* @return 加载成功返回 true
*/
bool loadFromString(const std::string& xmlContent);
/**
* @brief 清空数据库
*/
void clear();
/**
* @brief 检查数据库是否已加载
* @return 已加载返回 true
*/
bool isLoaded() const;
// ---------------------------------------------------------------------------
// 统一查询接口
// ---------------------------------------------------------------------------
/**
* @brief 统一查询:输入任意 ID,自动判断类型
* @param id 音频 ID(可以是 EFFECT、MUSIC 或 RANDOM 的 ID
* @return 音频条目,未找到返回 std::nullopt
*/
std::optional<AudioEntry> get(const std::string& id);
/**
* @brief 获取音效 ID(自动处理 RANDOM 类型)
*
* 如果 id 是 RANDOM 类型,会按概率随机选择一个音效;
* 如果 id 是 EFFECT 类型,直接返回该 ID。
*
* @param id 音效 ID 或随机组 ID
* @return 音效 ID,未找到返回 std::nullopt
*/
std::optional<std::string> getSound(const std::string& id);
/**
* @brief 获取文件路径
*
* 对于 EFFECT 和 MUSIC 类型,返回对应的文件路径;
* 对于 RANDOM 类型,先随机选择一个音效再返回其文件路径。
*
* @param id 音频 ID
* @return 文件路径,未找到返回 std::nullopt
*/
std::optional<std::string> getFilePath(const std::string& id);
// ---------------------------------------------------------------------------
// 类型特定查询(高级用法)
// ---------------------------------------------------------------------------
/**
* @brief 获取音效配置
* @param id 音效 ID
* @return 音效配置指针,未找到返回 std::nullopt
*/
std::optional<const AudioEffect*> getEffect(const std::string& id);
/**
* @brief ????????????
* @param id ?????ID
* @return ????????????????????? std::nullopt
*/
std::optional<const AudioAmbient*> getAmbient(const std::string& id);
/**
* @brief 获取音乐配置
* @param id 音乐 ID
* @return 音乐配置指针,未找到返回 std::nullopt
*/
std::optional<const AudioMusic*> getMusic(const std::string& id);
/**
* @brief 获取随机音效组配置
* @param id 随机组 ID
* @return 随机组配置指针,未找到返回 std::nullopt
*/
std::optional<const AudioRandom*> getRandom(const std::string& id);
// ---------------------------------------------------------------------------
// 统计信息
// ---------------------------------------------------------------------------
/**
* @brief 获取音效数量
*/
size_t effectCount() const;
/**
* @brief ???????????
*/
size_t ambientCount() const;
/**
* @brief 获取音乐数量
*/
size_t musicCount() const;
/**
* @brief 获取随机组数量
*/
size_t randomCount() const;
// ---------------------------------------------------------------------------
// 便捷访问方法(推荐使用)
// ---------------------------------------------------------------------------
/**
* @brief 检查 ID 是否存在
* @param id 音频 ID
* @return 存在返回 true
*/
bool has(const std::string& id);
/**
* @brief 获取文件路径(不存在返回空字符串)
* @param id 音频 ID
* @return 文件路径,未找到返回空字符串
*/
std::string filePath(const std::string& id);
/**
* @brief 获取音效 ID(自动处理随机组,不存在返回空字符串)
* @param idOrRandomId 音效 ID 或随机组 ID
* @return 音效 ID,未找到返回空字符串
*/
std::string soundId(const std::string& idOrRandomId);
/**
* @brief 获取子组(EFFECT 的 SUBGROUP 属性)
* @param id 音频 ID
* @return 子组名,未找到或不存在返回空字符串
*/
std::string subgroup(const std::string& id);
/**
* @brief 获取条目类型
* @param id 音频 ID
* @return 条目类型,未找到返回 AudioEntryType::None
*/
AudioEntryType typeOf(const std::string& id);
// ---------------------------------------------------------------------------
// 带检查的访问方法
// ---------------------------------------------------------------------------
/**
* @brief 尝试获取文件路径,带成功标志
* @param id 音频 ID
* @param outPath 输出文件路径
* @return 成功返回 true
*/
bool tryGetFilePath(const std::string& id, std::string& outPath);
/**
* @brief 尝试获取音效 ID,带成功标志
* @param id 音效 ID 或随机组 ID
* @param outSoundId 输出音效 ID
* @return 成功返回 true
*/
bool tryGetSoundId(const std::string& id, std::string& outSoundId);
private:
AudioDatabase() = default;
~AudioDatabase() = default;
/**
* @brief 初始化随机数生成器
*/
void initializeRNG();
/**
* @brief 从随机组中按概率选择一个音效
* @param randomGroup 随机组配置
* @return 选中的音效 ID
*/
std::string selectFromRandom(const AudioRandom& randomGroup);
std::unordered_map<std::string, AudioEffect> effectMap_; ///< 音效映射
std::unordered_map<std::string, AudioAmbient> ambientMap_; ///< ????????
std::unordered_map<std::string, AudioMusic> musicMap_; ///< 音乐映射
std::unordered_map<std::string, AudioRandom> randomMap_; ///< 随机组映射
std::mt19937 rng_; ///< 随机数生成器
bool rngInitialized_ = false; ///< 随机数生成器是否已初始化
bool loaded_ = false;
};
} // namespace frostbite2D
@@ -0,0 +1,62 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <fstream>
#include <string>
#include <vector>
namespace frostbite2D {
class BinaryFileStreamReader {
public:
BinaryFileStreamReader() = default;
explicit BinaryFileStreamReader(const std::string& filePath);
~BinaryFileStreamReader();
bool open(const std::string& filePath);
void close();
bool isOpen() const;
size_t tell() const;
void seek(size_t pos);
void skip(size_t count);
bool eof() const;
size_t size() const;
size_t remaining() const;
size_t lastReadCount() const;
size_t read(char* buffer, size_t size);
std::vector<uint8> readBytes(size_t size);
template <typename T>
T read() {
T value{};
read(reinterpret_cast<char*>(&value), sizeof(T));
return value;
}
int8 readInt8();
int16 readInt16();
int32 readInt32();
int64 readInt64();
uint8 readUInt8();
uint16 readUInt16();
uint32 readUInt32();
uint64 readUInt64();
float readFloat();
double readDouble();
std::string readString(size_t length);
std::string readNullTerminatedString(size_t maxLength = 4096);
private:
std::ifstream stream_;
std::string filePath_;
size_t size_ = 0;
size_t position_ = 0;
size_t lastReadCount_ = 0;
};
} // namespace frostbite2D
@@ -0,0 +1,269 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <string>
#include <vector>
#include <cstring>
#include <algorithm>
namespace frostbite2D {
/**
* @brief 二进制文件读取器
*
* 将整个二进制文件加载到内存中,提供便捷的读取接口。
* 支持各种基本类型的读取、定位操作和 CRC 解码。
*
* @example
* BinaryReader reader("data.bin");
* if (reader.isOpen()) {
* int32 value = reader.read<int32>();
* std::string str = reader.readString(10);
* }
*/
class BinaryReader {
public:
/**
* @brief 默认构造函数
*/
BinaryReader() = default;
/**
* @brief 从文件构造
* @param filePath 文件路径
*/
explicit BinaryReader(const std::string& filePath);
/**
* @brief 从内存数据构造
* @param data 数据指针
* @param size 数据大小
*/
BinaryReader(const char* data, size_t size);
/**
* @brief 析构函数
*/
~BinaryReader() = default;
// ---------------------------------------------------------------------------
// 文件操作
// ---------------------------------------------------------------------------
/**
* @brief 打开文件
* @param filePath 文件路径
* @return 打开成功返回 true
*/
bool open(const std::string& filePath);
/**
* @brief 从内存加载数据
* @param data 数据指针
* @param size 数据大小
*/
void loadFromMemory(const char* data, size_t size);
/**
* @brief 关闭并清空数据
*/
void close();
/**
* @brief 检查文件是否成功打开
* @return 已打开返回 true
*/
bool isOpen() const;
// ---------------------------------------------------------------------------
// 位置操作
// ---------------------------------------------------------------------------
/**
* @brief 获取当前读取位置
* @return 当前位置(字节偏移)
*/
size_t tell() const;
/**
* @brief 设置读取位置
* @param pos 目标位置(字节偏移)
*/
void seek(size_t pos);
/**
* @brief 跳过指定字节数
* @param count 要跳过的字节数
*/
void skip(size_t count);
/**
* @brief 检查是否已到达文件末尾
* @return 到达末尾返回 true
*/
bool eof() const;
// ---------------------------------------------------------------------------
// 信息获取
// ---------------------------------------------------------------------------
/**
* @brief 获取数据总大小
* @return 数据大小(字节)
*/
size_t size() const;
/**
* @brief 获取剩余可读取的字节数
* @return 剩余字节数
*/
size_t remaining() const;
/**
* @brief 获取上一次读取的字节数
* @return 上次读取的字节数
*/
size_t lastReadCount() const;
// ---------------------------------------------------------------------------
// 原始数据读取
// ---------------------------------------------------------------------------
/**
* @brief 读取原始字节数据
* @param buffer 输出缓冲区
* @param size 要读取的字节数
* @return 实际读取的字节数
*/
size_t read(char* buffer, size_t size);
/**
* @brief 读取原始字节数据到 vector
* @param size 要读取的字节数
* @return 读取的数据
*/
std::vector<uint8> readBytes(size_t size);
/**
* @brief 获取数据指针(不移动读取位置)
* @return 当前位置的数据指针
*/
const char* data() const;
/**
* @brief 获取当前位置的数据指针(不移动读取位置)
* @return 当前位置的数据指针
*/
const char* currentData() const;
// ---------------------------------------------------------------------------
// 类型化读取(模板方法)
// ---------------------------------------------------------------------------
/**
* @brief 读取指定类型的值
* @tparam T 要读取的类型
* @return 读取的值
*/
template <typename T>
T read() {
T value;
read(reinterpret_cast<char*>(&value), sizeof(T));
return value;
}
/**
* @brief 读取 int8
* @return 读取的值
*/
int8 readInt8();
/**
* @brief 读取 int16
* @return 读取的值
*/
int16 readInt16();
/**
* @brief 读取 int32
* @return 读取的值
*/
int32 readInt32();
/**
* @brief 读取 int64
* @return 读取的值
*/
int64 readInt64();
/**
* @brief 读取 uint8
* @return 读取的值
*/
uint8 readUInt8();
/**
* @brief 读取 uint16
* @return 读取的值
*/
uint16 readUInt16();
/**
* @brief 读取 uint32
* @return 读取的值
*/
uint32 readUInt32();
/**
* @brief 读取 uint64
* @return 读取的值
*/
uint64 readUInt64();
/**
* @brief 读取 float
* @return 读取的值
*/
float readFloat();
/**
* @brief 读取 double
* @return 读取的值
*/
double readDouble();
// ---------------------------------------------------------------------------
// 字符串读取
// ---------------------------------------------------------------------------
/**
* @brief 读取指定长度的字符串
* @param length 字符串长度(字节)
* @return 读取的字符串
*/
std::string readString(size_t length);
/**
* @brief 读取以 null 结尾的字符串
* @return 读取的字符串
*/
std::string readNullTerminatedString();
// ---------------------------------------------------------------------------
// CRC 解码
// ---------------------------------------------------------------------------
/**
* @brief CRC 解码
* @param length 要解码的数据长度(字节)
* @param crc32 CRC32 值
*/
void crcDecode(size_t length, uint32 crc32);
private:
std::vector<char> data_; ///< 存储的数据
size_t position_ = 0; ///< 当前读取位置
size_t lastReadCount_ = 0; ///< 上一次读取的字节数
};
} // namespace frostbite2D
@@ -0,0 +1,131 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <frostbite2D/resource/asset.h>
#include <optional>
#include <string>
#include <vector>
#include <list>
#include <memory>
#include <unordered_map>
#include <zlib.h>
namespace frostbite2D {
class BinaryFileStreamReader;
struct ImageFrame {
int32 type = 0;
int32 compressionType = 0;
int32 width = 0;
int32 height = 0;
int32 xPos = 0;
int32 yPos = 0;
int32 frameXPos = 0;
int32 frameYPos = 0;
uint32 offset = 0;
int32 size = 0;
std::vector<uint8> data;
};
struct ImgRef {
std::string path;
std::string npkFile;
size_t frameCount = 0;
uint32 offset = 0;
uint32 size = 0;
bool loaded = false;
};
struct CachedImageData {
std::vector<ImageFrame> frames;
uint64 lastUseTime = 0;
size_t memoryUsage = 0;
};
class NpkArchive {
public:
static NpkArchive& get();
NpkArchive(const NpkArchive&) = delete;
NpkArchive& operator=(const NpkArchive&) = delete;
NpkArchive(NpkArchive&&) = delete;
NpkArchive& operator=(NpkArchive&&) = delete;
void setImagePackDirectory(const std::string& dir);
const std::string& getImagePackDirectory() const;
void init();
void close();
bool isOpen() const;
bool hasImg(const std::string& path) const;
std::optional<ImgRef> getImg(const std::string& path);
std::vector<std::string> listImgs() const;
std::optional<ImageFrame> getImageFrame(const ImgRef& img, size_t index);
size_t getFrameCount(const ImgRef& img) const;
void setCacheSize(size_t maxBytes);
void clearCache();
size_t getCacheUsage() const;
void setDefaultImg(const std::string& imgPath, size_t frameIndex = 0);
const std::string& getDefaultImgPath() const;
size_t getDefaultImgFrame() const;
private:
enum class IndexCacheValidationMode : uint32 {
TrustCache = 1,
StrictSourceState = 2
};
struct SourceFileState {
std::string fileName;
uint64 size = 0;
int64 writeTime = 0;
bool operator==(const SourceFileState& other) const {
return fileName == other.fileName && size == other.size &&
writeTime == other.writeTime;
}
};
NpkArchive() = default;
~NpkArchive() = default;
std::string normalizePath(const std::string& path) const;
std::string getCacheFilePath() const;
IndexCacheValidationMode getIndexCacheValidationMode() const;
std::vector<SourceFileState> collectSourceFileStates() const;
bool loadIndexCache(const std::vector<SourceFileState>& sourceFiles);
bool saveIndexCache(const std::vector<SourceFileState>& sourceFiles) const;
void scanNpkFiles();
bool parseNpkFile(const std::string& npkPath);
bool loadImgData(ImgRef& img);
void parseColor(const uint8* tab, int type, uint8* saveByte, int offset);
void evictCacheIfNeeded(size_t requiredSize);
void updateCacheUsage(const std::string& imgPath);
std::string readNpkInfoString(BinaryFileStreamReader& reader);
static const uint8 NPK_KEY[256];
std::string imagePackDirectory_ = "ImagePacks2";
bool initialized_ = false;
std::unordered_map<std::string, ImgRef> imgIndex_;
std::unordered_map<std::string, CachedImageData> imageCache_;
std::list<std::string> lruList_;
std::unordered_map<std::string, std::list<std::string>::iterator> lruLookup_;
size_t maxCacheSize_ = 512 * 1024 * 1024;
size_t currentCacheSize_ = 0;
std::string defaultImgPath_;
size_t defaultImgFrame_ = 0;
bool verboseFallbackLog_ =
#ifndef NDEBUG
true;
#else
false;
#endif
};
}
@@ -0,0 +1,95 @@
#pragma once
#include <frostbite2D/resource/binary_file_stream_reader.h>
#include <frostbite2D/types/type_alias.h>
#include <map>
#include <optional>
#include <string>
#include <vector>
namespace frostbite2D {
/**
* @brief PVF ???????
*/
struct PvfFileInfo {
size_t offset = 0; ///< ????????????
uint32 crc32 = 0; ///< CRC32 ???
size_t length = 0; ///< ????????
bool decoded = false; ///< ?????
};
/**
* @brief ?????????
*/
struct RawData {
std::unique_ptr<char[]> data; ///< ??????????????????
size_t size; ///< ????????
};
/**
* @brief PVF ??????
*
* ??????? PVF ???????????
* ??????????????????????????
*/
class PvfArchive {
public:
static PvfArchive& get();
PvfArchive(const PvfArchive&) = delete;
PvfArchive& operator=(const PvfArchive&) = delete;
PvfArchive(PvfArchive&&) = delete;
PvfArchive& operator=(PvfArchive&&) = delete;
bool open(const std::string& filePath = "Script.pvf");
void close();
bool isOpen() const;
void init();
void initHeader();
void initBinStringTable();
void initLoadStrings();
bool hasFile(const std::string& path) const;
std::optional<PvfFileInfo> getFileInfo(const std::string& path) const;
std::vector<std::string> listFiles() const;
std::optional<std::string> getFileContent(const std::string& path);
std::optional<std::vector<uint8>> getFileBytes(const std::string& path);
std::optional<RawData> getFileRawData(const std::string& path);
std::optional<std::string> getBinString(int key) const;
std::optional<std::string> getLoadString(const std::string& type,
const std::string& key) const;
bool hasBinString(int key) const;
bool hasLoadString(const std::string& type, const std::string& key) const;
std::string normalizePath(const std::string& path) const;
std::string resolvePath(const std::string& baseDir,
const std::string& path) const;
private:
PvfArchive() = default;
~PvfArchive() = default;
std::vector<std::string> splitString(const std::string& str,
const std::string& delimiter) const;
bool decodeFile(const std::string& normalizedPath, PvfFileInfo& info);
void clearInitData();
std::optional<std::vector<uint8>> readArchiveBytes(size_t absoluteOffset,
size_t length);
std::optional<std::vector<uint8>> readDecodedFileBytes(
const PvfFileInfo& info);
static void crcDecodeBuffer(std::vector<uint8>& data, uint32 crc32);
BinaryFileStreamReader reader_;
size_t dataStartPos_ = 0;
std::map<std::string, PvfFileInfo> fileInfo_;
std::map<int, std::string> binStringTable_;
std::map<std::string, std::map<std::string, std::string>> loadStrings_;
std::map<std::string, std::vector<uint8>> decodedFileCache_;
};
} // namespace frostbite2D
@@ -0,0 +1,115 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <json/json.hpp>
#include <optional>
#include <string>
#include <vector>
namespace frostbite2D {
/**
* @brief Save system runtime configuration.
*/
struct SaveSystemConfig {
int32 slotCount = 10;
std::string saveRoot = ".save";
std::string indexFile = "index.json";
std::string slotPrefix = "slot_";
};
/**
* @brief Optional display metadata stored in each slot document.
*/
struct SaveMeta {
std::string displayName;
double playTimeSeconds = 0.0;
std::string userTag;
};
/**
* @brief Cached per-slot summary used by save slot list UI.
*/
struct SaveSlotInfo {
int32 slot = -1;
bool exists = false;
std::string updatedAtUtc;
uint64 fileSize = 0;
int32 schemaVersion = 1;
SaveMeta meta;
};
/**
* @brief Manual save/load service using Asset as the only filesystem backend.
*/
class SaveSystem {
public:
/**
* @brief Get singleton instance.
*/
static SaveSystem& get();
SaveSystem(const SaveSystem&) = delete;
SaveSystem& operator=(const SaveSystem&) = delete;
/**
* @brief Initialize save root, slot cache and index file.
*/
bool init(const SaveSystemConfig& config = SaveSystemConfig{});
/**
* @brief Save payload into a fixed slot and update slot index metadata.
*/
bool saveSlot(int32 slot, const nlohmann::json& payload,
const SaveMeta& meta = SaveMeta{});
/**
* @brief Load payload object from a slot.
*/
std::optional<nlohmann::json> loadSlotPayload(int32 slot) const;
/**
* @brief Query summary info for one slot.
*/
std::optional<SaveSlotInfo> getSlotInfo(int32 slot) const;
/**
* @brief List summary info of all slots.
*/
std::vector<SaveSlotInfo> listSlots() const;
/**
* @brief Delete a slot file and clear its index entry.
*/
bool deleteSlot(int32 slot);
/**
* @brief Check whether slot has an existing save file.
*/
bool hasSlot(int32 slot) const;
private:
SaveSystem() = default;
~SaveSystem() = default;
bool isValidSlot(int32 slot) const;
void resetSlots();
bool ensureSaveDirectory() const;
bool refreshSlotsFromDisk();
bool tryReadSlotDocument(int32 slot, nlohmann::json& outDoc) const;
bool writeIndexFile() const;
std::string indexPath() const;
std::string slotPath(int32 slot) const;
std::string slotFileName(int32 slot) const;
static std::string makeUtcTimestamp();
static nlohmann::json toJson(const SaveMeta& meta);
static SaveMeta fromJson(const nlohmann::json& json);
SaveSystemConfig config_;
std::vector<SaveSlotInfo> slots_;
bool initialized_ = false;
};
} // namespace frostbite2D
@@ -0,0 +1,208 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <frostbite2D/resource/pvf_archive.h>
#include <optional>
#include <string>
#include <vector>
#include <cstring>
namespace frostbite2D {
/**
* @brief 脚本指令操作码枚举
*/
enum class ScriptOpcode : uint8 {
Integer = 2, ///< 整数值
Float = 4, ///< 浮点值
StringRef5 = 5, ///< 二进制字符串引用(类型5)
StringRef6 = 6, ///< 二进制字符串引用(类型6)
StringRef7 = 7, ///< 二进制字符串引用(类型7)
StringRef8 = 8, ///< 二进制字符串引用(类型8)
ExtendedString9 = 9, ///< 扩展字符串(带额外字节)
ExtendedString10 = 10, ///< 扩展字符串
};
/**
* @brief 脚本值类型
*/
enum class ScriptValueType {
Empty, ///< 空值
Integer, ///< 整数值
Float, ///< 浮点值
String, ///< 字符串值
};
/**
* @brief 脚本解析结果值
*/
struct ScriptValue {
ScriptValueType type = ScriptValueType::Empty; ///< 值类型
int32 intValue = 0; ///< 整数值(type == Integer 时有效)
double floatValue = 0.0; ///< 浮点值(type == Float 时有效)
std::string stringValue; ///< 字符串值(type == String 时有效)
/**
* @brief 判断是否为空值
*/
bool isEmpty() const { return type == ScriptValueType::Empty; }
/**
* @brief 转换为字符串表示
*/
std::string toString() const;
};
/**
* @brief 脚本二进制数据解析器
*
* 用于解析 PVF 存档中的脚本二进制数据。
* 每条指令为 5 字节:1 字节操作码 + 4 字节数据。
* 支持与 PvfArchive 交互获取字符串资源。
*
* @example
* auto& archive = PvfArchive::get();
* if (auto rawData = archive.getFileRawData("script/example.bin")) {
* ScriptParser parser(*rawData, "script/example.bin");
*
* // 迭代解析
* while (!parser.isEnd()) {
* if (auto value = parser.next()) {
* // 处理 value
* }
* }
*
* // 或者一次性解析所有
* auto allValues = parser.parseAll();
* }
*/
class ScriptParser {
public:
/**
* @brief 从 RawData 构造
* @param data 原始数据
* @param filePath 文件路径(用于提取文件类型)
*/
ScriptParser(const RawData& data, const std::string& filePath);
/**
* @brief 从字节数组构造
* @param data 字节数组
* @param filePath 文件路径(用于提取文件类型)
*/
ScriptParser(const std::vector<uint8>& data, const std::string& filePath);
/**
* @brief 从指针和大小构造
* @param data 数据指针
* @param size 数据大小
* @param filePath 文件路径(用于提取文件类型)
*/
ScriptParser(const char* data, size_t size, const std::string& filePath);
// ---------------------------------------------------------------------------
// 迭代器风格 API
// ---------------------------------------------------------------------------
/**
* @brief 获取下一个值
* @return 解析的值,失败或结束返回 std::nullopt
*/
std::optional<ScriptValue> next();
/**
* @brief 回退一个指令
*/
void back();
/**
* @brief 检查是否已到达数据末尾
* @return 到达末尾返回 true
*/
bool isEnd() const;
/**
* @brief 重置解析位置到开头
*/
void reset();
// ---------------------------------------------------------------------------
// 批量解析
// ---------------------------------------------------------------------------
/**
* @brief 解析所有值
* @return 所有解析值的列表
*/
std::vector<ScriptValue> parseAll();
// ---------------------------------------------------------------------------
// 状态查询
// ---------------------------------------------------------------------------
/**
* @brief 获取当前解析位置
* @return 当前位置(字节偏移)
*/
size_t position() const;
/**
* @brief 获取数据总大小
* @return 数据大小(字节)
*/
size_t size() const;
/**
* @brief 获取文件路径
* @return 文件路径
*/
const std::string& filePath() const;
/**
* @brief 获取文件类型(从路径提取)
* @return 文件类型
*/
const std::string& fileType() const;
/**
* @brief 检查解析器是否有效(有数据)
* @return 有效返回 true
*/
bool isValid() const;
private:
/**
* @brief 从文件路径提取文件类型
* @param filePath 文件路径
* @return 文件类型
*/
std::string extractFileType(const std::string& filePath) const;
/**
* @brief 读取 1 字节
* @param offset 偏移位置
* @return 字节值
*/
uint8 readByte(size_t offset) const;
/**
* @brief 读取 4 字节整数
* @param offset 偏移位置
* @return 整数值
*/
int32 readInt32(size_t offset) const;
/**
* @brief 解析单个值
* @param offset 偏移位置
* @return 解析的值
*/
std::optional<ScriptValue> parseValueAt(size_t offset) const;
std::vector<char> data_; ///< 数据副本
size_t position_ = 2; ///< 当前解析位置(从第 2 字节开始)
std::string filePath_; ///< 文件路径
std::string fileType_; ///< 文件类型
};
} // namespace frostbite2D
@@ -0,0 +1,84 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <frostbite2D/resource/binary_reader.h>
#include <frostbite2D/resource/asset.h>
#include <optional>
#include <string>
#include <map>
#include <vector>
#include <list>
#include <memory>
namespace frostbite2D {
class BinaryFileStreamReader;
struct AudioRef {
std::string path;
std::string npkFile;
uint32 offset = 0;
uint32 size = 0;
bool loaded = false;
};
struct CachedAudioData {
std::vector<uint8> data;
uint64 lastUseTime = 0;
size_t memoryUsage = 0;
};
class SoundPackArchive {
public:
static SoundPackArchive& get();
SoundPackArchive(const SoundPackArchive&) = delete;
SoundPackArchive& operator=(const SoundPackArchive&) = delete;
SoundPackArchive(SoundPackArchive&&) = delete;
SoundPackArchive& operator=(SoundPackArchive&&) = delete;
void setSoundPackDirectory(const std::string& dir);
const std::string& getSoundPackDirectory() const;
void init();
void close();
bool isOpen() const;
bool hasAudio(const std::string& path) const;
std::optional<AudioRef> getAudio(const std::string& path);
std::vector<std::string> listAudios() const;
std::optional<std::vector<uint8>> getAudioData(const AudioRef& audio);
void setCacheSize(size_t maxBytes);
void clearCache();
size_t getCacheUsage() const;
void setDefaultAudio(const std::string& audioPath);
const std::string& getDefaultAudioPath() const;
private:
SoundPackArchive() = default;
~SoundPackArchive() = default;
std::string normalizePath(const std::string& path) const;
void scanNpkFiles();
bool parseNpkFile(const std::string& npkPath);
bool loadAudioData(AudioRef& audio);
void evictCacheIfNeeded(size_t requiredSize);
void updateCacheUsage(const std::string& audioPath);
std::string readNpkInfoString(BinaryFileStreamReader& reader);
static const uint8 NPK_KEY[256];
std::string soundPackDirectory_ = "SoundPacks";
bool initialized_ = false;
std::map<std::string, AudioRef> audioIndex_;
std::map<std::string, CachedAudioData> audioCache_;
std::list<std::string> lruList_;
size_t maxCacheSize_ = 256 * 1024 * 1024;
size_t currentCacheSize_ = 0;
std::string defaultAudioPath_;
};
}
@@ -0,0 +1,49 @@
#pragma once
#include <frostbite2D/2d/actor.h>
#include <frostbite2D/event/event.h>
#include <frostbite2D/graphics/render_style.h>
#include <vector>
namespace frostbite2D {
class Scene : public Actor {
public:
Scene();
virtual ~Scene();
virtual void onEnter();
virtual void onExit();
void Update(float deltaTime) override;
void Render() override;
bool OnEvent(const Event& event) override;
/**
* @brief 为当前场景指定渲染风格预设
*/
void SetRenderStyleProfile(RenderStyleProfileId profile);
/**
* @brief 清除场景级渲染风格覆盖,回退到应用默认值
*/
void ClearRenderStyleProfileOverride();
bool HasRenderStyleProfileOverride() const { return hasRenderStyleProfileOverride_; }
/**
* @brief 解析当前场景最终应使用的渲染风格
*/
RenderStyleProfileId ResolveRenderStyleProfile(
RenderStyleProfileId defaultProfile) const;
static Scene* GetCurrent();
private:
bool dispatchToChildren(const Event& event);
static Scene* current_;
bool hasRenderStyleProfileOverride_ = false;
RenderStyleProfileId renderStyleProfileOverride_ = RenderStyleProfileId::Hybrid2D;
friend class SceneManager;
};
}
@@ -0,0 +1,49 @@
#pragma once
#include <frostbite2D/event/event.h>
#include <frostbite2D/types/type_alias.h>
#include <vector>
namespace frostbite2D {
class Scene;
class UIScene;
class SceneManager {
public:
static SceneManager& get();
SceneManager(const SceneManager&) = delete;
SceneManager& operator=(const SceneManager&) = delete;
void PushScene(Ptr<Scene> scene);
void PopScene();
void ReplaceScene(Ptr<Scene> scene);
void PushUIScene(Ptr<UIScene> scene);
void PopUIScene();
void ReplaceUIScene(Ptr<UIScene> scene);
bool RemoveUIScene(UIScene* scene);
void ClearUIScenes();
void ClearAll();
void Update(float deltaTime);
void UpdateUI(float deltaTime);
void Render();
bool DispatchEvent(const Event& event);
bool DispatchUIEvent(const Event& event);
Scene* GetCurrentScene() const;
UIScene* GetCurrentUIScene() const;
bool HasActiveScene() const;
bool HasActiveUIScene() const;
private:
SceneManager() = default;
std::vector<Ptr<Scene>> sceneStack_;
std::vector<Ptr<UIScene>> uiSceneStack_;
};
} // namespace frostbite2D
@@ -0,0 +1,20 @@
#pragma once
#include <frostbite2D/graphics/camera.h>
#include <frostbite2D/scene/scene.h>
namespace frostbite2D {
class UIScene : public Scene {
public:
UIScene();
~UIScene() override = default;
Camera* GetCamera() { return &camera_; }
const Camera* GetCamera() const { return &camera_; }
private:
Camera camera_;
};
} // namespace frostbite2D
@@ -0,0 +1,4 @@
#pragma once
// Backward-compatible forwarding header. Prefer quickjs_bridge_base.h.
#include <frostbite2D/script/quickjs_bridge_base.h>
@@ -0,0 +1,151 @@
#pragma once
#include <frostbite2D/2d/actor.h>
#include <memory>
#include <string>
#include <utility>
namespace qjs {
class Value;
}
namespace frostbite2D {
/**
* @brief Basic bridge object between QuickJS and engine Vec2.
*/
class JsVec2 {
public:
double x = 0.0;
double y = 0.0;
JsVec2() = default;
JsVec2(double xValue, double yValue) : x(xValue), y(yValue) {}
explicit JsVec2(const Vec2& value) : x(value.x), y(value.y) {}
Vec2 toNative() const {
return Vec2(static_cast<float>(x), static_cast<float>(y));
}
};
/**
* @brief Basic bridge base between QuickJS and engine Actor.
*
* This type only exposes the smallest stable surface that all script-side
* Actor wrappers need, so engine bindings and Game-side bindings can share it.
*/
class JsActor {
public:
JsActor() : actor_(MakePtr<Actor>()) {}
explicit JsActor(Ptr<Actor> actor) : actor_(std::move(actor)) {
if (!actor_) {
actor_ = MakePtr<Actor>();
}
}
std::string GetName() const { return actor_->GetName(); }
void SetName(const std::string& name) { actor_->SetName(name); }
std::shared_ptr<JsVec2> GetPosition() const {
return std::make_shared<JsVec2>(actor_->GetPosition());
}
void SetPosition(const std::shared_ptr<JsVec2>& position) {
if (position) {
actor_->SetPosition(position->toNative());
}
}
void SetPositionXY(double x, double y) {
actor_->SetPosition(static_cast<float>(x), static_cast<float>(y));
}
double GetRotation() const { return actor_->GetRotation(); }
void SetRotation(double rotation) {
actor_->SetRotation(static_cast<float>(rotation));
}
std::shared_ptr<JsVec2> GetScale() const {
return std::make_shared<JsVec2>(actor_->GetScale());
}
void SetScale(const std::shared_ptr<JsVec2>& scale) {
if (scale) {
actor_->SetScale(scale->toNative());
}
}
void SetScaleValue(double scale) {
actor_->SetScale(static_cast<float>(scale));
}
bool IsVisible() const { return actor_->IsVisible(); }
void SetVisible(bool visible) { actor_->SetVisible(visible); }
void AddChild(const std::shared_ptr<JsActor>& child) {
if (!actor_ || !child) {
return;
}
actor_->AddChild(child->native());
}
void RemoveAllChildren() {
if (actor_) {
actor_->RemoveAllChildren();
}
}
std::shared_ptr<JsVec2> GetSize() const {
return std::make_shared<JsVec2>(actor_->GetSize());
}
void SetSize(const std::shared_ptr<JsVec2>& size) {
if (size) {
actor_->SetSize(size->toNative());
}
}
void SetSizeWH(double width, double height) {
actor_->SetSize(static_cast<float>(width), static_cast<float>(height));
}
std::shared_ptr<JsVec2> GetAnchor() const {
return std::make_shared<JsVec2>(actor_->GetAnchor());
}
void SetAnchor(const std::shared_ptr<JsVec2>& anchor) {
if (anchor) {
actor_->SetAnchor(anchor->toNative());
}
}
void SetAnchorXY(double x, double y) {
actor_->SetAnchor(static_cast<float>(x), static_cast<float>(y));
}
std::shared_ptr<JsActor> GetParent() const;
std::shared_ptr<JsVec2> GetTopLeftPosition() const;
void SetTopLeftPosition(const std::shared_ptr<JsVec2>& position);
void SetTopLeftPositionXY(double x, double y);
std::shared_ptr<JsVec2> LocalToWorld(const std::shared_ptr<JsVec2>& point) const;
std::shared_ptr<JsVec2> WorldToLocal(const std::shared_ptr<JsVec2>& point) const;
bool ContainsWorldPoint(double x, double y) const;
bool IsEventReceiveEnabled() const {
return actor_ && actor_->IsEventReceiveEnabled();
}
void EnableEventReceive() {
if (actor_) {
actor_->EnableEventReceive();
}
}
void DisableEventReceive() {
if (actor_) {
actor_->DisableEventReceive();
}
}
uint32 AddEventListener(const std::string& type,
std::function<bool(qjs::Value)> callback);
bool RemoveEventListener(uint32 listenerId);
void ClearEventListeners();
const Ptr<Actor>& native() const { return actor_; }
protected:
Ptr<Actor> actor_;
};
} // namespace frostbite2D
@@ -0,0 +1,87 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include <frostbite2D/event/event.h>
namespace qjs {
class Runtime;
class Context;
class Value;
} // namespace qjs
namespace frostbite2D {
class Actor;
struct Vec2;
class QuickJSVM {
public:
using BindingInstaller = std::function<bool(qjs::Context&)>;
static QuickJSVM& get();
void setScriptDirectory(const std::string& path);
void setEntryScript(const std::string& path);
void setDebugMode(bool enable);
bool init();
void shutdown();
bool run();
bool isInitialized() const { return initialized_; }
qjs::Context* getContext() const { return context_.get(); }
const std::string& getScriptDirectory() const { return scriptDirectory_; }
bool runPendingJobs();
void logException(const char* stage);
qjs::Value newObject();
qjs::Value loadModule(const std::string& relativePath);
qjs::Value loadModule(const std::string& relativePath,
const std::string& cacheTag);
qjs::Value construct(const qjs::Value& constructor, const qjs::Value& argument);
qjs::Value callMethod(const qjs::Value& object, const char* name);
qjs::Value callMethod(const qjs::Value& object, const char* name,
const qjs::Value& argument);
qjs::Value callMethod(const qjs::Value& object, const char* name,
double argument);
qjs::Value wrapActor(const Ptr<Actor>& actor);
qjs::Value wrapVec2(const Vec2& value);
qjs::Value wrapEvent(const Event& event);
std::optional<EventType> parseEventTypeName(std::string_view typeName) const;
void RegisterBindingInstaller(BindingInstaller installer);
void RegisterBootstrapScript(const std::string& script);
private:
QuickJSVM() = default;
~QuickJSVM();
QuickJSVM(const QuickJSVM&) = delete;
QuickJSVM& operator=(const QuickJSVM&) = delete;
bool registerBindings();
bool installGlobals();
void logScriptException(const char* stage);
std::string buildEntryPath() const;
bool initialized_ = false;
bool debugMode_ = false;
std::string scriptDirectory_ = "assets/scripts";
std::string entryScript_ = "main.js";
uint64 nextEvalId_ = 1;
std::vector<BindingInstaller> bindingInstallers_;
std::vector<std::string> bootstrapScripts_;
std::unique_ptr<qjs::Runtime> runtime_;
std::unique_ptr<qjs::Context> context_;
};
} // namespace frostbite2D
@@ -0,0 +1,65 @@
#pragma once
#include <cstdint>
#include <functional>
#include <memory>
#include <frostbite2D/base/base.h>
namespace frostbite2D {
// ---------------------------------------------------------------------------
// 宏定义
// ---------------------------------------------------------------------------
#define E2D_CONCAT_IMPL(a, b) a##b
#define E2D_CONCAT(a, b) E2D_CONCAT_IMPL(a, b)
// ---------------------------------------------------------------------------
// 智能指针别名
// ---------------------------------------------------------------------------
template <typename T> using Ptr = RefPtr<T>;
template <typename T> using SharedPtr = RefPtr<T>;
template <typename T> using UniquePtr = std::unique_ptr<T>;
template <typename T> using WeakPtr = std::weak_ptr<T>;
/// 创建 shared_ptr 的便捷函数
template <typename T, typename... Args> inline Ptr<T> makePtr(Args &&...args) {
return MakePtr<T>(std::forward<Args>(args)...);
}
template <typename T, typename... Args>
inline SharedPtr<T> makeShared(Args &&...args) {
return MakePtr<T>(std::forward<Args>(args)...);
}
/// 创建 unique_ptr 的便捷函数
template <typename T, typename... Args>
inline UniquePtr<T> makeUnique(Args &&...args) {
return std::make_unique<T>(std::forward<Args>(args)...);
}
// ---------------------------------------------------------------------------
// 函数别名
// ---------------------------------------------------------------------------
template <typename Sig> using Function = std::function<Sig>;
// ---------------------------------------------------------------------------
// 基础类型别名
// ---------------------------------------------------------------------------
using int8 = std::int8_t;
using int16 = std::int16_t;
using int32 = std::int32_t;
using int64 = std::int64_t;
using uint8 = std::uint8_t;
using uint16 = std::uint16_t;
using uint32 = std::uint32_t;
using uint64 = std::uint64_t;
/**
* @brief 平台类型枚举
*/
enum class PlatformType { Auto, Windows, Switch, Linux, macOS };
} // namespace frostbite2D
@@ -0,0 +1,156 @@
#pragma once
#include <algorithm>
#include <cstdint>
#include <glm/vec4.hpp>
namespace frostbite2D {
/// RGB 颜色(字节,每通道 0-255)
struct Color3B {
uint8_t r = 255;
uint8_t g = 255;
uint8_t b = 255;
constexpr Color3B() = default;
constexpr Color3B(uint8_t r, uint8_t g, uint8_t b) : r(r), g(g), b(b) {}
constexpr bool operator==(const Color3B &other) const {
return r == other.r && g == other.g && b == other.b;
}
constexpr bool operator!=(const Color3B &other) const {
return !(*this == other);
}
Color3B operator+(const Color3B &other) const {
return Color3B(
static_cast<uint8_t>(std::min(255, static_cast<int>(r) + other.r)),
static_cast<uint8_t>(std::min(255, static_cast<int>(g) + other.g)),
static_cast<uint8_t>(std::min(255, static_cast<int>(b) + other.b)));
}
Color3B operator-(const Color3B &other) const {
return Color3B(
static_cast<uint8_t>(std::max(0, static_cast<int>(r) - other.r)),
static_cast<uint8_t>(std::max(0, static_cast<int>(g) - other.g)),
static_cast<uint8_t>(std::max(0, static_cast<int>(b) - other.b)));
}
};
/// RGBA 颜色(浮点数,每通道 0.0 - 1.0)
struct Color {
float r = 0.0f;
float g = 0.0f;
float b = 0.0f;
float a = 1.0f;
constexpr Color() = default;
constexpr Color(float r, float g, float b, float a = 1.0f)
: r(r), g(g), b(b), a(a) {}
/// 从 0xRRGGBB 整数构造
constexpr explicit Color(uint32_t rgb, float a = 1.0f)
: r(static_cast<float>((rgb >> 16) & 0xFF) / 255.0f),
g(static_cast<float>((rgb >> 8) & 0xFF) / 255.0f),
b(static_cast<float>((rgb) & 0xFF) / 255.0f), a(a) {}
/// 从 0-255 整数构造
static constexpr Color fromRGBA(uint8_t r, uint8_t g, uint8_t b,
uint8_t a = 255) {
return Color(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
}
/// 转换为 glm::vec4
glm::vec4 toVec4() const { return {r, g, b, a}; }
/// 线性插值
static Color lerp(const Color &a, const Color &b, float t) {
t = std::clamp(t, 0.0f, 1.0f);
return Color(a.r + (b.r - a.r) * t, a.g + (b.g - a.g) * t,
a.b + (b.b - a.b) * t, a.a + (b.a - a.a) * t);
}
bool operator==(const Color &other) const {
return r == other.r && g == other.g && b == other.b && a == other.a;
}
bool operator!=(const Color &other) const { return !(*this == other); }
// 算术运算符
Color operator+(const Color &other) const {
return Color(r + other.r, g + other.g, b + other.b, a + other.a);
}
Color operator-(const Color &other) const {
return Color(r - other.r, g - other.g, b - other.b, a - other.a);
}
Color operator*(float scalar) const {
return Color(r * scalar, g * scalar, b * scalar, a * scalar);
}
Color operator/(float scalar) const {
return Color(r / scalar, g / scalar, b / scalar, a / scalar);
}
Color &operator+=(const Color &other) {
r += other.r;
g += other.g;
b += other.b;
a += other.a;
return *this;
}
Color &operator-=(const Color &other) {
r -= other.r;
g -= other.g;
b -= other.b;
a -= other.a;
return *this;
}
Color &operator*=(float scalar) {
r *= scalar;
g *= scalar;
b *= scalar;
a *= scalar;
return *this;
}
Color &operator/=(float scalar) {
r /= scalar;
g /= scalar;
b /= scalar;
a /= scalar;
return *this;
}
};
// 命名颜色常量
namespace Colors {
inline constexpr Color White{1.0f, 1.0f, 1.0f, 1.0f};
inline constexpr Color Black{0.0f, 0.0f, 0.0f, 1.0f};
inline constexpr Color Red{1.0f, 0.0f, 0.0f, 1.0f};
inline constexpr Color Green{0.0f, 1.0f, 0.0f, 1.0f};
inline constexpr Color Blue{0.0f, 0.0f, 1.0f, 1.0f};
inline constexpr Color Yellow{1.0f, 1.0f, 0.0f, 1.0f};
inline constexpr Color Cyan{0.0f, 1.0f, 1.0f, 1.0f};
inline constexpr Color Magenta{1.0f, 0.0f, 1.0f, 1.0f};
inline constexpr Color Orange{1.0f, 0.647f, 0.0f, 1.0f};
inline constexpr Color Purple{0.502f, 0.0f, 0.502f, 1.0f};
inline constexpr Color Pink{1.0f, 0.753f, 0.796f, 1.0f};
inline constexpr Color Gray{0.502f, 0.502f, 0.502f, 1.0f};
inline constexpr Color LightGray{0.827f, 0.827f, 0.827f, 1.0f};
inline constexpr Color DarkGray{0.412f, 0.412f, 0.412f, 1.0f};
inline constexpr Color Brown{0.647f, 0.165f, 0.165f, 1.0f};
inline constexpr Color Gold{1.0f, 0.843f, 0.0f, 1.0f};
inline constexpr Color Silver{0.753f, 0.753f, 0.753f, 1.0f};
inline constexpr Color SkyBlue{0.529f, 0.808f, 0.922f, 1.0f};
inline constexpr Color LimeGreen{0.196f, 0.804f, 0.196f, 1.0f};
inline constexpr Color Coral{1.0f, 0.498f, 0.314f, 1.0f};
inline constexpr Color Transparent{0.0f, 0.0f, 0.0f, 0.0f};
} // namespace Colors
} // namespace frostbite2D
@@ -0,0 +1,552 @@
#pragma once
#include <algorithm>
#include <cmath>
#include <frostbite2D/types/type_alias.h>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/mat4x4.hpp>
#include <glm/vec2.hpp>
namespace frostbite2D {
// ---------------------------------------------------------------------------
// 常量
// ---------------------------------------------------------------------------
constexpr float PI_F = 3.14159265358979323846f;
constexpr float DEG_TO_RAD = PI_F / 180.0f;
constexpr float RAD_TO_DEG = 180.0f / PI_F;
// ---------------------------------------------------------------------------
// 2D 向量
// ---------------------------------------------------------------------------
struct Vec2 {
float x = 0.0f;
float y = 0.0f;
constexpr Vec2() = default;
constexpr Vec2(float x, float y) : x(x), y(y) {}
explicit Vec2(const glm::vec2 &v) : x(v.x), y(v.y) {}
glm::vec2 toGlm() const { return {x, y}; }
static Vec2 fromGlm(const glm::vec2 &v) { return {v.x, v.y}; }
// 基础运算
Vec2 operator+(const Vec2 &v) const { return {x + v.x, y + v.y}; }
Vec2 operator-(const Vec2 &v) const { return {x - v.x, y - v.y}; }
Vec2 operator*(float s) const { return {x * s, y * s}; }
Vec2 operator/(float s) const { return {x / s, y / s}; }
Vec2 operator-() const { return {-x, -y}; }
Vec2 &operator+=(const Vec2 &v) {
x += v.x;
y += v.y;
return *this;
}
Vec2 &operator-=(const Vec2 &v) {
x -= v.x;
y -= v.y;
return *this;
}
Vec2 &operator*=(float s) {
x *= s;
y *= s;
return *this;
}
Vec2 &operator/=(float s) {
x /= s;
y /= s;
return *this;
}
bool operator==(const Vec2 &v) const { return x == v.x && y == v.y; }
bool operator!=(const Vec2 &v) const { return !(*this == v); }
// 向量运算
float length() const { return std::sqrt(x * x + y * y); }
float lengthSquared() const { return x * x + y * y; }
Vec2 normalized() const {
float len = length();
if (len > 0.0f)
return {x / len, y / len};
return {0.0f, 0.0f};
}
float dot(const Vec2 &v) const { return x * v.x + y * v.y; }
float cross(const Vec2 &v) const { return x * v.y - y * v.x; }
float distance(const Vec2 &v) const { return (*this - v).length(); }
float angle() const { return std::atan2(y, x) * RAD_TO_DEG; }
static Vec2 lerp(const Vec2 &a, const Vec2 &b, float t) {
return a + (b - a) * t;
}
static constexpr Vec2 Zero() { return {0.0f, 0.0f}; }
static constexpr Vec2 One() { return {1.0f, 1.0f}; }
static constexpr Vec2 UnitX() { return {1.0f, 0.0f}; }
static constexpr Vec2 UnitY() { return {0.0f, 1.0f}; }
};
inline Vec2 operator*(float s, const Vec2 &v) { return v * s; }
using Point = Vec2;
// ---------------------------------------------------------------------------
// 3D 向量 (用于3D动作)
// ---------------------------------------------------------------------------
struct Vec3 {
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
constexpr Vec3() = default;
constexpr Vec3(float x, float y, float z) : x(x), y(y), z(z) {}
explicit Vec3(const glm::vec3 &v) : x(v.x), y(v.y), z(v.z) {}
glm::vec3 toGlm() const { return {x, y, z}; }
static Vec3 fromGlm(const glm::vec3 &v) { return {v.x, v.y, v.z}; }
Vec3 operator+(const Vec3 &v) const { return {x + v.x, y + v.y, z + v.z}; }
Vec3 operator-(const Vec3 &v) const { return {x - v.x, y - v.y, z - v.z}; }
Vec3 operator*(float s) const { return {x * s, y * s, z * s}; }
Vec3 operator/(float s) const { return {x / s, y / s, z / s}; }
Vec3 operator-() const { return {-x, -y, -z}; }
Vec3 &operator+=(const Vec3 &v) {
x += v.x;
y += v.y;
z += v.z;
return *this;
}
Vec3 &operator-=(const Vec3 &v) {
x -= v.x;
y -= v.y;
z -= v.z;
return *this;
}
Vec3 &operator*=(float s) {
x *= s;
y *= s;
z *= s;
return *this;
}
Vec3 &operator/=(float s) {
x /= s;
y /= s;
z /= s;
return *this;
}
bool operator==(const Vec3 &v) const {
return x == v.x && y == v.y && z == v.z;
}
bool operator!=(const Vec3 &v) const { return !(*this == v); }
float length() const { return std::sqrt(x * x + y * y + z * z); }
float lengthSquared() const { return x * x + y * y + z * z; }
Vec3 normalized() const {
float len = length();
if (len > 0.0f)
return {x / len, y / len, z / len};
return {0.0f, 0.0f, 0.0f};
}
float dot(const Vec3 &v) const { return x * v.x + y * v.y + z * v.z; }
static Vec3 lerp(const Vec3 &a, const Vec3 &b, float t) {
return a + (b - a) * t;
}
static constexpr Vec3 Zero() { return {0.0f, 0.0f, 0.0f}; }
static constexpr Vec3 One() { return {1.0f, 1.0f, 1.0f}; }
};
inline Vec3 operator*(float s, const Vec3 &v) { return v * s; }
// ---------------------------------------------------------------------------
// 2D 尺寸
// ---------------------------------------------------------------------------
struct Size {
float width = 0.0f;
float height = 0.0f;
constexpr Size() = default;
constexpr Size(float w, float h) : width(w), height(h) {}
bool operator==(const Size &s) const {
return width == s.width && height == s.height;
}
bool operator!=(const Size &s) const { return !(*this == s); }
float area() const { return width * height; }
bool empty() const { return width <= 0.0f || height <= 0.0f; }
static constexpr Size Zero() { return {0.0f, 0.0f}; }
};
// ---------------------------------------------------------------------------
// 2D 矩形
// ---------------------------------------------------------------------------
struct Rect {
Point origin;
Size size;
constexpr Rect() = default;
constexpr Rect(float x, float y, float w, float h)
: origin(x, y), size(w, h) {}
constexpr Rect(const Point& o, const Size& s) : origin(o), size(s) {}
float left() const { return origin.x; }
float top() const { return origin.y; }
float right() const { return origin.x + size.width; }
float bottom() const { return origin.y + size.height; }
float width() const { return size.width; }
float height() const { return size.height; }
Point center() const {
return {origin.x + size.width * 0.5f, origin.y + size.height * 0.5f};
}
bool empty() const { return size.empty(); }
bool containsPoint(const Point &p) const {
return p.x >= left() && p.x <= right() && p.y >= top() && p.y <= bottom();
}
bool contains(const Rect &r) const {
return r.left() >= left() && r.right() <= right() && r.top() >= top() &&
r.bottom() <= bottom();
}
bool intersects(const Rect &r) const {
return !(left() > r.right() || right() < r.left() || top() > r.bottom() ||
bottom() < r.top());
}
Rect intersection(const Rect &r) const {
float l = std::max(left(), r.left());
float t = std::max(top(), r.top());
float ri = std::min(right(), r.right());
float b = std::min(bottom(), r.bottom());
if (l < ri && t < b)
return {l, t, ri - l, b - t};
return {};
}
Rect unionWith(const Rect &r) const {
if (empty())
return r;
if (r.empty())
return *this;
float l = std::min(left(), r.left());
float t = std::min(top(), r.top());
float ri = std::max(right(), r.right());
float b = std::max(bottom(), r.bottom());
return {l, t, ri - l, b - t};
}
bool operator==(const Rect &r) const {
return origin == r.origin && size == r.size;
}
bool operator!=(const Rect &r) const { return !(*this == r); }
static constexpr Rect Zero() { return {0, 0, 0, 0}; }
};
// ---------------------------------------------------------------------------
// 2D 变换矩阵(基于 glm::mat4,兼容 OpenGL
// ---------------------------------------------------------------------------
struct Transform2D {
glm::mat4 matrix{1.0f}; // 单位矩阵
Transform2D() = default;
explicit Transform2D(const glm::mat4 &m) : matrix(m) {}
static Transform2D identity() { return Transform2D{}; }
static Transform2D translation(float x, float y) {
Transform2D t;
t.matrix = glm::translate(glm::mat4(1.0f), glm::vec3(x, y, 0.0f));
return t;
}
static Transform2D translation(const Vec2 &v) {
return translation(v.x, v.y);
}
static Transform2D rotation(float degrees) {
Transform2D t;
t.matrix = glm::rotate(glm::mat4(1.0f), degrees * DEG_TO_RAD,
glm::vec3(0.0f, 0.0f, 1.0f));
return t;
}
static Transform2D scaling(float sx, float sy) {
Transform2D t;
t.matrix = glm::scale(glm::mat4(1.0f), glm::vec3(sx, sy, 1.0f));
return t;
}
static Transform2D scaling(float s) { return scaling(s, s); }
static Transform2D skewing(float skewX, float skewY) {
Transform2D t;
t.matrix = glm::mat4(1.0f);
t.matrix[1][0] = std::tan(skewX * DEG_TO_RAD);
t.matrix[0][1] = std::tan(skewY * DEG_TO_RAD);
return t;
}
Transform2D operator*(const Transform2D &other) const {
return Transform2D(matrix * other.matrix);
}
Transform2D &operator*=(const Transform2D &other) {
matrix *= other.matrix;
return *this;
}
Vec2 transformPoint(const Vec2 &p) const {
glm::vec4 result = matrix * glm::vec4(p.x, p.y, 0.0f, 1.0f);
return {result.x, result.y};
}
Transform2D inverse() const { return Transform2D(glm::inverse(matrix)); }
};
// ---------------------------------------------------------------------------
// 数学工具函数
// ---------------------------------------------------------------------------
namespace math {
inline float clamp(float value, float minVal, float maxVal) {
return std::clamp(value, minVal, maxVal);
}
inline float lerp(float a, float b, float t) { return a + (b - a) * t; }
inline float degrees(float radians) { return radians * RAD_TO_DEG; }
inline float radians(float degrees) { return degrees * DEG_TO_RAD; }
// ---------------------------------------------------------------------------
// 角度工具函数
// ---------------------------------------------------------------------------
/**
* @brief 规范化角度到 [0, 360) 范围
* @param degrees 输入角度(度数)
* @return 规范化后的角度,范围 [0, 360)
*/
inline float normalizeAngle360(float degrees) {
degrees = std::fmod(degrees, 360.0f);
if (degrees < 0.0f) {
degrees += 360.0f;
}
return degrees;
}
/**
* @brief 规范化角度到 [-180, 180) 范围
* @param degrees 输入角度(度数)
* @return 规范化后的角度,范围 [-180, 180)
*/
inline float normalizeAngle180(float degrees) {
degrees = std::fmod(degrees + 180.0f, 360.0f);
if (degrees < 0.0f) {
degrees += 360.0f;
}
return degrees - 180.0f;
}
/**
* @brief 计算两个角度之间的最短差值
* @param from 起始角度(度数)
* @param to 目标角度(度数)
* @return 从 from 到 to 的最短角度差,范围 [-180, 180]
*/
inline float angleDifference(float from, float to) {
float diff = normalizeAngle360(to - from);
if (diff > 180.0f) {
diff -= 360.0f;
}
return diff;
}
/**
* @brief 线性插值角度
* @param from 起始角度(度数)
* @param to 目标角度(度数)
* @param t 插值因子 [0, 1]
* @return 插值后的角度
*/
inline float lerpAngle(float from, float to, float t) {
return from + angleDifference(from, to) * t;
}
// ---------------------------------------------------------------------------
// 向量工具函数
// ---------------------------------------------------------------------------
/**
* @brief 计算方向向量(从 from 指向 to 的单位向量)
* @param from 起始点
* @param to 目标点
* @return 归一化的方向向量
*/
inline Vec2 direction(const Vec2 &from, const Vec2 &to) {
return (to - from).normalized();
}
/**
* @brief 计算两点之间的角度
* @param from 起始点
* @param to 目标点
* @return 角度(度数),范围 [-180, 180]
*/
inline float angleBetween(const Vec2 &from, const Vec2 &to) {
Vec2 dir = to - from;
return std::atan2(dir.y, dir.x) * RAD_TO_DEG;
}
/**
* @brief 根据角度创建方向向量
* @param degrees 角度(度数),0度指向右方,逆时针为正
* @return 单位方向向量
*/
inline Vec2 angleToVector(float degrees) {
float rad = degrees * DEG_TO_RAD;
return {std::cos(rad), std::sin(rad)};
}
/**
* @brief 将向量旋转指定角度
* @param v 原始向量
* @param degrees 旋转角度(度数),正值为逆时针旋转
* @return 旋转后的向量
*/
inline Vec2 rotateVector(const Vec2 &v, float degrees) {
float rad = degrees * DEG_TO_RAD;
float cosA = std::cos(rad);
float sinA = std::sin(rad);
return {v.x * cosA - v.y * sinA, v.x * sinA + v.y * cosA};
}
// ---------------------------------------------------------------------------
// 坐标系转换工具
// ---------------------------------------------------------------------------
/**
* @brief Y轴向上坐标转Y轴向下坐标
* @param pos Y轴向上坐标系中的位置
* @param height 画布/屏幕高度
* @return Y轴向下坐标系中的位置
*/
inline Vec2 flipY(const Vec2 &pos, float height) {
return {pos.x, height - pos.y};
}
/**
* @brief Y轴向下坐标转Y轴向上坐标
* @param pos Y轴向下坐标系中的位置
* @param height 画布/屏幕高度
* @return Y轴向上坐标系中的位置
*/
inline Vec2 unflipY(const Vec2 &pos, float height) {
return {pos.x, height - pos.y};
}
// ---------------------------------------------------------------------------
// 矩阵工具函数
// ---------------------------------------------------------------------------
/**
* @brief 从变换矩阵提取位置
* @param matrix 4x4变换矩阵
* @return 提取的位置向量
*/
inline Vec2 extractPosition(const glm::mat4 &matrix) {
return {matrix[3][0], matrix[3][1]};
}
/**
* @brief 从变换矩阵提取缩放
* @param matrix 4x4变换矩阵
* @return 提取的缩放向量
*/
inline Vec2 extractScale(const glm::mat4 &matrix) {
float scaleX =
std::sqrt(matrix[0][0] * matrix[0][0] + matrix[0][1] * matrix[0][1]);
float scaleY =
std::sqrt(matrix[1][0] * matrix[1][0] + matrix[1][1] * matrix[1][1]);
return {scaleX, scaleY};
}
/**
* @brief 从变换矩阵提取旋转角度
* @param matrix 4x4变换矩阵
* @return 提取的旋转角度(度数)
*/
inline float extractRotation(const glm::mat4 &matrix) {
return std::atan2(matrix[0][1], matrix[0][0]) * RAD_TO_DEG;
}
// ---------------------------------------------------------------------------
// 碰撞检测工具
// ---------------------------------------------------------------------------
/**
* @brief 判断点是否在矩形内
* @param point 要检测的点
* @param rect 矩形区域
* @return 如果点在矩形内返回 true,否则返回 false
*/
inline bool pointInRect(const Vec2 &point, const Rect &rect) {
return point.x >= rect.left() && point.x <= rect.right() &&
point.y >= rect.top() && point.y <= rect.bottom();
}
/**
* @brief 判断点是否在圆内
* @param point 要检测的点
* @param center 圆心
* @param radius 圆的半径
* @return 如果点在圆内返回 true,否则返回 false
*/
inline bool pointInCircle(const Vec2 &point, const Vec2 &center, float radius) {
float dx = point.x - center.x;
float dy = point.y - center.y;
return (dx * dx + dy * dy) <= (radius * radius);
}
/**
* @brief 判断两个矩形是否相交
* @param a 第一个矩形
* @param b 第二个矩形
* @return 如果矩形相交返回 true,否则返回 false
*/
inline bool rectsIntersect(const Rect &a, const Rect &b) {
return a.intersects(b);
}
/**
* @brief 判断两个圆是否相交
* @param center1 第一个圆的圆心
* @param radius1 第一个圆的半径
* @param center2 第二个圆的圆心
* @param radius2 第二个圆的半径
* @return 如果圆相交返回 true,否则返回 false
*/
inline bool circlesIntersect(const Vec2 &center1, float radius1,
const Vec2 &center2, float radius2) {
float dx = center2.x - center1.x;
float dy = center2.y - center1.y;
float distSq = dx * dx + dy * dy;
float radiusSum = radius1 + radius2;
return distSq <= (radiusSum * radiusSum);
}
} // namespace math
} // namespace frostbite2D
@@ -0,0 +1,36 @@
#pragma once
#include <array>
#include <cstdint>
#include <cstdlib>
#include <string>
namespace frostbite2D {
class UUID {
public:
static UUID generate();
static UUID fromString(const std::string& str);
std::string toString() const;
UUID() = default;
bool operator==(const UUID& other) const { return data_ == other.data_; }
bool operator!=(const UUID& other) const { return data_ != other.data_; }
bool isValid() const {
for (auto byte : data_) {
if (byte != 0) return true;
}
return false;
}
const std::array<uint8_t, 16>& data() const { return data_; }
private:
explicit UUID(const std::array<uint8_t, 16>& data) : data_(data) {}
std::array<uint8_t, 16> data_{};
};
} // namespace frostbite2D
@@ -0,0 +1,395 @@
#pragma once
#include <type_traits>
#include <iterator>
#include <stdexcept>
#include <cassert>
namespace frostbite2D {
template <typename _PtrTy>
class IntrusiveListValue;
template <typename _PtrTy>
class IntrusiveList {
public:
using value_type = typename std::pointer_traits<_PtrTy>::pointer;
using pointer = value_type*;
using reference = value_type&;
IntrusiveList()
: first_(nullptr)
, last_(nullptr) {
}
~IntrusiveList() {
Clear();
}
const value_type& GetFirst() const {
return first_;
}
value_type& GetFirst() {
return first_;
}
const value_type& GetLast() const {
return last_;
}
value_type& GetLast() {
return last_;
}
inline bool IsEmpty() const {
return first_ == nullptr;
}
void PushBack(reference child) {
if (child->GetPrev()) {
child->GetPrev()->GetNext() = child->GetNext();
}
if (child->GetNext()) {
child->GetNext()->GetPrev() = child->GetPrev();
}
child->GetPrev() = last_;
child->GetNext() = nullptr;
if (first_) {
last_->GetNext() = child;
} else {
first_ = child;
}
last_ = child;
}
void PushFront(reference child) {
if (child->GetPrev()) {
child->GetPrev()->GetNext() = child->GetNext();
}
if (child->GetNext()) {
child->GetNext()->GetPrev() = child->GetPrev();
}
child->GetPrev() = nullptr;
child->GetNext() = first_;
if (first_) {
first_->GetPrev() = child;
} else {
last_ = child;
}
first_ = child;
}
void InsertBefore(reference child, reference before) {
if (child->GetPrev()) {
child->GetPrev()->GetNext() = child->GetNext();
}
if (child->GetNext()) {
child->GetNext()->GetPrev() = child->GetPrev();
}
if (before->GetPrev()) {
before->GetPrev()->GetNext() = child;
} else {
first_ = child;
}
child->GetPrev() = before->GetPrev();
child->GetNext() = before;
before->GetPrev() = child;
}
void InsertAfter(reference child, reference after) {
if (child->GetPrev()) {
child->GetPrev()->GetNext() = child->GetNext();
}
if (child->GetNext()) {
child->GetNext()->GetPrev() = child->GetPrev();
}
if (after->GetNext()) {
after->GetNext()->GetPrev() = child;
} else {
last_ = child;
}
child->GetNext() = after->GetNext();
child->GetPrev() = after;
after->GetNext() = child;
}
void Remove(reference child) {
if (child->GetNext()) {
child->GetNext()->GetPrev() = child->GetPrev();
} else {
last_ = child->GetPrev();
}
if (child->GetPrev()) {
child->GetPrev()->GetNext() = child->GetNext();
} else {
first_ = child->GetNext();
}
child->GetPrev() = nullptr;
child->GetNext() = nullptr;
}
void Clear() {
value_type p = first_;
while (p) {
value_type tmp = p;
p = p->GetNext();
if (tmp) {
tmp->GetNext() = nullptr;
tmp->GetPrev() = nullptr;
}
}
first_ = nullptr;
last_ = nullptr;
}
bool CheckValid() {
if (!first_) {
return true;
}
int pos = 0;
value_type p = first_;
value_type tmp = p;
do {
tmp = p;
p = p->GetNext();
++pos;
if (p) {
if (p->GetPrev() != tmp) {
return false;
}
} else {
if (tmp != last_) {
return false;
}
}
} while (p);
return true;
}
public:
template <typename _IterPtrTy>
struct Iterator {
using iterator_category = std::bidirectional_iterator_tag;
using value_type = _IterPtrTy;
using pointer = _IterPtrTy*;
using reference = _IterPtrTy&;
using difference_type = ptrdiff_t;
inline Iterator(value_type ptr = nullptr, bool is_end = false)
: base_(ptr)
, is_end_(is_end) {
}
inline reference operator*() const {
assert(base_ && !is_end_);
return const_cast<reference>(base_);
}
inline pointer operator->() const {
return std::pointer_traits<pointer>::pointer_to(**this);
}
inline Iterator& operator++() {
assert(base_ && !is_end_);
value_type next = base_->GetNext();
if (next) {
base_ = next;
} else {
is_end_ = true;
}
return (*this);
}
inline Iterator operator++(int) {
Iterator old = (*this);
++(*this);
return old;
}
inline Iterator& operator--() {
assert(base_);
if (is_end_) {
is_end_ = false;
} else {
base_ = base_->GetPrev();
}
return (*this);
}
inline Iterator operator--(int) {
Iterator old = (*this);
--(*this);
return old;
}
inline bool operator==(const Iterator& other) const {
return base_ == other.base_ && is_end_ == other.is_end_;
}
inline bool operator!=(const Iterator& other) const {
return !(*this == other);
}
inline operator bool() const {
return base_ != nullptr && !is_end_;
}
private:
bool is_end_;
typename std::remove_const<value_type>::type base_;
};
public:
using iterator = Iterator<value_type>;
using const_iterator = Iterator<const value_type>;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
inline iterator begin() {
return iterator(first_, first_ == nullptr);
}
inline const_iterator begin() const {
return const_iterator(first_, first_ == nullptr);
}
inline const_iterator cbegin() const {
return begin();
}
inline iterator end() {
return iterator(last_, true);
}
inline const_iterator end() const {
return const_iterator(last_, true);
}
inline const_iterator cend() const {
return end();
}
inline reverse_iterator rbegin() {
return reverse_iterator(end());
}
inline const_reverse_iterator rbegin() const {
return const_reverse_iterator(end());
}
inline const_reverse_iterator crbegin() const {
return rbegin();
}
inline reverse_iterator rend() {
return reverse_iterator(begin());
}
inline const_reverse_iterator rend() const {
return const_reverse_iterator(begin());
}
inline const_reverse_iterator crend() const {
return rend();
}
inline value_type& front() {
if (IsEmpty()) {
throw std::out_of_range("front() called on empty list");
}
return first_;
}
inline const value_type& front() const {
if (IsEmpty()) {
throw std::out_of_range("front() called on empty list");
}
return first_;
}
inline value_type& back() {
if (IsEmpty()) {
throw std::out_of_range("back() called on empty list");
}
return last_;
}
inline const value_type& back() const {
if (IsEmpty()) {
throw std::out_of_range("back() called on empty list");
}
return last_;
}
private:
value_type first_;
value_type last_;
template <typename T>
friend class IntrusiveListValue;
};
template <typename _PtrTy>
class IntrusiveListValue {
public:
using value_type = typename std::pointer_traits<_PtrTy>::pointer;
using reference = value_type&;
using pointer = value_type*;
IntrusiveListValue()
: prev_(nullptr)
, next_(nullptr) {
}
IntrusiveListValue(value_type rhs)
: prev_(nullptr)
, next_(nullptr) {
if (rhs) {
prev_ = rhs->GetPrev();
next_ = rhs->GetNext();
}
}
const value_type& GetPrev() const {
return prev_;
}
value_type& GetPrev() {
return prev_;
}
const value_type& GetNext() const {
return next_;
}
value_type& GetNext() {
return next_;
}
private:
value_type prev_;
value_type next_;
template <typename T>
friend class IntrusiveList;
};
}
@@ -0,0 +1,152 @@
#pragma once
#include <SDL2/SDL.h>
#include <fstream>
#include <mutex>
#include <string>
namespace frostbite2D {
class StartupTrace {
public:
static constexpr bool enabled() {
#ifndef NDEBUG
return true;
#else
return false;
#endif
}
static void reset(const char* label = nullptr) {
if (!enabled()) {
return;
}
Uint64 now = SDL_GetPerformanceCounter();
std::string line;
{
std::lock_guard<std::mutex> lock(mutex());
startCounter() = now;
if (label && label[0] != '\0') {
line = std::string("[Startup] reset: ") + label;
clearLogFileUnlocked();
writeLineUnlocked(line);
} else {
clearLogFileUnlocked();
}
}
if (!line.empty()) {
SDL_Log("%s", line.c_str());
}
}
static double totalElapsedMs() {
if (!enabled()) {
return 0.0;
}
Uint64 start = 0;
{
std::lock_guard<std::mutex> lock(mutex());
start = startCounter();
}
if (start == 0) {
return 0.0;
}
Uint64 now = SDL_GetPerformanceCounter();
Uint64 frequency = SDL_GetPerformanceFrequency();
if (frequency == 0) {
return 0.0;
}
return static_cast<double>(now - start) * 1000.0 /
static_cast<double>(frequency);
}
static void mark(const char* label) {
if (!enabled()) {
return;
}
char buffer[256] = {};
SDL_snprintf(buffer, sizeof(buffer),
"[Startup] %s reached at %.2f ms (thread=%u)",
label ? label : "<unnamed>", totalElapsedMs(), SDL_ThreadID());
logLine(buffer);
SDL_Log("%s", buffer);
}
static void logLine(const std::string& line) {
if (!enabled()) {
return;
}
std::lock_guard<std::mutex> lock(mutex());
writeLineUnlocked(line);
}
private:
static Uint64& startCounter() {
static Uint64 counter = 0;
return counter;
}
static std::mutex& mutex() {
static std::mutex value;
return value;
}
static const char* logFilePath() {
return "startup_trace.log";
}
static void clearLogFileUnlocked() {
std::ofstream stream(logFilePath(), std::ios::trunc);
}
static void writeLineUnlocked(const std::string& line) {
std::ofstream stream(logFilePath(), std::ios::app);
if (!stream) {
return;
}
stream << line << '\n';
}
};
class ScopedStartupTrace {
public:
explicit ScopedStartupTrace(const char* label)
: label_(label ? label : "<unnamed>"),
startCounter_(SDL_GetPerformanceCounter()) {}
~ScopedStartupTrace() {
if (!StartupTrace::enabled()) {
return;
}
Uint64 now = SDL_GetPerformanceCounter();
Uint64 frequency = SDL_GetPerformanceFrequency();
double elapsedMs = 0.0;
if (frequency != 0) {
elapsedMs = static_cast<double>(now - startCounter_) * 1000.0 /
static_cast<double>(frequency);
}
char buffer[256] = {};
SDL_snprintf(buffer, sizeof(buffer),
"[Startup] %s took %.2f ms (total %.2f ms, thread=%u)",
label_, elapsedMs, StartupTrace::totalElapsedMs(),
SDL_ThreadID());
StartupTrace::logLine(buffer);
SDL_Log("%s", buffer);
}
private:
const char* label_;
Uint64 startCounter_ = 0;
};
} // namespace frostbite2D