一套跨平台五子棋网游的开发经历(二)

接上文http://wchrt.blog.51cto.com/8472636/1661524

4、游戏通信协议设计

因为是PC、手机都能玩的游戏,考虑到糟糕的手机网络环境,通信采用客户端单方发起请求,服务器回复的方式,使服务器不用考虑确保手机信号不好或IP变更的情况,类似于web方式。

游戏没有设计固定的用户,采用的是游戏每次向服务器申请一个游戏ID,使用这个游戏ID在互联网上和其他用户对战。于是协议报文设计了两种:普通请求/回复报文gamequest、游戏数据报文nextquest。

#include <iostream>
#include <string>
#include <cstring>

#define NEWID       (char)1
#define NEWGAME     (char)3
#define NEXTSTEP    (char)5
#define GETNEXTSTEP (char)6
#define GAMEEND     (char)10

#define NEWID_FAIL       0
#define NEWID_SECC       1

#define NEWGAME_FAIL     0
#define NEWGAME_ISFIRST  1
#define NEWGAME_ISSEC    2

#define NEXTSTEP_FAIL    1
#define NEXTSTEP_SEC     1

struct gamequest
{
	unsigned int id;
	char type;
	unsigned int data;
};

struct nextstephead
{
	unsigned int id;
	char type;
	char x;
	char y;
	char mac;//游戏数据校验
	short stepno;
};

NEWID:申请一个新的游戏ID的请求与回复

NEWGAME:申请开始游戏的请求与回复

NEXTSTEP:更新游戏对局数据的请求与回复

GETNEXSTEP:获取游戏对局数据的请求与回复

GAMEEND:终止或结束游戏的请求

关于游戏请求与游戏对局时的通信,因为采用的是请求加回复的方式,服务器不能主动通知客户端有新的游戏开始或是对手已经喜下了下一步棋,因此需要客户端主动向服务器获取相应的信息。于是这部分被设计为客户端定时向服务器发送更新数据的请求,服务器一旦接收到请求,就把通过该请求的TCP连接发回去。这样虽然增加了网络的流量,但为了数据的稳定性必须做出牺牲。好的是该协议报文很小,而且因为是对局游戏,就算有几万人同时在玩,实际单位时间的数据量也不会太多,最重要的是在处理并发数据的情况。

5、服务器实现:

    这是最重要最核心的部分。一个高效、稳定的游戏服务器程序直接决定了游戏的体验。在实际的游戏服务器开发中,游戏逻辑与网络通信逻辑可能分工由不同的人员开发。因此,游戏逻辑与网络通信逻辑应在保证效率的情况下尽可能地实现低耦合。我这里虽然是独立开发的,是因为游戏的逻辑很简单,但如果比如去开发一个像GTAOL这样的游戏服务器,本来做网络通信的人想要做出GTA的游戏逻辑那就相当地困难,需要写处理世界、物体、角色,还要和游戏端的逻辑一致,累成狗狗。

所以说游戏的逻辑与网络的通信需要尽可能地独立,就这个五子棋服务器而言,网络通信端使用PPC、select、epoll都和游戏逻辑无关,只要能接收分类并交给游戏逻辑处理,并将游戏逻辑处理好的数据发出即可。该服务器选用的epoll实现的,因篇幅原因,网络通信部分已经在这篇文章中说明清楚:epoll模型的理解封装与应用

关于服务器的游戏逻辑,首先看看我们的服务器要做哪些事情:

1、用户游戏ID的申请与管理

2、对局数据的处理与管理

大致就以上这两种事情。但是因为游戏的客户端数量很多,不同的客户端之间进行对局,必须要清晰地处理与管理这些数据。我这里建立了一个idpool,用于id的储存于申请,以防发生错误给用户分配无效或是重复的id。

对局数据的处理与管理:

在两个用户都有id的情况下,双方都能申请进行游戏。这是服务端要做的就是匹配好这些用户并通知这些用户开始游戏。为方便说明,我先把代码粘上来:

#ifndef  _GAME_H_
#define  _GAME_H_

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<stdlib.h>
#include<list>

#include "ssock.h"
#include "gameprotocol.h"

using namespace std;

#define idpoollength 1000
#define datapoollength 50

//链式IDpool
class idpool
{
    list<unsigned int> ids;
public:
    idpool()
    {
        for(int i=1;i<idpoollength;i++)
        {
            ids.push_back(i);
        }
    }
    unsigned getid()
    {
        if(ids.empty())
        {
            return 0;
        }
        unsigned re=ids.front();
        ids.pop_front();
        return re;
    }
    void freeid(unsigned int x)
    {
        ids.push_front(x);
    }

};

//对局匹配类
class p2p
{
    unsigned int with[idpoollength];
    unsigned int info[idpoollength];
public:
    p2p()
    {
        for(int i=0;i<idpoollength;i++)
        {
            with[i]=i;
        }
    }
    bool ispair(unsigned int x1)
    {
        return with[x1]!=x1&&with[x1]!=0;
    }
    //设置为该id等待匹配
    void setwait(unsigned int x1)
    {
        with[x1]=0;
    }
    //自动匹配函数
    bool makepair(unsigned int x1)
    {
        for(int i=1;i<idpoollength;i++)
        {
            if(with[i]==0&&x1!=i)
            {
                setp2p(x1,i);
                return true;
            }
        }
        return false;
    }
    //设置两id匹配
    void setp2p(unsigned int x1,unsigned x2)
    {
        with[x1]=x2;
        with[x2]=x1;
        info[x1]=1;
        info[x2]=2;
    }
    //释放匹配(单方向)
    void freep2p(unsigned int x1)
    {
        //with[with[x1]]=with[x1];
        with[x1]=x1;
    }
    unsigned int getotherid(unsigned int x1)
    {
        return with[x1];
    }
    unsigned int getp2pinfo(unsigned int x1)
    {
        return info[x1];
    }
};

struct step
{
    unsigned short x;
	unsigned short y;
	short stepno;
};
//对于下棋状态类
class stepstatus
{
    step idstep[idpoollength];
public:
    stepstatus()
    {
        for(int i=0;i<idpoollength;i++)
        {
            idstep[i].stepno=-1;
        }
    }
    bool setstep(unsigned int i,unsigned short xx,unsigned short yy,short sn)
    {
        idstep[i].x=xx;
        idstep[i].y=yy;
        idstep[i].stepno=sn;
        return true;
    }
    step *getstep(unsigned int i)
    {
        return idstep+i;
    }
};

//服务器游戏主逻辑类
class gamemain:public idpool,public p2p,public stepstatus
{
public:
    //报文缓冲数据池,用于自动分配可用的mdata用以存储待发送的数据
    mdata datapool[datapoollength];
    gamemain();
    mdata *getdatainpool();
    //api函数,释放用过的mdata到pool中
    void freedatainpool(mdata *data);

    //数据处理api函数,用于处理网络通信部分传入的数据,这个函数是线程安全的
    mdata *dealdata(mdata *data);
    //以下为游戏数据分类处理的函数
    mdata *newid(mdata *data);
    mdata *newgame(mdata *data);
    bool checkmac(nextstephead *nsh);
    mdata *nextstep(mdata *data);
    mdata *getnextstep(mdata *data);
    mdata *gameend(mdata *data);
};

#endif //_GAME_H_

p2p类:它的作用是用来匹配玩家的。当有客户端申请进行游戏时,服务器会先调用makepair函数来寻找可以进行匹配的另一个玩家,如果找到了合适的玩家,接下来就会调用setp2p简历这两个玩家有对局关系。如果没有匹配到,则会调用setwait等待其他的用户进行匹配。该类使用的数据结构为简单的hash映射。

setpstatus类:用于存放对局数据的类,使用的pool方式,客户端下棋的信息将会储存在这里,用以客户端获取对方下棋的信息。p2p类的info会直接映射到pool的对应下标。不同id的客户端查找数据会相当地迅速。

gamemain类:游戏的主类。给出api函数dealdata用以接收客户端的数据并将处理后的数据返回。

#include "game.h"

gamemain::gamemain()
{
    //:idpool(),p2p(),stepstatus()
    {
        for(int i=0;i<datapoollength;i++)
        {
            datapool[i].len=1;
        }
    }
}

mdata *gamemain::getdatainpool()
{
    for(int i=0;i<datapoollength;i++)
    {
        if(datapool[i].len==1)
        {
            return datapool+i;
        }
    }
    return NULL;
}
void gamemain::freedatainpool(mdata *data)
{
    data->len=1;
}

mdata *gamemain::dealdata(mdata *data)
{
    gamequest *gqh=(gamequest *)data->buf;
printf("this data:type:%d,id:%d\n",gqh->type,gqh->id);
    if(gqh->type==NEWID)
    {
        return newid(data);
    }
    else if(gqh->type==NEWGAME)
    {
        return newgame(data);
    }
    else if(gqh->type==NEXTSTEP)
    {
        return nextstep(data);
    }
    else if(gqh->type==GETNEXTSTEP)
    {
        return getnextstep(data);
    }
    else if(gqh->type==GAMEEND)
    {
        return gameend(data);
    }
}

mdata *gamemain::newid(mdata *data)
{
    mdata *newdata=getdatainpool();
    gamequest *rgqh=(gamequest *)newdata->buf;
    newdata->len=sizeof(gamequest);

    rgqh->type=NEWID;
    rgqh->id=0;
    rgqh->data=getid();
printf("a new id:%u send,len:%u\n",rgqh->data,newdata->len);
    return newdata;
}

mdata *gamemain::newgame(mdata *data)
{
    gamequest *gqh=(gamequest *)data->buf;
    mdata *newdata=getdatainpool();

    gamequest *rgqh=(gamequest *)newdata->buf;
    newdata->len=sizeof(gamequest);
    rgqh->type=NEWGAME;
    if(ispair(gqh->id)||makepair(gqh->id))
    {
        rgqh->id=getotherid(gqh->id);
        rgqh->data=getp2pinfo(gqh->id);
printf("a new game start:%d and %d\n",gqh->id,rgqh->id);
        return newdata;
    }
    setwait(gqh->id);
    rgqh->data=NEWGAME_FAIL;
    return newdata;
}

bool gamemain::checkmac(nextstephead *nsh)
{
    return nsh->mac==(nsh->type^nsh->x^nsh->y^nsh->stepno);
}
mdata *gamemain::nextstep(mdata *data)
{
    nextstephead *nsh=(nextstephead *)data->buf;
    mdata *newdata=getdatainpool();
    newdata->len=0;
printf("nextstep: %d %d %d %d\n",nsh->id,nsh->x,nsh->y,nsh->stepno);
    if(checkmac(nsh))
    {
        if(setstep(nsh->id,nsh->x,nsh->y,nsh->stepno))
        {
            gamequest *rgqh=(gamequest *)newdata->buf;
            newdata->len=sizeof(gamequest);
            rgqh->type=NEXTSTEP;
            rgqh->data=NEXTSTEP_SEC;
            return newdata;
        }

    }
    return newdata;
}

mdata *gamemain::getnextstep(mdata *data)
{
    gamequest *gqh=(gamequest *)data->buf;
    step *sh=getstep(getotherid(gqh->id));
    mdata *newdata=getdatainpool();
    if(sh->stepno!=-1)
    {
        nextstephead *rnsh=(nextstephead *)newdata->buf;
        newdata->len=sizeof(nextstephead);

        rnsh->type=GETNEXTSTEP;
        rnsh->id=getotherid(gqh->id);
        rnsh->x=sh->x;
        rnsh->y=sh->y;
        rnsh->stepno=sh->stepno;
        rnsh->mac=rnsh->type^rnsh->x^rnsh->y^rnsh->stepno;
printf("gnextstep: %d %d %d %d\n",rnsh->id,rnsh->x,rnsh->y,rnsh->stepno);
        sh->stepno=-1;
        return newdata;
    }

    newdata->len=0;
    return newdata;
}

mdata *gamemain::gameend(mdata *data)
{
    gamequest *gqh=(gamequest *)data->buf;
    mdata *newdata=getdatainpool();
    freep2p(gqh->id);
    newdata->len=0;
    return newdata;
}

这里的dealdata是线程安全的,方便网络通信部分用的各种方式调用。因为这该五子棋服务器的游戏逻辑的主要功能就是数据的存储转发,没有什么需要在后台一直运行的要求。因此该程序耦合很低,使用很简答,只需要创建、调用处理函数、获取处理结果即可。

6、网络游戏功能实现

    现在回到游戏客户端,前面已经实现的单机游戏的功能。现在要做的就是加入网络功能,其实就是把单机的ai部分接到服务器上。

首先是游戏id的获取。通过向服务器发送NEWID请求。会受到服务器分配的id。将这个id作为自己的游戏id,在告知服务器退出游戏或是服务器在长时间未受到该id的情况下自动释放前都有效。

图中两个客户端分别分配到id2与3。

当客户端分配到id后,就可以向服务器发起游戏匹配请求NEWGAME。为了防止匹配不到玩家,设置发送匹配请求最多只维持一分钟,在一分钟结束后,客户端向服务器发出停止匹配的请求。

