--> Каждый программист, который хочет написать более-менее приличную игру, рано или поздно сталкивается с вопросом загрузки различных данных, к примеру игровых уровней.
Естественно, данный вопрос коснулся и меня. С недавнего времени я решил оставить AndEngine и перешел на Cocos2d-x, т.к. купил себе MAC BOOK PRO и скачал XCode. И глупо было-бы ограничиться написанием игр только под android. Тем более Cocos2d-x позволяет писать мультиплатформенные приложения для iOS, Android, Win32, MacOS не вдаваясь в подробности реализации API OpenGL. Cocos2d-x это порт знаменитого движка Cocos2d. 2D-X написан на С++, соответственно и игры с применением этого движка пишутся на C++. Так как C++ был моим любимым языком со студенческих лет, то никаких сомнений не осталось! Этот движок - то что нужно для меня!
Итак, в этой статье я хочу рассказать, как использовать класс CCSAXParser. Этот класс написан нашим программистом Максимом Аксеновым и был включен в состав движка.
SAX парсинг - это последовательное чтение XML с вызовами CALLBACK ф-ций. Таким образом он является событийным парсером. События возникают каждый раз при чтении очередного элемента XML файла. Когда элемент поступает на вход парсера, он вызывает ф-цию обратного вызова OnStartElement. В своем обработчике данного события можно выполнить необходимые проверки и обычно получить массив атрибутов элемента. Когда на вход парсера поступает закрывающий тэг соотв. элемента, вызывается событие OnEndElement. В общем, об SAX парсере написано много статей в интернете и, при желании, можно получить более подробную информацию об этом способе разбора XML. После долгих поисков примеров применения SAX парсера в интернете, именно для Cocos2d-x, я ничего так и не нашел. Мне повезло писать SAX парсинг ранее на JAVA, когда я писал игру Truck adventure для android, таким образом, я уже имел опыт написания своего обработчика для SAX парсера.
Итак начнем.
Вот как объявлены классы для парсинга в кокосе:
class CC_DLL CCSAXDelegator
{
public:
virtual void startElement(void *ctx, const char *name, const char **atts) = 0;
virtual void endElement(void *ctx, const char *name) = 0;
virtual void textHandler(void *ctx, const char *s, int len) = 0;
};
class CC_DLL CCSAXParser
{
CCSAXDelegator* m_pDelegator;
public:
CCSAXParser();
~CCSAXParser(void);
bool init(const char *pszEncoding);
bool parse(const char* pXMLData, unsigned int uDataLength);
bool parse(const char *pszFile);
void setDelegator(CCSAXDelegator* pDelegator);
static void startElement(void *ctx, const CC_XML_CHAR *name, const CC_XML_CHAR **atts);
static void endElement(void *ctx, const CC_XML_CHAR *name);
static void textHandler(void *ctx, const CC_XML_CHAR *name, int len);
};
Из этого объявления понятно, что наш класс, в котором будет происходить парсинг, должен быть унаследован от CCSAXDelegator. А переопределенные нами методы startElement, endElement и textHandler и есть необходимые обработчики событий. Т.е. когда парсер во входных данных встретит начало элемента, соответственно будет вызвана ф-ция startElement(void *ctx, const CC_XML_CHAR *name, const CC_XML_CHAR **atts),
где name - это имя пришедшего XML элемента, а в массиве atts - содержатся все атрибуты элемента, причем, сначала идет название атрибута, а потом его значение (key, value).
Соответственно, при закрывающем теге элемента будет вызвана ф-ция endElement.
Теперь приведу примеры XML и своего кода парсинга.
Мой XML файл имеет следующий формат:
<?xml version="1.0" encoding="utf-8"?> <landscape>
<level number="1-1" length="10000">
<back backb="back_b" backf="back_f" />
<danger name="danger_brevno1" x="5522" y="356" angle="0"
type="revolute" x_axis="-1" y_axis="-1" speed="1" backgroup="true"
torque="30000" motor="true" center_r="true" w="241" h="241"/>
<element name="danger_brevno5" x="9875" y="383" angle="0" w="25" h="225"/>
<element name="danger_brevno4" x="3472" y="273" angle="0" w="269" h="25"/>
<element name="tree1" x="7717" y="357" angle="0" w="100" h="122"/>
...
</level>
... </landscape>
Парсинг этого файла происходит в классе LandLoader, который осуществляет загрузку необходимого уровня игры. Приведу пример объявления своего класса и его реализации:
class LandLoader: public CCSAXDelegator
{
public:
LandLoader(CCLayer* _gameLayer,
CCLayer* _backFrontLayer,
CCLayer* _backBackLayer,
b2World* _world,
DangerObjectManager* _dom);
~LandLoader();
void loadLevel(string namelevel);
private:
const char* TAG_LEVEL ="level";
const char* TAG_NUMBER ="number";
const char* TAG_LENGTH ="length";
const char* TAG_NAME ="name";
const char* TAG_X ="x";
const char* TAG_Y ="y";
const char* TAG_W ="w";
const char* TAG_H ="h";
const char* TAG_ELEMENT ="element";
const char* TAG_VULKAN ="vulkan";
const char* TAG_SLOWCPU ="slowcpu";
const char* TAG_FAKE ="fake";
const char* TAG_ANGLE ="angle";
const char* TAG_BACK ="back";
const char* TAG_BACKB ="backb";
const char* TAG_BACKF ="backf";
// danger
const char* TAG_DANGER="danger";
const char* TAG_DANGERD="danger_dyn";
const char* TAG_DANGERDB="danger_dyn_box";
const char* TAG_DANGERDC="danger_dyn_circle";
const char* TAG_TYPE = "type";
const char* TAG_CENTERR ="center_r";
const char* TAG_MOTOR="motor";
const char* TAG_TORQUE="torque";
const char* TAG_SPEED="speed";
const char* TAG_X_ANCHOR="x_anchor";
const char* TAG_Y_ANCHOR="y_anchor";
const char* TAG_X_AXIS="x_axis";
const char* TAG_Y_AXIS="y_axis";
const char* TAG_X_LEFT="x_left";
const char* TAG_Y_LEFT="y_left";
const char* TAG_X_RIGHT="x_right";
const char* TAG_Y_RIGHT="y_right";
const char* TAG_BACKGROUP="backgroup";
// bonus
const char* TAG_BONUS ="bonus";
const char* TAG_BONUSTYPE ="type";
// finish
const char* TAG_FINISH ="finish";
// check point
const char* TAG_CHECKPOINT ="checkpoint";
// car part
const char* TAG_CARPART="carpart";
const char* TAG_TRUCK="truck";
CCLayer* gameLayer;
CCLayer* backBackLayer;
CCLayer* backFrontLayer;
b2World* world;
DangerObjectManager* dangerManager;
CCSAXParser* parser;
bool bInLevel=false;
void initBorder();
//**************
void createElement(CCLayer* landLayer,string name, float pX, float pY);
void createDangerDynamic(CCLayer* landLayer,string name,float pX, float pY);
void createBackBBackF(CCLayer* backBackLayer, CCLayer* backForeLayer);
// parsing xml
void startElement(void *ctx, const char *name, const char **atts);
void endElement(void *ctx, const char *name);
void textHandler(void *ctx, const char *s, int len);
// get atts Key,Value
const char* getValue(const char* szKey,const char** atts);
};
Здесь видно, что он унаследован от CCSAXDelegator, соответственно, мы должны имплементировать методы о которых писалось ранее. Вот код реализации SAX парсинга (здесь приведу только необходимые для понимания парсинга методы класса). В добавление к сказанному, для понимания процесса: игровые уровни в моей игре строятся в процессе парсинга, когда парсер разбирает очередной элемент, в игре создаются соответствующие объекты и физические тела.
В конструкторе класса создаем парсер:
LandLoader::LandLoader(CCLayer* _gamelayer,CCLayer* _backFrontLayer,
CCLayer* _backBackLayer,b2World* _world,DangerObjectManager* _dom)
{
...
parser = new CCSAXParser();
parser->setDelegator(this);
};
В этом методе получаем полный путь к файлу уровней и начинаем процесс парсинга.
void LandLoader::loadLevel(string namelevel)
{
const char* fname = CCFileUtils::sharedFileUtils()->fullPathFromRelativePath("landlite.xml");
bInLevel=false;
parser->parse(fname);
}
В процессе парсинга, с помощью данного метода, получаем значение нужного атрибута из массива атрибутов. Написан он криво и может быть зациклен. Я привожу здесь пример без оптимизации данного метода. Перепишите его для себя сами.
const char* LandLoader::getValue(const char *szKey, const char **atts)
{
const char* res = NULL;
if (atts!=NULL)
{
bool bflag=false;
int i=0;
while (!bflag)
{
if (atts[i]!=NULL)
{
const char* key = atts[i];
const char* value = atts[i+1];
i+=2;
if (strcmp(szKey, key)==0)
{
res = value;
bflag=true;
}
}
}
}
return res;
}
Далее идут необходимые нам методы:
Этот метод устанавливает флаг bInLevel, если на вход пришел тег TAG_LEVEL с атрибутом TAG_NUMBER = "1-1" Это частный случай, только для примера.
TAG_LEVEL, TAG_NUMBER - это константы, объявленные в описании класса.
void LandLoader::startElement(void *ctx, const char *name, const char **atts)
{
if (strcmp(name, TAG_LEVEL)==0&&!bInLevel)
{
if (strcmp(getValue(TAG_NUMBER,atts),"1-1")==0)
{
bInLevel = true;
int nWidth = atoi(getValue(TAG_LENGTH,atts));
Const::me()->levelWidth = nWidth;
initBorder();
}
}
else
// back
if (strcmp(name, TAG_BACK)==0&&bInLevel)
{
const char* backB = getValue(TAG_BACKB,atts);
const char* backf = getValue(TAG_BACKF,atts);
}
else
// element
if (strcmp(name, TAG_ELEMENT)==0&&bInLevel)
{
float pY=0;
float pX=atoi(getValue(TAG_X, atts));
const char* value = getValue(TAG_Y,atts);
if (strcmp(value,"bottom")==0) pY = Const::me()->SCREEN_HEIGHT-100;
else pY = atof(value);
const char* szname = getValue(TAG_NAME,atts);
string name(szname);
createElement(gameLayer,name,pX,pY);
}
else
// DANGER DYNAMIC
if (
(strcmp(name,TAG_DANGERD)==0||
strcmp(name,TAG_DANGERDB)==0||
strcmp(name,TAG_DANGERDC)==0)&&bInLevel
)
{
float pY=0;
float pX=atoi(getValue(TAG_X, atts));
const char* value = getValue(TAG_Y,atts);
if (strcmp(value,"bottom")==0) pY = Const::me()->SCREEN_HEIGHT-100;
else pY = atof(value);
const char* szname = getValue(TAG_NAME,atts);
string name(szname);
createDangerDynamic(gameLayer,name,pX,pY);
}
}
void LandLoader::endElement(void *ctx, const char *name){
if (strcmp(name, TAG_LEVEL)==0&&bInLevel)
{
dangerManager->createDDs(gameLayer);
bInLevel=false;
}
}
void LandLoader::textHandler(void *ctx, const char *s, int len)
{
CCLog("text handler");
}
Я думаю, кому надо разберутся в данном коде. И надеюсь, что эта статья поможет Вам написать свои парсеры для своих нужд.
С наилучшими пожеланиями )
Комментариев нет:
Отправить комментарий