學習手機網遊的開發,說實話,網遊開發這個話題太大,我也沒啥經驗,畢業這麼多年參與的20多款遊戲中,一共只做了3個網遊項目,前兩個是kjava的手機網遊(這個實在是,當時所在單位想法是好的,但現實很殘酷,j2me性能不足以滿足需要),第三個是as3的webgame(因爲種種原因難產),但很遺憾,沒一個成功的,因爲本人能力的先天不足(我大學主修的不是計算機),不過就通過對九秒開源例子打小三,談談網遊基本的開發思路吧,打小三是9秒出的一款基於firefly服務器引擎的開源例子(最近9秒很不務正業,說實話,作爲一個有自己很好產品線的技術企業,現在不把精力用在推廣自己的幾個開源的產品上,而是去搞其他產品的推廣和培訓,我覺得有些不理解),回到我們的話題上來,我們看下打小三是個什麼東東,我們先把它運行起來
首先啓動服務器:如下面
swordfishx@swordfishx-virtual-machine:~$ cd mzdemo
swordfishx@swordfishx-virtual-machine:~/mzdemo$ ls
app appmain.py config.json game mzdemo.sql nbproject startmaster.py tool
swordfishx@swordfishx-virtual-machine:~/mzdemo$ python startmaster.py
2015-06-02 11:24:43+0800 [-] Log opened.
2015-06-02 11:24:43+0800 [-] DelaySite starting on 7777
2015-06-02 11:24:43+0800 [-] Starting factory <firefly.web.delayrequest.DelaySite instance at 0xa5f0cac>
2015-06-02 11:24:43+0800 [-] BilateralFactory starting on 8888
2015-06-02 11:24:43+0800 [-] Starting factory <firefly.distributed.root.BilateralFactory instance at 0xa5f826c>
2015-06-02 11:24:53+0800 [-] Log opened.
2015-06-02 11:24:53+0800 [-] game start...
2015-06-02 11:24:53+0800 [-] game pid: 8381
2015-06-02 11:24:54+0800 [-] Log opened.
2015-06-02 11:24:54+0800 [BilateralBroker,0,127.0.0.1] node [game] takeProxy ready
2015-06-02 11:24:54+0800 [-] net start...
2015-06-02 11:24:54+0800 [-] net pid: 8380
2015-06-02 11:24:54+0800 [BilateralBroker,1,127.0.0.1] node [net] takeProxy ready
2015-06-02 11:24:55+0800 [-] Log opened.
2015-06-02 11:24:56+0800 [-] Log opened.
2015-06-02 11:24:56+0800 [-] init_globals()
2015-06-02 11:24:56+0800 [-] init_globals()
2015-06-02 11:24:56+0800 [-] gate start...
2015-06-02 11:24:56+0800 [-] gate pid: 8377
2015-06-02 11:24:56+0800 [-] data start...
2015-06-02 11:24:56+0800 [-] data pid: 8382
2015-06-02 11:24:56+0800 [BilateralBroker,2,127.0.0.1] node [gate] takeProxy ready
2015-06-02 11:24:56+0800 [Broker,client] call method remote_connect on service[single]
2015-06-02 11:24:56+0800 [Broker,client] Starting factory <twisted.spread.pb.PBClientFactory instance at 0xa27950c>
2015-06-02 11:24:56+0800 [Broker,client] call method remote_connect on service[single]
2015-06-02 11:24:56+0800 [Broker,client] Starting factory <twisted.spread.pb.PBClientFactory instance at 0x9d518ec>
2015-06-02 11:24:56+0800 [BilateralBroker,3,127.0.0.1] node [data] takeProxy ready
2015-06-02 11:24:56+0800 [BilateralBroker,0,127.0.0.1] node [game] takeProxy ready
2015-06-02 11:24:56+0800 [BilateralBroker,1,127.0.0.1] node [net] takeProxy ready
打小三的服務器是基於9秒的開源服務器firefly製作的,具體安裝搭建方法請自行學習(可以參見我寫的無憂跑起烽煙ol,這裏就不費篇幅了)。服務端啓動完成了,我們來看客戶端應該怎麼辦,打小三的客戶端使用了cocos2dx引擎的2.20版本,當然2.23版本我也做了win32版本,可以加9秒交流羣去下載,具體編譯呢,很簡單,我們先看下單機版本
我們不是講cocos2dx開發,所以具體單機版怎麼做的,我就不說了,看下差異
先是單機版AppDelegate.cpp中有這樣的代碼
bool AppDelegate::applicationDidFinishLaunching() {
// initialize director
CCDirector* pDirector = CCDirector::sharedDirector();
CCEGLView* pEGLView = CCEGLView::sharedOpenGLView();
pDirector->setOpenGLView(pEGLView);
// turn on display FPS
pDirector->setDisplayStats(true);
// set FPS. the default value is 1.0/60 if you don't call this
pDirector->setAnimationInterval(1.0 / 60);
// create a scene. it's an autorelease object
// CCScene *pScene = HelloWorld::scene();
CCScene*pscene=GameScene::scene();
// run
pDirector->runWithScene(pscene);
return true;
}
GameScene.cpp中是這樣的,bool GameScene::init(){
CCLOG("jkajskas");
_gamelayer=GameLayer::create();
this->addChild(_gamelayer);//地圖類調用
_hudLayer=HudLayer::create();
this->addChild(_hudLayer);
return true;
}
最後GameLayer.cpp中是這樣的
#include "GameLayer.h"
USING_NS_CC;
#include "Robot.h"
#include"GameScene.h"
//--------------------聲音
#include "SimpleAudioEngine.h"
using namespace CocosDenshion;
//---------------------------------聲音
#define random_range(low,high) (rand()%(high-low+1))+low
#define frandom (float)rand()/UINT64_C(0x100000000)
#define frandom_range(low,high) ((high-low)*frandom)+low
//獲取當前時間
static float GetCurTime()
{
timeval time;
gettimeofday(&time,NULL);
unsigned long millisecs=(time.tv_sec*1000)+(time.tv_usec)/1000;
return (float)millisecs;
}
bool GameLayer::init(){
this->Soundinit();
//SimpleAudioEngine::sharedEngine()->playBackgroundMusic("latin_industries.wav");
this->initTiledmap();
this->initActionSprite();
this->initHero();
this->initRobots();
this->setTouchEnabled(true);
this->scheduleUpdate();
return true;
}
//預加載音樂和音效------------------
void GameLayer::Soundinit(){
SimpleAudioEngine::sharedEngine()->preloadBackgroundMusic("latin_industries.wav");
SimpleAudioEngine::sharedEngine()->preloadEffect("pd_hit0.wav");
SimpleAudioEngine::sharedEngine()->preloadEffect("pd_hit1.wav");
SimpleAudioEngine::sharedEngine()->preloadEffect("pd_herodeath.wav");
SimpleAudioEngine::sharedEngine()->preloadEffect("pd_botdeath.wav");
}
void GameLayer::ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent){
this->_hero->attack();
this->HitCreate();
}
void GameLayer::HitCreate(){
CCObject *object=NULL;
CCARRAY_FOREACH(this->_robots,object){
Robot*robot=(Robot*)object;
if(robot->_actionState!=kActionStateDead){
//fabsf計算浮點絕對值,英雄和敵人在偏差爲10的直線上
if(fabsf(_hero->getPositionY()-robot->getPositionY())<10){
//手的盒子intersectsRect(hit盒子)
if(_hero->_attackBox.actual.intersectsRect(robot->_hitBox.actual)){
CCLOG("pengzhuang");
robot->hurtWithDamage(_hero->_damage);
}
}
}
}
}
//繪製人物動作
void GameLayer::initActionSprite(){
CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("mz_sprites.plist");
_actors=CCSpriteBatchNode::create("mz_sprites.png");
_actors->getTexture()->setAliasTexParameters();//抗鋸齒
this->addChild(_actors);
}
//繪製tile地圖
void GameLayer::initTiledmap(){
_tilemp=CCTMXTiledMap::create("mz_tilemap.tmx");
CCObject *obj=NULL;
CCARRAY_FOREACH(_tilemp->getChildren(),obj){
CCTMXLayer *_child=(CCTMXLayer*)obj;
_child->getTexture()->setAliasTexParameters();//關閉抗鋸齒
}
this->addChild(_tilemp);
}
//添加英雄
void GameLayer::initHero(){
this->_hero=Hero::create();
this->_actors->addChild(this->_hero);
this->_hero->setPosition(ccp(this->_hero->_centerToSides,80));
this->_hero->_desiredPosition=this->_hero->getPosition();
this->_hero->idle();
}
// there's no 'id' in cpp, so we recommend returning the class instance pointer
CCScene* GameLayer::scene(){
CCScene*scene=CCScene::create();
GameLayer*layer=GameLayer::create();
scene->addChild(layer);
return scene;
}
//執行 行走動作
void GameLayer::didChangeDirectorTo(SimpleDPad* simpleDPad,CCPoint direction){
this->_hero->walkWithDirection(direction);
}
//執行 空閒動作
void GameLayer::isHoldingDirector(SimpleDPad* simpleDPad,CCPoint direction){
this->_hero->walkWithDirection(direction);
}
//觸摸方向鍵結束調用的函數
void GameLayer::simpleDPadTouchEnded(SimpleDPad* simpleDpad){
if(this->_hero->_actionState==kActionStateWalk){
this->_hero->idle();
}
}
//設置英雄位置
void GameLayer::HeroPosition(){
float posX=MIN(this->_tilemp->getMapSize().width*this->_tilemp->getTileSize().width-this->_hero->_centerToSides,MAX(this->_hero->_centerToSides,this->_hero->_desiredPosition.x));
float posY=MIN(3*this->_tilemp->getTileSize().height+this->_hero->_centerToBottom,MAX(_hero->_centerToBottom,_hero->_desiredPosition.y));
this->_hero->setPosition(ccp(posX,posY));
CCSize winSize=CCDirector::sharedDirector()->getWinSize();
int x=MAX(_hero->getPositionX(),winSize.width/2);
int y=MAX(_hero->getPositionY(),winSize.height/2);
x=MIN(x,(this->_tilemp->getMapSize().width*this->_tilemp->getTileSize().width)-winSize.width/2);
y=MIN(y,(this->_tilemp->getMapSize().height*this->_tilemp->getTileSize().height)-winSize.height/2);
CCPoint actualPosition=ccp(x,y);
CCPoint centerOfView=ccp(winSize.width/2,winSize.height/2);
CCPoint viewPoint=ccpSub(centerOfView,actualPosition);
this->setPosition(viewPoint);
}
//設置機器人的位置
void GameLayer::RobotPosition(){
//限定機器人只能在地圖範圍內運動,跟英雄一樣的判定
CCObject* pObject=NULL;
CCARRAY_FOREACH(this->_robots,pObject){
Robot* robot=(Robot*)pObject;
float posX=MIN(this->_tilemp->getMapSize().width*this->_tilemp->getTileSize().width-robot->_centerToSides,MAX(robot->_centerToSides,robot->_desiredPosition.x));
float posY=MIN(3*this->_tilemp->getTileSize().height+robot->_centerToBottom,MAX(robot->_centerToBottom,robot->_desiredPosition.y));
robot->setPosition(ccp(posX,posY));}
}
void GameLayer::update(float dt){
this->_hero->updateDesiredPosition(dt);
HeroPosition();
RobotPosition();
ChangeHeroAndRobotZ();
this->updateRobots(dt);
}
//改變機器人和英雄的z軸順序
void GameLayer::ChangeHeroAndRobotZ(){
CCObject* object=NULL;
//_actors獲取之後CCARRAY_FOREACH遍歷,放入object,之後用reorderChild(新的排序之後的順序),1000是個足夠大的數,不一定要1000
CCARRAY_FOREACH(this->_actors->getChildren(),object){
this->_actors->reorderChild((CCNode*)object,1000-((ActionSprite*)object)->getPositionY());
}
}
//添加機器人
void GameLayer::initRobots(){
this->_robots=CCArray::create();
this->_robots->retain();
for (int i = 0; i < ADD_ROBOT_COUNT; i++)
{
Robot* robot=Robot::create();
this->_actors->addChild(robot);
this->_robots->addObject(robot);//加入數組
//初始化機器人位置
CCSize winSize=CCDirector::sharedDirector()->getWinSize();
int minX=winSize.width/2+robot->_centerToSides;
int maxX=this->_tilemp->getMapSize().width*this->_tilemp->getTileSize().width-robot->_centerToSides;
int minY=robot->_centerToBottom;
int maxY=3*this->_tilemp->getTileSize().height+robot->_centerToBottom;
robot->setScaleX(-1);
robot->setPosition(ccp((rand()%(maxX-minX+1))+minX,(rand()%(maxY-minY+1))+minY));
robot->_desiredPosition=robot->getPosition();
robot->idle();
}
}
GameLayer::GameLayer(void)
{
this->_hero=NULL;
this->_robots=NULL;
}
GameLayer::~GameLayer(void)
{
}
void GameLayer::updateRobots(float dt){
int alive=0;//當前活着的機器人的數量
int distanceSQ;//保存兩點距離的平方
int randomChoice=0;//隨機選擇
CCObject*pObject=NULL;
CCARRAY_FOREACH(this->_robots,pObject){
Robot*robot=(Robot*)pObject;//因爲數組中取出的是CCObject類型
robot->updateDesiredPosition(dt);
//robot->updateDesiredPosition(dt);//獲得每個機器人的位置
//當不是死亡狀態
if(robot->_actionState!=kActionStateDead){
alive++;
//判斷當前時間是否大於抉擇時間
if(::GetCurTime()>robot->_nextDecisionTime){
distanceSQ=ccpDistanceSQ(robot->getPosition(),this->_hero->getPosition());//計算機器人和英雄之間的距離
if(distanceSQ<=50*50){
robot->_nextDecisionTime=::GetCurTime()+frandom_range(0.1,0.5)*1000;
randomChoice=random_range(0,1);//在0,1之間隨機選擇一個
if(randomChoice==0){//執行攻擊動作
if (this->_hero->getPositionX()>robot->getPositionX())
{
robot->setScaleX(1.0f);//機器人位置在英雄左邊,機器人朝向發生變化
}else
{
robot->setScaleX(-1.0f);
}
robot->_nextDecisionTime=robot->_nextDecisionTime+frandom_range(0.1,0.5)*2000;
robot->attack();
//檢測是否攻擊到英雄
if (fabsf(this->_hero->getPositionY()-robot->getPositionY()<10))
{
//英雄的盒子碰撞到了robot的_attackBox
if(_hero->_hitBox.actual.intersectsRect(robot->_attackBox.actual)){
_hero->hurtWithDamage(robot->_damage);//英雄受傷
//如果英雄死亡並且這時沒有點擊重新開始菜單
if(_hero->_actionState==kActionStateDead && (((GameScene*)(this->getParent()))->_hudLayer->getActionByTag(537)==NULL)){
this->endGame();
}
}
}
}else
{//執行空閒動作
robot->idle();
}
}else if (distanceSQ<=CCDirector::sharedDirector()->getWinSize().width*CCDirector::sharedDirector()->getWinSize().width)
{
robot->_nextDecisionTime=::GetCurTime()+frandom_range(0.5,1.0)*1000;
randomChoice=random_range(0,2);
if(randomChoice==0){//機器人執行行走動作
//求標準化向量ccpNormalize,ccpSub減法
CCPoint moveDirection=ccpNormalize(ccpSub(_hero->getPosition(),robot->getPosition()));
robot->walkWithDirection(moveDirection);
robot->updateDesiredPosition(dt*20);//更新機器人位置
}else
{
//執行空閒動作
robot->idle();
}
}
}
}
}
//如果機器人數量爲0,並且沒有調用重新開始menu
if(alive==0 && ((GameScene*)(this->getParent()))->_hudLayer->getChildByTag(537)==NULL){
this->endGame();
}
}
//結束遊戲
void GameLayer::endGame(){
CCSize size=CCDirector::sharedDirector()->getVisibleSize();
CCLabelTTF*label=CCLabelTTF::create("restart","Arial",40);
CCMenuItemLabel*restartItem=CCMenuItemLabel::create(label,this,callfuncO_selector(GameLayer::restartGame));
CCMenu*menu=CCMenu::create(restartItem,NULL);
menu->setPosition(ccp(size.width/2,size.height/2));
menu->setTag(537);
if(((GameScene*)(this->getParent()))->_hudLayer !=NULL){
//獲取到父節點GameScene的指針
((GameScene*)(this->getParent()))->_hudLayer->addChild(menu);
}
}
//重啓遊戲回調
void GameLayer::restartGame(CCObject* pSender){
CCDirector::sharedDirector()->replaceScene((CCScene*)GameScene::create());
}
我們通過上面3個代碼片段可以看出,單機版的遊戲全部邏輯都是在本地也就是GameLayer中實現的,這個跟我們的網絡版打小三有什麼不同呢?我們貼下工程資源管理
我們可以看到沒有需要多餘的工具包,就完成了,人物移動,打怪等等操作,現在我們關閉單機版本,開啓9秒的網絡版本。
我們看下工程
剛剛我們打開的是GameUseTool,現在我們打開beatgirl
我們看下資源管理器是不是跟單機版的很像,只不過在類名前面多了MZ兩個字母
的確,那網絡版本的實現是不是也跟單機版差不多呢,答案是差不多,不過裁決方式不同,單機版使用的裁決方式是客戶端裁決,而網絡版使用的服務器端裁決機制,而這個機制是怎麼實現的呢?
我們看下網絡版的代碼
bool AppDelegate::applicationDidFinishLaunching() {
// initialize director
CCDirector* pDirector = CCDirector::sharedDirector();
CCEGLView* pEGLView = CCEGLView::sharedOpenGLView();
pDirector->setOpenGLView(pEGLView);
pEGLView->setDesignResolutionSize(960, 640, kResolutionFixedHeight);
// turn on display FPS
pDirector->setDisplayStats(true);
// set FPS. the default value is 1.0/60 if you don't call this
pDirector->setAnimationInterval(1.0 / 60);
// create a scene. it's an autorelease object
// CCScene *pScene = HelloWorld::scene();
// CCScene* pScene = MZGameScene::create();
CCScene* pScene = MZStartAnimation::scene();
// run
pDirector->runWithScene(pScene);
return true;
}
跟上面一樣,我們也有也看看入口scene實現了什麼
跟剛剛有點不同的是,我們進入時多了一個登錄頁面,那這個登錄頁面都實現了什麼呢,我們看看代碼
//
// MZStartAnimation.cpp
// mzdemo_client
//
// Created by stevenLi on 13-12-20.
//
//
#include "MZStartAnimation.h"
#include "resource.h"
#include "MZStartGame.h"
#include "MZDataManager.h"
#include "MZDefines.h"
#include "LodingLayer.h"
#include "message.h"
bool MZStartAnimation::init(){
if (!CCLayer::init())
return false;
createTheInterface();
CCNotificationCenter::sharedNotificationCenter()->addObserver(this, menu_selector(MZStartAnimation::screenAction), "ScreenAction", NULL);
return true;
}
CCScene* MZStartAnimation::scene(){
CCScene* scene = CCScene::create();
MZStartAnimation* layer = MZStartAnimation::create();
scene->addChild(layer);
return scene;
}
void MZStartAnimation::onExit(){
CCNotificationCenter::sharedNotificationCenter()->removeObserver(this , "ScreenAction");
CCLayer::onExit();
}
void MZStartAnimation::callBack(CCObject* pSender){
std::string str = CCUserDefault::sharedUserDefault()->getStringForKey("NAME");
str = "111";
if (!str.empty() && str.compare("") != 0) {
MZDataManager* dataManager = MZDataManager::sharedDataManager();
/* CSCSJson::FastWriter writer;
CSCSJson::Value data;*/
CSJson::FastWriter writer;
CSJson::Value data;
data["name"] = str.c_str();
std::string CSJson_data = writer.write(data);
dataManager->sendMessage(CSJson_data.c_str(), 1000);
this->schedule(schedule_selector(MZStartAnimation::receive_1000));
_loading = Loading::create();
this->addChild(_loading, 5);
}
}
void MZStartAnimation::receive_1000(float dt)
{
MZDataManager* dataManager = MZDataManager::sharedDataManager();
Message* msg = (Message *)dataManager->m_dictionary->objectForKey(1000);
dataManager->m_dictionary->removeObjectForKey(1000);
if (msg) {
_loading->removeFromParent();
this->unschedule(schedule_selector(MZStartAnimation::receive_1000));
char* receive_data = msg->data;
/* CSCSJson::Reader reader;
CSCSJson::Value root;*/
CSJson::Reader reader;
CSJson::Value root;
if (!reader.parse(receive_data, root)) {
return;
}
//CSCSJson::Value data;
CSJson::Value data;
data = root["rev_data"];
dataManager->setUserID(data["user_id"].asInt());
dataManager->setUserInfo(receive_data);
CCScene* scene = CCScene::create();
MZStartGame* layer = MZStartGame::create();
scene->addChild(layer);
CCTransitionFade* transitionScene = CCTransitionFade::create(0.1f, scene);
CCDirector::sharedDirector()->pushScene(transitionScene);
}
}
void MZStartAnimation::createTheInterface(){
CCSprite* _bgSpr = CCSprite::create(mz_MZStartAnimation_bgImage);
_bgSpr->setPosition(CENTER);
this->addChild(_bgSpr);
CCSprite* _borderSpr = CCSprite::create(mz_MZStartAnimation_Border);
_borderSpr->setPosition(ccp(ScreenWidth* 0.5, ScreenHeight* 0.42));
this->addChild(_borderSpr, 1);
CCMenuItem* item = CCMenuItemImage::create(mz_MZStartAnimation_Login1, mz_MZStartAnimation_Login2, this, menu_selector(MZStartAnimation::callBack));
item->setPosition(ccp(ScreenWidth* 0.5, ScreenHeight* 0.22));
CCMenu* menu = CCMenu::create(item, NULL);
menu->setPosition(CCPointZero);
this->addChild(menu, 1);
std::string str = CCUserDefault::sharedUserDefault()->getStringForKey("NAME");
if (str.c_str() != NULL) {
_textField = MZCursorTextField::textFieldWithPlaceHolder("請輸入名字", Arial, 20);
}
else{
_textField = MZCursorTextField::textFieldWithPlaceHolder(str.c_str(), Arial, 20);
}
_textField->setPosition( ccp(ScreenWidth* 0.5, ScreenHeight* 0.37));
_textField->setScale(2);
_textField->setColor(WhiteColor);
_textField->enableStroke(WhiteColor, 0.4f);
this->addChild(_textField, 2);
}
void MZStartAnimation::screenAction(CCObject* pSender){
if (!pSender) {
CCFiniteTimeAction* _actionMove = CCMoveTo::create(0.18f,ccp(0, getPositionY()+ 220));
this->runAction(CCSequence::create(_actionMove, NULL));
}else{
CCFiniteTimeAction* _actionMove = CCMoveTo::create(0.18f,ccp(0, getPositionY()- 220));
this->runAction(CCSequence::create(_actionMove, NULL));
}
}
init()我們看下createTheInterface()這個方法
這個方法中畫出了我們這個屏幕中顯視的所有東西,包括,背景,按鈕,輸入框等等,我們不贅述其他的,只關心裏面的一個callback,我們看看callback裏面實現了什麼東西
void MZStartAnimation::callBack(CCObject* pSender){
std::string str = CCUserDefault::sharedUserDefault()->getStringForKey("NAME");
str = "111";
if (!str.empty() && str.compare("") != 0) {
MZDataManager* dataManager = MZDataManager::sharedDataManager();
/* CSCSJson::FastWriter writer;
CSCSJson::Value data;*/
CSJson::FastWriter writer;
CSJson::Value data;
data["name"] = str.c_str();
std::string CSJson_data = writer.write(data);
dataManager->sendMessage(CSJson_data.c_str(), 1000);
this->schedule(schedule_selector(MZStartAnimation::receive_1000));
_loading = Loading::create();
this->addChild(_loading, 5);
}
}
我們可以看到這裏面通過json把str字符串做了格式化,然後用sendMessage方法把格式化好的字符串發出,而消息號是1000
在發出消息之後用一個計劃來監聽receive_1000
當接收到服務器端的指令之後
void MZStartAnimation::receive_1000(float dt)
{
MZDataManager* dataManager = MZDataManager::sharedDataManager();
Message* msg = (Message *)dataManager->m_dictionary->objectForKey(1000);
dataManager->m_dictionary->removeObjectForKey(1000);
if (msg) {
_loading->removeFromParent();
this->unschedule(schedule_selector(MZStartAnimation::receive_1000));
char* receive_data = msg->data;
/* CSCSJson::Reader reader;
CSCSJson::Value root;*/
CSJson::Reader reader;
CSJson::Value root;
if (!reader.parse(receive_data, root)) {
return;
}
//CSCSJson::Value data;
CSJson::Value data;
data = root["rev_data"];
dataManager->setUserID(data["user_id"].asInt());
dataManager->setUserInfo(receive_data);
CCScene* scene = CCScene::create();
MZStartGame* layer = MZStartGame::create();
scene->addChild(layer);
CCTransitionFade* transitionScene = CCTransitionFade::create(0.1f, scene);
CCDirector::sharedDirector()->pushScene(transitionScene);
}
在這個方法中用json做解析還原,然後按照服務器指令來做跳轉場景的動作,我們可以看到,跟上面的單機版相比,我們的命令不在內存中直接傳遞,而是經過了客戶端格式化發送,服務器接收解析處理髮回,客戶端接收解析的一個過程。
我們看下這個時候服務器做了什麼,我們點賬號登錄
2015-06-02 12:39:20+0800 [firefly.netconnect.protoc.LiberateFactory] Client 0 login in.[192.168.0.102,54537]
2015-06-02 12:39:20+0800 [LiberateProtocol,0,192.168.0.102] call method call_gate_0 on service[single]
2015-06-02 12:39:21+0800 [BilateralBroker,1,127.0.0.1] call method reply_net_call on service[single]
2015-06-02 12:39:21+0800 [BilateralBroker,1,127.0.0.1] gateapp *** reply_net_call()
2015-06-02 12:39:21+0800 [BilateralBroker,1,127.0.0.1] call method gate_localservice_1000 on service[single]
2015-06-02 12:39:23+0800 [BilateralBroker,1,127.0.0.1] ('search user_id, resoult = ', {'money': 200L, 'hitpoints': 1000.0, 'user_id': 106L, 'name': u'111', 'damage': 200.0})
2015-06-02 12:39:23+0800 [LiberateProtocol,0,192.168.0.102] call method call_gate_0 on service[single]
2015-06-02 12:39:23+0800 [BilateralBroker,1,127.0.0.1] call method reply_net_call on service[single]
2015-06-02 12:39:23+0800 [BilateralBroker,1,127.0.0.1] gateapp *** reply_net_call()
2015-06-02 12:39:23+0800 [BilateralBroker,1,127.0.0.1] call method gate_localservice_2000 on service[single]
2015-06-02 12:39:23+0800 [LiberateProtocol,0,192.168.0.102] call method call_gate_0 on service[single]
2015-06-02 12:39:23+0800 [BilateralBroker,1,127.0.0.1] call method reply_net_call on service[single]
2015-06-02 12:39:23+0800 [BilateralBroker,1,127.0.0.1] gateapp *** reply_net_call()
2015-06-02 12:39:23+0800 [BilateralBroker,1,127.0.0.1] call method gate_localservice_4000 on service[single]
看到了吧,服務器在當有客戶端消息的時候做了這些東西,並且調用了gate_localservice_1000, gate_localservice_2000, gate_localservice_4000這3個方法,怎麼調用的我們後面說,那麼2000.和4000的消息在哪呢
MZStartGame* layer = MZStartGame::create();
scene->addChild(layer);上面有這麼兩行代碼,我們看下這個類
//
// MZStartGame.cpp
// mzdemo_client
//
// Created by stevenLi on 13-12-20.
//
//
#include "MZStartGame.h"
#include "resource.h"
#include "MZGameScene.h"
#include "MZDefines.h"
#include "MZDataManager.h"
#include "message.h"
#include "MZTableView.h"
#include "LodingLayer.h"
MZStartGame::MZStartGame(){
}
MZStartGame::~MZStartGame(){
}
bool MZStartGame::init(){
if (!CCLayer::init()) {
return false;
}
MZDataManager* dataManager = MZDataManager::sharedDataManager();
// CSCSJson::FastWriter writer;
// CSCSJson::Value value;
CSJson::FastWriter writer;
CSJson::Value value;
value["user_id"] = dataManager->getUserID();
std::string CSJson_data = writer.write(value);
dataManager->sendMessage(CSJson_data.c_str(), 2000);
this->schedule(schedule_selector(MZStartGame::receive_2000));
_loading = Loading::create();
this->addChild(_loading);
CCSprite* _bgSpr = CCSprite::create(mz_MZStartGame_bgImage);
_bgSpr->setPosition(CENTER);
this->addChild(_bgSpr);
return true;
}
void MZStartGame::receive_2000(float dt)
{
MZDataManager* dataManager = MZDataManager::sharedDataManager();
Message* msg = (Message *)dataManager->m_dictionary->objectForKey(2000);
dataManager->m_dictionary->removeObjectForKey(2000);
if (msg) {
_loading->removeFromParent();
this->unschedule(schedule_selector(MZStartGame::receive_2000));
CCDictionary* dic = CCDictionary::create();
dataManager->setDicItems(dic);
char* receive_data = msg->data;
CCLog("receive_2000 :::\n%s", receive_data);
// CSCSJson::Reader reader;
// CSCSJson::Value root;
CSJson::Reader reader;
CSJson::Value root;
if (!reader.parse(receive_data, root)) {
return;
}
// CSCSJson::Value data;
//data = root["rev_data"];
CSJson::Value data;
data = root["rev_data"];
int count = data.size();
for (int i = 0; i < count; i++) {
CCLOG("item :::::::::::: %d", data[i]["item_id"].asInt());
CCString* strIndex = CCString::createWithFormat("%d",data[i]["item_id"].asInt());
dataManager->getDicItems()->setObject(strIndex, strIndex->getCString());
}
}
initLiftButton();
initRightButton();
}
void MZStartGame::initLiftButton(){
CCSprite* kSpr = CCSprite::create(mz_MZStartGame_Kuang);
kSpr->setPosition(ccp(ScreenWidth* 0.25, ScreenHeight* 0.65));
this->addChild(kSpr, 2);
CCSprite* tSpr = CCSprite::create(mz_MZStartGame_Title);
tSpr->setPosition(ccp(ScreenWidth* 0.25, ScreenHeight* 0.89));
this->addChild(tSpr, 3);
CCSprite* dSpr = CCSprite::create(mz_MZStartGame_Down);
dSpr->setPosition(ccp(ScreenWidth* 0.25, ScreenHeight* 0.395));
this->addChild(dSpr, 3);
CCSprite* jbSpr = CCSprite::create(mz_MZStartGame_JinBi);
jbSpr->setPosition(ccp(ScreenWidth* 0.15, ScreenHeight* 0.27));
jbSpr->setScaleX(0.8f);
this->addChild(jbSpr,2);
CCSprite* ybSpr = CCSprite::create(mz_MZStartGame_YuanBao);
ybSpr->setPosition(ccp(ScreenWidth* 0.35, ScreenHeight* 0.27));
ybSpr->setScaleX(0.8f);
this->addChild(ybSpr, 2);
CCMenuItem* startItem = CCMenuItemImage::create(mz_MZStartGame_Start1, mz_MZStartGame_Start2, this, menu_selector(MZStartGame::startCallBack));
startItem->setPosition(ccp(ScreenWidth* 0.25, ScreenHeight* 0.12));
CCMenu* menu = CCMenu::create(startItem, NULL);
menu->setPosition(CCPointZero);
this->addChild(menu, 2);
MZTableView* layer = MZTableView::create();
this->addChild(layer, 2);
}
void MZStartGame::startCallBack(CCObject* pSender){
CCScene* scene = CCScene::create();
MZGameScene* layer = MZGameScene::create();
scene->addChild(layer);
CCTransitionFade* transitionScene = CCTransitionFade::create(0.1f, scene);
CCDirector::sharedDirector()->pushScene(transitionScene);
}
void MZStartGame::initRightButton(){
CCSprite* bgSpr = CCSprite::create(mz_MZStartGame_Hero_bgImage);
bgSpr->setPosition(ccp(ScreenWidth* 0.75, ScreenHeight* 0.5));
this->addChild(bgSpr, 1);
MZDataManager* dataManager = MZDataManager::sharedDataManager();
CCDictionary* dicItems = dataManager->getDicItems();
CCSprite* liftSkill1 = CCSprite::create(mz_MZStartGame_Lift_Skills1_2);
liftSkill1->setPosition(ccp(ScreenWidth* 0.59, ScreenHeight* 0.86));
liftSkill1->setTag(Tag_Lift_Skills1);
this->addChild(liftSkill1, 2);
liftItem1 = CCMenuItemImage::create(mz_MZStartGame_Buy1, mz_MZStartGame_Buy2, this, menu_selector(MZStartGame::buyCallBack));
liftItem1->setPosition(ccp(ScreenWidth* 0.59, ScreenHeight* 0.76));
liftItem1->setTag(Tag_Lift_Button1);
CCMenu* menu1 = CCMenu::create(liftItem1, NULL);
menu1->setPosition(CCPointZero);
this->addChild(menu1, 3);
if (dicItems->valueForKey("10001")->compare("10001") == 0) {
CCLog("has 10001");
liftItem1->removeFromParent();
}
CCSprite* liftSkill2 = CCSprite::create(mz_MZStartGame_Lift_Skills2_2);
liftSkill2->setPosition(ccp(ScreenWidth* 0.59, ScreenHeight* 0.62));
liftSkill2->setTag(Tag_Lift_Skills2);
this->addChild(liftSkill2, 2);
liftItem2 = CCMenuItemImage::create(mz_MZStartGame_Buy1, mz_MZStartGame_Buy2, this, menu_selector(MZStartGame::buyCallBack));
liftItem2->setPosition(ccp(ScreenWidth* 0.59, ScreenHeight* 0.52));
liftItem2->setTag(Tag_Lift_Button2);
CCMenu* menu2 = CCMenu::create(liftItem2, NULL);
menu2->setPosition(CCPointZero);
this->addChild(menu2, 3);
if (dicItems->valueForKey("10002")->compare("10002") == 0) {
liftItem2->removeFromParent();
CCLog("has 10002");
}
CCSprite* liftSkill3 = CCSprite::create(mz_MZStartGame_Lift_Skills3_2);
liftSkill3->setPosition(ccp(ScreenWidth* 0.59, ScreenHeight* 0.38));
liftSkill3->setTag(Tag_Lift_Skills3);
this->addChild(liftSkill3, 2);
liftItem3 = CCMenuItemImage::create(mz_MZStartGame_Buy1, mz_MZStartGame_Buy2, this, menu_selector(MZStartGame::buyCallBack));
liftItem3->setPosition(ccp(ScreenWidth* 0.59, ScreenHeight* 0.28));
liftItem3->setTag(Tag_Lift_Button3);
CCMenu* menu3 = CCMenu::create(liftItem3, NULL);
menu3->setPosition(CCPointZero);
this->addChild(menu3, 3);
if (dicItems->valueForKey("10003")->compare("10003") == 0) {
liftItem3->removeFromParent();
CCLog("has 10003");
}
CCSprite* rightSkill1 = CCSprite::create(mz_MZStartGame_Right_Skills1_2);
rightSkill1->setPosition(ccp(ScreenWidth* 0.91, ScreenHeight* 0.86));
rightSkill1->setTag(Tag_Right_Skills1);
this->addChild(rightSkill1, 2);
rightItem1 = CCMenuItemImage::create(mz_MZStartGame_Buy1, mz_MZStartGame_Buy2, this, menu_selector(MZStartGame::buyCallBack));
rightItem1->setPosition(ccp(ScreenWidth* 0.91, ScreenHeight* 0.76));
rightItem1->setTag(Tag_Right_Button1);
CCMenu* menu4 = CCMenu::create(rightItem1, NULL);
menu4->setPosition(CCPointZero);
this->addChild(menu4, 3);
if (dicItems->valueForKey("10004")->compare("10004") == 0) {
rightItem1->removeFromParent();
CCLog("has 10004");
}
CCSprite* rightSkill2 = CCSprite::create(mz_MZStartGame_Right_Skills2_2);
rightSkill2->setPosition(ccp(ScreenWidth* 0.91, ScreenHeight* 0.62));
rightSkill2->setTag(Tag_Right_Skills2);
this->addChild(rightSkill2, 2);
rightItem2 = CCMenuItemImage::create(mz_MZStartGame_Buy1, mz_MZStartGame_Buy2, this, menu_selector(MZStartGame::buyCallBack));
rightItem2->setPosition(ccp(ScreenWidth* 0.91, ScreenHeight* 0.52));
rightItem2->setTag(Tag_Right_Button2);
CCMenu* menu5 = CCMenu::create(rightItem2, NULL);
menu5->setPosition(CCPointZero);
this->addChild(menu5, 3);
if (dicItems->valueForKey("10005")->compare("10005") == 0) {
rightItem2->removeFromParent();
CCLog("has 10005");
}
CCSprite* rightSkill3 = CCSprite::create(mz_MZStartGame_Right_Skills3_2);
rightSkill3->setPosition(ccp(ScreenWidth* 0.91, ScreenHeight* 0.38));
rightSkill3->setTag(Tag_Right_Skills3);
this->addChild(rightSkill3, 2);
rightItem3 = CCMenuItemImage::create(mz_MZStartGame_Buy1, mz_MZStartGame_Buy2, this, menu_selector(MZStartGame::buyCallBack));
rightItem3->setPosition(ccp(ScreenWidth* 0.91, ScreenHeight* 0.28));
rightItem3->setTag(Tag_Right_Button3);
CCMenu* menu6 = CCMenu::create(rightItem3, NULL);
menu6->setPosition(CCPointZero);
this->addChild(menu6, 3);
if (dicItems->valueForKey("10006")->compare("10006") == 0) {
rightItem3->removeFromParent();
CCLog("has 10006");
}
CCSprite* mpSpr = CCSprite::create(mz_MZStartGame_MingPai);
mpSpr->setPosition(ccp(ScreenWidth* 0.75, ScreenHeight* 0.92 + 300));
mpSpr->setScale(0.85f);
this->addChild(mpSpr, 5, 0x6666);
std::string str = CCUserDefault::sharedUserDefault()->getStringForKey("NAME");
CCLabelTTF* nameLab = CCLabelTTF::create(str.c_str(), Arial, 30);
nameLab->setPosition(ccp(ScreenWidth* 0.75, ScreenHeight* 0.92 + 300));
nameLab->setColor(GreenColor);
nameLab->enableStroke(GreenColor, 1.0f);
this->addChild(nameLab, 6, 0x7777);
initHero();
initLabel();
addLabel();
this->scheduleOnce(SEL_SCHEDULE(&MZStartGame::moveTo), 0.5f);
}
void MZStartGame::moveTo(float dt){
CCNode* node1 = this->getChildByTag(0x6666);
CCFiniteTimeAction* actionMove1 = CCMoveTo::create(0.7f,ccp(ScreenWidth* 0.75, ScreenHeight* 0.92));
CCFiniteTimeAction* actionSpawn1 = CCSpawn::create(actionMove1, NULL);
node1->runAction(CCSequence::create(actionSpawn1, NULL));
CCNode* node2 = this->getChildByTag(0x7777);
CCFiniteTimeAction* actionMove2 = CCMoveTo::create(0.7f,ccp(ScreenWidth* 0.75, ScreenHeight* 0.92));
CCFiniteTimeAction* actionSpawn2 = CCSpawn::create(actionMove2, NULL);
node2->runAction(CCSequence::create(actionSpawn2, NULL));
}
void MZStartGame::initLabel(){
MZDataManager* dataManager = MZDataManager::sharedDataManager();
char* userInfo = dataManager->getUserInfo();
/* CSCSJson::Reader reader;
CSCSJson::Value root;
CSCSJson::Value data;*/
CSJson::Reader reader;
CSJson::Value root;
CSJson::Value data;
int hitpoints = 0;
int damage = 0;
int money = 0;
if (reader.parse(userInfo, root)) {
data = root["rev_data"];
money = data["money"].asInt();
hitpoints = data["hitpoints"].asInt();
damage = data["damage"].asInt();
}
CCLabelTTF* hpLabel = CCLabelTTF::create("血量: 1000 ( )", Arial, 35, CCSizeMake(400, 30), kCCTextAlignmentCenter, kCCVerticalTextAlignmentCenter);
hpLabel->setColor(WhiteColor);
hpLabel->enableStroke(WhiteColor, 1);
hpLabel->setPosition(ccp(ScreenWidth* 0.75, ScreenHeight* 0.18));
this->addChild(hpLabel, 2);
CCLabelTTF* atLabel = CCLabelTTF::create("攻擊: 200 ( )", Arial, 35, CCSizeMake(400, 30), kCCTextAlignmentCenter, kCCVerticalTextAlignmentCenter);
atLabel->setColor(WhiteColor);
atLabel->enableStroke(WhiteColor, 1);
atLabel->setPosition(ccp(ScreenWidth* 0.75, ScreenHeight* 0.09));
this->addChild(atLabel, 2);
CCString* jbStr = CCString::createWithFormat("%d", money);
jbLabel = CCLabelTTF::create(jbStr->getCString(), Arial, 28, CCSizeMake(150, 30), kCCTextAlignmentCenter, kCCVerticalTextAlignmentCenter);
jbLabel->setColor(WhiteColor);
jbLabel->enableStroke(WhiteColor, 1);
jbLabel->setPosition(ccp(ScreenWidth* 0.17, ScreenHeight* 0.267));
this->addChild(jbLabel, 4);
CCString* ybStr = CCString::createWithFormat("0");
CCLabelTTF* ybLabel = CCLabelTTF::create(ybStr->getCString(), Arial, 28, CCSizeMake(150, 30), kCCTextAlignmentCenter, kCCVerticalTextAlignmentCenter);
ybLabel->setColor(WhiteColor);
ybLabel->enableStroke(WhiteColor, 1);
ybLabel->setPosition(ccp(ScreenWidth* 0.37, ScreenHeight* 0.267));
this->addChild(ybLabel, 4);
}
void MZStartGame::initHero(){
CCSpriteFrameCache* cache = CCSpriteFrameCache::sharedSpriteFrameCache();
cache->addSpriteFramesWithFile("mz_hero.plist", "mz_hero.png");
heroSpr = CCSprite::createWithSpriteFrameName("mz_hero0.png");
heroSpr->setPosition(ccp(ScreenWidth* 0.638, ScreenHeight* 0.55));
this->addChild(heroSpr, 5);
CCArray *fileName = CCArray::createWithCapacity(3);
for (int i = 0; i < 4; i++)
{
CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("mz_hero%d.png", i)->getCString());
fileName->addObject(frame);
}
CCAnimation *hurtAnimation = CCAnimation::createWithSpriteFrames(fileName, float(2 / 12.0));
heroSpr->runAction(CCRepeatForever::create(CCSequence::create( CCAnimate::create(hurtAnimation), NULL)));
}
void MZStartGame::buyCallBack(CCObject* pSender){
CCMenuItem* item = (CCMenuItem* )pSender;
int itemID = 0;
switch (item->getTag()) {
case Tag_Lift_Button1:{
CCLog("購買左技能1");
itemID = 10001;
}
break;
case Tag_Lift_Button2:{
CCLog("購買左技能2");
itemID = 10002;
}
break;
case Tag_Lift_Button3:{
CCLog("購買左技能3");
itemID = 10003;
}
break;
case Tag_Right_Button1:{
CCLog("購買右技能1");
itemID = 10004;
}
break;
case Tag_Right_Button2:{
CCLog("購買右技能2");
itemID = 10005;
}
break;
case Tag_Right_Button3:{
CCLog("購買右技能3");
itemID = 10006;
}
break;
default:
break;
}
if (itemID != 0) {
MZDataManager* dataManager = MZDataManager::sharedDataManager();
/*CSCSJson::FastWriter writer;
CSCSJson::Value data;*/
CSJson::FastWriter writer;
CSJson::Value data;
data["user_id"] = dataManager->getUserID();
data["item_id"] = itemID;
std::string CSJson_data = writer.write(data);
dataManager->sendMessage(CSJson_data.c_str(), 3000);
this->schedule(schedule_selector(MZStartGame::receive_3000));
_loading = Loading::create();
this->addChild(_loading, 10);
}
}
void MZStartGame::receive_3000(float dt)
{
MZDataManager* dataManager = MZDataManager::sharedDataManager();
Message* msg = (Message* )dataManager->m_dictionary->objectForKey(3000);
dataManager->m_dictionary->removeObjectForKey(3000);
if (msg) {
_loading->removeFromParent();
this->unschedule(schedule_selector(MZStartGame::receive_3000));
char* receive_data = msg->data;
CCLog("receive_3000 :::\n%s", receive_data);
/* CSCSJson::Reader reader;
CSCSJson::Value root;*/
CSJson::Reader reader;
CSJson::Value root;
if (!reader.parse(receive_data, root)) {
return;
}
//CSCSJson::Value data;
CSJson::Value data;
data = root["rev_data"];
dataManager->setUserInfo(receive_data);
if (root["state_code"].asInt() != 0) {
return;
}
else {
int itemID = root["item_id"].asInt();
switch (itemID) {
case 10001:{
liftItem1->removeFromParent();
}
break;
case 10002:{
liftItem2->removeFromParent();
}
break;
case 10003:{
CCLog("購買左技能3");
liftItem3->removeFromParent();
}
break;
case 10004:{
rightItem1->removeFromParent();
}
break;
case 10005:{
rightItem2->removeFromParent();
}
break;
case 10006:{
rightItem3->removeFromParent();
}
break;
default:
break;
}
}
CCString* strJB = CCString::createWithFormat("%d", data["money"].asInt());
jbLabel->setString(strJB->getCString());
CCString* strHP = CCString::createWithFormat("+%d", data["hitpoints"].asInt() - 1000);
hpLabel->setString(strHP->getCString());
CCString* strAK = CCString::createWithFormat("+%d", data["damage"].asInt() - 200);
atLabel->setString(strAK->getCString());
}
}
void MZStartGame::addLabel(){
MZDataManager* dataManager = MZDataManager::sharedDataManager();
char* userInfo = dataManager->getUserInfo();
/* CSCSJson::Reader reader;
CSCSJson::Value root;
CSCSJson::Value data;*/
CSJson::Reader reader;
CSJson::Value root;
CSJson::Value data;
int hitpoints = 0;
int damage = 0;
int money = 0;
if (reader.parse(userInfo, root)) {
data = root["rev_data"];
money = data["money"].asInt();
hitpoints = data["hitpoints"].asInt();
damage = data["damage"].asInt();
}
CCString* str1 = CCString::createWithFormat("+%d", hitpoints - 1000);
hpLabel = CCLabelTTF::create(str1->getCString(), Arial, 35, CCSizeMake(400, 30), kCCTextAlignmentCenter, kCCVerticalTextAlignmentCenter);
hpLabel->setColor(GreenColor);
hpLabel->enableStroke(GreenColor, 1);
hpLabel->setPosition(ccp(ScreenWidth* 0.86, ScreenHeight* 0.175));
this->addChild(hpLabel, 2);
CCString* str2 = CCString::createWithFormat("+%d", damage - 200);
atLabel = CCLabelTTF::create(str2->getCString(), Arial, 35, CCSizeMake(400, 30), kCCTextAlignmentCenter, kCCVerticalTextAlignmentCenter);
atLabel->setColor(GreenColor);
atLabel->enableStroke(GreenColor, 1);
atLabel->setPosition(ccp(ScreenWidth* 0.86, ScreenHeight* 0.085));
this->addChild(atLabel, 2);
}
我們繼續點擊開始遊戲
2015-06-02 12:42:51+0800 [LiberateProtocol,0,192.168.0.102] call method call_gate_0 on service[single]
2015-06-02 12:42:51+0800 [BilateralBroker,1,127.0.0.1] call method reply_net_call on service[single]
2015-06-02 12:42:51+0800 [BilateralBroker,1,127.0.0.1] gateapp *** reply_net_call()
2015-06-02 12:42:51+0800 [BilateralBroker,1,127.0.0.1] call method gate_localservice_3000 on service[single]
我們可以看到又多了這麼個gate_localservice_3000的方法,我們看下游戲畫面
void MZStartGame::startCallBack(CCObject* pSender){
CCScene* scene = CCScene::create();
MZGameScene* layer = MZGameScene::create();
scene->addChild(layer);
CCTransitionFade* transitionScene = CCTransitionFade::create(0.1f, scene);
CCDirector::sharedDirector()->pushScene(transitionScene);
}
通過這個回調函數,我們轉到了
//
// MZGameScene.cpp
// mzdemo_client
//
// Created by stevenLi on 13-12-20.
//
//
#include "MZGameScene.h"
#include "MZDataManager.h"
using namespace cocos2d;
MZGameScene::MZGameScene(void)
{
_gameLayer = NULL;
_hudLayer = NULL;
}
MZGameScene::~MZGameScene(void)
{
}
bool MZGameScene::init()
{
bool bRet = false;
do
{
CC_BREAK_IF(!CCScene::init());
MZDataManager::sharedDataManager();
_gameLayer = MZGameLayer::create();
this->addChild(_gameLayer, 0);
_hudLayer = MZHudLayer::create();
this->addChild(_hudLayer, 1);
_hudLayer->getDPad()->setDelegate(_gameLayer);
_gameLayer->setMZHud(_hudLayer);
bRet = true;
} while (0);
return bRet;
}
可以比較下跟上面的單機的gamescene是不是一樣呢,其實我們實現這些只是把邏輯放到了服務器來完成,而我們通過本地發送和接收消息字符串來告訴服務器我們下一步要做什麼,然後服務器聽我們的命令傳達指令
我們看下,單機c++的通用網絡模塊都包含哪些東西
其實就是這個網絡通用包,其實我們不需要知道它底層是怎麼實現的,只要知道發送,接收原理和方法就可以了,
這是客戶端,我們看下服務器端是怎麼實現的,關掉vs,和服務器,看看代碼,
對了,忘了說了,我在9秒羣裏發win32版本的時候,有些小夥伴加載了我的測試好的代碼,卻出了很奇怪的一個錯誤,這個是因爲使用上面的c++通過模塊需要使用win自帶的api,只要在鏈接器裏面添加ws2_32.lib就可以了如圖,
好了看服務器。我們爲方便打開eclipse
插件我們選Pydev
我們關閉其他的,只看mzdemo,我們看看服務器是怎麼運行的
打開config.json
{
"master":{"rootport":8888,"webport":7777},
"servers":{
"gate":{
"rootport":11111,
"name":"gate",
"app":"app.gateapp",
"db":true,
"mem":true,
"log":"app/logs/gate.log"
},
"net":{"netport":22222,
"remoteport":[{"rootport":11111,"rootname":"gate"}],
"name":"net",
"app":"app.netapp",
"log":"app/logs/net.log"
},
"game":{
"remoteport":[{"rootport":11111,"rootname":"gate"}],
"name":"game",
"app":"app.gameapp",
"log":"app/logs/game.log"
},
"data":{
"name":"data",
"app":"app.dataapp",
"db":true,
"mem":true,
"log":"app/logs/data.log"
}
},
"db":{
"host":"127.0.0.1",
"user":"root",
"passwd":"1234",
"port":3306,
"db":"mzdemo",
"charset":"utf8"
},
"memcached":{
"urls":["127.0.0.1:11211"],
"hostname":"mzdemo"
}
}
看配置文件,當服務器啓動時,servers中會啓動gate,net,game,data,這些服務,分別通過一個python文件來轉給服務器源碼,我們以gate爲例
app.gateapp,我們看下打開app文件夾下的gateapp.py
#coding:utf8
from firefly.server.globalobject import GlobalObject, rootserviceHandle
from gate.gatelocalservice import gateLocalService
# @rootserviceHandle
def gate_side(conn, data):
print("gateapp *** send_to_game1()")
d = GlobalObject().root.callChild("game1", "game1_side", conn, data)
# d = GlobalObject().remote["game1"].callRemote("game1_side", conn, data)
return d
#end def
@rootserviceHandle
def reply_net_call(targetKey, clientID, requestData):
print("gateapp *** reply_net_call()")
if gateLocalService._targets.has_key(targetKey):
return gateLocalService.callTarget(targetKey, clientID, requestData)
else:
print("not found the target with key:%s" % targetKey)
#end def
看下firefly api
Firefly框架參考
在遊戲服務器端,往往需要處理大量的各種各樣的任務,每一項任務所需的系統資源也可能不同。而這些複雜的任務只用一個單獨的服務器進程是很難支撐和管理起來的。所以,遊戲服務器端的開發者往往需要花費大量的時間精力在諸如服務器類型的劃分,進程數量的分配,以及這些進程的維護,進程間的通訊,請求的路由等等這些底層的問題上。而Firefly完全可以完成這些重複而繁瑣的工作,從而將上層的遊戲開發者解放出來,把精力更多的放在遊戲邏輯的實現上面。
一、Firefly特性
1、採用單線程多進程架構,支持自定義的分佈式架構。
2、方便的服務器擴展機制,可快速擴展服務器類型和數量
3、與客戶端採用TCP長連接,無需考慮粘包等問題。
二、Firefly思路
一個最基本的服務器就是一個在不停運行着的應用程序。在分佈式遊戲服務器中,我們需要的服務器具有的功能有,監聽客戶端的連接,監聽其他服務進程的消息,連接其他的服務進程,有些需要有數據庫連接和緩存服務。如圖
net connect 做客戶端連接,root監聽其他服務進程消息,node連接其他服務進程,db數據庫,cache緩存。是否需要監聽客戶端連接,是否監聽其他服務進程消息等這是都是可以在config.json中進行配置。包括各個服務器的名稱以及各個服務器之間的連接關係。這樣就可以自定義出自己的分佈式架構。
三、Firefly結構
從功能職責上來看,Firefly框架結構如下圖所示:
1、management, firefly 是個多進程、分佈式的遊戲服務器。因此各遊戲server(進程)的管理和擴展是firefly很重要的部分,框架通過抽象使服務器的擴展非常容易。
2、Network,客戶端連接通信、server進程間的通信等構成了整個遊戲框架的脈絡,所有遊戲流程都構建在這個脈絡上。與客戶端的通信採用的是請求/迴應式的,所有收到的客戶端的請求,服務端都會給出相應的迴應,服務端也能主動的推送,廣播給客戶端消息。這些請求是基於指令號的請求。(例如定義101爲登陸指令)server進程之間的通信時採用的異步回調的方式,這樣就減少了的進程間通過網絡通信中的時間消耗。
3、Data, 數據處理是網遊的重要部分。在網遊有大量的數據需要存儲,需要更新,這使得數據庫的讀寫效率成爲服務器的最大的性能瓶頸。firefly的db處理能夠將數據庫表中的數據緩存到memcache中並能以對象的形式進行調用相應的對象方法對數據進行操作。可以在不同的進程中通過實例化相同的名稱的緩存實例,得到同步的數據。並能將緩存對象中的數據寫回數據庫中。
1.從config.json中讀取配置數據
2.根據配置中定義的服務端的架構,啓動相應的服務進程。並建立節點之間的連接。有配置數據庫的,實例化數據庫連接池。有配置memcached的,建立memcached的連接。
3.根據配置相應的的進程啓動的入口模塊。
from gate.gatelocalservice import gateLocalService這句
還記得上面的gateLocalService_1000,gateLocalService_2000,gateLocalService_3000嗎?我們看下它裏面有什麼
#coding:utf8
from firefly.utils.services import CommandService
from twisted.internet import defer
from twisted.python import log
import json
from ..data.JSONEncoder import CJsonEncoder
from ..data import datautils
from ..data import commondata
class GateLocalService(CommandService):
def callTargetSingle(self,targetKey,*args,**kw):
'''call Target by Single
@param conn: client connection
@param targetKey: target ID
@param data: client data
'''
self._lock.acquire()
try:
target = self.getTarget(targetKey)
if not target:
log.err('the command '+str(targetKey)+' not Found on service')
return None
if targetKey not in self.unDisplay:
log.msg("call method %s on service[single]"%target.__name__)
defer_data = target(targetKey,*args,**kw)
if not defer_data:
return None
if isinstance(defer_data,defer.Deferred):
return defer_data
d = defer.Deferred()
d.callback(defer_data)
finally:
self._lock.release()
return d
#end def
#end class
gateLocalService = GateLocalService("gateLocalService")
def gate_localservice_handle(target):
gateLocalService.mapTarget(target)
#end def
# 用戶登陸
@gate_localservice_handle
def gate_localservice_1000(targetKey, clientID, requestData):
response = {}
response[commondata.rev_data] = {}
argument = json.loads(requestData)
name = argument.get("name")
resoult = datautils.check_user(name)
if not resoult:
resoult = datautils.create_mcuser(name)
response[commondata.rev_data] = resoult
else:
response[commondata.rev_data] = resoult
response[commondata.command_id] = targetKey
return json.dumps(response, cls = CJsonEncoder)
#end def
# 所有道具
@gate_localservice_handle
def gate_localservice_2000(targetKey, clientID, requestData):
argument = json.loads(requestData)
userId = argument.get("user_id")
response = {}
resoult = datautils.get_user_item(userId)
if resoult:
response[commondata.state_code] = 0
response[commondata.rev_data] = resoult
else:
response[commondata.state_code] = 1
response[commondata.rev_data] = {}
response[commondata.command_id] = targetKey
return json.dumps(response, cls = CJsonEncoder)
#end def
# 買道具
@gate_localservice_handle
def gate_localservice_3000(targetKey, clientID, requestData):
argument = json.loads(requestData)
user_id = argument.get("user_id")
item_id = argument.get("item_id")
response = {}
response[commondata.rev_data] = {}
resoult = datautils.buy_item(user_id, item_id)
if resoult:
response[commondata.state_code] = 0
else:
response[commondata.state_code] = 1
response[commondata.rev_data] = resoult
response["item_id"] = item_id
response[commondata.command_id] = targetKey
return json.dumps(response, cls = CJsonEncoder)
#end def
# 排名
@gate_localservice_handle
def gate_localservice_4000(targetKey, clientID, requestData):
argument = json.loads(requestData)
userId = argument.get("user_id")
response = {}
resoult = datautils.get_user_ranking(userId)
if resoult:
response[commondata.state_code] = 0
response[commondata.rev_data] = resoult
else:
response[commondata.state_code] = 1
response[commondata.rev_data] = {}
response[commondata.command_id] = targetKey
return json.dumps(response, cls = CJsonEncoder)
#end def
# 上傳戰鬥結果
@gate_localservice_handle
def gate_localservice_5000(targetKey, clientID, requestData):
argument = json.loads(requestData)
userId = argument.get("user_id")
count = argument.get("count")
response = {}
resoult = datautils.set_resoult(userId, count)
if resoult:
response[commondata.state_code] = 0
response[commondata.rev_data] = resoult
else:
response[commondata.state_code] = 1
response[commondata.rev_data] = {}
response[commondata.command_id] = targetKey
return json.dumps(response, cls = CJsonEncoder)
#end def
看到了吧,我們服務器處理客戶端的幾個消息的方法都在這了,其實網絡遊戲我們就可以把它看成一個我們本應該在本地做的工作,通過命令發到服務器去做,這個跟我們現在說的雲有點相似,上面這些方法處理上面都有一個@gate_localservice_handle修飾,我們可以把它看成是一個方法的參數,最後這個參數被gate_localservice_handle調用,就是target這個東西,由 gateLocalService.mapTarget(target)來處理。
好了,這篇就到這,下篇我們看下我們一直以來做的仙劍demo劇情效果和製作方法,最後放出源碼
單機客戶端, 訪問密碼 f3fa
網絡客戶端, 訪問密碼 0ba7
服務器端, 訪問密碼 6107