当有两个客户端在这交叉的时段进行进行匹配,便可能匹配在一起开始游戏。

游戏匹配成功后,客户端将收到服务器发过来的对局基础信息,包括了对手id、先手还是后手。当游戏开始后,先手的下棋然后将数据提交到服务器,又后手的更新数据,然后照这样依次循环下去直到游戏结束。

    id2与id3匹配到了一起。

在游戏结束时,赢的一方会显示胜利,输的显示失败,双方都不再更新数据。退出对局后便能开始下继续匹配游戏。

游戏客户端需要注意的是对局数据的校验还有sock链接的问题。当在糟糕的网络环境下,客户端不应定能获取到正确的数据,因此要根据数据包总的mac进行校验。而tcp链接再侧重状态下将时断时续。因此要注意当连接中断后及时与服务器进行重连。

还有关于跨平台的问题。我将socket封装成类,不管是win还是linux都是同样的调用方式。在sock类中用ifdef区分开两个系统的不同api调用。

以下是客户端跨平台sock的封装:

#ifndef  _MSOCK_H_
#define  _MSOCK_H_

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>

#ifdef WIN32
#include<winsock2.h>
#else
#include<fcntl.h>
#include<sys/ioctl.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netdb.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/types.h>
#define SOCKET int
#define SOCKET_ERROR -1
#define INVALID_SOCKET -1
#endif

using namespace std;

static int networkinit()
{
#ifdef WIN32
	WSADATA wsadata={0};
	return WSAStartup(MAKEWORD(1,0),&wsadata);
#else
	return 0;
#endif
}
static int networkclose()
{
#ifdef WIN32
	return WSACleanup();
#endif

	return 0;
}

class msock_tcp
{
public:
    SOCKET sock;
	int info;
    sockaddr_in addr;
    msock_tcp()
    {
        newsocket();
        addr.sin_family=AF_INET;
    }

	void newsocket()
	{
		sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
		if(sock==INVALID_SOCKET)
		{
			puts("socket build error");
			exit(-1);
		}
	}

	void setioctl(bool x)
	{
#ifdef WIN32
		if(!x)
		{
			return;
		}
		unsigned long ul = 1;
		ioctlsocket(sock, FIONBIO, (unsigned long*)&ul);
#else
		fcntl(sock, F_SETFL, O_NONBLOCK);
#endif
}
    bool setip(string ip)
    {
		//解析域名IP
		hostent *hname=gethostbyname(ip.c_str());
		if(!hname)
		{
			puts("can‘t find address");
			return false;
		}//puts(inet_ntoa(addr.sin_addr));
#ifdef WIN32
		addr.sin_addr.S_un.S_addr=*(u_long *)hname->h_addr_list[0];
#else
		addr.sin_addr.s_addr=*(u_long *)hname->h_addr_list[0];
#endif
		return true;
    }
    void setport(int port)
    {
        addr.sin_port=htons(port);
    }
    int mconnect()
    {
        return connect(sock,(sockaddr *)&addr,sizeof(addr));
    }
    int msend(const char *data,const int len)
    {
		info=send(sock,data,len,0);
        if(info==SOCKET_ERROR)
		{
			mclose();
			newsocket();
			mconnect();
			info=send(sock,data,len,0);
		}
		return info;
    }
    int msend(const string data)
    {
        return msend(data.c_str(),data.length());
    }
    int mrecv(char *data,int len)
    {
        return recv(sock,data,len,0);
    }
    int mrecv(char *data)
    {
        return recv(sock,data,2047,0);
    }
	int mclose()
	{
	#ifdef WIN32
		return closesocket(sock);
	#else
		return close(sock);
	#endif
	}
};

#endif

网络匹配类:

#ifndef  _NETWORKSCENE_H_
#define  _NETWORKSCENE_H_

#include "cocos2d.h"
#include "NetGameMain.h"
USING_NS_CC;

class NETWorkScene:public Layer
{
public:
	msock_tcp *sock;
	char rdata[2048];
	int rlen;
	unsigned int gameid;
	unsigned int gameid2;
	CCLabelTTF* gameinfo;

	virtual bool init();
	//从服务器中获取id
	bool getidonserver();
	void showgameid();
	//发起匹配游戏请求
	bool findplayer();

	void findbutton(Ref* pSender);

	//开始新游戏,进入对局场景
	bool newgamestart(bool ismyround);

	NETGameMain *gamemain;

	//数据以及ui更新
	updatequest upq;
	void update_quest();
	void update(float delta);

	CREATE_FUNC(NETWorkScene);
};

#endif // _NETWORKSCENE_H_
#include "NetWorkScene.h"

bool NETWorkScene::init()
{
	if(networkinit())
	{
		CCLOG("network init fail");
		return false;
	}
	sock=new msock_tcp;
	sock->setioctl(true);
	//我用于测试的centos服务器
	sock->setip("wchrter.oicp.net");//127.0.0.1
	sock->setport(5940);
	//sock->setip("127.0.0.1");
	//sock->setport(5000);

	if(sock->mconnect()>=0)
	{
		CCLOG("sock connect error");
		//this->removeFromParentAndCleanup(true);
	}
	else
	{
		CCLOG("sock connect secc");
	}

	gameid=0;

	auto fdItem = MenuItemImage::create(
		"net_find1.png",
		"net_find2.png",
		CC_CALLBACK_1(NETWorkScene::findbutton, this));
	fdItem->setScale(2.0);
	// create menu, it‘s an autorelease object
	auto menu = Menu::create(fdItem, NULL);
	winsize=Director::sharedDirector()->getWinSize();
	menu->setPosition(ccp(winsize.x/2,winsize.y/2));
	this->addChild(menu, 1);

	gameinfo = CCLabelTTF::create("", "Arial", 30);
	gameinfo->setPosition(ccp(winsize.x/4, winsize.y/2));  
	this->addChild(gameinfo);  

	scheduleUpdate();
	return true;
}
bool NETWorkScene::getidonserver()
{
	gamequest quest;
	quest.id=0;
	quest.type=NEWID;

	if(SOCKET_ERROR==sock->msend((char *)&quest,sizeof(quest)))
	{
		CCLOG("getidonserver error");
		return false;
	}

	return true;
}
void NETWorkScene::showgameid()
{
	gameinfo->setString("your\ngame id:\n"+inttostring(gameid));
}
bool NETWorkScene::findplayer()
{
	if(gameid==0)
	{
		if(!getidonserver())
		{
			return false;
		}
		return false;
	}

	gamequest quest;
	quest.id=gameid;
	quest.type=NEWGAME;
	upq.set(quest,30);
	return true;
}

void NETWorkScene::findbutton(Ref* pSender)
{
	findplayer();
}

bool NETWorkScene::newgamestart(bool ismyround)
{
	upq.settle(0);

	NETGameMain *newgame=NETGameMain::create();
	newgame->setgameid(gameid,gameid2);
	newgame->setsock(sock);
	newgame->setismyround(ismyround);
	Point winsize=Director::sharedDirector()->getWinSize();
	newgame->setScale(winsize.y/defaultwinsize);

	auto director = Director::getInstance();
	auto scene = Scene::create();
	scene->addChild(newgame);
	director->pushScene(scene);

	return true;
}
void NETWorkScene::update_quest()
{
	if(upq.end())
	{
		return ;
	}
	if(!upq.push())
	{
		return;
	}

	if(SOCKET_ERROR==sock->msend((char *)&upq.quest,sizeof(upq.quest)))
	{
		CCLOG("socket error");
	}
	return;
}
void NETWorkScene::update(float delta)
{
	//CCLOG("JB");
	update_quest();

	rlen=sock->mrecv(rdata);
	if(rlen>0)
	{
		gamequest *gqh=(gamequest *)rdata;
		 CCLOG("%d: %d %02x %d\n",rlen,gqh->id,gqh->type,gqh->data);
		if(gqh->type==NEWID)
		{
			gameid=gqh->data;
			showgameid();
		}
		else if(gqh->type==NEWGAME)
		{
			gameid2=gqh->id;
			if(gqh->data==NEWGAME_ISFIRST)
			{
				newgamestart(true);
			}
			else if(gqh->data==NEWGAME_ISSEC)
			{
				newgamestart(false);
			}
			else
			{
				CCLOG("findplayer fail");
			}
		}
	}
	else
	{
		//CCLOG("no message");
	}
}

网络游戏对局类:

#ifndef  _NETGAMEMAIN_H_
#define  _NETGAMEMAIN_H_
#include "cocos2d.h"
#include "ChessMain.h"
#include "msock.h"
#include "gameprotocol.h"
USING_NS_CC;

#define defaulttoolwidth 200.0
#define defaulttoolheight 100.0
#define updatetime 20

