SDL2 游戏开发日记(五) 资源打包
游戏中的各种各样的资源,如果不想直接的给别人看到,那就需要对资源进行打包。
打包的时候可以加入加密解密算法对数据进行加密,防止别人直接拿来使用。
打包可以使文件夹看起来更简洁。
SDL提供了SDL_RWops来让我们直接从内存或者二进制流中加载各种游戏素材。
资源打包解包类
#pragma once
#include <string>
#include <map>
#include <iostream>
#include <fstream>
#include <SDL.h>
using namespace std;
//文件信息
struct FileInfo{
//文件在资源包中的位置
int offset;
//文件大小
int fileSize;
//文件名字长度
int nameSize;
//文件名
char*fileName;
//文件输入流,打包时用到
fstream fin;
~FileInfo(){
if(fileName != NULL)
delete[]fileName;
}
};
typedef map<string,FileInfo*> FileList;
//
class PackageRes{
private:
//文件列表,存储要打包的文件或者解包的文件
FileList mFileList;
//文件输入流,解包和读取资源时用
fstream mPackIn;
//文件索引长度
int mIndexLen;
public:
PackageRes(){
}
~PackageRes(){
FileList::iterator iter = mFileList.begin();
while (iter != mFileList.end()){
delete (iter->second);
iter++;
}
mFileList.clear();
if (mPackIn.is_open()){
mPackIn.close();
}
}
//添加文件到列表
void AddFile(string fileName){
int size = fileName.size()+1;
if (size > 0){
char *name = new char[size];
memset(name, 0, size);
strcpy_s(name,size, fileName.c_str());
FileInfo *info = new FileInfo();
info->fileName = name;
info->nameSize = size;
AddFile(fileName, info);
}
}
void AddFile(string fileName, FileInfo *info){
FileList::iterator iter = mFileList.find(fileName);
if (iter != mFileList.end()){
delete iter->second;
iter->second = info;
}
else{
mFileList.insert(make_pair(fileName, info));
}
}
//从列表中移除文件
void RemoveFile(string fileName){
FileList::iterator iter = mFileList.find(fileName);
if (iter != mFileList.end()){
delete (iter->second);
mFileList.erase(iter);
}
}
//打包
void Package(string fileName);
//解包和获取资源
void Unpack(string fileName);
SDL_RWops* GetResource(string fileName);
};
//PackageRes.cpp
#include "stdafx.h"
#include "PackageRes.h"
#include "SDLGame.h"
//SDL_RWops释放时释放new申请的内存
int RWFreeBuffer(SDL_RWops *context){
if (context != NULL){
//printf("free sdl rwops.\n");
printf_s("free: %p\n", context->hidden.unknown.data1);
delete[] context->hidden.unknown.data1;
SDL_FreeRW(context);
}
return 0;
}
//打包
void PackageRes::Package(string fileName){
int fileCount = mFileList.size();
if (fileCount <= 0)
return;
FileList::iterator iter = mFileList.begin();
//文件偏移量
int offset = 0;
//索引偏移量/
int indexLen = 0;
while (iter != mFileList.end()){
fstream fin;
FileInfo *info = iter->second;
fin.open(theGame.GetResourcePath() + info->fileName, ios::binary | ios::in);
fin.seekg(0, ios::end);
info->fileSize = (int)fin.tellg();
info->offset = offset;
offset += info->fileSize;
indexLen += sizeof(int)*3 + info->nameSize;
//fstream没有重载=,所以把fin转为右值
info->fin = std::move(fin);
iter++;
}
fstream fout;
fout.open(theGame.GetResourcePath() + fileName, ios::out | ios::binary);
//如果有需要,可以在这里接入加密算法。把数据加密了再写入文件
fout.write((char*)&fileCount, sizeof(int));
fout.write((char*)&indexLen, sizeof(int));
//写索引
iter = mFileList.begin();
while (iter != mFileList.end()){
FileInfo *info = iter->second;
fout.write((char*)&(info->offset), sizeof(int));
fout.write((char*)&(info->fileSize), sizeof(int));
fout.write((char*)&(info->nameSize), sizeof(int));
fout.write(info->fileName, info->nameSize);
iter++;
}
//写入文件内容
iter = mFileList.begin();
char buff[512];
while (iter != mFileList.end()){
FileInfo *info = iter->second;
info->fin.seekg(0, ios::beg);
while (!info->fin.eof()){
memset(buff, 0, sizeof(buff));
info->fin.read(buff, sizeof(buff));
int len = (int)info->fin.gcount();
if (len > 0){
fout.write(buff, len);
}
}
info->fin.close();
iter++;
}
fout.close();
}
//解包
void PackageRes::Unpack(string fileName){
if (mPackIn.is_open())
mPackIn.close();
mPackIn.open(theGame.GetResourcePath() + fileName, ios::in | ios::binary);
mPackIn.seekg(0, ios::beg);
int fileCount = 0;
int indexLen = 0;
//读取文件数量
mPackIn.read((char*)&fileCount, sizeof(int));
mPackIn.read((char*)&indexLen, sizeof(int));
mIndexLen = indexLen + 8;
//读取索引中的文件信息
for (int i = 0; i < fileCount; i++){
FileInfo *info = new FileInfo();
mPackIn.read((char*)&info->offset, sizeof(int));
mPackIn.read((char*)&info->fileSize, sizeof(int));
mPackIn.read((char*)&info->nameSize, sizeof(int));
if (info->nameSize <= 0 || info->fileSize <= 0 || info->offset < 0){
printf_s("unpack error. nameSize <= 0\n");
return;
}
char *fn = new char[info->nameSize];
memset(fn, 0, info->nameSize);
mPackIn.read(fn, info->nameSize);
info->fileName = fn;
//把文件信息添加到文件列表
AddFile(string(fn), info);
}
}
//读取资源
SDL_RWops* PackageRes::GetResource(string fileName){
SDL_RWops * rwOps = NULL;
FileList::iterator iter = mFileList.find(fileName);
if (iter != mFileList.end()){
FileInfo *info = iter->second;
char *buffer = new char[info->fileSize];
//输出内存所在的地址,检查内存释放。
printf_s("alloc: %p\n", buffer);
//从指定位置读取指定长度的数据
mPackIn.seekg(info->offset + mIndexLen, ios::beg);
mPackIn.read(buffer, info->fileSize);
int realRead = (int)mPackIn.gcount();
if (realRead != info->fileSize){
printf("%s,read error.%d/%d\n",fileName.c_str(),realRead,info->fileSize);
delete[] buffer;
return rwOps;
}
rwOps = SDL_RWFromMem(buffer, info->fileSize);
//rwOps释放时调用RWFreeBuffer释放new申请的buffer
rwOps->close = RWFreeBuffer;
}
return rwOps;
}
更新相关的类,从SDL_RWops中加载资源
图片加载:IMG_Load_RW
字体加载:TTF_OpenFontRW
音乐加载:Mix_LoadMUS_RW,Mix_LoadWAV_RW
更新GameSounds类,从SDL_RWops中读取声音
#define theSounds GameSound::Instance()
class GameSound{
//省略之前........
//加载音乐
//Mix_Music* LoadMusic(string fileName);
////加载音效
//Mix_Chunk* LoadSound(string fileName);
//新函数
Mix_Music* LoadMusic(string fileName, SDL_RWops *ops = NULL);
Mix_Chunk* LoadSound(string fileName, SDL_RWops *ops = NULL);
//省略之后.......
};
//cpp
Mix_Music* GameSound::LoadMusic(string fileName,SDL_RWops *ops){
Mix_Music *music = NULL;
MusicsMap::iterator iter = mMusics.find(fileName);
if (iter != mMusics.end()){
music = iter->second;
return music;
}
else{
if (ops != NULL){
//从SDL_RWops中加载,第二个参数表示SDL_RWops在不适用时自动释放
music = Mix_LoadMUS_RW(ops, 1);
}
else{
music = Mix_LoadMUS((theGame.GetResourcePath() + fileName).c_str());
}
if (music != NULL){
mMusics.insert(std::make_pair(fileName, music));
}
else{
return NULL;
}
}
return music;
}
//
Mix_Chunk* GameSound::LoadSound(string fileName,SDL_RWops *ops){
Mix_Chunk *chunk = NULL;
SoundsMap::iterator iter = mSounds.find(fileName);
if (iter != mSounds.end()){
chunk = iter->second;
return chunk;
}
if (ops != NULL){
//从SDL_RWops中加载,第二个参数表示SDL_RWops在不适用时自动释放
chunk = Mix_LoadWAV_RW(ops, 1);
}
else
{
chunk = Mix_LoadWAV((theGame.GetResourcePath() + fileName).c_str());
}
if (chunk != NULL){
mSounds.insert(std::make_pair(fileName, chunk));
}
else{
return NULL;
}
return chunk;
}
测试
资源打包
PackageRes package;
package.AddFile("background.wav");
package.AddFile("background.mp3");
package.AddFile("simkai.ttf");
package.AddFile("delete.wav");
package.AddFile("tetris.png");
package.AddFile("loading.png");
package.Package("resource.pak");
资源解包和加载
PackageRes package;
package.Unpack("resource.pak");
SDL_RWops *music = package.GetResource("background.mp3");
SDL_RWops *del = package.GetResource("delete.wav");
if (music != NULL){
printf("load music success.\n");
theSounds.LoadMusic("background.mp3",music);
theSounds.PlayMusic("background.mp3", -1);
}
if (del != NULL){
printf("load sound success.\n");
theSounds.LoadSound("delete.wav", del);
theSounds.PlaySoundEffect("delete.wav",0);
}