cocos2dx 心跳包游戏 在后台是怎么发心跳包的?有什么方法吗

从keep-alive原理 分析TCP游戏服务端心跳包的实用功能
招聘信息:
整理自知乎,文/游戏服务器常常有心跳包的设计。我们的心跳包就是为了防止Socket断开连接,或是TCP的连接断开吗?答案是否定的,TCP连接的通道是个虚拟的,连接的维持靠的是两端TCP软件对连接状态的维护。TCP 连接自身有维护连接的机制,说白了就是自身有长时间没有数据包情况下的判断连接是否还存在的检测,清除死连接,即使在没有数据来往的时候,TCP也就可以(在启动TCP这个功能的前提下)自动发包检测是否连接正常,这个不需要我们处理。服务端设计心跳包的目的:探知对端应用是否存活,服务端客户端都可以发心跳包,一般都是客户端发送心跳包,服务端用于判断客户端是否在线,从而对服务端内存缓存数据进行清理(玩家下线等);问题在于,通过TCP四次握手断开的设定,我们也是可以通过Socket的read方法来判断TCP连接是否断开,从而做出相应的清理内存动作,那么为什么我们还需要使用客户端发送心跳包来判断呢?第一种判断客户端是否在线策略:直接监控TCP传输协议的返回值,通过返回值处理应用层的存活判断比如在C++当中使用poll的IO复用方法时:if(fds[i].revents&&&POLLERR)
if(fds[i].events&&&POLLDHUP)通过上述判断可以探知TCP连接的正确性从而在服务器也关闭对应的连接,此时调用close()函数才会释放相关的资源。比如说进行如下处理:/*如果客户端关闭连接,则服务器也关闭对应的连接,并将用户总数减1*/users[fds[i].fd]&=&users[fds[user_counter].fd];
close(fds[i].fd);
fds[i]&=&fds[user_counter];
user_counter--;
printf("a&client&left\n");又比如在Java中:在Java的阻塞编程中:通过ServerSocket&ss&=&new&ServerSocket(10021);
Socket&so&=&ss.accept();
//&获取相关流对象
InputStream&in&=&so.getInputStream();
byte[]&bytes&=&new&byte[1024];
int&num&=&in.read(bytes);
if(num&==&-1)//表明读到了流的末尾,事实也就是client端断开了连接,比如调用close()
&&&&so.close();
}在Java的非阻塞编程当中:通过SelectionKey&key&=&selector.register(socketChannel,ops,handle);
SocketChannel&socketChanel&=&(SocketChannel)key.channel();
ByteBuffer&buffer&=&ByteBuffer.allocate(1024);
int&num&=&socketChannel.read(buffer);
if(num&==&-1)
&&&&key.channel().close();
}上述连接处理方式,返回-1也好,收到POLLERR POLLDHUP也好,都是收到了客户端的fin或者rst之后的反应,所以根据四次分手原则,我们调用close方法,发送fin给客户端。上面这种策略通过TCP协议的返回值来得知客户端TCP断开,从而得知客户端掉线。当前提是如果提前根据ip或者mac做了记录,所以可以在服务器端收到TCP连接中断的消息后,调用close,并且通过socket得到玩家socket数据(具体如IP地址),从而获得user信息从而清除数据。那么这种方式有什么不完美呢?或者说有什么缺陷呢?主要原因就是TCP的断开可能有时候是不能瞬时探知的,甚至是不能探知的,也可能有很长时间的延迟,如果前端没有正常的断开TCP连接,四次握手没有发起,服务端无从得知客户端的掉线,那么就要依靠开启TCP的keep alive机制,but TCP协议的keep alive机制是不建议开启的,即使开启了默认的时间间隔是2h,泪奔!如果要服务端维持一个2h的死链接,那是可怕的,如果我们调整了时间间隔,也是有problem的,因为TCP本身就不建议TCP层的心跳检测,因为这可能导致一个完好的TCP连接在中间网络中断的情况下重启⊙▂⊙。下面有必要先介绍下TCP的keep-alive机制。关于TCP自己的keep-alive机制,参看TCP/IP详解当中有如下介绍:使用keeplive机制,可以检测到网络异常。一、什么是keepalive定时器?在一个空闲的(idle)TCP连接上,没有任何的数据流,许多TCP/IP的初学者都对此感到惊奇。也就是说,如果TCP连接两端没有任何一个进程在向对方发送数据,那么在这两个TCP模块之间没有任何的数据交换。你可能在其它的网络协议中发现有轮询(polling),但在TCP中它不存在。言外之意就是我们只要启动一个客户端进程,同服务器建立了TCP连接,不管你离开几小时,几天,几星期或是几个月,连接依旧存在。中间的路由器可能崩溃或者重启,电话线可能go down或者back up,只要连接两端的主机没有重启,连接依旧保持建立。这就可以认为不管是客户端的还是服务器端的应用程序都没有应用程序级(application-level)的定时器来探测连接的不活动状态(inactivity),从而引起任何一个应用程序的终止。然而有的时候,服务器需要知道客户端主机是否已崩溃并且关闭,或者崩溃但重启。许多实现提供了存活定时器来完成这个任务。存活定时器是一个包含争议的特征。许多人认为,即使需要这个特征,这种对对方的轮询也应该由应用程序来完成,而不是由TCP中实现。此外,如果两个终端系统之间的某个中间网络上有连接的暂时中断,那么存活选项(option)就能够引起两个进程间一个良好连接的终止。例如,如果正好在某个中间路由器崩溃、重启的时候发送存活探测,TCP就将会认为客户端主机已经崩溃,但事实并非如此。存活(keepalive)并不是TCP规范的一部分。在Host Requirements RFC罗列有不使用它的三个理由:(1)在短暂的故障期间,它们可能引起一个良好连接(good connection)被释放(dropped);(2)它们消费了不必要的宽带;(3)在以数据包计费的互联网上它们(额外)花费金钱。然而,在许多的实现中提供了存活定时器;一些服务器应用程序可能代表客户端占用资源,它们需要知道客户端主机是否崩溃。存活定时器可以为这些应用程序提供探测服务。Telnet服务器和Rlogin服务器的许多版本都默认提供存活选项。个人计算机用户使用TCP/IP协议通过Telnet登录一台主机,这是能够说明需要使用存活定时器的一个常用例子。如果某个用户在使用结束时只是关掉了电源,而没有注销(log off),那么他就留下了一个半打开(half-open)的连接。在图18.16,我们看到如何在一个半打开连接上通过发送数据,得到一个复位(reset)返回,但那是在客户端,是由客户端发送的数据。如果客户端消失,留给了服务器端半打开的连接,并且服务器又在等待客户端的数据,那么等待将永远持续下去。存活特征的目的就是在服务器端检测这种半打开连接。二、keepalive如何工作?在此描述中,我们称使用存活选项的那一段为服务器,另一端为客户端。也可以在客户端设置该选项,且没有不允许这样做的理由,但通常设置在服务器。如果连接两端都需要探测对方是否消失,那么就可以在两端同时设置(比如NFS)。若在一个给定连接上,两小时之内无任何活动,服务器便向客户端发送一个探测段。(我们将在下面的例子中看到探测段的样子。)客户端主机必须是下列四种状态之一:1) 客户端主机依旧活跃(up)运行,并且从服务器可到达。从客户端TCP的正常响应,服务器知道对方仍然活跃。服务器的TCP为接下来的两小时复位存活定时器,如果在这两个小时到期之前,连接上发生应用程序的通信,则定时器重新为往下的两小时复位,并且接着交换数据。2) 客户端已经崩溃,或者已经关闭(down),或者正在重启过程中。在这两种情况下,它的TCP都不会响应。服务器没有收到对其发出探测的响应,并且在75秒之后超时。服务器将总共发送10个这样的探测,每个探测75秒。如果没有收到一个响应,它就认为客户端主机已经关闭并终止连接。3) 客户端曾经崩溃,但已经重启。这种情况下,服务器将会收到对其存活探测的响应,但该响应是一个复位,从而引起服务器对连接的终止。4) 客户端主机活跃运行,但从服务器不可到达。这与状态2类似,因为TCP无法区别它们两个。它所能表明的仅是未收到对其探测的回复。服务器不必担心客户端主机被关闭然后重启的情况(这里指的是操作员执行的正常关闭,而不是主机的崩溃)。当系统被操作员关闭时,所有的应用程序进程(也就是客户端进程)都将被终止,客户端TCP会在连接上发送一个FIN。收到这个FIN后,服务器TCP向服务器进程报告一个文件结束,以允许服务器检测这种状态。在第一种状态下,服务器应用程序不知道存活探测是否发生。凡事都是由TCP层处理的,存活探测对应用程序透明,直到后面2,3,4三种状态发生。在这三种状态下,通过服务器的TCP,返回给服务器应用程序错误信息。(通常服务器向网络发出一个读请求,等待客户端的数据。如果存活特征返回一个错误信息,则将该信息作为读操作的返回值返回给服务器。)在状态2,错误信息类似于“连接超时”。状态3则为“连接被对方复位”。第四种状态看起来像连接超时,或者根据是否收到与该连接相关的ICMP错误信息,而可能返回其它的错误信息。具体来说:在TCP协议的机制里面,本身的心跳包机制,也就是TCP协议中的SO_KEEPALIVE,系统默认是设置2小时的心跳频率。需要用要用setsockopt将SOL_SOCKET.SO_KEEPALIVE设置为1才是打开,并且可以设置三个参数tcp_keepalive_time/tcp_keepalive_probes/tcp_keepalive_intvl,分别表示连接闲置多久开始发keepalive的ACK包、发几个ACK包不回复才当对方死了、两个ACK包之间间隔多长。TCP协议会向对方发一个带有ACK标志的空数据包(KeepAlive探针),对方在收到ACK包以后,如果连接一切正常,应该回复一个ACK;如果连接出现错误了(例如对方重启了,连接状态丢失),则应当回复一个RST;如果对方没有回复,服务器每隔多少时间再发ACK,如果连续多个包都被无视了,说明连接被断开了。“心跳检测包”是属于TCP协议底层的检测机制,上位机软件只是解析显示网口的有用数据包,收到心跳包报文属于TCP协议层的数据,一般软件不会将它直接在应用层显示出来,所以看不到。以太网中的“心跳包”可以通过“以太网抓包软件”分析TCP/IP协议层的数据流看到。报文名称”TCP Keep-Alive”。一些比较可靠的以太网转串口模块,都有心跳包的检测,比如致远电子的ZNE-100TL模块,配置“心跳包检测”间隔时间设为“10”秒,使用一款”wireshark”的抓包软件来实际查看下TCP/IP协议层“心跳包”数据。看了上面的内容,使用TCP自己的keep-alive机制,也是可以实现连接维持,通过TCP传输层的心跳包探知两端TCP连接的正确性,从而得知应用层的情况(TCP在,应用一定在,TCP不在了,应用一定不在了),但是这不是最优选择!那么既然有TCP的心跳机制,我们为什么还要在应用层实现自己的心跳检测机制呢?评论中所说:tcpip详解卷1有网络异常中断的3种情况,比如os回收端口的时候发送的rst包,如果os挂了就不会发这个消息了。 另外如果网络异常,有可能收到路由器返回的icmp主机不可达消息从而关闭连接。 keepalive之所以不靠谱,是因为需要从socket error获知连接断开,而且是被动断开。 而应用层自己实现的heartbeat可以自主检测超时,更方便修改超时时间和断开前处理。以及@李乐所说:keepalive设计初衷清除和回收死亡时间长的连接,不适合实时性高的场合,而且它会先要求连接一定时间内没有活动,周期长,这样其实已经断开很长一段时间,没有及时性。而且keepalive好像还不能主动通知应用层,需要主动调用api去检测异常。三、使用自己应用层的心跳包,上述方法用于正常情况下的TCP连接维护场景举例如下:在游戏服务器当中,内存中维护着众多玩家的在线数据,以方便调用,比如玩家的英雄队伍信息,玩家的世界位置信息,在玩家下线的时候,服务器必须知道并且清除掉数据(不然就会出现一个已经下线的玩家出现在世界上),在上述举例中,在服务器端收到TCP连接中断的消息后,调用close,期间可以通过socket得到玩家socket数据,从而获得user信息(如果提前根据ip或者mac做了记录)从而清除数据。但是如果不是正常的玩家下线,TCP的四次握手没有成功,比如网络直接中断,client端的TCP协议的fin包没有发出去,服务端就不能及时探知玩家下线,如果依赖上面讲的TCP自己的keep alive探测机制,时间较长,做不到及时处理下线玩家,并且性能不佳,因为TCP/IP的设计者本身就不支持TCP的心跳,因为这可能因为中间网络的短暂中断导致两端良好的TCP连接断开。所以一般不启用TCP的心跳机制,那我们怎么处理这些异常下线呢?如果不处理,服务端将一直维护着TCP死连接,导致网络资源(一台服务器可以支持的TCP连接有限)和内存资源(内存中可能维护着该玩家的数据)的占用,所以就要用到应用层的心跳包了下面解释下应用层的心跳包:心跳包,通常是客户端每隔一小段时间向服务器发送的一个数据包,通知服务器自己仍然在线,服务器与客户端之间每隔一段时间 进行一次交互,来判断这个链接是否有效,并传输一些可能有必要的数据。通常是在建立了一个TCP的socket连接后无法保证这个连接是否持续有效,这时候两边应用会通过定时发送心跳包来保证连接是有效的。因按照一定的时间间隔发送,类似于心跳,所以叫做心跳包。事实上为了保持长连接(长连接指的是建立一次TCP连接之后,就认为连接有效,利用这个连接去不断传输数据,不断开TCP连接),至于包的内容,是没有特别规定的,不过一般都是很小的包,或者只是包含包头的一个空包。那么心跳包的意义就在于方便的在服务端管理客户端的在线情况,并且可以防止TCP的死连接问题,避免出现长时间不在线的死链接仍然出现在服务端的管理任务中。再举下面一个例子说明下为什么TCP自身的四次握手断开机制不能完全保证应用程序探知连接断开从而避免死连接。(1)做一个游戏客户端和服务器端的测试,手动强制关闭客户端进程,然后查看服务器的情况,结果往往是,服务器收到了客户端关闭的事件。其实, 这样会忽略一个问题,没有触发异常中断事件,比如网络中断。(2)手动关闭客户端进程,事实上并不能测试出想要的结果,因为进程是在应用层的,所以,这种测试方法不能保证网络驱动层也不发送数据报文给服务器。经过测试会发现,当应用层强制结束进程时,对于TCP连接,驱动层会发送reset数据包!而服务器收到这个数据包就可以正常关闭了!(3)那么,如果网络异常甚至直接拔掉网线呢,服务器收不到这个数据包,就会导致死连接存在!(4)所以,心跳包是必要的,或者使用TCP协议本身的Keep-alive来设置(但是keep-alive不够好)我们不能误解TCP连接如同一条绳子,一方断开了,另外一方必然会知道的。事实上TCP连接,这个“面向连接”的物理连接并不存在,它只是抽象出来的概念,是一个虚拟的连接,对于物理层,对于网线、光纤而言,不存在连接不连接的概念,因为,对它们而言,无非就是一些电流脉冲而已,具体就是一个一个的IP报文。TCP的连接,不过是通过ACK、SEQ这些机制来模拟实现的,具体比如差错重传,拥塞控制那么心跳包的检测发送处理对服务器资源的耗费怎么判断?这个要看心跳包发送的频率,我们可以自行设置另外这里有个例程,模拟了socket心跳包的C语言实现:《》
微信扫一扫
订阅每日移动开发及APP推广热点资讯公众号:CocoaChina
您还没有登录!请或
点击量13144点击量10879点击量6978点击量5966点击量4313点击量4193点击量3643点击量3437点击量3429
&2016 Chukong Technologies,Inc.
京公网安备89下次自动登录
现在的位置:
& 综合 & 正文
cocos2dx监听home键,锁屏,后台转前台暂停功能
根据cocos2d-x游戏原理当home键或者锁屏键被按下,游戏处于后台状态就会调用AppDelegate.cpp里面的方法applicationDidEnterBackground()
其实按照这个方法的意思就可以知道到底是怎么回事了。所以我们就在这个方法内部写自己的东西。
首先,我说一下原理, 自己写了一个Dialog 模态对话框(这个可以搜到), 然后写了自己写了一个方法
CCScene *PauseLayer::scene(CCRenderTexture *sqr, bool isFlip)
CCScene *scene = CCScene::create();
CCSize size = CCDirector::sharedDirector()-&getWinSize();
CCSprite *_spr = CCSprite::createWithTexture(sqr-&getSprite()-&getTexture());
_spr-&setPosition(ccp(size.width / 2, size.height / 2));
_spr-&setFlipY(isFlip);
_spr-&setColor(ccGRAY);
scene-&addChild(_spr);
PauseLayer* layer = PauseLayer::create();
scene-&addChild(layer);
传入一个CCrenderTexture 相当于一个正在运行的游戏的截图作为这个暂停对话框的背景 这样就看起来像是对话框在游戏界面之上,一般游戏当中都是这样子写的。
在applicationDidEnterBackground()里面这样写:
void AppDelegate::applicationDidEnterBackground()
CCDirector::sharedDirector()-&stopAnimation();
SimpleAudioEngine::sharedEngine()-&pauseBackgroundMusic();
CCSize size = CCDirector::sharedDirector()-&getWinSize();
CCRenderTexture* renderTexture = CCRenderTexture::create(size.width, size.height);
renderTexture-&retain();
CCScene *s = CCDirector::sharedDirector()-&getRunningScene();
CCLog("%d", s-&getTag());
if (s-&getTag() == 30)
renderTexture-&begin();
s-&visit();
renderTexture-&end();
CCScene* pause = PauseLayer::scene(renderTexture, true);
pause-&setTag(30+1);
CCDirector::sharedDirector()-&pushScene(pause);
CCLog("do nothing");
我把我游戏的场景设置了一个Tag 这样子我就可以判断什么时候弹出暂停对话框:就是当游戏处于运行状态的时候才弹出对话框 当游戏处于选择关卡什么的界面的时候没有必要弹出对话框的。
当写了以上后 成功运行在android上面,但是发现游戏暂停是暂停了
但是背景音乐没有暂停,这个时候就得说起另外一个方法applicationWillEnterForeground 当home键以后再进入游戏就会执行这个
所以我这样写的:
CCDirector::sharedDirector()-&startAnimation();
int tag = CCDirector::sharedDirector()-&getRunningScene()-&getTag();
switch (tag)
CCLog("Tag = 30 do nothing");
CCLog("Tag = 31 do nothing");
CCLog("Tag = other do something");
SimpleAudioEngine::sharedEngine()-&resumeBackgroundMusic();
然后在pauseLayer里面当点击继续游戏的时候恢复声音 这种用户体验很好
void PauseLayer::NextMenuItemCallback(CCObject *pSender)
SimpleAudioEngine::sharedEngine()-&resumeBackgroundMusic();
CCDirector::sharedDirector()-&popScene();
对了为什么要用 CCDirector::sharedDirector()-&pushScene(pause);呢
是因为在punshScene我的这个暂停CCScene以后 原来的那个CCScene在栈底处于暂停状态 所以我们在pauseLayer里面当点击继续游戏按钮的时候 就把pauseLayer从栈中pop弹出 然后游戏场景就会恢复正常。
&&&&推荐文章:
【上篇】【下篇】&&&&需要暂停时,一般都是将游戏界面变灰,暂停所有动作,然后弹出一堆的按钮。
&&&&这该如何实现暂停界面呢?
&&&&我定义了两个类:Game游戏类、GamePause暂停类。
&&&&Game类中调用doPause()函数进行游戏暂停,却换到GamePause暂停画面。
2、doPause()
&&&&这里需要用到CCRenderTexture这个纹理类,顾名思义就是可以动态创建纹理。
&&&&在doPause类中使用该类,将游戏界面截个图。
//创建CCRenderTexture,纹理画布大小为窗口大小(480,320)
CCRenderTexture&*renderTexture&=&CCRenderTexture::create(480,320);
//遍历Game类的所有子节点信息,画入renderTexture中。
//这里类似截图。
renderTexture-&begin();&
this-&getParent()-&visit();
renderTexture-&end();
//将游戏界面暂停,压入场景堆栈。并切换到GamePause界面
CCDirector::sharedDirector()-&pushScene(GamePause::scene(renderTexture));
3、GamePause
&&&&(1)在类中添加一个用于创建GamePause场景的静态函数,且传入参数为在doPause()函数中截图的动态纹理图片renderTexture。
static&cocos2d::CCScene*&scene(CCRenderTexture*&sqr);
&&&&(2)实现创建GamePause场景的静态函数。
CCScene*&GamePause::scene(CCRenderTexture*&sqr)&{
CCScene&*myscene&=&CCScene::create();&&
GamePause*&mylayer&=&GamePause::create();
myscene-&addChild(mylayer);
//增加部分:使用Game界面中截图的sqr纹理图片创建Sprite
//并将Sprite添加到GamePause场景层中
CCSprite&*_spr&=&CCSprite::createWithTexture(sqr-&getSprite()-&getTexture());&&
_spr-&setPosition(ccp(240,135));&//窗口大小(480,320),这个相对于中心位置。
_spr-&setFlipY(true);&&&&&&&&&&&&//翻转,因为UI坐标和OpenGL坐标不同
_spr-&setColor(cocos2d::ccGRAY);&//图片颜色变灰色
myscene-&addChild(_spr);
4、继续游戏
&&&&若要继续游戏,由于Game界面是push到场景堆栈中的。所以只要popScene,即可还原到游戏界面,即可继续游戏。
CCDirector::sharedDirector()-&popScene();
本文出自 “夏天的风” ,请务必保留此出处http://shahdza./9515一、前提:
完成Hello Game项目的创建编译。
具体参考:
二、本篇目标:
l& 分析proj.win32工程的主要构成
l& 分析proj.android工程的主要构成
l& 新建一个MyScene.cpp然后在游戏中显示出来
l& 在android真机上运行查看效果
三、分析:
我们游戏开发通常是这样的,首先在Microsoft Visual Studio 2012中proj.win32工程编写代码并且在windows上调试运行,当游戏主体开发完成后,进行so文件的编译打包,然后继续在eclipse的proj.android工程中编写少量的代码完成游戏在android平台下的打包开发。
l& 分析proj.win32工程的主要构成
用Microsoft Visual Studio 2012打开proj.win32工程
整个hellogame的解决方案由hellogame、libbox2d、libcocos2d、libSpine四个工程项目构成。
1、Hellogame工程:游戏主工程,我们开发工作主要在这个工程中完成
2、libbox2d工程:模拟2D刚体物体的C++物理引擎,大名鼎鼎植物大战僵尸、愤怒的小鸟等游戏均有这个引擎的功劳
3、libcocos2d工程:这个不用说了,整个cocos2dx核心
4、libSpine工程:工具软件支持库
接下来主要对Hellogame工程的代码进行解析,libbox2d工程在后面的物理引擎篇的时候在进行讲解,至于其它2个工程在后续使用到的篇幅中在进行讲解。
Hellogame工程的源代码:
工程主要由src目录下的AppDelegate.cpp、AppDelegate.h、HelloWorldScene.cpp、HelloWorldScene.h四个源文件和win32目录下的main.cpp、main.h两个源文件组成。
src目录下的源文件是所有6个平台共用的代码文件,不管是android还是ios都使用这个目录下的源文件,属于真正跨平台部分的代码。
win32目录下的源文件只是一个main主入口文件,负责win32平台下对游戏的调用。其实在对应的proj.android的工程里也有一个android平台对应的main主入口文件,只是由于平台的不同实现代码也各有不同,但是目的一样。
AppDelegate.cpp源代码:
AppDelegate类似于android的Application的作用,提供一些应用程序级别的状态的回调,整个游戏应用程序由这个文件方法进行控制。下面是几个主要方法的说明和解释:
#include "AppDelegate.h"
#include "HelloWorldScene.h"
//命名空间宏,偷懒引入cocos2d的头文件
USING_NS_CC;
AppDelegate::AppDelegate() {
AppDelegate::~AppDelegate()
//设置 OpenGL context
//这个设置对所有平台都有效
void AppDelegate::initGLContextAttrs()
//设置 OpenGL context 属性,目前只能设置6个属性
//red,green,blue,alpha,depth,stencil
GLContextAttrs glContextAttrs = {8, 8, 8, 8, 24, 8};
GLView::setGLContextAttrs(glContextAttrs);
//当应用程序启动时执行,游戏程序启动入口
//在这里我们启动了第一个scene(场景)
//在具体游戏中通常在这里启动loading界面
//你的游戏从这里开始!
bool AppDelegate::applicationDidFinishLaunching() {
// 初始化 director
auto director = Director::getInstance();
auto glview = director-&getOpenGLView();
if(!glview) {
glview = GLViewImpl::create("My Game");
director-&setOpenGLView(glview);
// 在屏幕上显示FPS数
// 开发阶段建议开启这个设置,可以通过这个对自己游戏性能有个大体了解
// 等游戏正式发布时关闭这个设置
director-&setDisplayStats(true);
// 设置 FPS数 默认值为 1.0/60
director-&setAnimationInterval(1.0 / 60);
// 创建一个HelloWorld的scene.这个是自动回收的对象
auto scene = HelloWorld::createScene();
// 告诉director运行HelloWorld的scene
director-&runWithScene(scene);
return true;
// 当游戏进入后台时会调用这个方法
// 比如玩游戏时按下android手机的home按键
// 比如当游戏时有电话打入直接显示来电界面
void AppDelegate::applicationDidEnterBackground() {
Director::getInstance()-&stopAnimation();
// 如果你的游戏使用了SimpleAudioEngine,必须在这里进行暂停
// 暂停代码如下:
// SimpleAudioEngine::getInstance()-&pauseBackgroundMusic();
// 当游戏恢复到前台运行时会调用这个方法
// 比如接电话结束是游戏界面又恢复到前台时
void AppDelegate::applicationWillEnterForeground() {
Director::getInstance()-&startAnimation();
// 如果你的游戏使用了SimpleAudioEngine, 必须在这里进行恢复
// 恢复代码如下:
// SimpleAudioEngine::getInstance()-&resumeBackgroundMusic();
上述代码解释中的提到的director(导演:负责游戏场景的显示切换等,像电影导演一样掌控整个电影的一切)、scene(场景:负责显示一个游戏场景,就像电影的一个场景镜头)。
上面代码中最重要的方法为applicationDidFinishLaunching(),因为你的游戏从这个方法开始!
HelloWorldScene.cpp源代码:
上面代码中在AppDelegate类的applicationDidFinishLaunching()方法中创建了一个HelloWorldScene的场景,并且运行这个场景,HelloWorldScene.cpp就是这个场景具体的代码实现。下面是几个主要方法的说明和解释:
#include "HelloWorldScene.h"
USING_NS_CC;
//创建场景
Scene* HelloWorld::createScene()
//创建一个自释放的场景对象
auto scene = Scene::create();
//创建一个自释放的画面层对象
auto layer = HelloWorld::create();
//把创建的画面层添加到场景中
//一个场景可以添加多个画面层
scene-&addChild(layer);
//返回这个创建的场景
// 场景初始化方法
bool HelloWorld::init()
// 1. 首先进行父类初始化
if ( !Layer::init() )
//如果初始化父类失败返回false
return false;
//获取整个手机可视屏幕尺寸
Size visibleSize = Director::getInstance()-&getVisibleSize();
//获取手机可视屏原点的坐标
Vec2 origin = Director::getInstance()-&getVisibleOrigin();
// 创建一个带图标的关闭按钮
// 点击后调用menuCloseCallback方法退出游戏
auto closeItem = MenuItemImage::create(
"CloseNormal.png",
"CloseSelected.png",
CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));
// 设置关闭按钮的显示位置
// 显示在可视屏幕的右下角
closeItem-&setPosition(Vec2(origin.x + visibleSize.width - closeItem-&getContentSize().width/2 ,
origin.y + closeItem-&getContentSize().height/2));
// 创建一个可自释放的菜单
auto menu = Menu::create(closeItem, NULL);
menu-&setPosition(Vec2::ZERO);
this-&addChild(menu, 1);
//创建一个显示"Hello Game"文字的Label
auto label = Label::createWithTTF("Hello Game", "fonts/Marker Felt.ttf", 24);
// 设置label在屏幕中的显示位置
label-&setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - label-&getContentSize().height));
// 把label添加到画面层
this-&addChild(label, 1);
// 创建一个带图片的精灵
auto sprite = Sprite::create("HelloWorld.png");
// 设置图片精灵的显示位置
sprite-&setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
// 添加图片精灵到画面层
this-&addChild(sprite, 0);
return true;
// 退出按钮事件
void HelloWorld::menuCloseCallback(Ref* pSender)
//当是wp8或者winrt平台的时候
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WP8) || (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT)
MessageBox("You pressed the close button. Windows Store Apps do not implement a close button.","Alert");
//结束Director
Director::getInstance()-&end();
//当是ios平台的时候退出
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
上述的代码是一个简单的Scene(场景)实现代码,当我们真正开发游戏时,其实就是制作一个一个的场景,并且通过Director进行调度组织构成一个完整的游戏。
l& 分析proj.android工程的主要构成
用eclipse打开proj.android工程
整个hellogame的相比win32的要简单多了,除了android项目必须的一些组成部分之外:
1、& Classes文件夹:这里面的源文件和上面proj.win32中的属于同一份共享。
2、& jni/hellocpp/main.cpp:这个相当于proj.win32中的win32目录下的源文件,主入口。
3、& libs/armeabi/libcocos2dcpp.so:这个是整个游戏代码的编译包,其实真正游戏代码实现都最终由C++文件编译打包成这个so类库供android代码调用运行游戏。
整个游戏的开发基本上不需要在eclipse中编写代码,android平台只需要调用编译好的so文件即可运行游戏,eclipse中只需要把包含了so文件的android工程打包成apk文件发布即可。
l& 新建一个MyScene.cpp然后在游戏中显示出来
用Microsoft Visual Studio 2012打开proj.win32工程,我们将在这个工程里新加一个自己的Scence(场景)并且显示出来。这个看着是个很简单的任务但是对新手来说还是会碰到很多困难,所以这里特别的做一下演示。
文件新建:cpp文件这里有2个新建方法,
1、第一种方式
第一步:在右边的解决方案资源管理器中右键src新建类。
问题:到这里你会发现&浏览&按钮不可以用,新建的cpp只能新建到目录hellogame\proj.win32下,这样会导致一个问题。
第二步:先不管这个我们按照提示继续点击&添加&进入类向导界面输入类名MyScene然后点击完成类的创建,这个时候在proj.win32目录下新加MyScene.cpp和MyScene.h两个文件。
2、第二种方式
第一步:在右边的解决方案资源管理器中右键src新建项
问题:到这里你会发现&浏览&按钮可以用,点击修改一下目录为hellogame\Classes下,而且需要选择是新建.ccp文件还是.h文件.
第二步:这里先选择.cpp类型然后输入MyScene.cpp然后完成创建,然后继续前面的步骤新建MyScene.h文件。这个时候在Classes目录下会出现新加的MyScene.cpp和MyScene.h两个文件。
代码编写:
MyScene.h:
#include "cocos2d.h"
class MyScene : public cocos2d::Layer
static cocos2d::Scene* createScene();
virtual bool init();
CREATE_FUNC(MyScene);
MyScene.cpp:
#include "MyScene.h"
USING_NS_CC;
Scene* MyScene::createScene()
auto scene=Scene::create();
auto layer=MyScene::create();
scene-&addChild(layer);
bool MyScene::init()
if(!Layer::init())
return false;
//获取整个手机可视屏幕尺寸
Size visibleSize = Director::getInstance()-&getVisibleSize();
//获取手机可视屏原点的坐标
Vec2 origin = Director::getInstance()-&getVisibleOrigin();
//创建一个显示"MyScene"文字的Label
auto label = Label::createWithTTF("MyScene", "fonts/Marker Felt.ttf", 24);
//设置白色
label-&setColor(Color3B::WHITE);
// 设置label在屏幕中的显示位置
label-&setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - label-&getContentSize().height));
// 把label添加到画面层
this-&addChild(label, 1);
return true;
完成MyScene编写后,我们要先在游戏开启后的界面中添加一个按钮菜单点击后进入MyScene&场景。游戏开启后的界面场景是HelloWorldScene,所以我们需要在HelloWorldScene中添加一个按钮,并且为这个按钮添加一个点击启动MyScene的事件。
HelloWorldScene.h:
在这个文件中首先声明一个按钮点击事件:
//切换到下一个scene事件
void menuNextCallback(cocos2d::Ref* pSender);
HelloWorldScene.cpp:
1、首先引入MyScene.h
#include "HelloWorldScene.h"
#include "MyScene.h"
USING_NS_CC;
2、实现menuNextCallback事件代码
// 按钮点击事件,点击后启动MyScene
void HelloWorld::menuNextCallback(Ref* pSender)
//新建MyScene实例
auto scene = MyScene::createScene();
//用这MyScene实例替换当前scene
Director::getInstance()-&replaceScene(scene);
添加在屏幕上添加启动按钮
// 设置关闭按钮的显示位置
// 显示在可视屏幕的右下角
closeItem-&setPosition(Vec2(origin.x + visibleSize.width - closeItem-&getContentSize().width/2 ,
origin.y + closeItem-&getContentSize().height/2));
// 新建一个带图片的按钮菜单
auto goItem=MenuItemImage::create("next_1.png","next_2.png",
CC_CALLBACK_1(HelloWorld::menuNextCallback, this));
goItem-&setPosition(Vec2(origin.x + visibleSize.width/2 - closeItem-&getContentSize().width/2 ,origin.y/2 + closeItem-&getContentSize().height/2));
// 创建一个可自释放的菜单
auto menu = Menu::create(closeItem,goItem, NULL);
menu-&setPosition(Vec2::ZERO);
this-&addChild(menu, 1);
好了到此为止我们完成了所有代码的编写,现在开始调试运行查看一下效果。
这个时候如果你是用第二中方法创建的MyScene能正常编译运行而第一种方法创建的MyScene会发无法通过编译没办法运行会报如下错误:
IntelliSense: 无法打开 源 文件 "MyScene.h"&&&
error C1083: 无法打开包括文件:&MyScene.h&: No such file or directory
第一步:项目名点击右键属性
第二步:选择左边的C/C++然后在右边的附加包含目录追添加:;$(ProjectDir)
这个是我本人的附加包含目录,每个人环境不同应该有点区别
$(EngineRoot)cocos\audio\$(EngineRoot)$(EngineRoot)external\chipmunk\include\$(EngineRoot)..\C..;%(AdditionalIncludeDirectories) ;$(ProjectDir)
完成如上设置后在进行项目调试运行就能正常运行起来了,并且点击按钮后成功的显示MyScene的画面达到了我们设定的目标。
l& 在android真机上运行查看效果
要在android真机运行,首先需要进行so文件的打包编译。
第一步:如果上面的步骤中是按照第一种方法创建的MyScene那么请把MyScene.cpp、MyScene.h两个文件从proj.win32文件拷贝到Classes文件夹下。如果是按照第二种方法创建的MyScene那么可以忽略本步骤。
第二步:用EditPlus等文本编辑器打开proj.android\jni目录下的Android.mk文件,把MyScene.cpp添加到需要编译的源文件清单中然后保存,如下:
LOCAL_SRC_FILES := hellocpp/main.cpp \
&&&&&&&&&& ../../Classes/AppDelegate.cpp \
&&&&&&&&&& ../../Classes/HelloWorldScene.cpp \
& & & & & &../../Classes/MyScene.cpp
第三步:开启Cygwin开始编译打包so文件,如果不会请参考:Cocos2dx.3x_Hello Game项目创建篇
第四步:打包so文件成功后,然后开启eclipse打开游戏工程并且连接android手机运行工程看看真机运行的效果是否跟前面windows中的效果是否一样。
Cocos2d-x3.3入门三部曲到这里就算是完成了,接下来将以具体一个游戏为一个系列的方式继续深入实战,第一个实战系列为:《Cocos2d-x3.x塔防游戏(保卫萝卜)从零开始》,这个系列总共有多少篇待定,目标是直到做完一个完整的符合上线标准的游戏为止绝非草草的练习之作。
作者交流QQ:
& & & & & &邮箱:
阅读(...) 评论()

我要回帖

更多关于 cocos2dx lua 的文章

 

随机推荐