feat(资源管理): 添加NPK文件格式支持及精灵创建功能
实现NPK文件格式的解析和缓存管理,支持从NPK文件中加载精灵图像 添加Sprite::createFromNpk方法用于从NPK创建精灵 修改Texture和Sprite相关方法以支持const数据 添加zlib依赖用于NPK文件解压 优化Asset::listFilesWithExtension的扩展名匹配逻辑
This commit is contained in:
@@ -15,7 +15,8 @@ public:
|
||||
virtual ~Sprite();
|
||||
|
||||
static Ptr<Sprite> createFromFile(const std::string& path);
|
||||
static Ptr<Sprite> createFromMemory(uint8* data, int width, int height, int channels);
|
||||
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;
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace frostbite2D {
|
||||
class Texture : public RefObject {
|
||||
public:
|
||||
static Ptr<Texture> loadFromFile(const std::string& path);
|
||||
static Ptr<Texture> createFromMemory(uint8* data, int width, int height, int channels);
|
||||
static Ptr<Texture> createFromMemory(const uint8* data, int width, int height, int channels);
|
||||
static Ptr<Texture> createEmpty(int width, int height);
|
||||
|
||||
~Texture();
|
||||
|
||||
102
Frostbite2D/include/frostbite2D/resource/npk_archive.h
Normal file
102
Frostbite2D/include/frostbite2D/resource/npk_archive.h
Normal file
@@ -0,0 +1,102 @@
|
||||
#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>
|
||||
#include <zlib.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
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:
|
||||
NpkArchive() = default;
|
||||
~NpkArchive() = default;
|
||||
|
||||
std::string normalizePath(const std::string& path) 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(BinaryReader& reader);
|
||||
|
||||
static const uint8 NPK_KEY[256];
|
||||
|
||||
std::string imagePackDirectory_ = "ImagePacks2";
|
||||
bool initialized_ = false;
|
||||
std::map<std::string, ImgRef> imgIndex_;
|
||||
std::map<std::string, CachedImageData> imageCache_;
|
||||
std::list<std::string> lruList_;
|
||||
size_t maxCacheSize_ = 512 * 1024 * 1024;
|
||||
size_t currentCacheSize_ = 0;
|
||||
std::string defaultImgPath_;
|
||||
size_t defaultImgFrame_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <frostbite2D/2d/sprite.h>
|
||||
#include <frostbite2D/core/application.h>
|
||||
#include <frostbite2D/graphics/renderer.h>
|
||||
#include <frostbite2D/resource/npk_archive.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <algorithm>
|
||||
|
||||
@@ -30,7 +31,7 @@ Ptr<Sprite> Sprite::createFromFile(const std::string &path) {
|
||||
return sprite;
|
||||
}
|
||||
|
||||
Ptr<Sprite> Sprite::createFromMemory(uint8* data, int width, int height, int channels) {
|
||||
Ptr<Sprite> Sprite::createFromMemory(const uint8* data, int width, int height, int channels) {
|
||||
Ptr<Texture> texture = Texture::createFromMemory(data, width, height, channels);
|
||||
if (!texture) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
@@ -176,5 +177,68 @@ Quad Sprite::createQuad() const {
|
||||
return quad;
|
||||
}
|
||||
|
||||
Ptr<Sprite> Sprite::createFromNpk(const std::string& imgPath, size_t frameIndex) {
|
||||
NpkArchive& npk = NpkArchive::get();
|
||||
|
||||
auto imgOpt = npk.getImg(imgPath);
|
||||
if (imgOpt) {
|
||||
auto frameOpt = npk.getImageFrame(*imgOpt, frameIndex);
|
||||
if (frameOpt && !frameOpt->data.empty()) {
|
||||
const ImageFrame& frame = *frameOpt;
|
||||
|
||||
std::vector<uint8> convertedData(frame.data.size());
|
||||
for (size_t i = 0; i < frame.data.size(); i += 4) {
|
||||
uint8 b = frame.data[i + 0];
|
||||
uint8 g = frame.data[i + 1];
|
||||
uint8 r = frame.data[i + 2];
|
||||
uint8 a = frame.data[i + 3];
|
||||
convertedData[i + 0] = r;
|
||||
convertedData[i + 1] = g;
|
||||
convertedData[i + 2] = b;
|
||||
convertedData[i + 3] = a;
|
||||
}
|
||||
|
||||
return createFromMemory(
|
||||
convertedData.data(),
|
||||
frame.width,
|
||||
frame.height,
|
||||
4
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& defaultPath = npk.getDefaultImgPath();
|
||||
if (!defaultPath.empty()) {
|
||||
auto defaultImgOpt = npk.getImg(defaultPath);
|
||||
if (defaultImgOpt) {
|
||||
auto defaultFrameOpt = npk.getImageFrame(*defaultImgOpt, npk.getDefaultImgFrame());
|
||||
if (defaultFrameOpt && !defaultFrameOpt->data.empty()) {
|
||||
const ImageFrame& frame = *defaultFrameOpt;
|
||||
|
||||
std::vector<uint8> convertedData(frame.data.size());
|
||||
for (size_t i = 0; i < frame.data.size(); i += 4) {
|
||||
uint8 b = frame.data[i + 0];
|
||||
uint8 g = frame.data[i + 1];
|
||||
uint8 r = frame.data[i + 2];
|
||||
uint8 a = frame.data[i + 3];
|
||||
convertedData[i + 0] = r;
|
||||
convertedData[i + 1] = g;
|
||||
convertedData[i + 2] = b;
|
||||
convertedData[i + 3] = a;
|
||||
}
|
||||
|
||||
return createFromMemory(
|
||||
convertedData.data(),
|
||||
frame.width,
|
||||
frame.height,
|
||||
4
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ Ptr<Texture> Texture::loadFromFile(const std::string& path) {
|
||||
return texture;
|
||||
}
|
||||
|
||||
Ptr<Texture> Texture::createFromMemory(uint8* data, int width, int height, int channels) {
|
||||
Ptr<Texture> Texture::createFromMemory(const uint8* data, int width, int height, int channels) {
|
||||
if (!data) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create texture from memory: null data");
|
||||
return nullptr;
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <system_error>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
@@ -268,10 +270,13 @@ std::vector<std::string> Asset::listAll(const std::string &directoryPath,
|
||||
|
||||
std::vector<std::string>
|
||||
Asset::listFilesWithExtension(const std::string &directoryPath,
|
||||
const std::string &extension, bool recursive) {
|
||||
const std::string &extension, bool recursive) {
|
||||
std::vector<std::string> files;
|
||||
fs::path fullPath = toPath(resolveFullPath(directoryPath));
|
||||
fs::path ext = toPath(extension);
|
||||
|
||||
std::string extLower = extension;
|
||||
std::transform(extLower.begin(), extLower.end(), extLower.begin(),
|
||||
[](unsigned char c) { return std::tolower(c); });
|
||||
|
||||
std::error_code ec;
|
||||
if (!fs::exists(fullPath, ec) || !fs::is_directory(fullPath, ec)) {
|
||||
@@ -279,15 +284,22 @@ Asset::listFilesWithExtension(const std::string &directoryPath,
|
||||
}
|
||||
|
||||
try {
|
||||
auto checkExtension = [&](const fs::path& filePath) {
|
||||
std::string fileExt = fromPath(filePath.extension());
|
||||
std::transform(fileExt.begin(), fileExt.end(), fileExt.begin(),
|
||||
[](unsigned char c) { return std::tolower(c); });
|
||||
return fileExt == extLower;
|
||||
};
|
||||
|
||||
if (recursive) {
|
||||
for (const auto &entry : fs::recursive_directory_iterator(fullPath, ec)) {
|
||||
if (entry.is_regular_file() && entry.path().extension() == ext) {
|
||||
if (entry.is_regular_file() && checkExtension(entry.path())) {
|
||||
files.push_back(fromPath(entry.path()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const auto &entry : fs::directory_iterator(fullPath, ec)) {
|
||||
if (entry.is_regular_file() && entry.path().extension() == ext) {
|
||||
if (entry.is_regular_file() && checkExtension(entry.path())) {
|
||||
files.push_back(fromPath(entry.path()));
|
||||
}
|
||||
}
|
||||
|
||||
394
Frostbite2D/src/frostbite2D/resource/npk_archive.cpp
Normal file
394
Frostbite2D/src/frostbite2D/resource/npk_archive.cpp
Normal file
@@ -0,0 +1,394 @@
|
||||
#include <frostbite2D/resource/npk_archive.h>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <SDL.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
const uint8 NpkArchive::NPK_KEY[256] = {
|
||||
112, 117, 99, 104, 105, 107, 111, 110, 64, 110, 101, 111, 112, 108, 101, 32,
|
||||
100, 117, 110, 103, 101, 111, 110, 32, 97, 110, 100, 32, 102, 105, 103, 104,
|
||||
116, 101, 114, 32, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78,
|
||||
70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70,
|
||||
68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68,
|
||||
78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78,
|
||||
70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70,
|
||||
68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68,
|
||||
78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78,
|
||||
70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70,
|
||||
68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 0
|
||||
};
|
||||
|
||||
NpkArchive& NpkArchive::get() {
|
||||
static NpkArchive instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void NpkArchive::setImagePackDirectory(const std::string& dir) {
|
||||
imagePackDirectory_ = dir;
|
||||
}
|
||||
|
||||
const std::string& NpkArchive::getImagePackDirectory() const {
|
||||
return imagePackDirectory_;
|
||||
}
|
||||
|
||||
void NpkArchive::init() {
|
||||
if (initialized_) {
|
||||
close();
|
||||
}
|
||||
|
||||
scanNpkFiles();
|
||||
initialized_ = true;
|
||||
}
|
||||
|
||||
void NpkArchive::close() {
|
||||
imgIndex_.clear();
|
||||
imageCache_.clear();
|
||||
lruList_.clear();
|
||||
currentCacheSize_ = 0;
|
||||
initialized_ = false;
|
||||
}
|
||||
|
||||
bool NpkArchive::isOpen() const {
|
||||
return initialized_;
|
||||
}
|
||||
|
||||
std::string NpkArchive::normalizePath(const std::string& path) const {
|
||||
std::string result = path;
|
||||
std::transform(result.begin(), result.end(), result.begin(),
|
||||
[](unsigned char c) { return std::tolower(c); });
|
||||
return result;
|
||||
}
|
||||
|
||||
void NpkArchive::scanNpkFiles() {
|
||||
Asset& asset = Asset::get();
|
||||
std::string npkDir = asset.resolvePath(imagePackDirectory_);
|
||||
if (!asset.isDirectory(npkDir)) {
|
||||
SDL_Log("NPK directory not found: %s", npkDir.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string> files = asset.listFilesWithExtension(npkDir, ".npk");
|
||||
SDL_Log("Scanning %d NPK files...", static_cast<int>(files.size()));
|
||||
for (const auto &file : files) {
|
||||
parseNpkFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
bool NpkArchive::parseNpkFile(const std::string& npkPath) {
|
||||
Asset& asset = Asset::get();
|
||||
BinaryReader reader(npkPath);
|
||||
|
||||
if (!reader.isOpen()) {
|
||||
SDL_Log("Failed to open NPK file: %s", npkPath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string npkFileName = asset.getFileName(npkPath);
|
||||
std::string header = reader.readNullTerminatedString();
|
||||
|
||||
if (header.find("NeoplePack_Bill") == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 imageCount = reader.readInt32();
|
||||
|
||||
for (int32 i = 0; i < imageCount; ++i) {
|
||||
int32 offset = reader.readInt32();
|
||||
int32 length = reader.readInt32();
|
||||
std::string imgPath = readNpkInfoString(reader);
|
||||
|
||||
ImgRef img;
|
||||
img.path = normalizePath(imgPath);
|
||||
img.npkFile = npkFileName;
|
||||
img.offset = static_cast<uint32>(offset);
|
||||
img.size = static_cast<uint32>(length);
|
||||
img.loaded = false;
|
||||
|
||||
imgIndex_[img.path] = img;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NpkArchive::hasImg(const std::string& path) const {
|
||||
return imgIndex_.find(normalizePath(path)) != imgIndex_.end();
|
||||
}
|
||||
|
||||
std::optional<ImgRef> NpkArchive::getImg(const std::string& path) {
|
||||
auto it = imgIndex_.find(normalizePath(path));
|
||||
if (it == imgIndex_.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
std::vector<std::string> NpkArchive::listImgs() const {
|
||||
std::vector<std::string> result;
|
||||
result.reserve(imgIndex_.size());
|
||||
for (const auto& pair : imgIndex_) {
|
||||
result.push_back(pair.first);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<ImageFrame> NpkArchive::getImageFrame(const ImgRef& img, size_t index) {
|
||||
std::string imgPath = normalizePath(img.path);
|
||||
|
||||
auto cacheIt = imageCache_.find(imgPath);
|
||||
if (cacheIt == imageCache_.end()) {
|
||||
auto it = imgIndex_.find(imgPath);
|
||||
if (it == imgIndex_.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (!loadImgData(it->second)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
cacheIt = imageCache_.find(imgPath);
|
||||
if (cacheIt == imageCache_.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
updateCacheUsage(imgPath);
|
||||
|
||||
if (index >= cacheIt->second.frames.size()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return cacheIt->second.frames[index];
|
||||
}
|
||||
|
||||
size_t NpkArchive::getFrameCount(const ImgRef& img) const {
|
||||
std::string imgPath = normalizePath(img.path);
|
||||
auto it = imgIndex_.find(imgPath);
|
||||
if (it == imgIndex_.end()) {
|
||||
return 0;
|
||||
}
|
||||
return it->second.frameCount;
|
||||
}
|
||||
|
||||
bool NpkArchive::loadImgData(ImgRef& img) {
|
||||
Asset& asset = Asset::get();
|
||||
std::string npkPath = asset.combinePath(imagePackDirectory_, img.npkFile);
|
||||
npkPath = asset.resolvePath(npkPath);
|
||||
|
||||
BinaryReader reader(npkPath);
|
||||
if (!reader.isOpen()) {
|
||||
SDL_Log("Failed to open NPK for IMG: %s", npkPath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
reader.seek(img.offset);
|
||||
std::string flag = reader.readNullTerminatedString();
|
||||
|
||||
if (flag.find("Neople Img File") == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int64 tableLength = reader.readInt64();
|
||||
reader.readInt32();
|
||||
int32 indexCount = reader.readInt32();
|
||||
|
||||
img.frameCount = static_cast<size_t>(indexCount);
|
||||
|
||||
CachedImageData cachedData;
|
||||
cachedData.frames.resize(indexCount);
|
||||
|
||||
size_t dataStartPos = img.offset + static_cast<uint32>(tableLength) + 32;
|
||||
|
||||
for (int32 i = 0; i < indexCount; ++i) {
|
||||
ImageFrame& frame = cachedData.frames[i];
|
||||
frame.type = reader.readInt32();
|
||||
|
||||
if (frame.type == 17) {
|
||||
int32 refIndex = reader.readInt32();
|
||||
frame.compressionType = refIndex;
|
||||
frame.width = cachedData.frames[refIndex].width;
|
||||
frame.height = cachedData.frames[refIndex].height;
|
||||
frame.xPos = cachedData.frames[refIndex].xPos;
|
||||
frame.yPos = cachedData.frames[refIndex].yPos;
|
||||
frame.frameXPos = cachedData.frames[refIndex].frameXPos;
|
||||
frame.frameYPos = cachedData.frames[refIndex].frameYPos;
|
||||
frame.offset = cachedData.frames[refIndex].offset;
|
||||
frame.size = cachedData.frames[refIndex].size;
|
||||
continue;
|
||||
}
|
||||
|
||||
frame.compressionType = reader.readInt32();
|
||||
frame.width = reader.readInt32();
|
||||
frame.height = reader.readInt32();
|
||||
int32 size = reader.readInt32();
|
||||
frame.xPos = reader.readInt32();
|
||||
frame.yPos = reader.readInt32();
|
||||
frame.frameXPos = reader.readInt32();
|
||||
frame.frameYPos = reader.readInt32();
|
||||
|
||||
frame.size = size;
|
||||
if (i == 0) {
|
||||
frame.offset = static_cast<uint32>(dataStartPos);
|
||||
} else {
|
||||
frame.offset = cachedData.frames[i - 1].offset + cachedData.frames[i - 1].size;
|
||||
}
|
||||
}
|
||||
|
||||
for (int32 i = 0; i < indexCount; ++i) {
|
||||
ImageFrame& frame = cachedData.frames[i];
|
||||
|
||||
if (frame.type == 17) {
|
||||
int32 refIndex = frame.compressionType;
|
||||
frame.data = cachedData.frames[refIndex].data;
|
||||
continue;
|
||||
}
|
||||
|
||||
reader.seek(frame.offset);
|
||||
std::vector<uint8> compressedData = reader.readBytes(static_cast<size_t>(frame.size));
|
||||
|
||||
int32 deSize = frame.width * frame.height * 4;
|
||||
std::vector<uint8> decompressedData(deSize);
|
||||
unsigned long realSize = static_cast<unsigned long>(deSize);
|
||||
|
||||
int uncompressResult = uncompress(
|
||||
decompressedData.data(),
|
||||
&realSize,
|
||||
compressedData.data(),
|
||||
static_cast<unsigned long>(frame.size)
|
||||
);
|
||||
|
||||
if (uncompressResult != Z_OK) {
|
||||
SDL_Log("Failed to uncompress image data: %d", uncompressResult);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (frame.type != 16) {
|
||||
int32 pngByteSize = deSize * 2;
|
||||
frame.data.resize(pngByteSize);
|
||||
|
||||
for (int32 e = 0; e < pngByteSize; e += 4) {
|
||||
uint8 needData[2] = {0, 0};
|
||||
if ((e / 4) * 2 + 1 < static_cast<int32>(decompressedData.size())) {
|
||||
needData[0] = decompressedData[(e / 4) * 2];
|
||||
needData[1] = decompressedData[(e / 4) * 2 + 1];
|
||||
}
|
||||
parseColor(needData, frame.type, frame.data.data(), e);
|
||||
}
|
||||
} else {
|
||||
frame.data = std::move(decompressedData);
|
||||
}
|
||||
|
||||
cachedData.memoryUsage += frame.data.size();
|
||||
}
|
||||
|
||||
imageCache_[img.path] = std::move(cachedData);
|
||||
currentCacheSize_ += imageCache_[img.path].memoryUsage;
|
||||
lruList_.push_front(img.path);
|
||||
|
||||
img.loaded = true;
|
||||
imgIndex_[img.path] = img;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void NpkArchive::parseColor(const uint8* tab, int type, uint8* saveByte, int offset) {
|
||||
uint8 a = 0, r = 0, g = 0, b = 0;
|
||||
|
||||
switch (type) {
|
||||
case 0x0e:
|
||||
a = static_cast<uint8>(tab[1] >> 7);
|
||||
r = static_cast<uint8>((tab[1] >> 2) & 0x1f);
|
||||
g = static_cast<uint8>((tab[0] >> 5) | ((tab[1] & 3) << 3));
|
||||
b = static_cast<uint8>(tab[0] & 0x1f);
|
||||
a = static_cast<uint8>(a * 0xff);
|
||||
r = static_cast<uint8>((r << 3) | (r >> 2));
|
||||
g = static_cast<uint8>((g << 3) | (g >> 2));
|
||||
b = static_cast<uint8>((b << 3) | (b >> 2));
|
||||
break;
|
||||
|
||||
case 0x0f:
|
||||
a = static_cast<uint8>(tab[1] & 0xf0);
|
||||
r = static_cast<uint8>((tab[1] & 0xf) << 4);
|
||||
g = static_cast<uint8>(tab[0] & 0xf0);
|
||||
b = static_cast<uint8>((tab[0] & 0xf) << 4);
|
||||
break;
|
||||
}
|
||||
|
||||
saveByte[offset + 0] = b;
|
||||
saveByte[offset + 1] = g;
|
||||
saveByte[offset + 2] = r;
|
||||
saveByte[offset + 3] = a;
|
||||
}
|
||||
|
||||
void NpkArchive::setCacheSize(size_t maxBytes) {
|
||||
maxCacheSize_ = maxBytes;
|
||||
evictCacheIfNeeded(0);
|
||||
}
|
||||
|
||||
void NpkArchive::clearCache() {
|
||||
imageCache_.clear();
|
||||
lruList_.clear();
|
||||
currentCacheSize_ = 0;
|
||||
}
|
||||
|
||||
size_t NpkArchive::getCacheUsage() const {
|
||||
return currentCacheSize_;
|
||||
}
|
||||
|
||||
void NpkArchive::evictCacheIfNeeded(size_t requiredSize) {
|
||||
while (currentCacheSize_ + requiredSize > maxCacheSize_ && !lruList_.empty()) {
|
||||
std::string oldest = lruList_.back();
|
||||
lruList_.pop_back();
|
||||
|
||||
auto it = imageCache_.find(oldest);
|
||||
if (it != imageCache_.end()) {
|
||||
currentCacheSize_ -= it->second.memoryUsage;
|
||||
imageCache_.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NpkArchive::updateCacheUsage(const std::string& imgPath) {
|
||||
auto it = std::find(lruList_.begin(), lruList_.end(), imgPath);
|
||||
if (it != lruList_.end()) {
|
||||
lruList_.erase(it);
|
||||
}
|
||||
lruList_.push_front(imgPath);
|
||||
|
||||
auto cacheIt = imageCache_.find(imgPath);
|
||||
if (cacheIt != imageCache_.end()) {
|
||||
cacheIt->second.lastUseTime = SDL_GetTicks();
|
||||
}
|
||||
}
|
||||
|
||||
std::string NpkArchive::readNpkInfoString(BinaryReader& reader) {
|
||||
if (reader.eof()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::vector<uint8> encrypted = reader.readBytes(256);
|
||||
if (encrypted.size() < 256) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::vector<char> decrypted(256);
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
decrypted[i] = static_cast<char>(encrypted[i] ^ NPK_KEY[i]);
|
||||
}
|
||||
|
||||
return std::string(decrypted.data());
|
||||
}
|
||||
|
||||
void NpkArchive::setDefaultImg(const std::string& imgPath, size_t frameIndex) {
|
||||
defaultImgPath_ = normalizePath(imgPath);
|
||||
defaultImgFrame_ = frameIndex;
|
||||
}
|
||||
|
||||
const std::string& NpkArchive::getDefaultImgPath() const {
|
||||
return defaultImgPath_;
|
||||
}
|
||||
|
||||
size_t NpkArchive::getDefaultImgFrame() const {
|
||||
return defaultImgFrame_;
|
||||
}
|
||||
|
||||
}
|
||||
BIN
Game/assets/ImagePacks2/!HUD_CW.NPK
Normal file
BIN
Game/assets/ImagePacks2/!HUD_CW.NPK
Normal file
Binary file not shown.
@@ -14,8 +14,9 @@
|
||||
#include <frostbite2D/resource/script_parser.h>
|
||||
|
||||
#include <frostbite2D/audio/audio_system.h>
|
||||
#include <frostbite2D/audio/sound.h>
|
||||
#include <frostbite2D/audio/music.h>
|
||||
#include <frostbite2D/audio/sound.h>
|
||||
#include <frostbite2D/resource/npk_archive.h>
|
||||
|
||||
using namespace frostbite2D;
|
||||
|
||||
@@ -42,59 +43,59 @@ int main(int argc, char **argv) {
|
||||
// 尝试加载精灵
|
||||
auto sprite = Sprite::createFromFile("assets/player.png");
|
||||
if (sprite) {
|
||||
sprite->SetPosition(100, 100);
|
||||
sprite->SetPosition(0, 0);
|
||||
menuScene->AddActor(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();
|
||||
if (archive.open("assets/Script.pvf")) {
|
||||
archive.init();
|
||||
// 文件内容读取
|
||||
if (auto rawData = archive.getFileRawData("region/balmayer_north.rgn")) {
|
||||
ScriptParser parser(*rawData, "script/example.bin");
|
||||
// // 方式1:迭代解析
|
||||
// while (!parser.isEnd()) {
|
||||
// if (auto value = parser.next()) {
|
||||
// switch (value->type) {
|
||||
// case ScriptValueType::Integer:
|
||||
// SDL_Log("Integer: %d", value->intValue);
|
||||
// break;
|
||||
// case ScriptValueType::Float:
|
||||
// SDL_Log("Float: %f", value->floatValue);
|
||||
// break;
|
||||
// case ScriptValueType::String:
|
||||
// SDL_Log("String: %s", value->stringValue.c_str());
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// auto &archive = PvfArchive::get();
|
||||
// if (archive.open("assets/Script.pvf")) {
|
||||
// archive.init();
|
||||
// // 文件内容读取
|
||||
// if (auto rawData = archive.getFileRawData("region/balmayer_north.rgn")) {
|
||||
// ScriptParser parser(*rawData, "script/example.bin");
|
||||
// // // 方式1:迭代解析
|
||||
// // while (!parser.isEnd()) {
|
||||
// // if (auto value = parser.next()) {
|
||||
// // switch (value->type) {
|
||||
// // case ScriptValueType::Integer:
|
||||
// // SDL_Log("Integer: %d", value->intValue);
|
||||
// // break;
|
||||
// // case ScriptValueType::Float:
|
||||
// // SDL_Log("Float: %f", value->floatValue);
|
||||
// // break;
|
||||
// // case ScriptValueType::String:
|
||||
// // SDL_Log("String: %s", value->stringValue.c_str());
|
||||
// // break;
|
||||
// // default:
|
||||
// // break;
|
||||
// // }
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// // 方式2:批量解析
|
||||
// parser.reset();
|
||||
auto allValues = parser.parseAll();
|
||||
for (const auto &value : allValues) {
|
||||
// 处理 value
|
||||
switch (value.type) {
|
||||
case ScriptValueType::Integer:
|
||||
SDL_Log("Integer: %d", value.intValue);
|
||||
break;
|
||||
case ScriptValueType::Float:
|
||||
SDL_Log("Float: %f", value.floatValue);
|
||||
break;
|
||||
case ScriptValueType::String:
|
||||
SDL_Log("String: %s", value.stringValue.c_str());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// // // 方式2:批量解析
|
||||
// // parser.reset();
|
||||
// auto allValues = parser.parseAll();
|
||||
// for (const auto &value : allValues) {
|
||||
// // 处理 value
|
||||
// switch (value.type) {
|
||||
// case ScriptValueType::Integer:
|
||||
// SDL_Log("Integer: %d", value.intValue);
|
||||
// break;
|
||||
// case ScriptValueType::Float:
|
||||
// SDL_Log("Float: %f", value.floatValue);
|
||||
// break;
|
||||
// case ScriptValueType::String:
|
||||
// SDL_Log("String: %s", value.stringValue.c_str());
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
AudioSystem::get().init();
|
||||
AudioSystem::get().setMasterVolume(1.0f);
|
||||
@@ -108,6 +109,19 @@ int main(int argc, char **argv) {
|
||||
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(0, 0);
|
||||
menuScene->AddActor(sprite1);
|
||||
} else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create sprite from NPK!");
|
||||
}
|
||||
|
||||
app.run();
|
||||
|
||||
app.shutdown();
|
||||
|
||||
@@ -3,6 +3,7 @@ add_requires("libsdl2", {configs = {shared = true,wayland = true}})
|
||||
add_requires("libsdl2_image")
|
||||
add_requires("libsdl2_mixer")
|
||||
add_requires("glm")
|
||||
add_requires("zlib")
|
||||
|
||||
target("Frostbite2D")
|
||||
set_kind("binary")
|
||||
@@ -17,6 +18,7 @@ target("Frostbite2D")
|
||||
add_packages("libsdl2_image")
|
||||
add_packages("libsdl2_mixer")
|
||||
add_packages("glm")
|
||||
add_packages("zlib")
|
||||
|
||||
-- 复制着色器文件到输出目录
|
||||
after_build(function (target)
|
||||
|
||||
@@ -5,6 +5,7 @@ add_requires("libsdl2", {configs = {shared = true}})
|
||||
add_requires("libsdl2_image", {configs = {shared = true}})
|
||||
add_requires("libsdl2_mixer", {configs = {shared = true}})
|
||||
add_requires("glm")
|
||||
add_requires("zlib")
|
||||
|
||||
target("Frostbite2D")
|
||||
set_kind("binary")
|
||||
@@ -19,6 +20,7 @@ target("Frostbite2D")
|
||||
add_packages("libsdl2_image")
|
||||
add_packages("libsdl2_mixer")
|
||||
add_packages("glm")
|
||||
add_packages("zlib")
|
||||
|
||||
-- 复制 assets 目录到输出目录
|
||||
after_build(function (target)
|
||||
|
||||
Reference in New Issue
Block a user