Cocos2dx学习笔记
cocos2dx是一个开源的C++游戏开发框架,实现了很多开发游戏常用的底层封装,我们可以通过调用框架实现的api来根据自己的想法构建游戏。
cocos2dx是跨平台的,使用cmake构建同一套源代码在不同平台下不同编译环境的项目结构,在相应的平台打包就可以得到相应的可执行程序。
本文使用windows平台进行开发,需要cmake生成visual studio2019的项目结构,并使用vs2019打包出win32平台的可执行程序。
(其实也尝试了在Android Studio中生成Android平台安装包,但是由于中文编码问题失败了)
搭建开发环境
必要工具
cmake3.7
这个可以直接在vs2019中的工具里获取
然后在单个组件里搜索cmake,安装那个用于windows的cmake然后保存修改就好了
python2.7
https://www.python.org/downloads/release/python-2718/
在这个网址里面安装下面列表中的这个版本
cocos2dx
https://www.cocos.com/cocos2dx
下载这个里面的4.0版本然后解压,放在你想放的目录里
环境变量
接下来检查系统环境变量里的这四个东西
一定要有这四个文件夹
cmake的bin文件夹
我电脑上的目录在
C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin
cocos2dx4的命令行工具bin文件夹
我的是在
C:\Cocos\Cocos2d-x\cocos2d-x-4.0\tools\cocos2d-console\bin
python的命令行工具
我的是在
C:\Python27
vs2019的MSVC命令行工具
我的在
C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.25.28610\bin\Hostx64\x64
如果这4个文件夹不在Path里的话,一定记得补上自己电脑上相应的目录
检查和添加完了以后,重启电脑
安装
然后进入解压后的cocos目录下运行setup.py,就算全部安装完了
建立项目
建立cocos项目
进入命令行工具,输入这个命令
1 | cocos new HelloWorld -l cpp -d E:\cocos\Projects |
其中HelloWorld是项目的名称
E:\cocos\Projects是项目的绝对路径
这两个可以是自定义的,便于演示后面就用这两个代替
使用cmake从通用项目中创建windows下的visual studio2019式项目结构
进入通用项目的目录
创建windows下的项目组织目录
1 | cd E:\cocos\Projects\HelloWorld |
然后你就能看到这个文件夹里生成了项目名称的vs2019的sln文件
从visual studio中生成,编译,运行cocos项目
打开HelloWorld.sln,在vs2019中启动工程
然后在HelloWorld上右键设为启动项目
然后就可以像平常一样生成,运行和调试了
项目结构
项目文件结构
进行cocos new和cmake生成的cocos2dx的项目文件目录大概长这个亚子
其中Classes文件夹是我们需要关心的源代码,也就是说所有的游戏代码都应该在这个文件夹中编写。
cocos2d文件夹中是复制过来的引擎框架,每一个项目都是一样的。这里面有cocos2dx的源代码。
proj.XXX系列文件夹是不同平台下cmake构建的文件夹,不用关心。
Resources文件夹也是我们需要关心的,这里面应该存放我们游戏中用到的字体、音乐、图片、视频、配置文件、字符映射表等等资源文件。
win32-build文件夹是cmake生成的临时文件夹,里面是vs2019的项目组织结构,每次cmake都会更新,不需要关注。
CmakeLists.txt是cmake组织源代码生成不同平台项目架构的依据,每在Classes中添加的文件,都要在CmakeLists中注册,并重新使用cmake生成。
代码结构
先来看看Classes中的结构
AppDelelgate就是程序的入口,控制程序窗口的各个参数,具体的用法可以看看其中的注释,解释的非常的详细。这份代码是框架提供给你的入口,从此以后的代码,就要你自己完成了。
运行模式
现在,让我们来理解一下使用cocos2dx编译的代码是怎么样运行的。
众所周知,游戏窗口是一直存在在桌面上的,但这个游戏程序确是要一直不停运行的,不可能在任何时刻让程序停下来等玩家进行交互,也就是说只要程序窗口还开着,我们的程序就在不停的执行一些代码,准确来说是执行一个循环,这个循环叫做主循环。主循环在cocos2dx引擎内部,我们无权改动任何cocos2dx内部的代码。每次循环中,虽然看起来游戏的窗口并没有变化,但其实整个游戏窗口的画面都会被抹去然后重新绘制,这样的每次循环我们叫做一帧,cocos2dx默认的帧率能达到每秒60次,所以我们才会感觉到窗口没有变化。当需要绘制的东西过多,循环效率明显低于每秒60次时,我们的肉眼就会直观的感觉到画面重绘的过程,也就是我们所说的游戏卡顿。以下的图就是cocos程序运行时的主循环的流程图。
既然游戏运行的时候一直在执行循环,而这个循环我们又无权改动,那要在哪里写代码才能改动游戏的窗口呢?同样我们设计的对象又要怎么样存活到游戏结束呢?游戏和玩家的交互要怎样执行呢?
遇到细节上不懂的问题,建议查阅官方api,api中有详细的注释
布置画面——UI组件与初始化
首先cocos提供各种各样的UI组件,包括默认不能交互的标签、可以点击的按钮、可以拖动的拉动条等等,他们是实实在在在内存中,并且出现在屏幕上出现相应图形的变量。
在主循环开始前,我们需要定义这些变量,并通过他们内置的成员函数对他们出现的位置,出现的顺序,文字内容、颜色等等的属性进行初始的设置,这个过程叫做初始化。在实际写代码中,对UI的布局基本都是在初始化里完成的。
内存管理
初始化所产生的这些组件,在主循环中依然需要存在,因此在初始化的时候应该按照动态内存管理。为了内存管理方便,cocos实现了一套自己的引用计数来达到内存自动释放的功能(也是cocos2dx被诟病的一大重要原因)。
绘图的依据——场景图
初始化的组件在绘图中如何被绘制在屏幕上呢?
cocos使用场景图组织屏幕上的组件。同时可能出现在同一个屏幕上的组件被组织在一个场景中,每个场景都在内存中实实在在纯在一颗组织所有组件的树,这就是场景图。
场景图是一种安排场景内对象的数据结构,它把场景内所有的 节点(Node) 都包含在一个 树(tree) 上。(场景图虽然叫做"图",但实际使用一个树结构来表示)。既然场景图是一个树结构,你就能遍历它,Cocos2d-x 使用
中序遍历
,先遍历左子树,然后根节点,最后是右子树。中序遍历下图的节点,能得到A, B, C, D, E, F, G, H, I
这样的序列。
初步了解了场景图,让我们看一下这个游戏场景。
分解这个场景,看一下它有哪些元素,这些最终会被渲染为一个树。
另一点要考虑的是,z-order 为负的元素,z-order 为负的节点会被放置在左子树,非负的节点会被放在右子树。实际开发的过程中,你可以按照任意顺序添加对象,他们会按照你指定的 z-order 自动排序。
每次主循环,cocos都会按照当前场景的场景树遍历并读取每个节点的渲染信息,然后将每个节点渲染在屏幕上。
完成交互——事件机制
接下来解决交互的问题,游戏肯定是需要有一些操作需要交互的,也就是说必须要等到用户有一些反应,我们才能得到相应的信息并进行相关的处理,那么如果我们没法改动主循环,怎样才能在用户做出反应以后做出响应呢?这主要靠cocos内部的事件监听与事件分发机制。在主循环的过程中,如果玩家做了一定的指定的会产生事件的交互,例如触控屏幕、移动鼠标、按下键盘等操作,那么cocos在当前的主循环中会产生一个和本次交互相关的事件,这个事件就是一个包含了本次交互信息的一个变量。
那么产生这个事件有什么用呢?
首先交互产生的事件是多样的,可能游戏在运行的时候,玩家会做出各种各样的交互。我们的需求是每个已经布置在屏幕上的东西,都可能会根据不同的事件做出不同的反应,因此cocos定义了很多种监听器,监听器可以提前定义并布置在一个已经被加入场景树的组件中,每种监听器在游戏运行的时候都会监听到产生的相应事件,并调用预先定义的用来相应的函数。比如如果在某个主循环的时刻,cocos产生了一个触摸事件,那么当前场景树上所有绑定了触摸监听器的部件,都会调用预先定义的用来响应触摸事件的回调函数。
cocos目前内置的监听器有5种类型,每种监听器响应一种事件。
EventListenerTouch
- 响应触摸事件EventListenerKeyboard
- 响应键盘事件EventListenerAcceleration
- 响应加速EventListenMouse
- 响应鼠标事件EventListenerCustom
- 响应自定义事件
场景树上不同部件的不同监听器响应不同事件的顺序是不同的。具体的顺序可以阅读事件分发的优先级这部分官方文档。
当你有一个监听器,已经接收到了期望的事件,并且不想让其他同类型监听器响应这个事件的时候,可以进行吞没,吞没可以避免了下游的其它监听器获取到此事件。
在所有事件机制中,有关物理引擎的事件与响应比较特殊。如果在布置画面的时候启用了物理引擎,那么物理引擎会产生一些像刚体碰撞的事件,并让碰撞的刚体对其作出反应。这些对事件的反应可以通过对物理世界的刚体属性进行设置来修改。
锦囊——回调函数
可以发现,前面我们提到了一种需要提前写好,但是又不在代码中确切的使用命名加括号显式调用,而在以后的运行中由一个对象进行调用的函数,这种函数叫做回调函数。
回调函数其实更像是一个变量,可以被赋值,可以被移动,可以被通过当前的变量名调用,可以被作为参数传递,所以被称为函数对象。函数对象的值就是一个具备确定参数类型的确定代码段。这种想法可以使用c++11的lambda表达式和std::function实现。
封装好的交互控件
对于一些功能确定并且响应特定事件的控件,cocos做了一些常用的实现。比如我们在游戏设计中经常需要用到的按钮、菜单栏、开关,他们的功能和需要响应的事件都非常固定,因此cocos做了封装。在代码中,我们只需要通过lambda表达式提前定义他们响应事件的回调函数就可以了。
其实整个游戏逻辑运行,绝大部分内容都是在事件机制涉及到的回调函数中执行的。除了布置在画布上的UI控件,在很多动画,动作序列中,也可以通过回调函数实现比较奇妙的功能。
资源文件
游戏中需要用到的所有的图片、动画、音乐,都需要以资源文件的形式使用。cocos提供了很多可以将各种形式和文件格式的资源文件处理并运用到游戏中的类,比如对人物动画、手绘地图、3D模型进行加载的api。在具体的处理上可能会用到一些其他的辅助软件,比如手绘地图的tilemap,处理人物动画的texturepacker等等。游戏中用到的素材与资源文件大多数需要经过人工处理,下面介绍一下常见的资源文件与在cocos中的机制。
图片资源
cocos对图片的加载通过sprite组件完成
精灵(Sprite)** 是一个能通过改变自身的属性:角度,位置,缩放,颜色等,变成可控制动画的 2D 图像。
通过sprite类可以把已经有的png图片加载成可以添加到场景树上具有属性的对象,并且可以设置屏幕上出现的位置,对图片旋转,设置图片rgb色彩,甚至对sprite引入物理引擎属性,作用动作动画等等。
位置
精灵的位置受锚点影响,看一下这个具体是怎样的,以红线红点为环境参考,看精灵的不同位置。注意,我们设置了锚点值,精灵的位置变化了,我们并没有使用
setPosition()
方法设置精灵的位置。
当我们想设置一个精灵的位置时,主要是使用
setPosition()
方法,只有想改变精灵与基准坐标点的相对位置时,才考虑使用setAnchorPoint()
设置锚点。
1
2 // position a sprite to a specific position of x = 100, y = 200.
mySprite->setPosition(Vec2(100, 200));旋转
通过
setRotation()
方法,设置一个角度值可以控制精灵的旋转,正值精灵顺时针旋转,负值精灵逆时针旋转,默认位置的角度值是 0.0。
1
2
3
4
5
6
7
8
9
10
11 // rotate sprite by +20 degrees
mySprite->setRotation(20.0f);
// rotate sprite by -20 degrees
mySprite->setRotation(-20.0f);
// rotate sprite by +60 degrees
mySprite->setRotation(60.0f);
// rotate sprite by -60 degrees
mySprite->setRotation(-60.0f);
缩放
通过
setScale()
方法控制精灵的缩放。可以控制精灵水平缩放,垂直缩放,也可以整体缩放。默认水平和竖直的缩放值都是 1.0。
1
2
3
4
5
6
7
8 // increases X and Y size by 2.0 uniformly
mySprite->setScale(2.0);
// increases just X scale by 2.0
mySprite->setScaleX(2.0);
// increases just Y scale by 2.0
mySprite->setScaleY(2.0);
倾斜
通过
setSkewX()
控制精灵的倾斜度,可以控制精灵水平倾斜,竖直倾斜,或者水平竖直同时倾斜,默认水平和竖直的倾斜值都是 0.0。
1
2
3
4
5 // adjusts the X skew by 20.0
mySprite->setSkewX(20.0f);
// adjusts the Y skew by 20.0
mySprite->setSkewY(20.0f);
颜色
通过
setColor()
控制精灵的颜色。将一个 RGB 值设置到Color3B
对象,调用精灵的setColor()
,就能完成精灵颜色的设置。RGB 是三个从 0-255 的值,三个值分别代表红绿蓝的颜色深度,数值越大,颜色越深。特别的 RGB(255, 255, 255) 是白色。如果你不想自己指定 RGB 的三个值,也可以使用 Cocos2d-x 提供的预定义颜色,比如:Color3B::White
,Color3B::Red
。
1
2
3
4
5 // set the color by passing in a pre-defined Color3B object.
mySprite->setColor(Color3B::WHITE);
// Set the color by passing in a Color3B object.
mySprite->setColor(Color3B(255, 255, 255)); // Same as Color3B::WHITE
透明度
精灵的透明度可以通过
setOpacity()
传入一个特定的值来设置,这个值的范围是 0-255,数值越大透明度越低,255 代表完全不透明,0 代表完全透明。
1
2
3 // Set the opacity to 30, which makes this sprite 11.7% opaque.
// (30 divided by 256 equals 0.1171875...)
mySprite->setOpacity(30);
图集与动画
图集(Sprite Sheet) 是通过专门的工具将多张图片合并成一张大图,并通过 plist 等格式的文件索引的资源,使用图集比使用多个独立图像占用的磁盘空间更少,还会有更好的性能。这种方式已经是游戏行业中提高游戏性能的标准方法之一。
在使用图集时,首先将其全部加载到
SpriteFrameCache
中,SpriteFrameCache
是一个全局的缓存类,缓存了添加到其中的SpriteFrame
对象,提高了精灵的访问速度。SpriteFrame
只加载一次,后续一直保存在SpriteFrameCache
中。
通过已经制作好的图集可以很方便的实现人物动画。
制作图集一般使用texturepacker,需要提前有图片素材并进行处理。
字体
ttf格式文件即可,字体可以被指定在标签、按钮等地方使用。
音乐
你的游戏肯定会需要音乐和音效!Cocos2d-x 提供了一个
AudioEngine
类支持游戏内的音乐和音效。它可以被用来增加背景音乐,控制游戏音效。
AudioEngine
是一个静态对象,你可以在代码中的任何地方直接调用内部的静态方法。以下,我们会尽可能的为你展示它的各种使用方法。先来了解一下支持的文件格式。支持的音乐格式:
平台 支持的常见文件格式 备注 Android mp3, ogg, wav 支持Android OpenSL ES所支持的格式 iOS aac, caf, mp3, m4a, wav 可以播放AVAudioPlayer所支持的所有格式 Windows mid, mp3, wav 无 支持的音效格式:
平台 支持的常见文件格式 备注 Android mp3, ogg, wav 无 iOS caf, m4a 可以播放Cocos2d-iPhone CocosDesion所支持的所有格式 Windows mid, wav 无
游戏地图
tilemap这个软件可以用图集绘制出很好的地图,并保存为可解析的文本文件格式。cocos中有专门针对tilemap产生文件作解析并通过图集将绘制的地图还原到屏幕上的类。