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; }
窗口不同大小下的表现: