cocos2d-x 编辑器开发笔记(一)

    与cocos相伴4年了,从起初的cocos2d到如今的cocos2d-x,静静地看着触控拖沓的前进着。一路沿着cocos的代码一点一点学习游戏开发,对cocos的情怀难以言喻,尽管cocos有着这样那样的缺陷,unity有着这样那样的优势,依然抱着cocos不肯撒手。很久很久以前就想自己动手写一个基于cocos的编辑器,但是一会儿我一个小屁程序员怎么搞的定,一会儿想想cocosstudio几百个人的团队做着呢,一会儿想想这么一个没钱途的事情,做了有什么意义,一会儿想想就自己那懒样,哪能做得起那么大一个工程,就硬生生的一直拖着。不过最近呢有些想通了,平时多余的时间拼命想着怎么赚钱压力大而且真的赚不到什么,发发呆又有些浪费,那么就发展一下兴趣爱好吧,正好又看到“红孩儿”博客写的“一个勤奋的人 可以超越一家懒惰的公司”,真是人生榜样(当然,我非常自觉的知道自己绝不是勤奋之人,平时是能省一分力觉不会多浪费一厘)。最后呢,还是拿起很久很久以前的兴趣,着手自己diy一个cocos的编辑器。(后文中出现的cocos泛指cocos2d-x)

 

    步入正题,关于编辑器,我选择在mac平台上开发,纯属个人爱好。在实现方式上,我选择重用大部分cocos引擎的代码,在引擎代码之上拦截Events,重新提供一套编辑器使用的mainloop和事件的处理方案。

    那么,第一件事,就是重写编辑器专用的GLViewImpl,查看cocos的代码之后,差点吐血,在mac和windows上完全是使用glfw创建了一个不可改变大小的窗口,然后再看看glfw的代码,stop了objc的runloop,自己另起炉灶,然后cocos在代码里写了一个

while(!_exit) {
    mainloop();
    sleep();
}

,这让我只是在mac版本上简单修改扩展的梦想完全破灭。虽然glfw做了很多便捷的处理,但是我是没有办法通过glfw和mac app一起混编。只好自己动手了,一步一步来实现一个GlView。鉴于方便的考虑,我的代码只直接使用cocos2dx 3.8创建了一个空项目,然后在项目生成的ios_mac工程中修改的,那第一件事的第一步就是新建一个editor专用的target,咱要从main.m开始重写了,建好基本的mac应用需要的内容,目录大致如下:

 

其中CXMMainView将作为编辑器的主窗口(暂时没他什么事),CCGLViewImpl-CXM就是这次的重点,重写的GLView。

重写create方法首当其冲,原本的CCGLViewImple-desktop在create中主要做了3件事,设置glfw窗口需要的参数,设置glfw窗口事件的回调,最后创建opengl窗口,我们要做的呢就是从glfw源码中把相关的代码一点一点挖出来,其实还是很简单的,直接贴上一些代码:

在.h中增加一些属性方法:

  bool initWithRect(const std::string& viewName, Rect rect, float frameZoomFactor);

void* _glView;
void* _glPixelFormat;
void* _glContext;

在.mm增加实现:

bool CXMGLViewImpl::initWithRect(const std::string& viewName, Rect rect, float frameZoomFactor)
{
    setViewName(viewName);
    _screenSize = rect.size;
    _designResolutionSize = rect.size;
    _frameZoomFactor = frameZoomFactor;
    _viewPortRect = rect;
    
    //init view
    _glView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, _screenSize.width, _screenSize.height)];
    
    //init pixel format
#define ADD_ATTR(x) { attributes[attributeCount++] = x; }
#define ADD_ATTR2(x, y) { ADD_ATTR(x); ADD_ATTR(y); }
    unsigned int attributeCount = 0;
    NSOpenGLPixelFormatAttribute attributes[40];
    ADD_ATTR(NSOpenGLPFAAccelerated);
    ADD_ATTR(NSOpenGLPFAClosestPolicy);
    ADD_ATTR2(NSOpenGLPFAAuxBuffers, 0);
    ADD_ATTR2(NSOpenGLPFAAccumSize, 0);
    int colorBits = _glContextAttrs.redBits + _glContextAttrs.greenBits + _glContextAttrs.blueBits;
    ADD_ATTR2(NSOpenGLPFAColorSize, colorBits);
    ADD_ATTR2(NSOpenGLPFAAlphaSize, _glContextAttrs.alphaBits);
    ADD_ATTR2(NSOpenGLPFADepthSize, _glContextAttrs.depthBits);
    ADD_ATTR2(NSOpenGLPFAStencilSize, _glContextAttrs.stencilBits);
    ADD_ATTR(NSOpenGLPFADoubleBuffer);
    ADD_ATTR2(NSOpenGLPFASampleBuffers, 4);
    ADD_ATTR(0);
#undef ADD_ATTR
#undef ADD_ATTR2
    
    _glPixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes];
    
    if(!_glPixelFormat) {
        MessageBox("Failed to create pixel format.", "error");
        return false;
    }
    
    //init content
    _glContext = [[NSOpenGLContext alloc] initWithFormat:(NSOpenGLPixelFormat*)_glPixelFormat shareContext:nil];
    
    [(NSOpenGLContext*)_glContext setView:(NSView*)_glView];
    [(NSOpenGLContext*)_glContext makeCurrentContext];
    
    // check OpenGL version at first
    const GLubyte* glVersion = glGetString(GL_VERSION);
    
    if ( utils::atof((const char*)glVersion) < 1.5 )
    {
        char strComplain[256] = {0};
        sprintf(strComplain,
                "OpenGL 1.5 or higher is required (your version is %s). Please upgrade the driver of your video card.",
                glVersion);
        MessageBox(strComplain, "OpenGL version too old");
        return false;
    }
    
    // Enable point size by default.
    glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
    
    return true;
}

