ps:目录是2dx概述!!!
上一节中我大概简述了2dx引擎环境的搭建,在交叉编译一概而过,这一节我将详细说下这方面的内容,
有人提出不用cygwin来编译,用adt毫无压力,额 我只想说 adt从我搞android就没有碰过那玩意,一直用idea,现在的旗舰版idea12对android支持相当完美,搭配maven管理,发挥到极致。这就不说了,这里我放出cygwin离线安装:http://pan.baidu.com/share/link?shareid=4138200214&uk=3088193979,如果下在不了就告诉我下哈。
欲开发比如利器准备,这里我推荐cygiwn+vs2012(屌丝一枚 不像土豪)+idea12+vs助手;
cygin:模拟win上的linux编译环境,vs开发语言c++ ,为啥vs2012 ,额这个主题不错深色的,不伤眼睛,idea12编译app;
1.创建一个项目,当然创建项目多样化,一种是直接在vs上创建(前提你已经按照上一节中安装了模版),另一种就是用python脚本来创建(也比较方便可以多个平台都创建),这里先说后面一种:
1.搭建python环境,这里要装pyhton版本是27的,笔者试过33的不行哈,环境搭建好了,切换cocos2d-x-2.2的目录中进入到\tools\project-creator目录下运行如下命令:
python create_project.py -project HelloWorld -package xxx.xxx.xxx -language cpp
hello word 是项目名称,后面的是android上的命名规范,这样执行,会在cocos2d-x的文件下project中生成helloword项目:
到这里我们用python就创建完毕了;注意这里提一点,如果你要把这个项目拷贝出cocos2d-x文件夹单独放另外地方,那么注意不要忘记拷贝依赖裤哈;
下面我直接用vs2012来创建,步骤:
1.注意不要勾的地方:
2.拷贝依赖库到项目的proj.win32下,运行如下:
3.接下来是交叉编译,创建android项目 额前面创建了哈 当然也可以用creat-android-prioject.bat,注意指定好你的NDK路径(上一节中每一提),首先编译要组织文件这里分享笔者用的:
http://pan.baidu.com/share/link?shareid=153393070&uk=3088193979
点击,找到项目根目录:
4.接下来就是编译:
如果这样,那么就成功一般了,接下来编译app(注:笔者之前是编译了cocos2d-x的库的 所以编译很快就结束了,如果你第一次编译就慢慢等吧)像这样:
5.打开idea,选择我们的proj.android,别慌,先把cocos2d-x-2.1.4\cocos2dx\platform\android\java\src项目下的java文件拷贝到项目的src下,这个就是jni java套子 用这个来搞NDK 专用,不然不干净 也是没得其他 途径的 ,无法完成发布app;
这就是idea,支持的android,目录简单介绍下:assets资源文件,gen我就不说了,搞android的都晓得,jni,mk和cpp,src中java文件,lib编译过来的.so文件。
6.最后自己选择模拟器,自己手机也行,应该可以成功了,不行的话留言;
7.关于ios的编译问题,因为ios中用xcode,所以你直接创建一个cocos2d-x项目,将class和资源文件拷贝到项目相应的目录,那么直接编译发布就行,这里就不讲解了笔者每一mac,唉等段时间还得呵呵;
学习一个引擎,应当对引擎有所了解,笔者在接触他的时候,直接看源码,当然首先还是基础了的,了解他是有什么组成,心里有个数,不是东打西撞,笔者整理了下关于2dx的概述,本想下一个章节发布详说,还是就这里说了吧,下一节就开始真正的coocs2d-x之旅了;
大概如下,以下类容整理资源来自互联网以及自己修改一些,希望大家仔细认真摸清楚,心里有个结构上的认识,不然写代码额这个干啥干啥,不好搞哦 死托硬搞 累,毕竟我是记不住,顶多熟悉,不清楚了看源码的testcpp,源码 ,直接上菜(注意一定要搞清楚!!!):
Cocos2d-x 整体描述
和传统的游戏引擎一样,cocos2d-x作为一个2d的游戏引擎,其也有以下几大概念组成:
导演(CCDiretor):
在cocos2d-x引擎中,CCDirector类是整个游戏的组织和控制核心,游戏的运行规则,游戏内的CCScene(场景)、布景(CCLayer)、角色(CCSprite)等的运动,均由CCDirector管理,其在游戏中起着指定游戏规则让游戏内的场景、布景和任务有序的运行。在整个游戏里面,一般只有一个导演,游戏开始和结束时,都需要调用CCDirector的方法完成游戏初始化或销毁工作,其提供了一些场景管理的方法,如runWithScene、DrawScene、pushScene和replaceScen等。
它控制FPS的显示隐藏,窗口大小,游戏的进入,退出,关卡的切换,取得正交模式,取得正在运行的场景,取得GL视图,游戏主循环入口等等
摄像机(CCCamera)
游戏中每个节点都需要摄像机,当节点发生缩放,旋转和位置变化时,都需要覆盖摄像机,让这个节点通过摄像机进行重新渲染。
官方对于摄像机的描述是这样的:CCCamera应用于每个CCNode中。 在从不同视野观察时起作用,OpenGL中的gluLookAt函数用来定位摄像机。 假如这个物体通过绽放、旋转、或者平移,那么这些操作将修改摄像机。 注意:使用摄像机或者操作旋转、绽放、平移等属性,你只能选其中一个,假如你使用摄像机,世界坐标就会失去作用。 局限性: 几种节点,如CCParallaxNode,CCParticle使用世界节点坐标,并且他们并不能正常工作,假如你使用摄像机移动他们(或者他们的任何父类)。 在成批的结点上(即一个结点上包含多个节点)如CCSprite对象不会正常工作当他们都从属于一个CCSpriteBatchNode对象时。 推荐你只在创建3d特效时使用。对于2d特效来说,用CCFollow动作或者平移、绽放、旋转更好。
场景(CCScene)
在游戏里,场景就是关卡。关卡由人物角色和背景构成。在电影里,场景就是电影中的各种场面,各种场面主要由人物活动和背景等构成。而在Cocos2d-x 引擎中,场景存放的是需要渲染的布景,任务角色和菜单,它可以作为一个整体,一起渲染,一起销毁,一起被场景切换使用。
即游戏关卡的实施者,其管理这多个CCLayer,进行场景展现,相关业务逻辑的处理等;
布景(CCLayer)
从概念上说,布景就是场景里的背景。其实就是层次的概念,这种概念在KJava时代就已经存在,就是手动地把游戏中的场景分层(也有靠地图编辑器实现)。
每个游戏场景中都可以有很多层,每一层有各自的职责任务,例如专门负责显示背景,专门负责显示道具,专门负责显示任务角色等,在每一层上面可以放置不同的元素,包括文本,精灵和菜单等,通过层与层之间的组合关系,我们可以很容易的控制和显示各种各样的界面了,当然,其和photoshop中的层一样,也有透明之说,所以为了能够看到每一层上面的东西,很多层都设置为透明或半透明的,否则只能看到最上面的一层了。
一般一个场景可以有很多层,比如背景图层,物件层,NPC层,角色层,UI层等等,每个层负责自己的功能业务,总的在一个CCScene里共存,随着场景的隐藏而隐藏;不同的层之间可以通过设置不同alpha通道(透明度)进行显示控制;
角色(CCSprite)
精灵是整个游戏开发处理的主要对象,包括主角和敌人、NPC等,甚至随机飘过的一片云或飞鸟从技术上讲,也是精灵,因为精灵在cocos2d-x中,就是一个可以不断变化的图片,这些变化包括位置变化,旋转、放大缩小和运动等。
动作(CCAction)
角色所具有的动作,一般用于精灵发生动作时使用,如移动,释放魔法等。
总体来说以上的对象主要关联如下:
Cocos2d-x的目录结构如下:
目录的具体结构介绍如下:
Box2D:物理引擎Box2D的相关源文件
Chipmunk:物理引擎chipmunk的相关源文件
cocos2dx:cocos2d-x引擎的核心部分,存放了引擎的大部分源文件
CocosDenshion:声音模块相关源文件
Debug.win32:在Windows 上调试输出目录
Doxygen:生成doxygen项目文档时需要的配置文件
HelloLua:在游戏中使用lua的示例代码
HelloWorld:测试代码helloworld
Js:cocos2d-x js脚本支持源码目录
Licenses:许可文件存放目录
Lua:脚本语言lua支持的相关源文件
Template:包括编译iOS和Android等平台开发时需要的配置文件
Testjs:cocos2d-x引擎各平台js语言的api示例代码
Tests:cocos2d-x引擎所有api的示例代码
Tools:包括"tolua的配置文件"和“xcode4的模板生成工具”
build-win32.bat:编译cocos2d-x引擎的windwos项目脚本
cocos2d-win32.vc2008.sln:windows项目vs2008解决方案文件
cocos2d-win32.vc2010.sln:windows项目vs2010解决方案文件
create-android-project.bat :在windows上面创建android项目的脚本
create-android-project.sh:在linux上创建android的脚本
install-templates-msvc.bat:创建vs2010或vs2008的工程模板脚本
install-templates-xcode.sh:创建xcode工程模板的脚本
在cocos2d-x中有两种坐标系,分别是屏幕坐标系和open gl坐标系。
在opengl坐标体系中有两个非常重要的参数,即锚点和位置。
锚点作为位置的参考点,锚点在设置某一个物件(如CCSprite,CCLayer)的位置时,其参照位置,数据为(0.0-1.0)之间,默认是(0.5, 0.5),也就是说其默认参照位置是该物件的中心。如果把某一物体放到屏幕的中心,对于默认anchor来说,就是把该物体的中心放到屏幕中心 ,取值在0到1之间的好处就是,锚点不会和具体物体的大小耦合,也即不用关注物件大小,而应取其对应比率,如果把锚点改成(0,0),则进行放置位置时,以图片左上角作为起始点,也就是放置位置设置对应的点。
位置顾名思义就是物体的位置。
在调用任何需要设置位置的函数或从函数获取位置信息前,必须要明确这个函数使用的是哪个坐标系,如调用CCNode类的setPosition函数,它使用的就是GL坐标系,而在处理触摸时间时CCTouch对象在宏的坐标就是屏幕坐标。
另一个重要的坐标系就是和Node相关的本地坐标系。这个结构和一般做3D用的场景树的概念是一样的。所以从Node拿到的位置是该节点的本地坐标,需要通过特定的函数才能把本地坐标转换为世界坐标。而且这里的坐标都用的是GL坐标系。在CCNode对象中有几个方便的函数可以做坐标转换。convertToWorldSpace方法可以把基于当前node的本地坐标系下的坐标转换到世界坐标系中。convertToNodeSpace方法可以把世界坐标转换到当前node的本地坐标系中。
类继承图:
主要函数:
virtual CCObject * copyWithZone (CCZone *pZone)//克隆对象
virtual bool isDone (void)//动作是否已经换成
virtual void startWithTarget (CCNode *pTarget)//设置动作关联的对象,动作运行前调用该方法
virtual void stop (void)//停止动作
virtual void step (ccTime dt)//设置动作的间隔时间
CCNode * getTarget (void)//获取动作关联的对象
void setTarget (CCNode *pTarget)//设置动作的关联的对象
int getTag (void)//获取动作的tag
void setTag (int nTag)//设置动作的tag
static CCAction * action ()//生成action
CCSprite是一副2D图像,CCSprite可以通过图像或者图像中的一个矩形子区域创建 如果它的父节点或者任意继承树上的节点是CCspriteBatchNode则具有下述特性:
父节点是CCSpriteBatchNode时具有的特性: 更快的渲染速度,特别时CCSpriteBatchNode有很多子节点的情况下,但有以下限制:
A、不支持照相功能(例如:CCOrbitCamera动作不能执行)
B、不支持基于网格的动作(例如:CCLens,CCRipple,CCTwirl)
C、Alias / Antialias属性属于CCSpriteBatchNode,所以你不能单独设置aliase属性
D、渲染函数属性属于CCSpriteBatchNode,所以你不能单独设置渲染函数属性
E、不支持视差滚动,但是可以通过“代理”精灵模拟 如果它的父节点时普通CCNode,那么CCSprite具有和其它任何CCNode一样的行为。
虽然有以上限制,但它也有以下优点:
A、支持渲染(Blending)功能
B、支持Alias / Antialias • 但是渲染速度会比较慢,每次只渲染一个子节点 CCSprite默认的锚点时(0.5, 0.5)
可以说,所有与图片有关系的,都可以用这个来处理,主要是提供图片的加载展示,可以加载序列帧,以便动画播放
类继承图
其主要函数除了CCNode继承下来的函数外,还有以下函数:
virtual void setTexture (CCTexture2D *texture)//设置精灵的纹理
virtual CCTexture2D * getTexture (void)//获取精灵的纹理
bool initWithTexture (CCTexture2D *pTexture)//根据纹理初始化精灵
bool initWithTexture (CCTexture2D *pTexture, const CCRect &rect)//根据纹理指定的区域初始化精灵
bool initWithSpriteFrame (CCSpriteFrame *pSpriteFrame)//根据SpriteFrame初始化精灵
bool initWithSpriteFrameName (const char *pszSpriteFrameName)//根据spriteFrame的名称初始化精灵
bool initWithFile (const char *pszFilename)//根据plist文件初始化精灵
bool initWithFile (const char *pszFilename, const CCRect &rect)//根据plist文件初始化精灵
bool initWithBatchNode (CCSpriteBatchNode *batchNode, const CCRect &rect)//批量根据节点初始化精灵
void setDisplayFrameWithAnimationName (const char *animationName, int frameIndex)
static CCSprite * spriteWithTexture (CCTexture2D *pTexture) //根据纹理生成精灵
static CCSprite * spriteWithTexture (CCTexture2D *pTexture, const CCRect &rect)//根据纹理指定的区域生成精灵
static CCSprite * spriteWithTexture (CCTexture2D *pTexture, const CCRect &rect, const CCPoint &offset)//根据纹理指定的区域和点偏移生成精灵
static CCSprite * spriteWithSpriteFrame (CCSpriteFrame *pSpriteFrame)//根据SpriteFrame生成精灵
static CCSprite * spriteWithSpriteFrameName (const char *pszSpriteFrameName)//根据SpriteFrame的名称生成精灵
static CCSprite * spriteWithFile (const char *pszFileName)//根据plist文件按生成CCSprite
static CCSprite * spriteWithFile (const char *pszFileName, const CCRect &rect)//根据plist文件生成精灵
static CCSprite * spriteWithBatchNode (CCSpriteBatchNode *batchNode, const CCRect &rect)//根据节点批量生成精灵
首先是类结构:
可以看到CCNode 几乎是游戏中处理的大部分类的父类,其主要有以下函数:
virtual int getZOrder (void) //获取节点的顺序
virtual const CCPoint & getPosition (void) //获取节点的位置
virtual void setPosition (const CCPoint &var) //设置节点的位置
virtual CCArray * getChildren (void) //获取其所有子节点
virtual CCCamera * getCamera (void)//获取其对应的摄像机
virtual bool getIsVisible (void) //判断节点是否可见
virtual void setIsVisible (bool var) //设置节点可见性
virtual const CCPoint & getAnchorPoint (void) //获取节点的锚点的位置
virtual void setAnchorPoint (const CCPoint &var) //设置节点的锚点位置
virtual bool getIsRunning (void) //判断节点是否在运行
virtual CCNode * getParent (void)//获取父及节点指针
virtual void setParent (CCNode *var) //设置节点的父节点
virtual int getTag (void) //获取节点的tag
virtual void setTag (int var) //设置节点的tag
char * description (void) //返回节点的描述
virtual void onEnter () //进入节点时的回调函数
virtual void onEnterTransitionDidFinish ()//进入节点后的回调函数
virtual void onExit ()//离开节点时的回调函数
virtual void addChild (CCNode *child)//增加节点
virtual void addChild (CCNode *child, int zOrder) //通过顺序添加节点
virtual void addChild (CCNode *child, int zOrder, int tag)//通过顺序和tag添加节点
void removeFromParentAndCleanup (bool cleanup)//删除父节点中的当前节点并清除动作及回调函数
virtual void removeChild (CCNode *child, bool cleanup) //删除节点
void removeChildByTag (int tag, bool cleanup)//通过tag删除节点
virtual void removeAllChildrenWithCleanup (bool cleanup)//删除节点并清除动作及回调函数
CCNode * getChildByTag (int tag)//通过tag获取节点
virtual void reorderChild (CCNode *child, int zOrder)//根据order重新排序
virtual void cleanup (void)//清除动作
virtual void draw (void)//绘制自己
virtual void visit (void)//访问节点
CCAction * runAction (CCAction *action) //运行动作
void stopAllActions (void)//停止所有的动作
void stopAction (CCAction *action)//停止动作
void stopActionByTag (int tag) //通过tag停止动作
CCAction * getActionByTag (int tag)//通过tag获取动作的指针
unsigned int numberOfRunningActions (void)//正在运行的动作的总数
void schedule (SEL_SCHEDULE selector)//定义一个定时器
void schedule (SEL_SCHEDULE selector, ccTime interval)//定义一个定时器
void unschedule (SEL_SCHEDULE selector)//取消一个定时器
void unscheduleAllSelectors (void)//取消所有定时器
void resumeSchedulerAndActions (void)//恢复定时器和动作
void pauseSchedulerAndActions (void)//暂停定时器和动作
static CCNode * node (void)//生成一个节点
CCNode是cocos2d-x的渲染链,写游戏基本上就是和他打交道了,cocos2d-x同时只能渲染一个CCScene,因此CCScene是渲染的根节点。在构建游戏时,一般是一个Scene中添加一个或者多个CCLayer,一个Layer中又添加多个CCSprite或者CCMenu,CCSprite中还可以添加CCParticleSystem等等。这样就构建了一个渲染树,cocos2d-x历遍这个树来将图像显示在屏幕上。
coco2d-x的渲染实际上是调用visit()函数来完成的:即visit()这个函数调用它包含的Child的zOrder<0的visit()函数,之后调用draw()函数,再调用Child的zOrder>=0的visit()函数,它实际上是一个深度优先的算法。他的Child是按照zOrder排序的,以保证渲染的正确性。draw()的作用是绘制自己。在CCSprite这些确实需要绘制的类中,draw()调用openGL的函数来完成绘制功能——把一个纹理映射到一个矩形上。
如果要自定义绘制一些图像,可以重写draw()函数,不过不要忘记调用父类的draw()函数。
可以进行网状关系的管理,其实CCScene,CCLayer也是一个CCNode.它提供节点增删,包含,提供节点缩放,每个节点有一个照相机,提供动画支持,也就是说每一个从CCNode派生的类都可以执行动画操作;
类继承图:
CCLayer 是CCNode的子类 ,实现了TouchEventsDelegate接口,继承了CCNode所有的特性,并且附加了一些自己的特性,它能够接收iPhone的触摸事件,也能够接收Accelerometer的输入。
其主要函数如下:
bool init () //初始化函数
virtual void onEnter ()//进入时的回调函数
virtual void onExit ()//离开时的回调函数
virtual void onEnterTransitionDidFinish ()//进入后的回调函数
virtual bool ccTouchBegan (CCTouch *pTouch, CCEvent *pEvent)//单点触屏事件
virtual void ccTouchesBegan (CCSet *pTouches, CCEvent *pEvent)//多点触屏事件
virtual void ccTouchesMoved (CCSet *pTouches, CCEvent *pEvent)//在触摸屏幕移动事件
virtual void ccTouchesEnded (CCSet *pTouches, CCEvent *pEvent)//触摸屏幕移动时间结束
virtual void ccTouchesCancelled (CCSet *pTouches, CCEvent *pEvent)//触屏事件取消
static CCLayer * node (void)//生成layer
场景(CCScene)
类结构:
CCScene主要有以下两个函数:
bool init () //初始化函数
static CCScene * node (void) //生CCScene
作为场景类,为何只有这两个函数而没有其他方法呢,因为目前在CCScene承担的是一个容器的功能,游戏开发时需要渲染的对象反正CCScene里面统一管理,包括创建、销毁和场景切换等,而且从其类的结构图可看出,其直接继承了CCNode类,下章我们会具体介绍CCNode类
在cocos2d-x中,每个节点(CCNode)都需要用到,即当节点发生旋转、缩放和位置变化等时,都需要覆盖CCCamera,然后这个节点通过CCCamera重新渲染。
类结构:
其主要函数如下:
char * description (void) //返回当前摄像机的描述
void restore (void) //重置摄像机,使摄像机回到默认位置
void locate (void) //按设置的eye坐标放置摄像机
void setEyeXYZ (float fEyeX, float fEyeY, float fEyeZ) //设置摄像机eye坐标
void setCenterXYZ (float fCenterX, float fCenterY, float fCenterZ)//设置center的坐标
void setUpXYZ (float fUpX, float fUpY, float fUpZ)//设置up的坐标
void getEyeXYZ (float *pEyeX, float *pEyeY, float *pEyeZ) //获取eye的坐标
void getCenterXYZ (float *pCenterX, float *pCenterY, float *pCenterZ)//获取center的坐标
void getUpXYZ (float *pUpX, float *pUpY, float *pUpZ)//获取Up的坐标
在cocos2d-x里面,游戏的任何时间,只有一个场景对象实例处于运行状态,该对象可以作为当前游戏内容的整体包对象
Cocos2d-x引擎除了提供了CCDirector,还提供了一个CCDisplayLinkDirector,CCDisplayLinkDirector是一个可以自动刷新的CCDdirector。
CCDirector主要函数如下:
CCScene * getRunningScene (void) //获取当前运行的场景
double getAnimationInterval (void)//获取当前的FPS
virtual void setAnimationInterval (double dValue)=0 //设置FPS
bool isDisplayFPS (void) //是否在屏幕底部显示FPS
void setDisplayFPS (bool bDisplayFPS) //设置在屏幕底部显示FPS
CC_GLVIEW * getOpenGLView (void) //获取CCEGLView
void setOpenGLView (CC_GLVIEW *pobOpenGLView) //设置CCEGLView
bool isPaused (void) //当然是否适合暂停
unsigned int getFrames (void) //获取从director开始起,所有被调用的Frames的数量
ccDirectorProjection getProjection (void) //获取OpenGl的projection
void setProjection (ccDirectorProjection kProjection) //设置Opengl 的projection
bool isSendCleanupToScene (void) // 场景替换时,是否接收到cleanup时间,若新的的场景是被push进来的,旧的场景不会收到cleanup 事件,若新的场景是被替换进来的,旧的场景就能够收到cleanup事件。
CCSize getWinSize (void) //获取Open gl view的大小,单位为点
CCSize getWinSizeInPixels (void) // 获取以像素为单位的open gl view大小
CCSize getDisplaySizeInPixels (void) //获取以像素为单位的open gl view 的显示区域大小
void reshapeProjection (const CCSize &newWindowSize) //改变projection的大小
CCPoint convertToGL (const CCPoint &obPoint) //将UIKIT坐标体系转化为opengl坐标体系
CCPoint convertToUI (const CCPoint &obPoint)//将opengl坐标体系转化为UIKIT坐标体系
float getZEye (void) //获取缺省镜头Z的位置
void runWithScene (CCScene *pScene) //运行场景
void pushScene (CCScene *pScene) //push场景
void popScene (void)//pop场景
void replaceScene (CCScene *pScene) //替换场景
void pause (void) //暂停游戏
void resume (void) //回复游戏
virtual void stopAnimation (void)=0 //停止动画
virtual void startAnimation (void)=0 //开始动画
void drawScene (void) //每次frame都会被调用,无需自己调用
void purgeCachedData (void) //删除缓存的数据
void setGLDefaultValues (void) //设置Opengl的默认值
void setAlphaBlending (bool bOn) //设置是否启用opengl的alpha通道
void setDepthTest (bool bOn)//设置是否测试景深
virtual void mainLoop (void)=0//游戏主循环
void applyOrientation (void) //是设备方向设置生效
ccDeviceOrientation getDeviceOrientation (void) //获取设备方向
void setDeviceOrientation (ccDeviceOrientation kDeviceOrientation) //设置游戏横屏
bool enableRetinaDisplay (bool enabled) //设置是否启用视网膜屏支持
bool isRetinaDisplay ()//是否启用视网膜屏支持
void resetDirector () //重置游戏
static bool setDirectorType (ccDirectorType obDirectorType) //设置directortype,目前主要有kCCDirectorTypeNSTimer、kCCDirectorTypeMainLloop,kCCDirectorType-ThreadMainLoop、kCCDiretorTypeDisplayLknk
红薯这个编辑器不是很好 图片太多 上传不了,今天喝了点小酒有点累了,大家可以直接在这个链接上找到关于
cocos2d-x的连接:
http://pan.baidu.com/share/link?shareid=367419575&uk=3088193979(有图有描述 相比这个较好点 最后提醒一定熟悉这个!)
好吧今天就这样,下一章节我们就进入2dx引擎真正的分解了,再次声明 本系列会持续更新,有想搭车的赶紧哈
ps:因最近公司项目紧张,所以本系列文章更新时间在1周或者2周吧,本人擅长游戏服务器端开发,游戏爱好开发者群:qq:275459491 个人qq:502959937 加时说明来自osc!