SDL農場遊戲開發 3.StaticData類
前面說過,StaticData類負責管理程式在執行過程中不會發生變化的資料,如下為Resources目錄結構
data資料夾儲存著一些靜態資料,比如crops.csv檔案儲存著作物資訊,soils.csv檔案儲存著可擴充套件土壤所需要的等級和金錢。
1.外部檔案
首先看一下crops.csv
前兩行為描述欄位,而從第三行起,每一行儲存了一個植物的種子和作物的相關資訊,比如名稱、描述等。稍微看植物的生長期,即growns這個欄位,它是以空格為分隔符的字串,植物的不同生長期對應了不同階段的貼圖(以小時為單位)。
吸引欄位作為擴充套件欄位,可以認為不同的植物可能會吸引不同的害蟲。
接著是soils.csv檔案
在前一節時提過,初始的6塊土地的id為{12,13,14,15,16,17}(當然,也可以改為0, 1, 2, 3, 4, 5,這樣的話初始土地則會在右上角而不是左下角),因此擴充套件土地是以id為11開始,依次遞減,直到0為止。
如果初始的土壤的id為{12,13,14,15,16,17}的話,在程式中需要對soils.csv的id欄位的值進行轉換:(12 - id) = {11, 10, ..., 0}
最後則是static_data.plist檔案,該檔案儲存的是一些靜態資料,比如作物的開始圖片(種子圖片)、作物的枯萎圖片等,以及一些執行期間不會改變的字串,如“已枯萎”。
2.StaticData類
StaticData類是單例類。下面是標頭檔案:
StaticData.h
#ifndef __StaticData_H__ #define __StaticData_H__ #include <map> #include <string> #include <sstream> #include "SDL_Engine/SDL_Engine.h" USING_NS_SDL; using namespace std; class Crop; //定義一些常用的巨集 #define STATIC_DATA_PATH "data/static_data.plist" /*簡化使用*/ #define STATIC_DATA_STRING(key) (StaticData::getInstance()->getValueForKey(key)->asString()) #define STATIC_DATA_INT(key) (StaticData::getInstance()->getValueForKey(key)->asInt()) #define STATIC_DATA_FLOAT(key) (StaticData::getInstance()->getValueForKey(key)->asFloat()) #define STATIC_DATA_BOOLEAN(key) (StaticData::getInstance()->getValueForKey(key)->asBool()) #define STATIC_DATA_POINT(key) (StaticData::getInstance()->getPointForKey(key)) #define STATIC_DATA_SIZE(key) (StaticData::getInstance()->getSizeForKey(key)) #define STATIC_DATA_ARRAY(key) (StaticData::getInstance()->getValueForKey(key)->asValueVector())
新增一些巨集來簡化StaticData的函式呼叫。
//作物結構體
struct CropStruct
{
string name;//作物名稱
string desc;//作物描述
vector<int> growns;//作物生長期
int harvestCount;//收貨次數
int seedValue;//種子價格
int fruitValue;//果實價格
int number;//果實理論個數
int numberVar;//果實個數浮動值
string absorb;//吸引 擴充套件介面
int level;//需求等級
int exp;//得到經驗值
};
//土地需求等級和金錢
//當前id 表示擴充套件的第幾塊土地 (12-extensible_soil)
struct ExtensibleSoilStruct
{
int value;//價值
int lv;//等級
};
以上的兩個結構體,CropStruct用來儲存crops.csv中的資料,而ExtensibleSoilStruct則是儲存著可擴充套件土壤所需要的等級和金幣。
class StaticData
{
public:
static StaticData* getInstance()
{
if (s_pInstance == nullptr)
{
s_pInstance = new StaticData();
s_pInstance->init();
}
return s_pInstance;
}
static void purge()
{
SDL_SAFE_DELETE(s_pInstance);
}
private:
static StaticData* s_pInstance;
private:
//鍵值對
ValueMap m_valueMap;
//儲存作物配置檔案中的資料
map<int, CropStruct> m_cropMap;
//儲存可擴充套件土地需要的等級和金錢
map<int, ExtensibleSoilStruct> m_extensibleSoilMap;
StaticData為單例類,且內部使用map來儲存以上的兩個結構體,而m_valueMap則是讀取static_data.plist檔案讀取得到的ValueMap。
private:
StaticData();
~StaticData();
bool init();
bool loadCropConfigFile();
bool loadSoilConfigFile();
//載入csv檔案
bool loadCsvFile(const string& filename
, const function<void (int, const Value&)>&, int skips = 0);
public:
/**
* 根據鍵獲取值
* @param key 要查詢的鍵
* @return 返回值,如果不存在對應的值,則返回nullptr
*/
Value* getValueForKey(const string& key);
/**
* 獲取鍵所對應的值,並轉化為Point
* @param key 要查詢的鍵
* @return 返回值,不存在返回Point::ZERO
*/
Point getPointForKey(const string& key);
/**
* 獲取鍵對應的值,並轉化為Size
* @param key 要查詢的鍵
* @return 返回值,不存在則返回Size::ZERO
*/
Size getSizeForKey(const string& key);
上面幾個get*方法是經典的StaticData的方法,不同的遊戲上述的幾個get函式的實現一般不會改變。
接著是對應程式碼的實現了。
bool StaticData::init()
{
//讀取檔案並儲存鍵值對
m_valueMap = FileUtils::getInstance()->getValueMapFromFile(STATIC_DATA_PATH);
//讀取配置檔案
this->loadCropConfigFile();
this->loadSoilConfigFile();
return true;
}
plist檔案是蘋果規定的一種XML格式的檔案,並且有提供解析該格式檔案的API,而其他系統則需要自己在程式碼中進行解析,當然,這些都是封裝在引擎內部(cocos2dx)的,因此並不需要自己操心檔案的讀取以及如何轉化成ValueMap或ValueVector。
bool StaticData::loadCsvFile(const string& filename
, const function<void (int, const Value&)>& callback, int skips)
{
//載入資料
istringstream in(FileUtils::getInstance()->getDataFromFile(filename));
string line;
while (getline(in, line))
{
if (skips != 0)
{
skips--;
continue;
}
//解析資料
StringUtils::split(line, ",", callback);
}
return true;
}
這個函式負責解析csv格式的檔案,並把獲得到的每一行作為形參來呼叫StringUtils::split()函式,這個函式是SDL_Engine特有的,cocos2dx應該沒有,其功能是按照分隔符;來分割字串,得到Value後呼叫對應的回撥函式,其實現如下:
void split(const std::string& src, const std::string& token
,const std::function<void (int,const Value&)>& callback)
{
size_t nend = 0;
size_t nbegin = 0;
size_t tokenSize = token.size();
size_t index = 0;
while(nend != std::string::npos)
{
nend = src.find(token,nbegin);
if(nend == std::string::npos)
{
//避免最後為空
auto str = src.substr(nbegin, src.length()-nbegin);
if (!str.empty())
callback(index,Value(str));
}
else
{
callback(index,Value(src.substr(nbegin, nend-nbegin)));
}
nbegin = nend + tokenSize;
index++;
}
}
此函式是我在cocos2dx提供的split函式的基礎上稍微修改的。
bool StaticData::loadCropConfigFile()
{
int id = 0;
CropStruct cropSt;
auto callback = [&id, &cropSt, this](int col, const Value& value)
{
switch (col)
{
case 0: id = value.asInt(); break;
case 1: cropSt.name = value.asString(); break;
case 2: cropSt.desc = value.asString(); break;
case 3:
{
string text = value.asString();
string sub;
size_t begin = 1, end = 1;
while (end != string::npos)
{
end = text.find(' ', begin);
if (end == string::npos)
sub = text.substr(begin, text.size() - begin - 1);
else
{
sub = text.substr(begin, end - begin);
begin = end + 1;
}
cropSt.growns.push_back(SDL_atoi(sub.c_str()));
}
}
break;
case 4: cropSt.harvestCount = value.asInt(); break;
case 5: cropSt.seedValue = value.asInt(); break;
case 6: cropSt.fruitValue = value.asInt(); break;
case 7: cropSt.number = value.asInt(); break;
case 8: cropSt.numberVar = value.asInt(); break;
case 9: cropSt.absorb = value.asString(); break;
case 10: cropSt.level = value.asInt(); break;
case 11:
{
cropSt.exp = value.asInt();
//存入資料
m_cropMap.insert(make_pair(id, cropSt));
break;
}
}
};
return this->loadCsvFile("data/crops.csv", callback, 2);
}
呼叫此函式來讀取crops.csv並對m_cropMap進行資料填充。
bool StaticData::loadSoilConfigFile()
{
int id = 0;
ExtensibleSoilStruct soilSt;
auto callback = [&id, &soilSt, this](int col, const Value& value)
{
switch (col)
{
case 0: id = value.asInt(); break;
case 1: soilSt.value = value.asInt(); break;
case 2:
soilSt.lv = value.asInt();
m_extensibleSoilMap.insert(make_pair(id, soilSt));
break;
}
};
return this->loadCsvFile("data/soils.csv", callback, 2);
}
該函式作用大致同上。
Value* StaticData::getValueForKey(const string& key)
{
auto iter = m_valueMap.find(key);
if(iter != m_valueMap.end())
return &iter->second;
return nullptr;
}
Point StaticData::getPointForKey(const string& key)
{
Point point;
auto value = this->getValueForKey(key);
if (value != nullptr)
{
point = PointFromString(value->asString());
}
return point;
}
Size StaticData::getSizeForKey(const string& key)
{
Size size;
auto value = this->getValueForKey(key);
if (value != nullptr)
{
size = SizeFromString(value->asString());
}
return size;
}
這幾個函式的作用就是獲取鍵所對應的值,做一些處理並返回,因為有的鍵可能並不存在,故getValueOfKey返回的是一個指標,其實使用指標並不算太好,引用也有著類似的問題,而使用值傳遞則會存在資料複製的開銷。
CropStruct* StaticData::getCropStructByID(int id)
{
auto it = m_cropMap.find(id);
CropStruct* cropSt = nullptr;
if (it != m_cropMap.end())
{
cropSt = &it->second;
}
return cropSt;
}
ExtensibleSoilStruct* StaticData::getExtensibleSoilStructByID(int id)
{
auto it = m_extensibleSoilMap.find(id);
if (it == m_extensibleSoilMap.end())
{
LOG("not found the soil of id:%d", id);
return nullptr;
}
return &it->second;
}
上面兩個函式比較簡單粗暴,就是根據鍵獲取到對應的結構體並返回。
本節實現了StaticData類中配置檔案的讀取,接下來將會對這些資料進行處理。
本節程式碼: