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);
}