闲来无事,因自己想要在服务器开发方面进行更深入的学习,积累更丰富的经验。决定写一套网络游戏的c/s。
昌图网站建设公司创新互联,昌图网站设计制作,有大型网站制作公司丰富经验。已为昌图1000多家提供企业网站建设服务。企业网站搭建\成都外贸网站制作要多少钱,请找那个售后服务好的昌图做网站的公司定做!
因为主要目的是服务器的开发,因此游戏我选用规则较为简单、画面特效没有要求的回合制游戏:五子棋。我曾经在刚接触编程的时候自己在控制台 下做过这个游戏,当时写的ai特nb我自己根本下不赢他。确定是制作五子棋了, 但是还要满足跨平台的特性,毕竟移动互联时代,得终端者得天下。游戏做成全平台才能更好的将各种玩家聚集在一起。跨平台?b/s是人们通常会第一个想到的 跨平台方式,的确现在市面上有很多基于b/s的页游,大部分使用的是flash作为游戏引擎。但手机上很少有人使用浏览器玩游戏。(其实根本不会 flash,html也烂得很,曾经给别人用php做的数据管理网站根本就没有像样的界面)于是选择了c++的跨平台游戏引擎cocos2dx,这引擎简 单好用,而且因为是c++作为游戏逻辑,移植特方便,以前也用过这个引擎(某比赛)。最终选用的版本是cocos2d-x 3.4。
既然是网络游戏的服务器,那么就得高效,而且是在linux下,因此我选epoll模型进行服务端的开发,epoll的部分写在这篇文章里:epoll模型的理解与封装实现,使用的linux系统为CENT OS 6.4,内核为linux2.6。
关于游戏开发步骤的思考:
按照自己以前习惯的套路来说,通信方式与协议的设计应该是放在首位的,然后是服务器、再到客户端(没有美工)。
而自己以前曾经玩到很多的单机游戏,更新版本后,游戏便增加了网络游戏功能。这似乎说明了很多游戏与网络协议之间是相互独立的。甚至网络协议是根据实际的游戏逻辑设计的,而不是游戏根据协议来设计自身的逻辑。
最终决定先把单机的版本做出来。于是制定了如下的开发流程:
1、游戏的算法与数据结构设计与实现
2、游戏交互设计与实现
3、单机游戏的实现
4、游戏通信协议设计
5、服务器实现(不可忽略掉的重点,自己写游戏的目的)
6、网络游戏功能实现
7、平台移植
1、游戏的算法与数据结构设计与实现:
五子棋这个游戏是一个二维平面上的游戏,我们将棋盘看做一个数组,每一个格子的状态分为两种:没棋和有棋,有棋因不同玩家而区别(数量不限,可直接作为多人多子棋的游戏基类)
代码:
- //Chess.h
- #ifndef _CHESS_H_
- #define _CHESS_H_
- #include "cocos2d.h"
- USING_NS_CC;
- //下棋坐标状态结构体
- struct Chesspos
- {
- int x,y;
- int player;//该步数所属玩家
- Chesspos(){};
- Chesspos(int px,int py,int pp)
- {
- x=px;
- y=py;
- player=pp;
- }
- };
- class Chessway
- {
- Chesspos *way;//路径数组
- int totallen;//总长度
- int len;//当前步数
- public:
- Chessway(int totalnum);
- ~Chessway(void);
- void setempty();
- bool addway(int x,int y,int player);//添加步数
- int getstep();
- Chesspos getnow();
- };
- class Chess
- {
- public:
- Chess(int width,int heigh,int winlen=5,int playernum=2);
- ~Chess(void);
- int **board;
- int w,h;
- int pnum; //palyer num
- int wlen; //how number can win
- Chessway *way;
- int playercnt;//player start at 1
- bool isgameend;
- bool init(int width,int heigh,int winlen=5,int playernum=2);
- void exit();
- void restart();
- bool nextstep(Chesspos np);//下棋,自动判断玩家
- bool nextstep(int x,int y);
- int getstatus(int x,int y);//获取游戏状态
- bool checklen(int x,int y);
- int checkwin();//判断游戏是否结束并返回胜利玩家
- };
- #endif //_CHESS_H_
检测胜利的逻辑很简单:找到一个下有棋的位置,检查这个位置下、右、左下、右下是否有连续相等的5个棋,即为游戏胜利。游戏一旦胜利是不可以继续下棋的,所以只会有一个玩家胜利。下面给出判断代码:
- //Chess.cpp
- //胜利检测代码
- bool Chess::checklen(int x,int y)
- {
- for(int i=1;i
- {
- if(x+i>=w)
- {
- break;
- }
- if(board[x+i][y]!=board[x][y])
- {
- break;
- }
- if(i==wlen-1)
- {
- return true;
- }
- }
- for(int i=1;i
- {
- if(y+i>=h)
- {
- break;
- }
- if(board[x][y+i]!=board[x][y])
- {
- break;
- }
- if(i==wlen-1)
- {
- return true;
- }
- }
- for(int i=1;i
- {
- if(x+i>=w||y+i>=h)
- {
- break;
- }
- if(board[x+i][y+i]!=board[x][y])
- {
- break;
- }
- if(i==wlen-1)
- {
- return true;
- }
- }
- for(int i=1;i
- {
- if(x-i<0||y+i>=h)
- {
- break;
- }
- if(board[x-i][y+i]!=board[x][y])
- {
- break;
- }
- if(i==wlen-1)
- {
- return true;
- }
- }
- return false;
- }
- int Chess::checkwin()
- {
- for(int i=0;i
- {
- for(int j=0;j
- {
- if(board[i][j])
- {
- if(checklen(i,j))
- {
- isgameend=true;
- return board[i][j];
- }
- }
- }
- }
- return 0;
- }
#p#
2、游戏交互设计与实现
涉及到游戏交互,这里就要使用到游戏引擎了。首先需要把游戏的一些图片资源大致搞定,这里用画图这画了几个不堪入目的图片资源:
别看这画的丑,我可是用鼠标和window自带的画图画出来的,到时候在游戏中看起来是毫无违和感的(笔者小学就会画H漫了)。
这里就要用到cocos2dx的东西了。首先为每一个下棋的格子设计一个个块状的节点,然后设计游戏主体布景层:
- class ChessNode:public Node
class ChessMain:public Layer
作为游戏棋盘,每一个格子的形态都是一样的,我只需要将它们拼接成矩阵就成了一个完整的棋盘。因此在游戏布景层里,我开了一个Vector 的ChessNode,将其依次紧凑地排列在屏幕上。在游戏初始状态时,chess_1.png、chess_2.png是不会显示的,如图(截图我直接 使用现成游戏的截图):
这样的棋盘看起来是不是很没有违和感?
当下棋后,就可以把对应的棋图显示出来:
后面发现好像真正的下棋是下在十字交叉处的。。
这部分的注意事项主要就在于触摸检测与棋盘屏幕大小。触摸的话计算相对棋盘布景层的坐标可以得出下棋的位置。棋盘就以静态值480px为标准,在其他地方调用的时候缩放即可。
- #ifndef _CHESSMAIN_H_
- #define _CHESSMAIN_H_
- #include "cocos2d.h"
- #include "Chess.h"
- USING_NS_CC;
- #define defaultwinsize 480.0
- #define chesspicsize 50.0
- static Point winsize;
- class ChessNode:public Node
- {
- public:
- ChessNode(int playernum=2);
- Vector
chesspicarr; - Sprite * basepic;
- };
- class ChessMain:public Layer
- {
- public:
- Chess *chessdata;
- Vector
basenode; - virtual bool init();
- //virtual void onEnter();
- void restart();
- void updateone(int x,int y);
- void updateall();
- bool nextstep(int x,int y);
- int checkwin();
- CREATE_FUNC(ChessMain);
- };
- #endif //_CHESSMAIN_H_
- #include "ChessMain.h"
- ChessNode::ChessNode(int playernum)
- {
- basepic=Sprite::create("chess_base_1.png");
- basepic->setAnchorPoint(ccp(0,0));
- this->addChild(basepic);
- char addname[]="chess_1.png";
- for(int i=0;i
- {
- addname[6]='0'+i+1;
- auto newsprite=Sprite::create(addname);
- chesspicarr.pushBack(newsprite);
- chesspicarr.back()->setAnchorPoint(ccp(0,0));
- this->addChild(chesspicarr.back());
- }
- }
- bool ChessMain::init()
- {
- winsize=Director::sharedDirector()->getWinSize();
- //默认值棋盘
- chessdata=new Chess(15,15);
- for(int i=0;i
w;i++) - {
- for(int j=0;j
h;j++) - {
- basenode.pushBack(new ChessNode());
- basenode.back()->setScale((defaultwinsize/chessdata->h)/chesspicsize);
- basenode.back()->setPosition(
- ccp(defaultwinsize/chessdata->w*i,defaultwinsize/chessdata->h*j)
- );
- basenode.back()->setAnchorPoint(ccp(0,0));
- this->addChild(basenode.back());
- }
- }
- restart();
- return true;
- }
- /*
- void ChessMain::onEnter()
- {
- ;
- }
- */
- void ChessMain::restart()
- {
- chessdata->restart();
- updateall();
- }
- void ChessMain::updateone(int x,int y)
- {
- for(int i=0;i
pnum;i++) - {
- if(chessdata->getstatus(x,y)==i+1)
- {
- basenode.at(x*chessdata->w+y)->
- chesspicarr.at(i)->setVisible(true);
- }
- else
- {
- basenode.at(x*chessdata->w+y)->
- chesspicarr.at(i)->setVisible(false);
- }
- }
- }
- void ChessMain::updateall()
- {
- for(int i=0;i
w;i++) - {
- for(int j=0;j
h;j++) - {
- updateone(i,j);
- }
- }
- }
- bool ChessMain::nextstep(int x,int y)
- {
- if(chessdata->isgameend)
- {
- return false;
- }
- if(!chessdata->nextstep(x,y))
- {
- return false;
- }
- updateone(x,y);
- checkwin();
- return true;
- }
- int ChessMain::checkwin()
- {
- return chessdata->checkwin();
- }
- /*
- bool ChessMain::onTouchBegan(Touch *touch, Event *unused_event)
- {
- Point pos=convertTouchToNodeSpace(touch);
- if(pos.x>defaultwinsize||pos.y>defaultwinsize)
- {
- return false;
- }
- int x=chessdata->w*(pos.x/defaultwinsize);
- int y=chessdata->h*(pos.y/defaultwinsize);
- return nextstep(x,y);
- }
- */
这里的触摸函数会由以后ChessMain的子类重写。
#p#
3、单机游戏的实现
单机游戏,只需写好对手的AI逻辑即可。幸好是五子棋不是围棋,AI很好写,能很快计算出必胜态。由于自己主要目的是写网络端。因此我把单机功能实现后并没有写AI,把接口留着的,只接了一个随机函数,等以后有闲情把AI逻辑加上。
总的来说这部分就是加上了进入游戏前的菜单以及单机游戏的选项和游戏结束的对话框:
- #include "ChessMain.h"
- ChessNode::ChessNode(int playernum)
- {
- basepic=Sprite::create("chess_base_1.png");
- basepic->setAnchorPoint(ccp(0,0));
- this->addChild(basepic);
- char addname[]="chess_1.png";
- for(int i=0;i
- {
- addname[6]='0'+i+1;
- auto newsprite=Sprite::create(addname);
- chesspicarr.pushBack(newsprite);
- chesspicarr.back()->setAnchorPoint(ccp(0,0));
- this->addChild(chesspicarr.back());
- }
- }
- bool ChessMain::init()
- {
- winsize=Director::sharedDirector()->getWinSize();
- //默认值棋盘
- chessdata=new Chess(15,15);
- for(int i=0;i
w;i++) - {
- for(int j=0;j
h;j++) - {
- basenode.pushBack(new ChessNode());
- basenode.back()->setScale((defaultwinsize/chessdata->h)/chesspicsize);
- basenode.back()->setPosition(
- ccp(defaultwinsize/chessdata->w*i,defaultwinsize/chessdata->h*j)
- );
- basenode.back()->setAnchorPoint(ccp(0,0));
- this->addChild(basenode.back());
- }
- }
- restart();
- return true;
- }
- /*
- void ChessMain::onEnter()
- {
- ;
- }
- */
- void ChessMain::restart()
- {
- chessdata->restart();
- updateall();
- }
- void ChessMain::updateone(int x,int y)
- {
- for(int i=0;i
pnum;i++) - {
- if(chessdata->getstatus(x,y)==i+1)
- {
- basenode.at(x*chessdata->w+y)->
- chesspicarr.at(i)->setVisible(true);
- }
- else
- {
- basenode.at(x*chessdata->w+y)->
- chesspicarr.at(i)->setVisible(false);
- }
- }
- }
- void ChessMain::updateall()
- {
- for(int i=0;i
w;i++) - {
- for(int j=0;j
h;j++) - {
- updateone(i,j);
- }
- }
- }
- bool ChessMain::nextstep(int x,int y)
- {
- if(chessdata->isgameend)
- {
- return false;
- }
- if(!chessdata->nextstep(x,y))
- {
- return false;
- }
- updateone(x,y);
- checkwin();
- return true;
- }
- int ChessMain::checkwin()
- {
- return chessdata->checkwin();
- }
- /*
- bool ChessMain::onTouchBegan(Touch *touch, Event *unused_event)
- {
- Point pos=convertTouchToNodeSpace(touch);
- if(pos.x>defaultwinsize||pos.y>defaultwinsize)
- {
- return false;
- }
- int x=chessdata->w*(pos.x/defaultwinsize);
- int y=chessdata->h*(pos.y/defaultwinsize);
- return nextstep(x,y);
- }
- */
现在一个能玩的游戏已经完成,接下来是重点的网络部分。
#p#
4、游戏通信协议设计
因为是PC、手机都能玩的游戏,考虑到糟糕的手机网络环境,通信采用客户端单方发起请求,服务器回复的方式,使服务器不用考虑确保手机信号不好或IP变更的情况,类似于web方式。
游戏没有设计固定的用户,采用的是游戏每次向服务器申请一个游戏ID,使用这个游戏ID在互联网上和其他用户对战。于是协议报文设计了两种:普通请求/回复报文gamequest、游戏数据报文nextquest。
- #include
- #include
- #include
- #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连接 发回去。这样虽然增加了网络的流量,但为了数据的稳定性必须做出牺牲。好的是该协议报文很小,而且因为是对局游戏,就算有几万人同时在玩,实际单位时间的 数据量也不会太多,最重要的是在处理并发数据的情况。
#p#
5、服务器实现:
这是最重要最核心的部分。一个高效、稳定的游戏服务器程序直接决定了游戏的体验。在实际的游戏服务器开 发中,游戏逻辑与网络通信逻辑可能分工由不同的人员开发。因此,游戏逻辑与网络通信逻辑应在保证效率的情况下尽可能地实现低耦合。我这里虽然是独立开发 的,是因为游戏的逻辑很简单,但如果比如去开发一个像GTAOL这样的游戏服务器,本来做网络通信的人想要做出GTA的游戏逻辑那就相当地困难,需要写处 理世界、物体、角色,还要和游戏端的逻辑一致,累成狗狗。
所以说游戏的逻辑与网络的通信需要尽可能地独立,就这个五子棋服务器而言,网络通信端使用PPC、select、epoll都和游戏逻辑无 关,只要能接收分类并交给游戏逻辑处理,并将游戏逻辑处理好的数据发出即可。该服务器选用的epoll实现的,因篇幅原因,网络通信部分已经在这篇文章中 说明清楚:epoll模型的理解封装与应用。
关于服务器的游戏逻辑,首先看看我们的服务器要做哪些事情:
1、用户游戏ID的申请与管理
2、对局数据的处理与管理
大致就以上这两种事情。但是因为游戏的客户端数量很多,不同的客户端之间进行对局,必须要清晰地处理与管理这些数据。我这里建立了一个idpool,用于id的储存于申请,以防发生错误给用户分配无效或是重复的id。
对局数据的处理与管理:
在两个用户都有id的情况下,双方都能申请进行游戏。这是服务端要做的就是匹配好这些用户并通知这些用户开始游戏。为方便说明,我先把代码粘上来:
- #ifndef _GAME_H_
- #define _GAME_H_
- #include
- #include
- #include
- #include
- #include
- #include
- #include "ssock.h"
- #include "gameprotocol.h"
- using namespace std;
- #define idpoollength 1000
- #define datapoollength 50
- //链式IDpool
- class idpool
- {
- list
ids; - public:
- idpool()
- {
- for(int i=1;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
- {
- 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
- {
- 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
分享名称:一套跨平台五子棋网游的开发经历
网站网址:http://www.mswzjz.cn/qtweb/news16/212816.html攀枝花网站建设、攀枝花网站运维推广公司-贝锐智能,是专注品牌与效果的网络营销公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 贝锐智能