feat(动画系统): 实现动画系统基础功能
添加动画组件及相关数据结构,支持从PVF加载动画资源 实现动画播放、帧控制、插值逻辑等功能 更新主程序以测试动画系统
This commit is contained in:
81
Frostbite2D/include/frostbite2D/animation/animation.h
Normal file
81
Frostbite2D/include/frostbite2D/animation/animation.h
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <frostbite2D/2d/actor.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 Update(float deltaTime) override;
|
||||||
|
void OnAdded(Actor* parent);
|
||||||
|
void SetVisible(bool visible);
|
||||||
|
|
||||||
|
public:
|
||||||
|
void FlushFrame(int index);
|
||||||
|
void Reset();
|
||||||
|
animation::AniFrame GetCurrentFrameInfo();
|
||||||
|
void SetFrameIndex(int index);
|
||||||
|
void InterpolationLogic();
|
||||||
|
|
||||||
|
Vec2 GetMaxSize() 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::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();
|
||||||
|
|
||||||
|
std::vector<animation::AniFrame> interpolationData_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace frostbite2D
|
||||||
102
Frostbite2D/include/frostbite2D/animation/animation_data.h
Normal file
102
Frostbite2D/include/frostbite2D/animation/animation_data.h
Normal file
@@ -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
|
||||||
292
Frostbite2D/src/frostbite2D/animation/animation.cpp
Normal file
292
Frostbite2D/src/frostbite2D/animation/animation.cpp
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
#include <frostbite2D/animation/animation.h>
|
||||||
|
#include <frostbite2D/2d/sprite.h>
|
||||||
|
#include <frostbite2D/resource/npk_archive.h>
|
||||||
|
#include <frostbite2D/resource/pvf_archive.h>
|
||||||
|
#include <frostbite2D/types/type_math.h>
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
using namespace frostbite2D::animation;
|
||||||
|
|
||||||
|
namespace frostbite2D {
|
||||||
|
|
||||||
|
Animation::Animation() {
|
||||||
|
}
|
||||||
|
|
||||||
|
Animation::Animation(const std::string& aniPath) {
|
||||||
|
Init(aniPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
Animation::Animation(const std::string& aniPath,
|
||||||
|
std::function<std::string(std::string, ReplaceData)> additionalOptions,
|
||||||
|
ReplaceData data)
|
||||||
|
: additionalOptions_(additionalOptions)
|
||||||
|
, additionalOptionsData_(data) {
|
||||||
|
Init(aniPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
Animation::~Animation() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::Init(const std::string& aniPath) {
|
||||||
|
auto info = animation::parseAniFromPvf(aniPath);
|
||||||
|
if (!info) {
|
||||||
|
SDL_Log("Failed to load animation: %s", aniPath.c_str());
|
||||||
|
usable_ = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
aniPath_ = aniPath;
|
||||||
|
animationFlag_ = info->flag;
|
||||||
|
frames_ = std::move(info->frames);
|
||||||
|
|
||||||
|
if (animationFlag_.count("LOOP")) {
|
||||||
|
isLooping_ = true;
|
||||||
|
} else {
|
||||||
|
isLooping_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < frames_.size(); i++) {
|
||||||
|
AniFrame& frameObj = frames_[i];
|
||||||
|
Ptr<Sprite> spriteObj = nullptr;
|
||||||
|
|
||||||
|
if (!frameObj.imgPath.empty()) {
|
||||||
|
if (additionalOptions_) {
|
||||||
|
frameObj.imgPath = additionalOptions_(frameObj.imgPath, additionalOptionsData_);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto sprite = Sprite::createFromNpk(frameObj.imgPath, frameObj.imgIndex);
|
||||||
|
if (sprite) {
|
||||||
|
sprite->SetPosition(frameObj.imgPos);
|
||||||
|
sprite->SetVisible(false);
|
||||||
|
spriteObj = sprite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!spriteObj) {
|
||||||
|
spriteObj = MakePtr<Sprite>();
|
||||||
|
spriteObj->SetVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
spriteFrames_.push_back(spriteObj);
|
||||||
|
|
||||||
|
auto spriteSize = spriteObj->GetSize();
|
||||||
|
if (maxSize_.x < spriteSize.x) maxSize_.x = spriteSize.x;
|
||||||
|
if (maxSize_.y < spriteSize.y) maxSize_.y = spriteSize.y;
|
||||||
|
|
||||||
|
AddChild(spriteObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentFrameTime_ == 0.0f && !spriteFrames_.empty()) {
|
||||||
|
SetSize(spriteFrames_[0]->GetSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
totalFrameCount_ = static_cast<int>(frames_.size());
|
||||||
|
|
||||||
|
auto alsPath = aniPath + ".als";
|
||||||
|
if (PvfArchive::get().hasFile(alsPath)) {
|
||||||
|
auto alsInfo = animation::parseAlsFromPvf(alsPath);
|
||||||
|
if (alsInfo && !alsInfo->aniList.empty()) {
|
||||||
|
size_t lastSlash = aniPath.find_last_of('/');
|
||||||
|
std::string dir = (lastSlash != std::string::npos) ? aniPath.substr(0, lastSlash + 1) : "";
|
||||||
|
|
||||||
|
for (auto& ani : alsInfo->aniList) {
|
||||||
|
auto subAni = MakePtr<Animation>(dir + ani.second.path);
|
||||||
|
if (ani.second.layer.size() >= 2) {
|
||||||
|
subAni->SetZOrder(ani.second.layer[1]);
|
||||||
|
}
|
||||||
|
AddChild(subAni);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FlushFrame(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::Update(float deltaTime) {
|
||||||
|
if (usable_ && IsVisible()) {
|
||||||
|
float dtMs = deltaTime * 1000.0f;
|
||||||
|
currentFrameTime_ += dtMs;
|
||||||
|
|
||||||
|
while (currentFrameTime_ >= nextFrameDelay_) {
|
||||||
|
currentFrameTime_ -= nextFrameDelay_;
|
||||||
|
|
||||||
|
if (currentFrameIndex_ < (totalFrameCount_ - 1)) {
|
||||||
|
FlushFrame(currentFrameIndex_ + 1);
|
||||||
|
} else {
|
||||||
|
if (isLooping_) {
|
||||||
|
FlushFrame(0);
|
||||||
|
} else {
|
||||||
|
usable_ = false;
|
||||||
|
if (endCallback_) {
|
||||||
|
endCallback_();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (auto& sp : spriteFrames_) {
|
||||||
|
sp->SetVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::OnAdded(Actor* parent) {
|
||||||
|
FlushFrame(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::SetVisible(bool visible) {
|
||||||
|
if (visible) {
|
||||||
|
if (currentFrame_) {
|
||||||
|
currentFrame_->SetVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Actor::SetVisible(visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::FlushFrame(int index) {
|
||||||
|
if (currentFrame_) {
|
||||||
|
currentFrame_->SetVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFrameIndex_ = index;
|
||||||
|
currentFrame_ = spriteFrames_[currentFrameIndex_];
|
||||||
|
currentFrame_->SetVisible(true);
|
||||||
|
|
||||||
|
AniFrame& frameInfo = frames_[currentFrameIndex_];
|
||||||
|
nextFrameDelay_ = static_cast<float>(frameInfo.delay);
|
||||||
|
|
||||||
|
auto& flagBuf = frameInfo.flag;
|
||||||
|
|
||||||
|
if (flagBuf.count("SET_FLAG")) {
|
||||||
|
if (changeFrameCallback_) {
|
||||||
|
auto flagValue = std::get<int>(flagBuf.at("SET_FLAG"));
|
||||||
|
changeFrameCallback_(flagValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flagBuf.count("PLAY_SOUND")) {
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flagBuf.count("IMAGE_RATE")) {
|
||||||
|
auto rate = std::get<Vec2>(flagBuf.at("IMAGE_RATE"));
|
||||||
|
currentFrame_->SetScale(rate);
|
||||||
|
currentFrame_->SetPosition(frameInfo.imgPos.x * rate.x, frameInfo.imgPos.y * rate.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flagBuf.count("GRAPHIC_EFFECT_LINEARDODGE")) {
|
||||||
|
currentFrame_->SetBlendMode(BlendMode::Additive);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flagBuf.count("IMAGE_ROTATE")) {
|
||||||
|
auto rotation = std::get<float>(flagBuf.at("IMAGE_ROTATE"));
|
||||||
|
currentFrame_->SetRotation(rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flagBuf.count("INTERPOLATION")) {
|
||||||
|
if (interpolationData_.empty()) {
|
||||||
|
interpolationData_.push_back(frames_[currentFrameIndex_]);
|
||||||
|
if (currentFrameIndex_ + 1 < static_cast<int>(frames_.size())) {
|
||||||
|
interpolationData_.push_back(frames_[currentFrameIndex_ + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!interpolationData_.empty()) {
|
||||||
|
interpolationData_.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SetSize(currentFrame_->GetSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::Reset() {
|
||||||
|
usable_ = true;
|
||||||
|
currentFrameTime_ = 0.0f;
|
||||||
|
FlushFrame(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
AniFrame Animation::GetCurrentFrameInfo() {
|
||||||
|
return frames_[currentFrameIndex_];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::SetFrameIndex(int index) {
|
||||||
|
FlushFrame(index);
|
||||||
|
currentFrameTime_ = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::InterpolationLogic() {
|
||||||
|
if (interpolationData_.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float interRate = math::lerp(0.0f, 1.0f, currentFrameTime_ / nextFrameDelay_);
|
||||||
|
|
||||||
|
AniFrame& oldData = interpolationData_[0];
|
||||||
|
AniFrame& newData = interpolationData_[1];
|
||||||
|
|
||||||
|
{
|
||||||
|
std::vector<int> oldRgbaData = {255, 255, 255, 250};
|
||||||
|
std::vector<int> newRgbaData = {255, 255, 255, 250};
|
||||||
|
|
||||||
|
if (oldData.flag.count("RGBA")) {
|
||||||
|
oldRgbaData = std::get<std::vector<int>>(oldData.flag.at("RGBA"));
|
||||||
|
}
|
||||||
|
if (newData.flag.count("RGBA")) {
|
||||||
|
newRgbaData = std::get<std::vector<int>>(newData.flag.at("RGBA"));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<int> rgbaData = {
|
||||||
|
static_cast<int>(oldRgbaData[0] + (newRgbaData[0] - oldRgbaData[0]) * interRate),
|
||||||
|
static_cast<int>(oldRgbaData[1] + (newRgbaData[1] - oldRgbaData[1]) * interRate),
|
||||||
|
static_cast<int>(oldRgbaData[2] + (newRgbaData[2] - oldRgbaData[2]) * interRate),
|
||||||
|
static_cast<int>(oldRgbaData[3] + (newRgbaData[3] - oldRgbaData[3]) * interRate)};
|
||||||
|
|
||||||
|
currentFrame_->SetColor(
|
||||||
|
static_cast<float>(rgbaData[0]) / 255.0f,
|
||||||
|
static_cast<float>(rgbaData[1]) / 255.0f,
|
||||||
|
static_cast<float>(rgbaData[2]) / 255.0f,
|
||||||
|
static_cast<float>(rgbaData[3]) / 255.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Vec2 posData = Vec2::lerp(oldData.imgPos, newData.imgPos, interRate);
|
||||||
|
currentFrame_->SetPosition(posData);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Vec2 oldRateData = Vec2::One();
|
||||||
|
Vec2 newRateData = Vec2::One();
|
||||||
|
|
||||||
|
if (oldData.flag.count("IMAGE_RATE")) {
|
||||||
|
oldRateData = std::get<Vec2>(oldData.flag.at("IMAGE_RATE"));
|
||||||
|
}
|
||||||
|
if (newData.flag.count("IMAGE_RATE")) {
|
||||||
|
newRateData = std::get<Vec2>(newData.flag.at("IMAGE_RATE"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 rateData = Vec2::lerp(oldRateData, newRateData, interRate);
|
||||||
|
currentFrame_->SetScale(rateData);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
float oldAngleData = 0.0f;
|
||||||
|
float newAngleData = 0.0f;
|
||||||
|
|
||||||
|
if (oldData.flag.count("IMAGE_ROTATE")) {
|
||||||
|
oldAngleData = std::get<float>(oldData.flag.at("IMAGE_ROTATE"));
|
||||||
|
}
|
||||||
|
if (newData.flag.count("IMAGE_ROTATE")) {
|
||||||
|
newAngleData = std::get<float>(newData.flag.at("IMAGE_ROTATE"));
|
||||||
|
}
|
||||||
|
|
||||||
|
float angleData = math::lerp(oldAngleData, newAngleData, interRate);
|
||||||
|
currentFrame_->SetRotation(angleData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 Animation::GetMaxSize() const {
|
||||||
|
return maxSize_;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace frostbite2D
|
||||||
381
Frostbite2D/src/frostbite2D/animation/animation_data.cpp
Normal file
381
Frostbite2D/src/frostbite2D/animation/animation_data.cpp
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
#include <frostbite2D/animation/animation_data.h>
|
||||||
|
#include <frostbite2D/resource/binary_reader.h>
|
||||||
|
#include <frostbite2D/resource/pvf_archive.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace frostbite2D {
|
||||||
|
|
||||||
|
namespace animation {
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 工具函数实现
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static std::string toLowerCase(const std::string& str) {
|
||||||
|
std::string result = str;
|
||||||
|
std::transform(result.begin(), result.end(), result.begin(),
|
||||||
|
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getAniFlag(int type) {
|
||||||
|
switch (type) {
|
||||||
|
case 0: return {"LOOP"};
|
||||||
|
case 1: return {"SHADOW"};
|
||||||
|
case 3: return {"COORD"};
|
||||||
|
case 7: return {"IMAGE_RATE"};
|
||||||
|
case 8: return {"IMAGE_ROTATE"};
|
||||||
|
case 9: return {"RGBA"};
|
||||||
|
case 10: return {"INTERPOLATION"};
|
||||||
|
case 11: return {"GRAPHIC_EFFECT"};
|
||||||
|
case 12: return {"DELAY"};
|
||||||
|
case 13: return {"DAMAGE_TYPE"};
|
||||||
|
case 14: return {"DAMAGE_BOX"};
|
||||||
|
case 15: return {"ATTACK_BOX"};
|
||||||
|
case 16: return {"PLAY_SOUND"};
|
||||||
|
case 17: return {"PRELOAD"};
|
||||||
|
case 18: return {"SPECTRUM"};
|
||||||
|
case 23: return {"SET_FLAG"};
|
||||||
|
case 24: return {"FLIP_TYPE"};
|
||||||
|
case 25: return {"LOOP_START"};
|
||||||
|
case 26: return {"LOOP_END"};
|
||||||
|
case 27: return {"CLIP"};
|
||||||
|
case 28: return {"OPERATION"};
|
||||||
|
default: return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getAniEffectType(int type) {
|
||||||
|
switch (type) {
|
||||||
|
case 0: return {"NONE"};
|
||||||
|
case 1: return {"DODGE"};
|
||||||
|
case 2: return {"LINEARDODGE"};
|
||||||
|
case 3: return {"DARK"};
|
||||||
|
case 4: return {"XOR"};
|
||||||
|
case 5: return {"MONOCHROME"};
|
||||||
|
case 6: return {"SPACEDISTORT"};
|
||||||
|
default: return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getAniFlipType(int type) {
|
||||||
|
switch (type) {
|
||||||
|
case 1: return {"HORIZON"};
|
||||||
|
case 2: return {"VERTICAL"};
|
||||||
|
case 3: return {"ALL"};
|
||||||
|
default: return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getAniDamageType(int type) {
|
||||||
|
switch (type) {
|
||||||
|
case 0: return {"NORMAL"};
|
||||||
|
case 1: return {"SUPERARMOR"};
|
||||||
|
case 2: return {"UNBREAKABLE"};
|
||||||
|
default: return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 辅助函数
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static uint8 read256(BinaryReader& reader) {
|
||||||
|
uint8 value = reader.readUInt8();
|
||||||
|
return value == 255 ? 256 : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16 readUShort(BinaryReader& reader) {
|
||||||
|
return reader.readUInt16();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int16 readShort(BinaryReader& reader) {
|
||||||
|
return reader.readInt16();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 解析函数实现
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
AniInfo parseAniInfo(const char* data, size_t size) {
|
||||||
|
AniInfo info;
|
||||||
|
BinaryReader reader(data, size);
|
||||||
|
|
||||||
|
if (!reader.isOpen() || reader.size() < 4) {
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16 frameMax = reader.readUInt16();
|
||||||
|
uint16 imgCount = reader.readUInt16();
|
||||||
|
|
||||||
|
for (uint16 i = 0; i < imgCount; i++) {
|
||||||
|
int buf = reader.readInt32();
|
||||||
|
std::string imgPath = "sprite/" + reader.readString(buf);
|
||||||
|
info.imgList.push_back(toLowerCase(imgPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16 aniHeaderItemCount = reader.readUInt16();
|
||||||
|
for (uint16 i = 0; i < aniHeaderItemCount; i++) {
|
||||||
|
uint16 type = reader.readUInt16();
|
||||||
|
switch (type) {
|
||||||
|
case 0:
|
||||||
|
case 1: {
|
||||||
|
std::string key = getAniFlag(type);
|
||||||
|
int value = reader.readUInt8();
|
||||||
|
if (!key.empty()) {
|
||||||
|
info.flag.emplace(key, value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
case 28: {
|
||||||
|
std::string key = getAniFlag(type);
|
||||||
|
int value = reader.readUInt16();
|
||||||
|
if (!key.empty()) {
|
||||||
|
info.flag.emplace(key, value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 18: {
|
||||||
|
reader.readUInt8();
|
||||||
|
reader.readInt32();
|
||||||
|
reader.readInt32();
|
||||||
|
reader.readInt32();
|
||||||
|
read256(reader);
|
||||||
|
read256(reader);
|
||||||
|
read256(reader);
|
||||||
|
read256(reader);
|
||||||
|
reader.readUInt16();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint16 i = 0; i < frameMax; i++) {
|
||||||
|
AniFrame frame;
|
||||||
|
|
||||||
|
uint16 boxItemCount = reader.readUInt16();
|
||||||
|
for (uint16 j = 0; j < boxItemCount; j++) {
|
||||||
|
uint16 boxType = reader.readUInt16();
|
||||||
|
std::vector<int> boxData;
|
||||||
|
for (int k = 0; k < 6; k++) {
|
||||||
|
boxData.push_back(reader.readInt32());
|
||||||
|
}
|
||||||
|
if (boxType == 15) {
|
||||||
|
frame.attackBox.push_back(boxData);
|
||||||
|
} else {
|
||||||
|
frame.damageBox.push_back(boxData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int16 indexBuf = reader.readInt16();
|
||||||
|
if (indexBuf != -1) {
|
||||||
|
frame.imgPath = info.imgList[indexBuf];
|
||||||
|
frame.imgIndex = reader.readUInt16();
|
||||||
|
} else {
|
||||||
|
frame.imgPath = "";
|
||||||
|
frame.imgIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
frame.imgPos.x = static_cast<float>(reader.readInt32());
|
||||||
|
frame.imgPos.y = static_cast<float>(reader.readInt32());
|
||||||
|
|
||||||
|
uint16 imgFlagCount = reader.readUInt16();
|
||||||
|
for (uint16 j = 0; j < imgFlagCount; j++) {
|
||||||
|
uint16 imgFlagType = reader.readUInt16();
|
||||||
|
std::string key;
|
||||||
|
int value;
|
||||||
|
|
||||||
|
switch (imgFlagType) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
case 10: {
|
||||||
|
key = getAniFlag(imgFlagType);
|
||||||
|
value = reader.readUInt8();
|
||||||
|
if (!key.empty()) {
|
||||||
|
frame.flag.emplace(key, value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
key = "COORD";
|
||||||
|
value = reader.readUInt16();
|
||||||
|
frame.flag.emplace(key, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 17: {
|
||||||
|
key = "PRELOAD";
|
||||||
|
value = 1;
|
||||||
|
frame.flag.emplace(key, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 7: {
|
||||||
|
key = "IMAGE_RATE";
|
||||||
|
Vec2 rate{reader.readFloat(), reader.readFloat()};
|
||||||
|
frame.flag.emplace(key, rate);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 8: {
|
||||||
|
key = "IMAGE_ROTATE";
|
||||||
|
float rateBuffer = reader.readFloat();
|
||||||
|
frame.flag.emplace(key, rateBuffer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 9: {
|
||||||
|
key = "RGBA";
|
||||||
|
std::vector<int> rgba = {
|
||||||
|
static_cast<int>(read256(reader)),
|
||||||
|
static_cast<int>(read256(reader)),
|
||||||
|
static_cast<int>(read256(reader)),
|
||||||
|
static_cast<int>(read256(reader))};
|
||||||
|
frame.flag.emplace(key, rgba);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 11: {
|
||||||
|
uint16 effectType = reader.readUInt16();
|
||||||
|
key = "GRAPHIC_EFFECT_" + getAniEffectType(effectType);
|
||||||
|
std::vector<float> effect;
|
||||||
|
switch (effectType) {
|
||||||
|
case 5:
|
||||||
|
effect.push_back(static_cast<float>(read256(reader)));
|
||||||
|
effect.push_back(static_cast<float>(read256(reader)));
|
||||||
|
effect.push_back(static_cast<float>(read256(reader)));
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
effect.push_back(static_cast<float>(read256(reader)));
|
||||||
|
effect.push_back(static_cast<float>(read256(reader)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
frame.flag.emplace(key, effect);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 12: {
|
||||||
|
value = reader.readInt32();
|
||||||
|
frame.delay = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 13: {
|
||||||
|
key = "DAMAGE_TYPE";
|
||||||
|
std::string dtype = getAniDamageType(reader.readUInt16());
|
||||||
|
frame.flag.emplace(key, dtype);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 16: {
|
||||||
|
int soundSize = reader.readInt32();
|
||||||
|
key = "PLAY_SOUND";
|
||||||
|
std::string sound = reader.readString(soundSize);
|
||||||
|
frame.flag.emplace(key, sound);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 23: {
|
||||||
|
key = "SET_FLAG";
|
||||||
|
value = reader.readInt32();
|
||||||
|
frame.flag.emplace(key, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 24: {
|
||||||
|
key = "FLIP_TYPE";
|
||||||
|
std::string ftValue = getAniFlipType(reader.readUInt16());
|
||||||
|
frame.flag.emplace(key, ftValue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 25: {
|
||||||
|
key = "LOOP_START";
|
||||||
|
frame.flag.emplace(key, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 26: {
|
||||||
|
key = "LOOP_END";
|
||||||
|
value = reader.readInt32();
|
||||||
|
frame.flag.emplace(key, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 27: {
|
||||||
|
key = "CLIP";
|
||||||
|
std::vector<int> clipArr = {
|
||||||
|
reader.readInt16(),
|
||||||
|
reader.readInt16(),
|
||||||
|
reader.readInt16(),
|
||||||
|
reader.readInt16()};
|
||||||
|
frame.flag.emplace(key, clipArr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info.frames.push_back(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<AniInfo> parseAniFromPvf(const std::string& path) {
|
||||||
|
auto& pvf = PvfArchive::get();
|
||||||
|
if (!pvf.isOpen()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto rawData = pvf.getFileRawData(path);
|
||||||
|
if (!rawData) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseAniInfo(rawData->data.get(), rawData->size);
|
||||||
|
}
|
||||||
|
|
||||||
|
AlsInfo parseAlsInfo(const std::string& data) {
|
||||||
|
AlsInfo info;
|
||||||
|
|
||||||
|
std::istringstream stream(data);
|
||||||
|
std::string line;
|
||||||
|
|
||||||
|
while (std::getline(stream, line)) {
|
||||||
|
if (line.empty()) continue;
|
||||||
|
|
||||||
|
std::istringstream lineStream(line);
|
||||||
|
std::string segment;
|
||||||
|
lineStream >> segment;
|
||||||
|
|
||||||
|
if (segment == "[use animation]") {
|
||||||
|
std::string aniPath, aniKey;
|
||||||
|
lineStream >> aniPath >> aniKey;
|
||||||
|
info.aniList[aniKey].path = toLowerCase(aniPath);
|
||||||
|
} else if (segment == "[none effect add]") {
|
||||||
|
int layer1, layer2;
|
||||||
|
std::string aniKey;
|
||||||
|
lineStream >> layer1 >> layer2 >> aniKey;
|
||||||
|
info.aniList[aniKey].layer = {layer1, layer2};
|
||||||
|
} else if (segment == "[add]") {
|
||||||
|
int layer1, layer2;
|
||||||
|
std::string aniKey;
|
||||||
|
lineStream >> layer1 >> layer2 >> aniKey;
|
||||||
|
info.aniList[aniKey].layer = {layer1, layer2};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<AlsInfo> parseAlsFromPvf(const std::string& path) {
|
||||||
|
auto& pvf = PvfArchive::get();
|
||||||
|
if (!pvf.isOpen()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto content = pvf.getFileContent(path);
|
||||||
|
if (!content) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseAlsInfo(*content);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace animation
|
||||||
|
|
||||||
|
} // namespace frostbite2D
|
||||||
Binary file not shown.
BIN
Game/assets/ImagePacks2/sprite_monster_goblin_event.NPK
Normal file
BIN
Game/assets/ImagePacks2/sprite_monster_goblin_event.NPK
Normal file
Binary file not shown.
@@ -24,6 +24,8 @@
|
|||||||
#include <frostbite2D/resource/audio_database.h>
|
#include <frostbite2D/resource/audio_database.h>
|
||||||
#include <frostbite2D/resource/sound_pack_archive.h>
|
#include <frostbite2D/resource/sound_pack_archive.h>
|
||||||
|
|
||||||
|
#include <frostbite2D/animation/animation.h>
|
||||||
|
|
||||||
using namespace frostbite2D;
|
using namespace frostbite2D;
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
@@ -43,9 +45,30 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
// SDL_Log("Starting main loop...");
|
// SDL_Log("Starting main loop...");
|
||||||
|
|
||||||
|
auto &pvf = PvfArchive::get();
|
||||||
|
if (pvf.open("assets/Script.pvf")) {
|
||||||
|
pvf.init();
|
||||||
|
SDL_Log("PVF initialized successfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
NpkArchive &npk = NpkArchive::get();
|
||||||
|
npk.setImagePackDirectory("assets/ImagePacks2");
|
||||||
|
npk.setDefaultImg("sprite/interface/base.img", 0);
|
||||||
|
npk.init();
|
||||||
|
|
||||||
auto menuScene = MakePtr<Scene>();
|
auto menuScene = MakePtr<Scene>();
|
||||||
SceneManager::get().PushScene(menuScene);
|
SceneManager::get().PushScene(menuScene);
|
||||||
|
|
||||||
|
auto ani = MakePtr<Animation>(
|
||||||
|
"monster/event/bluemarble/goblin/animation_goblin2/move.ani");
|
||||||
|
|
||||||
|
if (ani) {
|
||||||
|
SDL_Log("Animation created successfully");
|
||||||
|
ani->SetAnchor(Vec2(0.5f, 0.5f));
|
||||||
|
ani->SetPosition(640, 360);
|
||||||
|
menuScene->AddChild(ani);
|
||||||
|
}
|
||||||
|
|
||||||
auto TestActor = MakePtr<Actor>();
|
auto TestActor = MakePtr<Actor>();
|
||||||
|
|
||||||
menuScene->AddChild(TestActor);
|
menuScene->AddChild(TestActor);
|
||||||
@@ -85,20 +108,14 @@ int main(int argc, char **argv) {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// // 尝试加载精灵
|
auto sprite1 = Sprite::createFromNpk("sprite/newtitle/nangua.img", 0);
|
||||||
// auto sprite = Sprite::createFromFile("assets/player.png");
|
if (sprite1) {
|
||||||
// if (sprite) {
|
sprite1->SetPosition(220, 10);
|
||||||
// sprite->SetPosition(320, 300);
|
// sprite1->SetScale(2.0f);
|
||||||
// sprite->SetOpacity(0.8f);
|
menuScene->AddChild(sprite1);
|
||||||
// // sprite->SetAnchor(Vec2(0.5f, 0.5f));
|
} else {
|
||||||
// // sprite->SetRotation(30.f);
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create sprite from NPK!");
|
||||||
// sprite->SetZOrder(2000);
|
}
|
||||||
// // sprite->SetScale(Vec2(-1.0f, 1.0f));
|
|
||||||
// menuScene->AddChild(sprite);
|
|
||||||
// SDL_Log("Sprite created and added to scene");
|
|
||||||
// } else {
|
|
||||||
// SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create sprite from file!");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // auto &archive = PvfArchive::get();
|
// // auto &archive = PvfArchive::get();
|
||||||
// // if (archive.open("assets/Script.pvf")) {
|
// // if (archive.open("assets/Script.pvf")) {
|
||||||
@@ -159,19 +176,7 @@ int main(int argc, char **argv) {
|
|||||||
// SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load background music!");
|
// SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load background music!");
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// NpkArchive &npk = NpkArchive::get();
|
|
||||||
// npk.setImagePackDirectory("assets/ImagePacks2");
|
|
||||||
// npk.setDefaultImg("sprite/interface/base.img", 0);
|
|
||||||
// npk.init();
|
|
||||||
|
|
||||||
// auto sprite1 = Sprite::createFromNpk("sprite/newtitle/nangua.img", 0);
|
|
||||||
// if (sprite1) {
|
|
||||||
// sprite1->SetPosition(220, 10);
|
|
||||||
// // sprite1->SetScale(2.0f);
|
|
||||||
// sprite->AddChild(sprite1);
|
|
||||||
// } else {
|
|
||||||
// SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create sprite from NPK!");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// SoundPackArchive &archive = SoundPackArchive::get();
|
// SoundPackArchive &archive = SoundPackArchive::get();
|
||||||
// archive.setSoundPackDirectory("assets/SoundPacks");
|
// archive.setSoundPackDirectory("assets/SoundPacks");
|
||||||
|
|||||||
Reference in New Issue
Block a user