将Cocos2dX渲染到MFC窗口上

引用:http://www.cnblogs.com/windeer/archive/2012/11/18/2767750.html

引言

        现在智能手机已经慢慢进入大众化,移动类应用开始火爆起来,游戏类应用更是占据了手机用户的大部分碎片时间。

        现在手机开发游戏也逐渐流行开来,手机的平台目前主打是 Andoird、IOS和WindowPhone。Cocos2DX跨平台开发成为吸引手机开发商和独立游戏制作人的一大亮点。

        Cocos2dX脱胎于Cocos2D,有优良的血统,成熟的框架,加上不错的效率,成为跨平台手机游戏开发的首选。

         在游戏开发过程中,各种辅助工具的开发是难免的。下面的文章 http://www.cocos2dres.com/view.asp?id=55 中有 介绍网络上可以找到一些工具,其中一些需要收费。

         如果您是制作一些小游戏,网上找到的那些工具,也许可以解决制作中的问题,但是如果您在制作大型游戏,您就不得不自己动手制作工具了。

        本文介绍将Cocos2dX渲染到MFC窗口,是制作游戏工具所需的一些知识。

 

COCOS2DX开发环境

        本文的Cocos2DX的版本是 cocos2d-2.0-x-2.0.3,  点击进入下载页面  www.cocos2d-x.org/projects/cocos2d-x/wiki/Download

        Cocos2DX下载后以及windows下配置这里就不赘述了 相关教程链接 http://www.cocos2dres.com/post/7.html win7下 VS2010教程

        在理解了 helloCPP后,我们就可以进行下一步了。

 

MFC工程

      第一步,创建MFC工程,修改工程属性  

   打开 cocos2d-win32.vc2010.sln 解决方案,添加一个MFC单文档程序。不会添加MFC工程的同学,到网上找找教程。

       以下是工程(工程名叫MFCTest)截图:

                      

 

  其中 Cocos2DApp是自己添加的。

  在工程属性面板,

        

  图中红圈处设置成现HelloCPP一样就可以了。调试的工作路径,需要设置成自己的路径。

  第二步,分析HelloCpp下的Windows窗口

  查看main.cpp的代码:

复制代码
int APIENTRY _tWinMain(HINSTANCE hInstance,
                       HINSTANCE hPrevInstance,
                       LPTSTR    lpCmdLine,
                       int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // create the application instance
    AppDelegate app;
    CCEGLView* eglView = CCEGLView::sharedOpenGLView();
    eglView->setFrameSize( 960, 640 );
    return CCApplication::sharedApplication()->run();
}
复制代码

  这里首先创建一个app,然后设置窗口大小,然后就是run()了。Windows的窗口在哪里创建的呢?

  eglView应当是与窗口相关的,我们看看setFrameSize的相关实现:

void CCEGLView::setFrameSize(float width, float height)
{
    Create((LPCTSTR)m_szViewName, (int)width, (int)height );
    CCEGLViewProtocol::setFrameSize(width, height);
}

  首先映入眼帘就是Create方法,看来windows窗口的创建就在这个方法里面:

复制代码
bool CCEGLView::Create(LPCTSTR pTitle, int w, int h)
{
    bool bRet = false;
    if( hWnd == NULL )
    {
        do 
        {
            CC_BREAK_IF(m_hWnd);

            HINSTANCE hInstance = GetModuleHandle( NULL );
            WNDCLASS  wc;        // Windows Class Structure

            // Redraw On Size, And Own DC For Window.
            wc.style          = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;  
            wc.lpfnWndProc    = _WindowProc;                    // WndProc Handles Messages
            wc.cbClsExtra     = 0;                              // No Extra Window Data
            wc.cbWndExtra     = 0;                                // No Extra Window Data
            wc.hInstance      = hInstance;                        // Set The Instance
            wc.hIcon          = LoadIcon( NULL, IDI_WINLOGO );    // Load The Default Icon
            wc.hCursor        = LoadCursor( NULL, IDC_ARROW );    // Load The Arrow Pointer
            wc.hbrBackground  = NULL;                           // No Background Required For GL
            wc.lpszMenuName   = NULL;                           // We Don't Want A Menu
            wc.lpszClassName  = kWindowClassName;               // Set The Class Name

            CC_BREAK_IF(! RegisterClass(&wc) && 1410 != GetLastError());        

            // center window position
            RECT rcDesktop;
            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
                wszBuf,                                                // Window Title
                WS_CAPTION | WS_POPUPWINDOW | WS_MINIMIZEBOX,        // Defined Window Style
                0, 0,                                                // Window Position
                0,                                                  // Window Width
                0,                                                  // Window Height
                NULL,                                                // No Parent Window
                NULL,                                                // No Menu
                hInstance,                                            // Instance
                NULL );

            CC_BREAK_IF(! m_hWnd);

            resize(w, h);

            bRet = initGL();
            CC_BREAK_IF(!bRet);

            s_pMainWindow = this;
            bRet = true;
        } while (0);
    }return bRet;
}
复制代码

  果不出我所料,我们看到了熟悉的CreateWindowEx,然后调用initGL()初始化OpenGL相关。

  我们再看看 initGL的实现:

复制代码
bool CCEGLView::initGL()
{
    m_hDC = GetDC(m_hWnd); // 拿到窗口的DC
    SetupPixelFormat(m_hDC); // 设置像素格式
    //SetupPalette();
    m_hRC = wglCreateContext(m_hDC); /// 创建OpenGLES渲染环境
    wglMakeCurrent(m_hDC, m_hRC);    /// 设置当前渲染环境

    GLenum GlewInitResult = glewInit();
    if (GLEW_OK != GlewInitResult) 
    {
        fprintf(stderr,"ERROR: %s\n",glewGetErrorString(GlewInitResult));
        return false;
    }

    if (GLEW_ARB_vertex_shader && GLEW_ARB_fragment_shader)
    {
        CCLog("Ready for GLSL\n");
    }
    else 
    {
        CCLog("Not totally ready :( \n");
    }

    if (glewIsSupported("GL_VERSION_2_0"))
    {
        CCLog("Ready for OpenGL 2.0\n");
    }
    else
    {
        CCLog("OpenGL 2.0 not supported\n");
    }
    return true;
}
复制代码

  这样 Windows窗口就和OpenGLES的渲染环境关联起来了。

  我们如何将MFC的窗口和OpenGLES的渲染环境关联起来呢?

  MFC窗口是预先已经创建好的,我们只要在创建的时候,用已经创建好的窗口来做 initGL应当就可以实现。于是我将CCEGLView的Create和SetFrameSize做了调整,可以   传入窗口句柄。新的函数代码如下:

  

复制代码
void CCEGLView::setFrameSize(float width, float height,HWND hWnd)/// 将窗口句柄通过参数传进来
{
    Create((LPCTSTR)m_szViewName, (int)width, (int)height, hWnd);
    CCEGLViewProtocol::setFrameSize(width, height);
}

