cocos2dx 系统时间不可思议的每帧时间问题,求教

1271人阅读
cocos2dx(22)
在多人版的游戏开发过程中,我们会经常碰到这样一个问题:由于每个客户端网络环境差异导致接收服务器消息的时间不同,就会导致多个客户端呈现的画面不同(即画面不同步),例如:以彩期开奖为例,客户端A已经收到开奖结果的推送了,但客户端B没有收到,如果不做任何处理,会导致后面画面的差异越来越大。
因为网络环境的差异是一个客观的问题,所以我们并不能保证每个客户端能在同一时间收到服务器推送的消息。但我们可以在代码逻辑上可以避免差异的扩大化,尽量保证之后的差异不受前面的影响。
另外一个大前提:既然是多人版,就应该用的是长连接。
短连接:是指通讯双方有数据交互时,就建立一个连接,数据发送完成后,则断开此连接,即每次连接只完成一项业务的发送。
长连接:多用于操作频繁,点对点的通讯,而且连接数不能太多情况。每个TCP连接都需要三步握手,这需要时间,如果每个操作都是短连接,再操作的话那么处理速度会降低很多,所以每个操作完后都不断开,下次处理时直接发送数据包就OK了,不用建立TCP连接。
举一个很容易理解的例子:长短连接可比作为即时通话和语音。
本文以彩期下注开奖为例,做简单的同步,能基本保证画面同步。(长连接)
上图 gameStart为游戏开始(主动请求)、stopBet为停止下注(服务器推送)、gameResult为开奖结果(服务器推送)。
这里gameResult和gameComplete之间的时间是提供给客户端呈现开奖画面的时间(例如:转盘滚动到指定位置)
遇到的主要问题:
1.倒计时不同步(start和result结果之间会有一个开奖倒计时)
2.客户端由于网络延迟导致收到result消息晚了,导致画面不同步。(这里是收到结果之后才开始播动画)
3.两局之间衔接得不是很好(之前的做法是等到了gameComplete,也就是开奖动画结束后才开始请求下一局游戏,如果这个请求延迟了就会导致刚开始就画面不同步的问题)
首先处理问题3:直接给请求下一局预留缓冲,在拿到当前Result时候就去请求下一局的信息并保存下来。假若gameResult和complete之间有20s的时间,那么给这个请求和接收预留的缓冲时间就是20S。再假设20S都没接收到、那么建议是提示玩家重新连接游戏。
问题1和2有一个共性的问题就是:网络延迟。具体一点就是…算了,举例来说吧:假设客户端A给服务器发了一个gameStart的请求,发送这个请求的时间是T0。服务器在S0时收到这个请求,给客户端A发送应答消息。而客户端A收到这个请求的应答时间是T1,在这个gameStart的response数据中,服务端会告诉你它的当前服务器时间S1以及开奖时间S2。(用来做倒计时用)
不难看出,因为服务器时间是一直在走动的,如果发送和接收的时间差比较大的话(例如发送和接收均消耗10S),可能造成你收到的服务器时间误差较大。(你收到的是S1,其实当前服务器时间是S1+10)
有很多人的做法是取 S1+(T1 - T0)/2 作为当前服务器时间,这样做有一定的效果,但是当发送和接收消耗的时间差距较大时(例如发送到服务端时间为1S,消息应答从服务器返回消耗了19S),按上述计算出当前服务器时间为S1+10,但实际却为S1+19。
Service.gameStart(data,this._newGame,this);
this._sendTime = new Date().getTime();
_newGame:funciton(data){
this._receiveTime = new Date().getTime();
var currTime = data.currT
var curServerTime = 0;
var timeDiff = (this._receiveTime - this._sendTime)/2;
if(timeDiff
& 500 || Math.abs(currTime -
ServerTimeUtils.getServerTime()) & 1000){
curServerTime = ServerTimeUtils.updateServerTime(currTime+timeDiff);
curServerTime = ServerTimeUtils.getServerTime();
思路,即本地记录保存服务器时间LS,设置个clock让其一直走。
在上述公式基础上,加上判断:如果发送接收时间差小于1S,就按公式来即S1+(T1 - T0)/2。
如若大于1S,则服务器时间按LS来。
“Math.abs(currTime -
ServerTimeUtils.getServerTime()) & 1000”这个判断只是为了第一次的LS初始化。
ServerTimeUtils(全局变量):
var ServerTimeUtils = {
_serverTime: new Date().getTime(),
serverTimeClock:function(){
var self = this;
global.timers.setInterval(function(){
self._serverTime = self._serverTime + 1000;
getServerTime: function(){
return this._serverT
updateServerTime: function(newServerTime){
this._serverTime = newServerT
ServerTimeUtils.serverTimeClock();
问题2的遗留问题:即接收Result的快慢导致开奖转盘不同步问题,这里由于我的项目中转盘做的变减速运动、很难保证每个时间点转盘画面都同步。这里我是采取了一个折中的办法
var turnTime = this._turnS
var timeDiff = resultTime - ServerTimeUtils.getServerTime();
if(Math.abs(timeDiff) & 1000){
if(resultTime & ServerTimeUtils.getServerTime()){
turnTime = turnTime - Math.abs(timeDiff)/1000;
ServerTimeUtils.updateServerTime(resultTime);
思路:如果接收result时间慢了几秒,那么就让转盘转到目的位置的时间少消耗几秒。(即提高了速度)
这样保证了转盘指针到目的地的时间是一致的,即多个客户端即使受到result时间不同,到达目的地的时间也相同。这样做保证能同时进入下一局游戏。
时间画面同步个人感觉是一个比较复杂的问题、特别是前端逻辑以及动画比较复杂的环境。上述只能处理较简单的小游戏问题,而且还有部分极端情况未做处理(用重新登录去处理)。关于cocos2dx帧率补偿的小方法
1.使用setAnimationInterval(1.0&/
15)?发现在Android上运行没效果了吧!好的,那就去官方Github[]
update个代码更新Cocos2dxRenderer.java吧,再将+private
static long sAnimationInterval = ...;中的60改为你需要的帧率,这个代码现在是cocos2dx 3.3的测试版代码
2.你想在Runtime logic时使用setAnimationInterval?Oh,NO!Sorry!感觉也只有在IOS上跑得好爽,在Android上?效果呢?还是没有啊!!!放弃吧,少年。
3.用Director:getInstance():getScheduler():setTimeScale(1)&?如果你试过可以的话那就恭喜你咯,我自己试感觉不到效果。
4.好吧!还是说说我的小办法吧,顺带有个加速功能。[注:设备最高帧率小于你所设的帧率的话,加速就没效果了]
setAnimationInterval(1.0&/
60);//默认为系统自带
float&lkDelta&=&-1;
void&XXXXX::update(float&delta)
& &&if&(!isFastFPS) {
& & & &&lkDelta&+=
& & & &&if&(lkDelta&&=&0.0666f&&&&lkDelta&&=&0)
{//FPS:15 0.0666f每帧所需时间,lkDelta &= 0:[首帧时不检查这个时间]
& & & & & &&return;
& & & &&if&(lkDelta&&&0) {
& & & & & &&lkDelta&=&0;
& & & & }&else&{
& & & & & &&lkDelta&-=&0.0666f;
& & logic();
& & draw();
看过本文的人也看了:
我要留言技术领域:
取消收藏确定要取消收藏吗?
删除图谱提示你保存在该图谱下的知识内容也会被删除,建议你先将内容移到其他图谱中。你确定要删除知识图谱及其内容吗?
删除节点提示无法删除该知识节点,因该节点下仍保存有相关知识内容!
删除节点提示你确定要删除该知识节点吗?博主最新文章
博主热门文章
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)HelloCpp是Cocos2d-x自带的一个工程,它演示了Cocos2d-x最基本的使用方法和流程。先看一下它的基本构成
win32目录中包含了对应平台的代码,而Classes目录中包含了我们自己的实现代码。编译运行的结果如下图
&main函数变形记
看到main命名的文件就会想到著名的main函数一定在这个文件里面,那么就让我们先看看这个文件吧。main.h里面主要是include了各种各样的头文件,main.cpp是我们真正需要关注的
1 int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
lpCmdLine,
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// create the application instance
CCEGLView* eglView = CCEGLView::sharedOpenGLView();
eglView-&setFrameSize(960, 640 );
return CCApplication::sharedApplication()-&run();
1.1 &APIENTRY
首先出现的不明物体是APIENTRY。我们先看看它的定义
1 #define APIENTRY
APIENTRY是WINAPI的一个替身,而WINAPI是微软定义的宏,实际就是__stdcall
1 #define WINAPI
众所周知,__stdcall声明了函数从右至左将参数压栈并由被调用者清理堆栈的。
1.2 &_tWinMain
如果我说_tWinMain是程序的入口函数,你会不会发飙:我去,入口函数不应该是main吗?是的!对于常规的C/C++程序而言main是入口函数的归宿,但在Windows程序中WinMain才是入口函数。
好吧!但为什么这里对的入口函数是_tWinMain而不是WinMain呢?我们先来看看_tWinMain的真正定义
1 #ifdef _UNICODE
#define _tWinMain wWinMain
#define _tWinMain WinMain
为了支持UNICODE,C运行库对WinMain区分了UNICODE版和ANSI版。对UNICODE版的程序,C运行库将调用wWinMain;而对于ANSI版的应用,则调用WinMain。至于WinMain更深入的知识请阅读,这里只需要知道它是Windows程序的入口函数即可。
1.3 &UNREFERENCED_PARAMETER
进入_tWinMain函数后就是两句很神奇的语句
1 UNREFERENCED_PARAMETER(hPrevInstance);
2 UNREFERENCED_PARAMETER(lpCmdLine);
要理解这句话首先是要搞清楚UNREFERENCED_PARAMETER是神马玩意儿。如果我告诉你它神马都不是,你信吗?
1 #define UNREFERENCED_PARAMETER(P)
承认吧骚年,它真的神马都不是啊~(你逗我玩儿呐?)
&AppDelegate的前世今生
_tWinMain函数的真正主体是从AppDelegate app开始的,所以我们就首先从AppDelegate说起。我们先看看AppDelegate的宗族关系
要把AppDelegate弄清楚搞明白,我们还得从源头开始。
2.1 &CCApplicationProtocol
作为Cocos2d-x Application的源头,CCApplicationProtocal定义如下
1 class CC_DLL CCApplicationProtocol
virtual ~CCApplicationProtocol() {}
Implement CCDirector and CCScene init code here.
@return true
Initialize success, app continue.
@return false
Initialize failed, app terminate.
virtual bool applicationDidFinishLaunching() = 0;
The function be called when the application enter background
the pointer of the application
virtual void applicationDidEnterBackground() = 0;
The function be called when the application enter foreground
the pointer of the application
virtual void applicationWillEnterForeground() = 0;
Callback by CCDirector for limit FPS.
The time, expressed in seconds, between current frame and next.
virtual void setAnimationInterval(double interval) = 0;
@brief Get current language config
@return Current language config
virtual ccLanguageType getCurrentLanguage() = 0;
@brief Get target platform
virtual TargetPlatform getTargetPlatform() = 0;
可以看到,CCApplicationProtocol是一个抽象类,它定义并导出作为DLL的接口。这其中有一个陌生CC_DLL,它定义了在DLL中的符号是
1 #if defined(_USRDLL)
#define CC_DLL
__declspec(dllexport)
/* use a DLL library */
#define CC_DLL
__declspec(dllimport)
整个CCApplicationProtocol除了析构函数以外的其他所有函数都是纯虚函数,这也就是它为什么叫Protocol的原因。每个函数的含义和作用在注释里有简要的说明,但具体的实现何其作用需要进一步才能理解。
2.2 CCApplication
作为对Cocos2d-x Application的抽象,CCApplication所扮演的角色是非常重要的。它的定义如下
1 class CC_DLL CCApplication : public CCApplicationProtocol
CCApplication();
virtual ~CCApplication();
Run the message loop.
int run();
Get current applicaiton instance.
@return Current application instance pointer.
static CCApplication* sharedApplication();
/* override functions */
virtual void setAnimationInterval(double interval);
virtual ccLanguageType getCurrentLanguage();
@brief Get target platform
virtual TargetPlatform getTargetPlatform();
/* set the Resource root path */
void setResourceRootPath(const std::string& rootResDir);
/* get the Resource root path */
const std::string& getResourceRootPath(void)
return m_resourceRootP
void setStartupScriptFilename(const std::string& startupScriptFile);
const std::string& getStartupScriptFilename(void)
return m_startupScriptF
43 protected:
LARGE_INTEGER
m_nAnimationI
std::string
m_resourceRootP
std::string
m_startupScriptF
static CCApplication * sm_pSharedA
虽然CCApplication提供了public的构造函数,但我们却不能直接实例化CCApplication的,因为它没有实现CCApplicationProtocol定义的所有接口函数(它还是一个抽象类)。
就Hello World这个示例而言,我们需要关注的并不多:构造函数、sharedApplication函数和run函数,我们会进一步全面剖析这些函数。
2.2.1 &构造函数
CCApplication的构造函数所完成的工作就是对成员变量的初始化
1 CCApplication::CCApplication()
: m_hInstance(NULL)
, m_hAccelTable(NULL)
m_hInstance
= GetModuleHandle(NULL);
m_nAnimationInterval.QuadPart = 0;
CC_ASSERT(! sm_pSharedApplication);
sm_pSharedApplication = this;
m_hInstance保存了当前模块的句柄,而sm_pSharedApplicaton则保存了当前对象的指针。值得注意的是,sm_pSharedApplication是一个static的CCApplication对象指针。
2.2.2 &Application是唯一的
我们再把_tWinMain函数请出来仔细看看(这里去掉了几句无用的代码)
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
lpCmdLine,
// create the application instance
CCEGLView* eglView = CCEGLView::sharedOpenGLView();
eglView-&setFrameSize(960, 640 );
return CCApplication::sharedApplication()-&run();
是否对AppDelegate app这句感到疑惑:在定义了app以后却从未使用过它。那么我们是不是可以理解为这句是多余的呢?好吧!我们把它注释掉,结果程序崩溃了!崩溃了!!溃了!!!
这是由sm_pSharedApplication引发的断言异常。sm_pSharedApplication是CCApplication类的一个protected的static成员,虽然在构造函数中将它赋值为this,但它的初始化却是0(空指针)
1 // sharedApplication pointer
2 CCApplication * CCApplication::sm_pSharedApplication = 0;
同时CCApplication提供一个public的static函数来访问它
1 CCApplication* CCApplication::sharedApplication()
CC_ASSERT(sm_pSharedApplication);
return sm_pSharedA
若果你熟悉设计模式就能一眼看出来这个实现实际上是,它保证了CCApplication只有一个实例。
看到这里,你可能会惊呼:一定是_tWinMain函数的最后一句&return CCApplication::sharedApplication()-&run() 引入了错误。哎,事实总是残酷的!实际上异常早在这之前就发生了。我们在CCApplication::sharedApplication函数中下断点,F5运行程序,从CallStack可以看到异常在CCEGLView::WindowProc函数中就发生了
进入CCEGLView::WindowProc函数,很快我们就能发现引发异常的代码段
1 case WM_SIZE:
switch (wParam)
case SIZE_RESTORED:
CCApplication::sharedApplication()-&applicationWillEnterForeground();
case SIZE_MINIMIZED:
CCApplication::sharedApplication()-&applicationDidEnterBackground();
WM_SIZE是Windows消息机制中的一个重要消息,每当窗口的大小改变时它就会进入到程序的消息队列中。这段代码就是处理WM_SIZE消息的。当程序启动时,窗口的大小就会发生变化,此时就会调用CCApplication::sharedApplication函数获取CCApplication::sm_pSharedApplication指针。然而,因为我们注释掉了&AppDelegate app&这句代码导致CCApplication的构造函数没有被调用,使得CCApplication::sm_pSharedApplication始终为初始值0,导致调用CCApplication::sharedApplication函数时引发了其内部实现的断言。
是不是被绕晕了?我也有点晕!没关系,我们把CCEGLView插入到这里来分析一下cocos2d-x是如何构成完整Windows程序框架的。熬过这话题就不会晕了。
2.2.3 &构建Windows程序框架
从经典教科书中可以看到,典型的Windows程序的框架大体如下
1 int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPreInstance,
szCmdLine,
1)注册窗口类,并注册消息处理函数WindowProc
2)创建并显示窗口
3)循环获取消息
消息处理函数的结构如下
1 LRESULT CALLBACK WindowProc(HWND
WPARAM wParam,
LPARAM lParam)
switch (uMsg)
处理各种消息
&对比一下HelloWorld的_tWinMain函数
1 int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
lpCmdLine,
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// create the application instance
// 以下代码对应Windows程序设计的一般框架
CCEGLView* eglView = CCEGLView::sharedOpenGLView();
eglView-&setFrameSize(960, 640 );
return CCApplication::sharedApplication()-&run();
首先,我们看一下CCEGLView::sharedOpenGLView函数的实现细节
1 CCEGLView* CCEGLView::sharedOpenGLView()
static CCEGLView* s_pEglView = NULL;
if (s_pEglView == NULL)
s_pEglView = new CCEGLView();
return s_pEglV
这是单例模式的一种变形,通过CCEGLView::sharedOpenGLView函数始终获取同一个CCEGLView对象的指针。同时它也通过new操作符调用CCEGLView的构造函数实例化了一个CCEGLView对象,而CCEGLView的构造函数只不过是完成了成员变量的初始化。可见,&注册窗口类并同时注册消息处理函数&并非通过CCEGLView::sharedOpenGLView函数完成的。
接下来,我们分析CCEGLView::setFrameSize函数。其实现如下
1 void CCEGLView::setFrameSize(float width, float height)
Create((LPCTSTR)m_szViewName, (int)width, (int)height);
CCEGLViewProtocol::setFrameSize(width, height);
resize(width, height); // adjust window size for menubar
centerWindow();
哈哈,居然有一个CCEGLView::Create函数
1 bool CCEGLView::Create(LPCTSTR pTitle, int w, int h)
bool bRet = false;
CC_BREAK_IF(m_hWnd);
// 创建窗口类
HINSTANCE hInstance = GetModuleHandle( NULL );
// Windows Class Structure
// Redraw On Size, And Own DC For Window.
= CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wc.lpfnWndProc
= _WindowP
// WndProc Handles Messages
wc.cbClsExtra
// No Extra Window Data
wc.cbWndExtra
// No Extra Window Data
wc.hInstance
// Set The Instance
= LoadIcon( NULL, IDI_WINLOGO );
// Load The Default Icon
wc.hCursor
= LoadCursor( NULL, IDC_ARROW );
// Load The Arrow Pointer
wc.hbrBackground
// No Background Required For GL
wc.lpszMenuName
wc.lpszClassName
= kWindowClassN
// Set The Class Name
// 注册窗口类
CC_BREAK_IF(! RegisterClass(&wc) && 1410 != GetLastError());
// center window position
GetWindowRect(GetDesktopWindow(), &rcDesktop);
WCHAR wszBuf[50] = {0};
MultiByteToWideChar(CP_UTF8, 0, m_szViewName, -1, wszBuf, sizeof(wszBuf));
// 创建窗口(create window)
m_hWnd = CreateWindowEx(
WS_EX_APPWINDOW | WS_EX_WINDOWEDGE,
// Extended Style For The Window
kWindowClassName,
// Class Name
// Window Title
WS_CAPTION | WS_POPUPWINDOW | WS_MINIMIZEBOX,
// Defined Window Style
// Window Position
// Window Width
// Window Height
// No Parent Window
// No Menu
hInstance,
// Instance
CC_BREAK_IF(! m_hWnd);
resize(w, h);
bRet = initGL();
CC_BREAK_IF(!bRet);
s_pMainWindow = this;
bRet = true;
} while (0);
在CCEGLView::Create函数中完成了注册窗口类和注册消息处理函数_WindowProc,并且完成了窗口的创建。作为Windows程序的核心函数,我们有必要看看_WindowProc看看
1 static CCEGLView* s_pMainWindow = NULL;
3 static LRESULT CALLBACK _WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
if (s_pMainWindow && s_pMainWindow-&getHWnd() == hWnd)
return s_pMainWindow-&WindowProc(uMsg, wParam, lParam);
return DefWindowProc(hWnd, uMsg, wParam, lParam);
_WindowProc函数是一个全局函数,当满足一定条件时就将消息转给s_pMainWindow-&WindowProc函数处理。s_pMainWindow是一个全局变量,在CCEGLView::Create中赋值为this指针。那么,我们就得进入CCEGLView::WindowProc看看
1 LRESULT CCEGLView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
BOOL bProcessed = FALSE;
switch (message)
case WM_LBUTTONDOWN:
if (m_pDelegate && MK_LBUTTON == wParam)
POINT point = {(short)LOWORD(lParam), (short)HIWORD(lParam)};
CCPoint pt(point.x/CC_CONTENT_SCALE_FACTOR(), point.y/CC_CONTENT_SCALE_FACTOR());
CCPoint tmp = ccp(pt.x, m_obScreenSize.height - pt.y);
if (m_obViewPortRect.equals(CCRectZero) || m_obViewPortRect.containsPoint(tmp))
m_bCaptured = true;
SetCapture(m_hWnd);
int id = 0;
pt.x *= m_windowTouchScaleX;
pt.y *= m_windowTouchScaleY;
handleTouchesBegin(1, &id, &pt.x, &pt.y);
case WM_MOUSEMOVE:
if (MK_LBUTTON == wParam && m_bCaptured)
POINT point = {(short)LOWORD(lParam), (short)HIWORD(lParam)};
CCPoint pt(point.x/CC_CONTENT_SCALE_FACTOR(), point.y/CC_CONTENT_SCALE_FACTOR());
int id = 0;
pt.x *= m_windowTouchScaleX;
pt.y *= m_windowTouchScaleY;
handleTouchesMove(1, &id, &pt.x, &pt.y);
case WM_LBUTTONUP:
if (m_bCaptured)
POINT point = {(short)LOWORD(lParam), (short)HIWORD(lParam)};
CCPoint pt(point.x/CC_CONTENT_SCALE_FACTOR(), point.y/CC_CONTENT_SCALE_FACTOR());
int id = 0;
pt.x *= m_windowTouchScaleX;
pt.y *= m_windowTouchScaleY;
handleTouchesEnd(1, &id, &pt.x, &pt.y);
ReleaseCapture();
m_bCaptured = false;
case WM_SIZE:
switch (wParam)
case SIZE_RESTORED:
CCApplication::sharedApplication()-&applicationWillEnterForeground();
case SIZE_MINIMIZED:
CCApplication::sharedApplication()-&applicationDidEnterBackground();
case WM_KEYDOWN:
if (wParam == VK_F1 || wParam == VK_F2)
CCDirector* pDirector = CCDirector::sharedDirector();
if (GetKeyState(VK_LSHIFT) & 0 ||
GetKeyState(VK_RSHIFT) & 0 || GetKeyState(VK_SHIFT) & 0)
pDirector-&getKeypadDispatcher()-&dispatchKeypadMSG(wParam == VK_F1 ? kTypeBackClicked : kTypeMenuClicked);
if ( m_lpfnAccelerometerKeyHook!=NULL )
(*m_lpfnAccelerometerKeyHook)( message,wParam,lParam );
case WM_KEYUP:
if ( m_lpfnAccelerometerKeyHook!=NULL )
(*m_lpfnAccelerometerKeyHook)( message,wParam,lParam );
case WM_CHAR:
if (wParam & 0x20)
if (VK_BACK == wParam)
CCIMEDispatcher::sharedDispatcher()-&dispatchDeleteBackward();
else if (VK_RETURN == wParam)
CCIMEDispatcher::sharedDispatcher()-&dispatchInsertText("\n", 1);
else if (VK_TAB == wParam)
// tab input
else if (VK_ESCAPE == wParam)
// ESC input
//CCDirector::sharedDirector()-&end();
else if (wParam & 128)
// ascii char
CCIMEDispatcher::sharedDispatcher()-&dispatchInsertText((const char *)&wParam, 1);
char szUtf8[8] = {0};
int nLen = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)&wParam, 1, szUtf8, sizeof(szUtf8), NULL, NULL);
CCIMEDispatcher::sharedDispatcher()-&dispatchInsertText(szUtf8, nLen);
if ( m_lpfnAccelerometerKeyHook!=NULL )
(*m_lpfnAccelerometerKeyHook)( message,wParam,lParam );
case WM_PAINT:
PAINTSTRUCT
BeginPaint(m_hWnd, &ps);
EndPaint(m_hWnd, &ps);
case WM_CLOSE:
CCDirector::sharedDirector()-&end();
case WM_DESTROY:
destroyGL();
PostQuitMessage(0);
if (m_wndproc)
m_wndproc(message, wParam, lParam, &bProcessed);
if (bProcessed) break;
return DefWindowProc(m_hWnd, message, wParam, lParam);
if (m_wndproc && !bProcessed)
m_wndproc(message, wParam, lParam, &bProcessed);
如果我们抛开具体的消息及其处理过程,CCEGLView::WindowProc函数可以简化为
1 LRESULT CCEGLView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
switch (message)
// 处理各种消息
// 调用自定义的处理函数
if (m_wndproc && !bProcessed)
m_wndproc(message, wParam, lParam, &bProcessed);
当然,如果不满足指定的条件就会使用Windows默认的DefWindowProc方法处理消息。
最后,就剩下&循环获取消息&这部分了。这个时候你不是想起了_tWinMain函数的最后一句代码了吗?让我们再看他一眼
1 return CCApplication::sharedApplication()-&run();
锁定目标,我们进入CCApplication::run函数探究一番,毕竟这个函数也是我们之前列出的重要函数之一。
2.2.4 &run出来的消息
CCApplication::run函数的实现如下
1 int CCApplication::run()
PVRFrameEnableControlWindow(false);
// Main message loop:
LARGE_INTEGER nF
LARGE_INTEGER nL
LARGE_INTEGER nN
QueryPerformanceFrequency(&nFreq);
QueryPerformanceCounter(&nLast);
// Initialize instance and cocos2d.
if (!applicationDidFinishLaunching())
CCEGLView* pMainWnd = CCEGLView::sharedOpenGLView();
pMainWnd-&centerWindow();
ShowWindow(pMainWnd-&getHWnd(), SW_SHOW);
if (! PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
// Get current time tick.
QueryPerformanceCounter(&nNow);
// If it's the time to draw next frame, draw it, else sleep a while.
if (nNow.QuadPart - nLast.QuadPart & m_nAnimationInterval.QuadPart)
nLast.QuadPart = nNow.QuadP
CCDirector::sharedDirector()-&mainLoop();
if (WM_QUIT == msg.message)
// Quit message loop.
// Deal with windows message.
if (! m_hAccelTable || ! TranslateAccelerator(msg.hwnd, m_hAccelTable, &msg))
TranslateMessage(&msg);
DispatchMessage(&msg);
return (int) msg.wP
注意了,这里面居然有一个循环!而且是条件为&真&的循环!我们先把函数简化后再来分析
1 int CCApplication::run()
// 显示窗口
CCEGLView* pMainWnd = CCEGLView::sharedOpenGLView();
pMainWnd-&centerWindow();
ShowWindow(pMainWnd-&getHWnd(), SW_SHOW);
if (! PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
// 处理一些数据
if (WM_QUIT == msg.message)
// 退出消息循环(Quit message loop)
// 处理快捷键
return (int) msg.wP
这下整个CCApplication::run函数就清晰了,总体来说主要完成了两个功能:
1)显示窗口
2)进入消息循环
到此,典型的Windows程序框架就完整了!如果你还是晕的,请从头再看一遍吧。
2.3 &AppDelegate
作为对HelloWorld应用程序的抽象,AppDelegate从CCApplication派生而来,重载了纯虚函数
AppDelegate : private cocos2d::CCApplication
AppDelegate();
virtual ~AppDelegate();
Implement CCDirector and CCScene init code here.
@return true
Initialize success, app continue.
@return false
Initialize failed, app terminate.
virtual bool applicationDidFinishLaunching();
The function be called when the application enter background
the pointer of the application
virtual void applicationDidEnterBackground();
The function be called when the application enter foreground
the pointer of the application
virtual void applicationWillEnterForeground();
在AppDelegate类中最主要是实现了applicationDidFinishLaunching函数。在程序启动后,执行CCApplication::run函数的过程中就会调用这个函数
1 bool AppDelegate::applicationDidFinishLaunching()
// initialize director
CCDirector *pDirector = CCDirector::sharedDirector();
pDirector-&setOpenGLView(CCEGLView::sharedOpenGLView());
TargetPlatform target = getTargetPlatform();
if (target == kTargetIpad)
CCFileUtils::sharedFileUtils()-&setResourceDirectory("iphonehd");
// don't enable retina because we don't have ipad hd resource
CCEGLView::sharedOpenGLView()-&setDesignResolutionSize(960, 640, kResolutionNoBorder);
else if (target == kTargetIphone)
// try to enable retina on device
if (true == CCDirector::sharedDirector()-&enableRetinaDisplay(true))
// iphone hd
CCFileUtils::sharedFileUtils()-&setResourceDirectory("iphonehd");
CCFileUtils::sharedFileUtils()-&setResourceDirectory("iphone");
// android, windows, blackberry, linux or mac
// use 960*640 resources as design resolution size
CCFileUtils::sharedFileUtils()-&setResourceDirectory("iphonehd");
CCEGLView::sharedOpenGLView()-&setDesignResolutionSize(960, 640, kResolutionNoBorder);
// 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();
pDirector-&runWithScene(pScene);
return true;
这个函数主要是完成CCDirector类和CCScene类对象的初始化,设置资源路径、分辨率大小和帧率(FPS:Frames Per Second);最后通过CCDirector::runWithScene函数开始场景。
对于另外两个函数,他们的实现就相对简单的多(至少代码量就少很多):把所有事情都交给CCDirector去完成
1 // This function will be called when the app is inactive. When comes a phone call,it's be invoked too
2 void AppDelegate::applicationDidEnterBackground()
CCDirector::sharedDirector()-&stopAnimation();
// if you use SimpleAudioEngine, it must be pause
// SimpleAudioEngine::sharedEngine()-&pauseBackgroundMusic();
10 // this function will be called when the app is active again
11 void AppDelegate::applicationWillEnterForeground()
CCDirector::sharedDirector()-&startAnimation();
// if you use SimpleAudioEngine, it must resume here
// SimpleAudioEngine::sharedEngine()-&resumeBackgroundMusic();
如注释所陈述的,applicationDidEnterBackground函数会在程序进入&非活跃&状态(即失去窗口焦点)时被调用,而applicationWillEnterForeground函数会在程序进入&活跃&状态(即获得窗口焦点)时被调用(可以自己在这个函数里面下断点看看具体执行的流程)。
舞台需要场景
演员站在舞台上,却表演于场景中
& & & & & & & & & & & & & & & & && by 我挂科了
赋词一句略显文艺范儿,求勿喷!言归正传,首先我们来看看HelloWorld的继承关系
HelloWorld从CCLayer继承,而CCLayer又是一个非常复杂的(至少它的father太多了)。你一定觉得HelloWorld很复杂吧,其实它没有传说中那么复杂
1 class HelloWorld : public cocos2d::CCLayer
// Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone
virtual bool init();
// there's no 'id' in cpp, so we recommend returning the class instance pointer
static cocos2d::CCScene* scene();
// a selector callback
void menuCloseCallback(CCObject* pSender);
// touch callback
void ccTouchesBegan(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent);
// implement the "static node()" method manually
CREATE_FUNC(HelloWorld);
多么单纯的类啊!你也许会说:CREATE_FUNC是神马东东?它一点都不单纯啊~ &
3.1、CREATE_FUNC
其实HelloWorld还是很单纯的,因为CREATE_FUNC定义很简单
1 #define CREATE_FUNC(__TYPE__) \
2 static __TYPE__* create() \
__TYPE__ *pRet = new __TYPE__(); \
if (pRet && pRet-&init()) \
pRet-&autorelease(); \
return pR \
delete pR \
pRet = NULL; \
return NULL; \
CREATE_FUNC是一个宏,它定义了一个名为create的static函数,该函数完成下面几个事情:
1)创建__TYPE__类型的对象指针
2)如果创建成功,则调用该对象的init函数
a)如果init函数执行成功,则调用该对象的autorelease函数并返回该对象指针
b)如果init函数执行失败,则释放该对象并返回NULL
将这个宏在HelloWorld类中展开,HelloWorld就露出了它的真面目了
1 class HelloWorld : public cocos2d::CCLayer
// Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone
virtual bool init();
// there's no 'id' in cpp, so we recommend returning the class instance pointer
static cocos2d::CCScene* scene();
// a selector callback
void menuCloseCallback(CCObject* pSender);
// touch callback
void ccTouchesBegan(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent);
// implement the "static node()" method manually
static HelloWorld* create()
HelloWorld *pRet = new HelloWorld();
if (pRet && pRet-&init())
pRet-&autorelease();
pRet = NULL;
return NULL;
我比较奇怪的是,为什么注释上写的是&static node()&而不是&static create()&呢?隐约听到有人在喊:这一定是笔误!好吧,不要在意这些细节。
3.2 &梦回AppDelegate
还记得AppDelegate::applicationDidFinishLauching函数吗?它的实现中有这么一句
1 bool AppDelegate::applicationDidFinishLaunching()
// 其他操作
// create a scene. it's an autorelease object
CCScene *pScene = HelloWorld::scene();
pDirector-&runWithScene(pScene);
// 其他操作
这是我们的HelloWorld第一次在程序中被使用,那我们就从HelloWorld::scene函数入手吧
1 CCScene* HelloWorld::scene()
// 'scene' is an autorelease object
CCScene *scene = CCScene::create();
// 'layer' is an autorelease object
HelloWorld *layer = HelloWorld::create();
// add layer as a child to scene
scene-&addChild(layer);
// return the scene
这个函数的实现完成了3个事情:
1)创建了一个空的CCScene对象scene
2)通过上面分析过的HelloWorld::create函数创建了一个HelloWorld对象layer,并将layer作为scene的一个子节点添加到scene中
3)将scene返回
3.3 &初始化
在前面我们已经展示了HelloWorld::create函数,它创建了HelloWorld对象后会调用该对象的init函数来初始化对象。HelloWorld::init函数实现如下
1 // on "init" you need to initialize your instance
2 bool HelloWorld::init()
//////////////////////////////
// 1. super init first
if ( !CCLayer::init() )
return false;
CCSize visibleSize = CCDirector::sharedDirector()-&getVisibleSize();
CCPoint origin = CCDirector::sharedDirector()-&getVisibleOrigin();
/////////////////////////////
// 2. add a menu item with "X" image, which is clicked to quit the program
you may modify it.
// add a "close" icon to exit the progress. it's an autorelease object
CCMenuItemImage *pCloseItem = CCMenuItemImage::create(
"CloseNormal.png",
"CloseSelected.png",
menu_selector(HelloWorld::menuCloseCallback));
pCloseItem-&setPosition(ccp(origin.x + visibleSize.width - pCloseItem-&getContentSize().width/2 ,
origin.y + pCloseItem-&getContentSize().height/2));
// create menu, it's an autorelease object
CCMenu* pMenu = CCMenu::create(pCloseItem, NULL);
pMenu-&setPosition(CCPointZero);
this-&addChild(pMenu, 1);
/////////////////////////////
// 3. add your codes below...
// add a label shows "Hello World"
// create and initialize a label
CCLabelTTF* pLabel = CCLabelTTF::create("Hello World", "Arial", 24);
// position the label on the center of the screen
pLabel-&setPosition(ccp(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - pLabel-&getContentSize().height));
// add the label as a child to this layer
this-&addChild(pLabel, 1);
// add "HelloWorld" splash screen"
CCSprite* pSprite = CCSprite::create("HelloWorld.png");
// position the sprite on the center of the screen
pSprite-&setPosition(ccp(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
// add the sprite as a child to this layer
this-&addChild(pSprite, 0);
// enable standard touch
this-&setTouchEnabled(true);
return true;
不要看这个函数很长,实际上主要完成了4个事情
1)调用父类的初始化函数(CCLayer::init())完成对继承自父类成员的初始化
2)创建一个菜单(CCMenu)并在其中添加一个菜单项(CCMenuItem),将菜单显示在HelloWorld中。点击这个菜单项可以关闭程序
3)创建一个标签(CCLabel),让它在HelloWorld中显示&HelloWorld&字串
4)创建一个精灵(CCSprite),让它在HelloWorld中显示图片&HelloWorld.png&
HelloWorld::init函数中所创建的都是我们在运行起来的界面中所能看到的东西,这其中涉及到一些类将在后面学习,这里不深究。我比较好奇的是菜单项所实现的功能:点击后关闭程序(不信你亲自点一下试试)。这是怎么实现的呢?仔细分析一下菜单项的创建过程
1 // add a "close" icon to exit the progress. it's an autorelease object
2 CCMenuItemImage *pCloseItem = CCMenuItemImage::create(
"CloseNormal.png",
"CloseSelected.png",
menu_selector(HelloWorld::menuCloseCallback));
最后一个参数形式很特别啊~ 我们先看看menu_selector是神马东西
1 #define menu_selector(_SELECTOR) (SEL_MenuHandler)(&_SELECTOR)
又是一个宏!它所完成的事情是将_SELECTOR取址并强制转换为SEL_MenuHandler类型。那么SEL_MenuHandler又是什么类型呢?
1 typedef void (CCObject::*SEL_MenuHandler)(CCObject*);
它是一个函数指针,这类函数有一个CCObject*类型的参数。此时我们可以将创建菜单项的代码展开,看看其真实的原貌
1 // add a "close" icon to exit the progress. it's an autorelease object
2 CCMenuItemImage *pCloseItem = CCMenuItemImage::create(
"CloseNormal.png",
"CloseSelected.png",
(SEL_MenuHandler)&HelloWorld::menuCloseCallback);
HelloWorld::menuCloseCallback函数以回调函数的方式传入菜单项,在点击菜单项时被触发。也就是说实现关闭程序功能的是HelloWorld::menuCloseCallback函数
1 void HelloWorld::menuCloseCallback(CCObject* pSender)
CCDirector::sharedDirector()-&end();
5 #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
最终,由CCDirector::end函数完成程序的关闭(在IOS中还需要调用exit函数)。
欢迎转载,但请保留原文出处:&
阅读(...) 评论()}

我要回帖

更多关于 cocos2dx获取当前时间 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信