本文主要描述了 Cocos2D 中精灵、场景等基本元素以及帧动画、调度器和键盘事件的使用方法。
🚀 代码: Github
参考资料
主要文档:
某些问题解决方案
How to remove sprite after sequence of animation?
安装
Cocos2d-x 依赖于 python2,虽然可以同时存在 python3 和 python2,可以通过py -2
调用 python2,然而cocos
这个脚本调用的是python
命令,默认调用 python3,为了方便起见,就先把原来的 python3 删掉,装上 python2.
下载并解压cocos2d-x-3.16
然后运行里面的setup.py
1 | python ./setup.py |
如果不需要配置 Android 环境,那么里面就是配置路径项就可以回车忽略掉
然后使得环境变量生效,就可以使用cocos
命令了。
运行 cpp-tests 项目
cocos2d-x 目录下有一个cpp-tests
, 里面有各种的示例代码,可以通过 Vs 打开它,并且改变一下各个解决方案的目标 SDK,然后就可以编译运行了。
新建项目
1 | cocos new helloCocos -p cn.zhenly.hellococos -l cpp |
- new : 项目名称
- -p:包名
- -l:开发语言
- -d:项目存放目录(默认当前目录\项目名称)
打开项目
这里使用的 IDE 是 VS2017
使用 VS2017,打开新建项目目录下proj.win32
里面的helloCocos.sln
如果直接编译是会报错的。
打开之后,需要改变一下这里面的目标平台 SDK,因为里面的 SDK 默认是 8.1 的,我们要将他替换成我们环境中存在的。单击解决方案资源管理器,右键选择项目属性更改,5 个解决方案都需要改一下。
然后就可以编译运行了。
(第一次编译时间很久,可能是渣 CPU,而且编译完之后项目大小居然 4.57 GB,比一个系统镜像还要大)
添加元素
里面AppDelegate.cpp
是程序的主入口,管理各种生命周期的
HelloWorldScene.cpp
就是我们第一个场景,我们在里面添加一点元素
打开之后可以看到里面已经存在一些示例代码,放置了一个图片和文字,还有一个退出按钮,仿照他们就可以添加我们自己的元素。
在添加的时候发现中文的Label
是不显示的,我使用了 xml 来加载中文字符。
首先在资源目录下\Resources
新建一个Strings.xml
1 | <dict> |
这里有一点需要注意: 不能写成以下这种形式
1 | <key> |
虽然看上去是一样的,但是当获取名字为no
的 key 的时候,发现返回的是NULL
, 这是因为他的 key 名字把\n
也包含进去了。
然后在代码中获取这些资源
1 | auto *chnStrings = Dictionary::createWithContentsOfFile("Strings.xml"); |
然后放到label
里面
1 | auto labelNo = Label::createWithTTF(strNo, "fonts/Marker Felt.ttf", 24); |
然后我们再来添加一个菜单项
它的示例里面已经有添加一个关闭的图片按钮,我再添加一个label
按钮
1 | // add a "about" label to show about info. |
然后把他添加到 Menu 里面就可以了, 是不是很简单
1 |
|
这个星期 Cocos 是做一个并没有黄金矿工的黄金矿工。
首先这个提供了一个 Demo,和素材,这样做起来就十分地舒服
开始场景
这一部分需要添加三个东西进去,分别是标题,大石头和开始按钮
标题和大石头都是普通的 Sprite,因此用最普通的方法,计算好他们的坐标,然后添加进去就可以了。
然后开始按钮要求是按下是有一定的交互的,因此可以使用MenuItemImage
来实现
1 | auto startButton = MenuItemImage::create("start-0.png", "start-1.png", |
这些都是十分常规的操作。
然后切换场景也是十分地简单, 只需要调用Director
的实例的对应的replaceScene
方法,就可以完成了。
甚至我们还可以加一些特效。TranstionFlipX
就是以 x 轴为中心平面式地旋转切换。
1 | void MenuScene::startMenuCallback(cocos2d::Ref * pSender){ |
然后就可以得出以下效果
游戏场景
首先就是要布置场景,需要放置的有一只会动的老鼠,一块石头,一个背景还有一个 Shoot 的菜单按钮
背景和石头都是普通的 Sprite, Shoot 和上一个场景的 Start 是类似的,不过换成了一个Label
而已,代码也是大同小异的,重要的是那只会动的老鼠。
不过这部分需要使用到两个Layer
, 这就需要额外设置一下锚点和位置,只要调用setAnchorPoint
和setPosition
方法就可以了。
至于这只老鼠,我们可以仿照在上一个场景中那个抖脚矿工的动画,在AppDelegate.cpp
中加载plist
资源,然后把动画存在AnimationCache
当中,也是很容易就搞定了。
1 | SpriteFrameCache::getInstance()->addSpriteFramesWithFile("level-sheet.plist"); |
然后在GameScene.cpp
中加入
1 | auto mouse = Sprite::createWithSpriteFrameName("pulled-gem-mouse-0.png"); |
然后就可以得到这个场景了:
然后触摸事件里面添加一些方法,使得点击的地方生成一个奶酪并且老鼠会移动过去。
因为我的老鼠是在mouseLayer
里面的,这就涉及到了局部坐标和世界坐标的转换。
这个只需要调用对应layer
的convertToNodeSpace
方法就可以了
1 | auto gotoCheese = MoveTo::create(5.0f, mouseLayer->convertToNodeSpace(Vec2(location))); |
而奶酪需要出现后等待一定时候后淡出,而且还需要移除,因此我们可以使用一个Sequence
来实现
1 | auto newCheese = Sprite::create("cheese.png"); |
然后就可以完成这个部分
最后需要发射石头到老鼠位置,并且老鼠留下钻石并随机逃跑。
这个和上面老鼠移动到奶酪位置的做法是差不多的,不过这个需要生成一个指定范围的随机坐标。
这个使用C++11
中的Random
库就可以搞定
1 | std::default_random_engine randomEngine(time(NULL)); |
我这里加了限制使得老鼠逃离的地方在泥土里面。
然后就可以实现最后的功能了。
这个项目做到这里基本就完成了,不过还有一个小问题,就是如果你在老鼠移动未完成的时候再次执行移动的动作,那么老鼠最终到达的位置就不会是指定的位置,这是因为他的移动未完成之前,再次调用移动就会导致新的移动的坐标叠加旧的移动的目标地址上,也就是说,如果在一个地方同时点击两次,那么老鼠就会移动两倍的距离,这是非常影响体验的。因此我这里作于一个改进
1 | auto moveMouse = MoveTo::create(3.0f, mouseLayer->convertToNodeSpace(getRandomVec2())); |
就是给老鼠移动到奶酪或者移动到新地址的动作加上一个tag
,然后再次执行移动的时候,先通过tag
将当前的动作停止,然后老鼠的坐标就会被设置到当前位置,新的移动的坐标就会被叠加到当前位置上,就可以解决这个问题了。
这次的项目的要求比较简单,就是一个简单的横板游戏(虽然并没有什么可以玩的。涉及到的关键点也不多,因此很快就可以完成了。
布局
血条
这个是素材当中的一个元素,这里可以通过从素材图片中截取指定的矩形来提取出来。提取出来后得到一个血条的背景和一段粉红色的条。
1 | Sprite* sp0 = Sprite::create("hp.png", CC_RECT_PIXELS_TO_POINTS(Rect(0, 320, 420, 47))); |
然后就是对于一些类型和位置的设置
1 | pT = ProgressTimer::create(sp); |
角色人物
这里的素材是从一段帧动画从提取的第一帧画面,首先创建一个贴图,然后切割出一个关键帧,从而创建一个精灵,那么就可以了
1 | //创建一张贴图 |
控制按钮
控制按钮由MenuItemLabel
组成,首先是使用Label::createWithTTF
从指定的ttf
文件创建Label
, 然后绑定指定的回调事件,最后添加到Menu
里面,就可以搞定了。
这里比较需要关注的是回调函数,因为这 6 个按钮可以分为两种类型,因此可以设置两个回调函数,然后根据参数的不同而呈现不同的动作,这样就可以避免大量的重复代码
这里举一个例子:
1 | // .cpp |
这里将WASD
的移动动作写到一个函数里面,然后根据不同的参数决定不同的移动方向.
这里使用的回调类型为CC_CALLBACK_1
, 这个 1 的意思就是这个函数有一个自定义的参数,同样的有0
, 2
, 3
等其他回调类型。
动画
帧动画
这里使用到一些帧动画,是由一张图片切割出不同的帧组成的。
至于具体的切割操作也是比较简单
比如说是死亡动画
1 | // 死亡动画(帧数:22帧,高:90,宽:79) |
只要知道这个素材的帧数和高宽,就可以轻易生成一个动画了。
之后的使用方法:
1 | auto animation = Animation::createWithSpriteFrames(run, 0.05f); |
从这个容器里面加载出素材,指定速度创建一个Animation
,然后再创建为Animate
就可以了。
防止动画重复
因为有些动画在逻辑上是不应该同时或者重复发生的,因此需要做一些措施防止他们重复发生。
最简单的方法就是给指定的动画通过setTag
设置一个Tag
,然后通过调用getActionByTag(xxx)->isDone()
判断他们是否执行完成再进行下一步操作。
1 | if (player->getActionByTag(534) != nullptr && !(player->getActionByTag(534)->isDone())) return; |
同时发生的动画
对于移动操作,我们需要走路动画和移动动画同时发生,因此需要两个Animate
同时发生,因此可以使用Spawn
创建一个同时序列
1 | auto action = Spawn::createWithTwoActions(Animate::create(animation), move); |
调度器
这次项目要求显示一个倒计时,这个可以通过自定义一个 1s 的调度器实现。
调度器的生成
1 | void updateTime(float dt); |
调度器的停止
1 | this->unschedule(schedule_selector(HelloWorld::updateTime)); |
需要注意的是,调度器中的函数的参数必须是 float,否则会报错。
键盘事件
由于是横板游戏,在 PC 端肯定是需要加入一些键盘事件的,不然总是鼠标点击会异常反人类
创建键盘事件的方法也比较简单,如下:
1 | // 键盘事件 |
结果
亮点与改进
- 第一个项目
- 为文字添加了不同的样式(颜色、阴影、发光)
- 点击回调事件生成了一个特效
- 第二个项目
- 为矿工的嘴加上了动画
- 为老鼠的移动加上了特效(
EaseElasticOut
) - 为钻石加上了抖动特效
- 解决了 Demo 中老鼠会移动到界面之外的
feature
- 第三个项目
- 加上了键盘事件
- 实现了血条的增加和减少
遇到的问题
老鼠连续移动超出界面
如果你在老鼠移动未完成的时候再次执行移动的动作,那么老鼠最终到达的位置就不会是指定的位置,这是因为他的移动未完成之前,再次调用移动就会导致新的移动的坐标叠加旧的移动的目标地址上,也就是说,如果在一个地方同时点击两次,那么老鼠就会移动两倍的距离,这是非常影响体验的。因此我这里作于一个改进
1 | auto moveMouse = MoveTo::create(3.0f, mouseLayer->convertToNodeSpace(getRandomVec2())); |
就是给老鼠移动到奶酪或者移动到新地址的动作加上一个tag
,然后再次执行移动的时候,先通过tag
将当前的动作停止,然后老鼠的坐标就会被设置到当前位置,新的移动的坐标就会被叠加到当前位置上,就可以解决这个问题了。
不同帧动画的素材宽高不一致
因为移动的动画的素材的宽高和攻击动画的素材的宽高不同,然后一开始的静态动画是攻击的素材的第一帧,这个问题导致了人物在移动的时候会发生一次瞬移
, 而且每当切换攻击和移动动画的时候都会发生一次的瞬移。一开始我想通过MoveTo
解决的,但是导致逻辑变得复杂,而且效果也不是很好,因此需要解决这个问题最好的方法就是换素材了。
思考与总结
这次的实验是使用 Cocos2d-x 引擎制作游戏,而且使用的是C++
的版本。虽然C++
没有Js
那样灵活,但是它终究是一款编译型语言,具有强类型和编译时检查,更容易发现出 BUG。
对于游戏来说,Cocos2d-x 的布局比UWP
的xmal
布局更为灵活,也更加符合游戏。应用需要整齐规划的节目,而游戏更需要动态的节目。虽然根据坐标布局起来的确有点麻烦,但是如果在一开始设计的时候就把坐标包括在内,那么游戏开发的过程中就会变得很方便,只需要加载预先的设置,比如说存在plist
中的坐标之类的。对于一个scene
里面的元素,也可以预先设计好相应元素的坐标,那么就会很舒服。
虽然在业界对于 Cocos2d-x 的评价也不是比较好,但是对于游戏应用的入门倒是足够简单,提供的基本功能也够开发一些小游戏了。但是对于一些规模大一点的游戏,这个架构似乎就驾驭不起了。希望后续版本可以做到足够好吧。
由于我才写了三个项目,对于 Cocos2d-x 的理解并不是很深刻,也没有什么更加高深的见解,还是以后不断积累经验,再做更详细的总结吧。
其实我们学习这个框架也不是为了学习这个框架,只是为了学习一些游戏制作的一些理念和模式而已,就像 UWP 一样,和各种平台上的应用开发都有这很多的相似之处,只要掌握了方法,什么框架还驾驭不起呢。
不知道说些什么了,就这样吧。