bool CCEGLView::Create(LPCTSTR pTitle, int w, int h, HWND hWnd)
{
    bool bRet = false;
    if( hWnd == NULL ) /// 如果等于 NULL 则按以前的方式走
    {
        do 
        {
            CC_BREAK_IF(m_hWnd);

            HINSTANCE hInstance = GetModuleHandle( NULL );
            WNDCLASS  wc;        // Windows Class Structure

            // Redraw On Size, And Own DC For Window.
            wc.style          = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;  
            wc.lpfnWndProc    = _WindowProc;                    // WndProc Handles Messages
            wc.cbClsExtra     = 0;                              // No Extra Window Data
            wc.cbWndExtra     = 0;                                // No Extra Window Data
            wc.hInstance      = hInstance;                        // Set The Instance
            wc.hIcon          = LoadIcon( NULL, IDI_WINLOGO );    // Load The Default Icon
            wc.hCursor        = LoadCursor( NULL, IDC_ARROW );    // Load The Arrow Pointer
            wc.hbrBackground  = NULL;                           // No Background Required For GL
            wc.lpszMenuName   = NULL;                           // We Don't Want A Menu
            wc.lpszClassName  = kWindowClassName;               // Set The Class Name

            CC_BREAK_IF(! RegisterClass(&wc) && 1410 != GetLastError());        

            // center window position
            RECT rcDesktop;
            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
                wszBuf,                                                // Window Title
                WS_CAPTION | WS_POPUPWINDOW | WS_MINIMIZEBOX,        // Defined Window Style
                0, 0,                                                // Window Position
                0,                                                  // Window Width
                0,                                                  // Window Height
                NULL,                                                // No Parent Window
                NULL,                                                // No Menu
                hInstance,                                            // Instance
                NULL );

            CC_BREAK_IF(! m_hWnd);

            resize(w, h);

            bRet = initGL();
            CC_BREAK_IF(!bRet);

            s_pMainWindow = this;
            bRet = true;
        } while (0);
    }
    else
    {
        m_hWnd = hWnd; /// 直接将外部窗口当做当前窗口
        bRet = initGL(); /// 初始化OpenGLES相关
    }

    return bRet;
}
复制代码

  底层已经支持将外部窗口传入作为渲染窗口了,那我们就只要做相应的调整就可以了。

  第三步,添加Cocos2DApp

  在工程中添加 类 CCocos2DApp继承自 cocos2d::CCApplication, 这就相当于HelloCPP中的 AppDelegate.之所以在这里自己实现,是想将MFC的窗口句柄交给

  Cocos2D的渲染层。

  CCocos2DApp需要在AppDelegate的基础上,添加两个方法,代码如下:

复制代码
 1 /**
 2 @brief 系统初始化
 3 @param uWnd 窗口句柄
 4 @param nWidth 窗口宽
 5 @param nHeight 窗口高
 6 */
 7 void CCocos2DApp::init( uint32 uWnd, int32 nWidth, int32 nHeight )/// uint32 int32 是自定义数据类型 就是int 和 unsinged int
 8 {
 9     m_uWnd = uWnd;
10 
11     cocos2d::CCEGLView* eglView = cocos2d::CCEGLView::sharedOpenGLView();
12     if( eglView == NULL )
13     {
14         return;
15     }
16 
17     eglView->setFrameSize(nWidth, nHeight, (HWND)m_uWnd); /// 这里调用已经修改的cocos2d::CCEGLView方法
18 
19     // Initialize instance and cocos2d.
20     if (!applicationDidFinishLaunching())
21     {
22         return;
23     }
24 
25     n_bCocos2DInit = true;
26 }
27 //------------------------------------------------------------------------------
28 /**
29 @brief 系统运行 重载基类的run方法
30 @param
31 */
32 int CCocos2DApp::run()
33 {
34     if( m_uWnd == 0 )
35     {
36         return cocos2d::CCApplication::run();  /// 如果是引擎底层创建窗口,在消息循环在这里执行,在消息循环中执行mainLoop
37     }
38     else
39     {
40         if( n_bCocos2DInit)
41         {
42             cocos2d::CCDirector::sharedDirector()->mainLoop(); 
43         }
44 
45         return 1;
46     }
47 }
复制代码

   再把HelloWorldScene添加到工程。运行HelloCPP需要的图片资源,也需要复制到工作目录下。

  第四步 将CCocos2DApp添加到CMFCTestView中

    在CMFCTestView.h中添加

    CCocos2DApp       m_Cocos2DApp;

  我们希望在窗口初始后,调用CCocos2DApp的init方法,实现渲染环境初始化。

  切换到类视图,选择属性,选择重写(Overide)标签,找到OnInitialUpdate,添加重写CMFCTestView::InitUpdate方法

                                                      

  在其中添加代码,调用CCocos2DApp::init,代码如下:

