Initial engine repository
This commit is contained in:
@@ -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 ¢er, 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 ¢er1, float radius1,
|
||||
const Vec2 ¢er2, 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
|
||||
Reference in New Issue
Block a user