//更新类
class updatequest
{
	int timecnt;
	int timelimit;
public:
	gamequest quest;
	updatequest()
	{
		timecnt=0;
		timelimit=0;
	}
	void set(gamequest q,int tle=5)
	{
		quest=q;
		timelimit=tle*updatetime;
		timecnt=0;
	}
	void settle(int tle)
	{
		timelimit=tle;
	}
	bool end()
	{
		if(timelimit<0)
		{
			return false;
		}
		if(timecnt<timelimit)
		{
			return false;
		}
		return true;
	}
	bool push(int pt=1)
	{
		timecnt+=pt;
		if(timecnt%updatetime==0)
		{
			return true;
		}
		return false;
	}
};

//游戏菜单类
class NETGameEndTool:public Layer
{
public:
	NETGameEndTool(int type);
	bool init(int type);

	void gameEnd(Ref* pSender);
};

class NETGameMain:public ChessMain
{
public:
	virtual bool init();
	virtual void onEnter();

	msock_tcp *sock;
	char rdata[2048];
	int rlen;
	//自己id与对局者id
	unsigned int gameid;
	unsigned int gameid2;
	CCLabelTTF* idinfo;
	CCLabelTTF* roundinfo;

	void setgameid(unsigned int x,unsigned int y);
	void setsock(msock_tcp *s);
	void setismyround(bool x);

	//当前是否为自己回合
	bool ismyround;

	virtual bool onTouchBegan(Touch *touch, Event *unused_event);

	bool isnetsetp;
	void nextnetstep(int x,int y);
	//胜利检测
	void checkwin();

	//数据与ui更新
	updatequest upq;
	void update_quest();
	void update(float delta);

	CREATE_FUNC(NETGameMain);
};

string inttostring(int num);

#endif //_AIGAMEMAIN_H_

实现代码:

#include "NetWorkScene.h"

bool NETWorkScene::init()
{
	if(networkinit())
	{
		CCLOG("network init fail");
		return false;
	}
	sock=new msock_tcp;
	sock->setioctl(true);
	//我用于测试的centos服务器
	sock->setip("wchrter.oicp.net");//127.0.0.1
	sock->setport(5940);
	//sock->setip("127.0.0.1");
	//sock->setport(5000);

	if(sock->mconnect()>=0)
	{
		CCLOG("sock connect error");
		//this->removeFromParentAndCleanup(true);
	}
	else
	{
		CCLOG("sock connect secc");
	}

	gameid=0;

	auto fdItem = MenuItemImage::create(
		"net_find1.png",
		"net_find2.png",
		CC_CALLBACK_1(NETWorkScene::findbutton, this));
	fdItem->setScale(2.0);
	// create menu, it‘s an autorelease object
	auto menu = Menu::create(fdItem, NULL);
	winsize=Director::sharedDirector()->getWinSize();
	menu->setPosition(ccp(winsize.x/2,winsize.y/2));
	this->addChild(menu, 1);

	gameinfo = CCLabelTTF::create("", "Arial", 30);
	gameinfo->setPosition(ccp(winsize.x/4, winsize.y/2));  
	this->addChild(gameinfo);  

	scheduleUpdate();
	return true;
}
bool NETWorkScene::getidonserver()
{
	gamequest quest;
	quest.id=0;
	quest.type=NEWID;

	if(SOCKET_ERROR==sock->msend((char *)&quest,sizeof(quest)))
	{
		CCLOG("getidonserver error");
		return false;
	}

	return true;
}
void NETWorkScene::showgameid()
{
	gameinfo->setString("your\ngame id:\n"+inttostring(gameid));
}
bool NETWorkScene::findplayer()
{
	if(gameid==0)
	{
		if(!getidonserver())
		{
			return false;
		}
		return false;
	}

	gamequest quest;
	quest.id=gameid;
	quest.type=NEWGAME;
	upq.set(quest,30);
	return true;
}

void NETWorkScene::findbutton(Ref* pSender)
{
	findplayer();
}