复制代码
 1 void CMFCTestView::OnInitialUpdate()
 2 {
 3     CView::OnInitialUpdate();
 4 
 5     // TODO: 在此添加专用代码和/或调用基类
 6 
 7     RECT rc;
 8     ::GetClientRect( m_hWnd, &rc );
 9 
10     m_Cocos2DApp.init( (uint32)m_hWnd, rc.right - rc.left, rc.bottom - rc.top ); /// 传入窗口句柄和宽高
11     SetTimer( 1, 30, NULL ); /// 见下面的分析
12 }
复制代码

  在之前分析中已经提到,HelloCpp的Render是在消息循环中执行的,MFC窗口接管消息循环,我们Render放在哪里比较好呢?因为我们是用来制作工具,对渲染效率要求不是很高,所以我想用定时器来驱动我们的Render。所以在初始化的时候启动了一个定时器。

  在CMFCTestView中添加定时器处理方法:

复制代码
1 // CMFCTestView 消息处理程序
2 void CMFCTestView::OnTimer(UINT_PTR nIDEvent)
3 {
4     // TODO: 在此添加消息处理程序代码和/或调用默认值
5     m_Cocos2DApp.run(); /// 这里执行mainLoop,在mainLoop中执行Render
6     CView::OnTimer(nIDEvent);
7 }
复制代码

  编译运行应当就可以看到可爱的Cocoser了,如下图

                                       

   画面是渲染出来了,可是却收不到鼠标消息。原来引擎实现了CCEGLView::WindowProc用来处理Windows消息,系统在这里模拟了触屏的一些操作。那我们只要将MFC的窗口消息传递给CCEGLView::WindowProc就可以实现了。

  在CMFCTestView中添加重写(Ovreide)函数WindProc,代码如下:

复制代码
/**
@brief 窗口回调函数
@param
*/
LRESULT CCocos2DApp::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
    cocos2d::CCEGLView* eglView = cocos2d::CCEGLView::sharedOpenGLView();
    if( eglView )
    {
        return eglView->WindowProc( message, wParam, lParam );
    }

    return 0;
}
复制代码

   这样就可以正常收到windows窗口消息了。

 还有一个需要注意的问题,当窗口大小发生变化,我们也需要通知到引擎底层,所以我们需要再添加一个针对WM_SIZE的处理函数,函数代码如下:

复制代码
1 void CMFCTestView::OnSize(UINT nType, int cx, int cy)
2 {
3     CView::OnSize(nType, cx, cy);
4 
5     // TODO: 在此处添加消息处理程序代码
6     m_Cocos2DApp.OnSize( cx, cy );
7 }
复制代码

  在CCocos2DApp::OnSize中再对底层进行相关的设置,我们看下代码:

复制代码
/**
@brief 重置大小
@param
*/
void CCocos2DApp::OnSize( int nWidth, int nHeight )
{
    // TODO: 在此处添加消息处理程序代码  
    cocos2d::CCEGLView* eglView = cocos2d::CCEGLView::sharedOpenGLView();
    if( eglView )
    {if( n_bCocos2DInit )  
        {  
            //重新设置窗口大小及投影矩阵  
            cocos2d::CCEGLView::sharedOpenGLView()->resize(nWidth,nHeight);  
            cocos2d::CCDirector::sharedDirector()->reshapeProjection(cocos2d::CCSizeMake(nWidth,nHeight));
        }
    }    
}
复制代码

  至此,你已经看到怎样将Cocos2DX渲染到MFC窗口的全过程,相信聪明的你也一定可以制作你自己想要的游戏工具,最后希望大家Coding happy!!!

posted @ 2013-06-26 17:18  镇水古月  阅读(313)  评论(0编辑  收藏  举报