学习手机网游的开发,说实话,网游开发这个话题太大,我也没啥经验,毕业这么多年参与的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