bool NETWorkScene::newgamestart(bool ismyround)
{
	upq.settle(0);

	NETGameMain *newgame=NETGameMain::create();
	newgame->setgameid(gameid,gameid2);
	newgame->setsock(sock);
	newgame->setismyround(ismyround);
	Point winsize=Director::sharedDirector()->getWinSize();
	newgame->setScale(winsize.y/defaultwinsize);

	auto director = Director::getInstance();
	auto scene = Scene::create();
	scene->addChild(newgame);
	director->pushScene(scene);

	return true;
}
void NETWorkScene::update_quest()
{
	if(upq.end())
	{
		return ;
	}
	if(!upq.push())
	{
		return;
	}

	if(SOCKET_ERROR==sock->msend((char *)&upq.quest,sizeof(upq.quest)))
	{
		CCLOG("socket error");
	}
	return;
}
void NETWorkScene::update(float delta)
{
	//CCLOG("JB");
	update_quest();

	rlen=sock->mrecv(rdata);
	if(rlen>0)
	{
		gamequest *gqh=(gamequest *)rdata;
		 CCLOG("%d: %d %02x %d\n",rlen,gqh->id,gqh->type,gqh->data);
		if(gqh->type==NEWID)
		{
			gameid=gqh->data;
			showgameid();
		}
		else if(gqh->type==NEWGAME)
		{
			gameid2=gqh->id;
			if(gqh->data==NEWGAME_ISFIRST)
			{
				newgamestart(true);
			}
			else if(gqh->data==NEWGAME_ISSEC)
			{
				newgamestart(false);
			}
			else
			{
				CCLOG("findplayer fail");
			}
		}
	}
	else
	{
		//CCLOG("no message");
	}
}

游戏客户端就ok了。

7、平台移植:

整个项目搞定了就是爽哈,平台移植便是非常轻松的事情,只要自己写的代码没作死,用特定系统或编译器的api或是语法与库,平台移植就相当得快速。尤其是cocos2dx引擎,早已把移植的工作全都准备好了,只需要自己调调错即可(回想起了以前自己一个人把c++往android上交叉编译,叫那个苦啊)。

控制台傻瓜编译:

编译成功。

用手机打开游戏客户端,获取到的id为5。(联想P780,你值得信赖的充电宝手机)

手机与客户端实现网络游戏对局。

哈哈,手机也能和电脑一起联网玩游戏了。

这次做的这套五子棋网络游戏还有很多欠缺的东西,客户端还缺乏一定的容错能力,用户体验也不够人性化。在网络方面,通信的方式并不适合时效性要求较高的游戏,像一些及时对战游戏,请求/回复的方式需要很频繁的请求才能保证时效。这样也没错,糟糕的网络环境也不能用来玩这些游戏。自己对自己的美工挺满意的,嘿(哪里有美工啊?这个图片都算不上好不好)。

总的来说,这是一次很棒的开发经历,希望毕业以后也能有这样的闲功夫,去做自己真正想做的。

时间: 2024-10-12 20:35:26

一套跨平台五子棋网游的开发经历(二)的相关文章

一套跨平台五子棋网游的开发经历

闲来无事,因自己想要在服务器开发方面进行更深入的学习,积累更丰富的经验.决定写一套网络游戏的c/s. 因为主要目的是服务器的开发,因此游戏我选用规则较为简单.画面特效没有要求的回合制游戏:五子棋.我曾经在刚接触编程的时候自己在控制台下做过这个游戏,当时写的ai特nb我自己根本下不赢他.确定是制作五子棋了, 但是还要满足跨平台的特性,毕竟移动互联时代,得终端者得天下.游戏做成全平台才能更好的将各种玩家聚集在一起.跨平台?b/s是人们通常会第一个想到的跨平台方式,的确现在市面上有很多基于b/s的页游

一套跨平台五子棋网游的开发经历(一)

闲来无事,因自己想要在服务器开发方面进行更深入的学习,积累更丰富的经验.决定写一套网络游戏的c/s. 因为主要目的是服务器的开发,因此游戏我选用规则较为简单.画面特效没有要求的回合制游戏:五子棋.我曾经在刚接触编程的时候自己在控制台下做过这个游戏,当时写的ai特nb我自己根本下不赢他.确定是制作五子棋了, 但是还要满足跨平台的特性,毕竟移动互联时代,得终端者得天下.游戏做成全平台才能更好的将各种玩家聚集在一起.跨平台?b/s是人们通常会第一个想到的跨平台方式,的确现在市面上有很多基于b/s的页游

Unity网游开发生存指南—蒸汽之城

Posted by amy on 2013.03.07 文 / 王楠(梦加网络 游戏制作人) 前段时间关于Unity是否适合国内手游/网游创业团队的讨论非常火爆,本文从<蒸汽之城>的开发历程谈起,对于国内网游团队是否应该选择Unity引擎,以及如何解决使用Unity开发网游时遇到的各种主要问题进行讨论. 厦门梦加的蒸汽之城  <蒸汽之城>是厦门梦加网络的第一款作品,使用Unity引擎制作的蒸汽朋克风3D实时战斗MMORPG页游.游戏拥有幻想工业时代恢弘苍凉的场景:丰富的种族.职业和