其中大多数的代码都是搬来搬去复制过来的,请叫我大自然的搬运工。剩下还有一些简单重写的方法,比如isOpenGLReady、swapBuffers等等就不贴出来了。

 

完成了GLView的创建之后,还需要处理一下GLView的frameSize以及投影坐标的转换,designResoultionSize就先被废弃掉了,暂时不在编辑器中适应适配,来看下重写后的setFrameSize方法,如下:

void CXMGLViewImpl::setFrameSize(float width, float height)
{
    if(fabs(width-_screenSize.width)>FLT_EPSILON || fabs(height-_screenSize.height)>FLT_EPSILON) {
        _designResolutionSize = _screenSize = Size(width, height);
        
        //重新设置NSView的大小
        [(NSView*)_glView setFrameSize:NSMakeSize(width, height)];
        [(NSOpenGLContext*)_glContext update];
        //通过调用GLView方法,修改_viewPortRect、_winSizeInPoints,以及调用Director的setGLDefaultValues方法
        updateDesignResolutionSize();
    }
}

保持designResolutionSize和screenSize一致,这样_scaleX和_scaleY就一直是1。Director的setGLDefaultValues方法会调用GLView的setViewPortInPoints方法设置viewPort,以及设置默认的投影矩阵。setViewPortInPoints是另一个我们需要重写的方法:

void CXMGLViewImpl::setViewPortInPoints(float x , float y , float w , float h)
{
    //通过_frameZoomFactor调整画布的缩放
    experimental::Viewport vp(0, 0, _screenSize.width*_frameZoomFactor, _screenSize.height*_frameZoomFactor);
    Camera::setDefaultViewport(vp);
}

基于重写后的setFrameSize方法,传入的x和y永远都是0,w和h其实就是_screenSize的width和height。_frameZoomFactor是新增的属性,用来调整画布的缩放。最后的Camera不得不提一提,起初我在考虑如何实现编辑器镜头的随意移动和缩放时,第一个想法是修改投影矩阵,并且也这样实现了,所以在之前的目录结构图中会看到一个重写的Director,然后现实却深深的打击了我,我修改投影矩阵之后的cocos,并没有按照我所设想的良好运行,只有stats的显示符合我的预计,场景中其他物体的投影转换完全不搭理我的修改,再详细查看了Director的drawScene代码,才发现原来cocos的camera是真的有作用的……原地绕了一圈,白白浪费不少精力,只能怪学艺不精,setViewPortInPoints和Director中setProjection所修改的仅仅是cocos的“default" Viewport和"default" Projection,之后不在任何Scene中的渲染,才会用Default设置。

Scene渲染时会有如下调用:

director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, Camera::_visitingCamera->getViewProjectionMatrix());
camera->apply();

明白了Camera的作用之后,想要任意的移动编辑器镜头,就非常简单了,只需要简单的设置Scene的Camera位置就可以了,增加一个focus方法:

void CXMAppDelegate::focusOn(float x, float y)
{
    _focusPoint.x = x;
    _focusPoint.y = y;
    
    if(_cxmScene != nullptr) {
        auto winSize = Director::getInstance()->getWinSize();
        auto projection = Director::getInstance()->getProjection();
        switch (projection)
        {
            case Director::Projection::_2D:
            {
                _cxmScene->getDefaultCamera()->setPosition(x-winSize.width/2.0, y-winSize.height/2.0);
                break;
            }
            case Director::Projection::_3D:
            {
                float zeye = Director::getInstance()->getZEye();
                Vec3 eye(x, y, zeye), center(x, y, 0.0f), up(0.0, 1.0, 0.0);
                _cxmScene->getDefaultCamera()->setPosition3D(eye);
                _cxmScene->getDefaultCamera()->lookAt(center, up);
                break;
            }
            default:
                CCLOG("unrecognized projection");
                break;
        }
    }
}

 

最后,增加的预设窗口大小的设置,既我想在960*640分辨率的情况下设计游戏,那么预设窗口大小就是960*640,而引擎中的frameSize的含义是当前画布的大小。

最后的最后,举个例子:

bool CXMAppDelegate::applicationDidFinishLaunching()
{
    this->setDefaultWinSize(960, 640);
    this->focusOn(480, 320);
    
    auto director = Director::getInstance();
    director->setDisplayStats(true);
    director->setProjection(Director::Projection::_2D);
    
    _cxmScene = Scene::create();
    director->runWithScene(_cxmScene);
    
    LayerColor *layer1 = LayerColor::create(Color4B::GREEN, 20, 20);
    layer1->setPosition(Vec2(0,0));
    _cxmScene->addChild(layer1);
    
    LayerColor *layer2 = LayerColor::create(Color4B::GREEN, 20, 20);
    layer2->setPosition(Vec2(940,0));
    _cxmScene->addChild(layer2);
    
    LayerColor *layer3 = LayerColor::create(Color4B::GREEN, 20, 20);
    layer3->setPosition(Vec2(940,620));
    _cxmScene->addChild(layer3);
    
    LayerColor *layer4 = LayerColor::create(Color4B::GREEN, 20, 20);
    layer4->setPosition(Vec2(0,620));
    _cxmScene->addChild(layer4);
    
    return true;
}

窗口不同大小下的表现:

  

 

posted @ 2015-12-09 00:30  JasonZXX  阅读(1691)  评论(0编辑  收藏  举报