转游戏开发做的第一款手机网游的经历和体会

转游戏开发大半年以来,做过的游戏不多,刚开始就写单机版的游戏,不过也不多.后来就接触手机版网游,第一款游戏就是超级英雄,目前这款游戏还在升级维护当中,首次发布就是五月初,第一个月的收入就过千万.关于这款游戏直接看截图效果吧! 以上就是有关该款游戏的截图,有喜欢这款游戏可以下载试玩下,有想学的也可以下载看看交流下经验. 我以前是学C#,主要做桌面类型的软件会的语言也不多,也用过C++.在去年年底就接触到了cocos2d-x,了解到它是跨平台的然后就决心转手游开发了,初期阶段就看书,自己做些东西,也

决战JavaScript服务端网游开发

在经过三年多的手游客户端学习与开发练习后,我发现仅满足于前端仅能在游戏开发中占居一席之地.要想全面深入理解手游开发逻辑,特别是如今的网络时代的手游数据管理及通信逻辑,必须掌握一定的服务器端开发技术.作一个十分相似的比较,就像Web开发,只懂得前端技术(HTML/HTML5/CSS/DOM/BOM/JQUERY/JS),也只能是具备了一条腿走路的能力,而两条腿走路则更是每一个程序员所期望的.因此,作为一名优秀的Web前端程序员最好对于 服务器端开发技术也有一定程度的了解. 值得欣喜的是,相当多的程

张左峰的歪理邪说 之 对于瀑布式开发和敏捷开发在网游开发中的应用

本周小孩送回姥爷姥姥家,终于有时间更新一下自己的微博了,三年没更新了,我真TMD懒惰!我错了....这次努力更新一些东西 有些人问我,为啥不去一些大点的微博站写这些内容.我觉得没有必要啊,反正早晚都会被搜索引擎爬到,哪里都一样. 本文纯理论,是一个思想指导,你完全照搬,你就输了....尽可能写的雅俗共赏一些,一起研究学习进步! 正文开始.....(哪那么多废话...果然人老了) 首先,我们要明确两个概念 瀑布式开发:瀑布式,顾名思义,自上而下,连绵不绝,稳步推进.瀑布式开发,是一个我们最常规的开

VC++实战《星际传奇》网游课程第一部分网络游戏开发基础篇(游戏引擎设计)

本系列课程基于最新的DirectX11接口进行深入细致的讲解,内容涉及D3D11原理与应用.DirectInput.DirectSound等: 教程中专门针对新兴的D3D11接口展开深入的讲解,详细讲解了D3D11渲染管线.DirectComputer(参看<VC++游戏开发系列之Directcomputer并行计算原理与实践--DX11游戏实战开发>).Tessellation.多线程渲染.Shader动态链接等新内容.新知识.并且基于这些内容的基础,更进一步讲解了光照模型原理及实现.高级的

C++网络游戏零基础开发视频教程(300+课时,MINI快跑、水果忍者、DirectX技术、天鹰教3D网游)

C++网络游戏零基础开发视频教程(300+课时,MINI快跑.水果忍者.DirectX技术.天鹰教3D网游)下载联系QQ:1026270010 距离二十世纪八十年代c++语言的诞生,到如今已经有三十多年的历史,随着c++语言的发展,它被应用于在越来越多的领域.C++是一个多泛型的编程语言,它既可以面向过程,也是一门面向对象的语言.C++是一门使用非常广泛的计算机编程语言,因此它受到了越来越多程序员的亲睐.C++相关专家介绍:c++的就业领域很广泛,相对于java和其他语言来说有很多优点,很多大公

[盘点]现今热门的h5网游

各位好久不见,过年的休息时间已经结束,大家休息了一周,又要开始新的一年的奋斗了!于是小编从回来的第一天就开始花时间自己整理了一篇文章,所有各位是不是应该夸一夸小编呢?因为小编又要开始分享“干货”了.(都是小编自己的一些理解,若有错误或不足,请提出) 各位在过年的这一段时间里,都干了些啥呢? 别的不说,肯定有很多单身的童鞋们被长辈们同时逼问事业与爱情上的种种事情,当然小编也是一样,然后各种躲着不肯见人,于是无聊地掏出自己的手机,打开游戏,思考人生. (强行回归话题,感觉没啥关系,其实是扯淡) 说到