cocos2d-x 3.0 制作横版格斗游戏
闲聊一下,其实这次3.0版本吸引我的地方除了各种性能提升的传说之外,主要有两点:
取消了各种类、宏的CC前缀,例如CCSprite变为了Sprite,老实说我很不喜欢之前那种类似匈牙利的命名方式,总觉得既然有cocos2d这个namespace了干嘛还要画蛇添足的在每个类前面加个CC呢?我不知道是不是cocos2d-iPhone的传统(懒得研究),但真心不喜欢这个CC前缀。
加入了2.5D的支持,这个纯属个人喜好,因为在我脑子了已经勾勒出了一两个游戏,而且都是2.5d才能更好的表达游戏的效果(3d人物+2d场景的效果很炫,目前正苦逼的练习手绘+photoshop)
扯远了,进入正题,横版格斗游戏《PompaDroid》的制作过程
使用create-multi-platform-projects.py
纳尼!!!之前的install-templates-msvc.bat
呢,没有模版怎么在vs里新建工程??查阅了一番文档后发现其实从2.1.4开始,所有cocos2dx项目都用create-multi-platform-projects.py脚本来创建了(唉,落伍啊)。于是乎:
Wonderful!!我很喜欢这样的目录结构,简单明了。
接下来先从Windows平台开始吧,进入proj.win32并打开PompaDroid.sln
在解决方案中有6个项目:
项目说明PompaDroid很明显,需要我们实现的游戏项目libBox2D物理引擎(这里用不到,不管它)libchipmunk貌似和box2d一样是物理引擎libcocos2d这个就不用解释了libCocosDenshion音频引擎(我没在游戏里加入音效)libExtensions扩展库,具体参考这里依照惯例,cocos2dx新工程里会有个HelloWorld实例,展开cyclone-kid\Classes就可以看到了,果断编译运行,一切ok!
好了,删除HelloWorld.h和HelloWorld.cpp文件,开始实现CycloneKid的代码吧!
首先点击这里下载所需的美术资源,解压放到Resources目录下。
一款游戏中会有很多个场景(Scene):主菜单、游戏、游戏结束、过场动画等等,由于只是学习,我仅做了主游戏场景(GameScene),该场景包涵两个图层(Layer):游戏层和操作层。
顾名思义,游戏层主要负责游戏的内容(地图的渲染,各个精灵的调度等);而操作层负责响应触摸(前进、后退、攻击等操作)。
因此根据上图所示,我们需要建立三个类,GameScene
?GameLayer
?OptionLayer
务必把类文件建立在Classes目录下,方便之后夸平台编译
GameScene.h
在编译中我的环境会出现两个警告:
现默认库“LIBCMT”与其他库的使用冲突;请使用 /NODEFAULTLIB:library
?在属性-->配置属性-->链接器-->输入-->忽略特定库:增加LIBCMT即可;忽略“/EDITANDCONTINUE”(由于“/SAFESEH”规范)
?在属性-->配置属性-->链接器-->高级-->映像具有安全异常处理程序:改为“否”即可。主角和机器人的资源包涵在pd_sprites.plist
与pd_sprites.pvr.ccz
当中,打开pd_sprites.plist文件可以看到很多hero_xx_xx.png
和robot_xx_xx.png
,这些便是主角英雄和机器人的动作序列图片。从plist文件中可以清楚的看到,不论是“hero”还是“robot”都具备了idle
空闲、attack
攻击、walk
行走、knockout
(被)击倒、hurt
受伤这五个动作,因此需要建立一个动作类ActionSprite
来统一实现动作播放。
如上图所示,我们需要新建三个类ActionSprite
、Hero
、Robot
,其中ActionSprite既然是“动作精灵”所以需要继承cocos2d的Sprite类。
原本我想ActionSprite类只负责调用精灵每个动作的动画,精灵的“攻击力”、“生命值”、“移动”等属性方法放到另一个新的类当中管理,但为了省事我还是决定把这些内容完全塞到ActionSprite当中(尽管我知道这是一种灾难,但是。。。让bug来得更猛烈些吧!谁让我懒呢)。
首先,新建三个类ActionSprite
、Hero
、Robot
ActionSprite.h
机器人与hero一样是动作精灵,不考虑AI(人工智能,之后实现),它的实现过程几乎和Hero一模一样,根据plist文件把相关的五个动作动画实现就行了。
Robot.h
在上面一节,hero和robots都已经出现了,不过只是一直出于idle状态,现在首先让hero“动”起来,具体实现OptionLayer
,这个类负责响应触控操作。其实GameLayer与OptionLayer一样都继承于Layer
,完全可以在GameLayer中实现这些功能,只不过我认为把“游戏”和“操作”解耦之后更有利于把这个“操作”复用到不同的“游戏”中,或者让这个“游戏”使用多种不同的“操作”方式(例如纯触控、虚拟按钮等)。原文教程就是用虚拟按钮的方式实现控制的!
But,我比较反感在触屏上设置虚拟按钮的方式进行操控,主要的问题在于,用这种方式不仅占用了游戏空间,而且玩家在操作之间必须先把注意力集中到寻找按钮的位置上,这是中相当糟糕的体验,所以我更喜欢发挥出现代智能手机的潜力,直接用触控手势的方式取代虚拟按钮。回顾上面的内容可以看到,玩家能控制hero的动作也就只有attack
、walk
两种,那么可以把手机屏幕中间“一分为二”变为左屏
和右屏
,左半部分交给玩家的左手用来控制hero的walk,右半部分交给玩家的右手用来控制hero的attack。既然是左右屏,就有左右屏同时操作的可能,所以必须使用多点触控。
cocos2dx3.0中的多点触控接口貌似就这四个:
接口说明ccTouchesBegan触控开始事件,手指碰到屏幕ccTouchesMoved触控移动事件,手指在屏幕上滑动ccTouchesEnded停止触控时间, 手指离开屏幕ccTouchesCancelled触控被取消事件,例如手指画出屏幕外真心觉得很悲催啊,如果cocos2dx能加入手势识别不是会方便很多,例如“拖拽”、“滑动”、“点击”什么的。。。。唉,算了,还是老老实实先把这个游戏的操作层实现吧。除了最后一个Cancelled不需要外(大部分场合都用不到它),其他三个我们都需要,思路基本是这样:
基本思路出来了,开始动工吧!新建OptionLayer
类(如果没有的话),根据前面的分析,OptionLayer中大概需要这么几个东西:
began
、moved
和ended
这三个事件joystick
及它的相关函数(激活、更新什么的)OptionDelegate
类,定义onWalk
、onAttack
和onStop
接口(由GameLayer实现)OptionLayer.h:
目前为止,控制hero的部分其实已经完成了,因为思路和上一节的“摇杆”是一样的,现在无非是调用一下“委托”通知GameLayer层,让它改变hero的相关属性,所以首先完善一下手上的代码。
OptionLayer.cpp中
ccTouchesBegan
函数的right
右屏部分发起“攻击”信号,“左屏”部分暂时不用写什么(该写的前面都已经写了);ccTouchesMoved
中发起hero的“行走”信号,就之前的思路而言,只有“左屏”会产生“滑动”的手势;ccTouchesEnded
中发起“停止移动”信号,让hero恢复为“idle”状态。-。-!!!逻辑不对呀!有几个问题:
回忆下之前的ActionSprite类的实现,我参考教程把walk
和attack
两个方法都添加了参数,其目的就是当“角色”行走的时候会根据参数direction
判断并修改朝向(问题1的错误也在此),当“角色”受伤的时候根据damage
参数,减少“角色”的hp,当hp为0后自动出发角色“死亡”,这种小聪明走到现在发现还真有点画蛇添足!“操作层”发来的onWalk
是持续的,意味着那个“direction”参数随时都在改变,可只要“操作层”没有发送onStop
信号时hero将会一直处于ACTION_STATE_WALK
状态,在ActionSprite类实现中有一点很重要:只要精灵已经处于要改变的状态时,就不会在做改变的处理,所以尽管“direction”一直在变,也一直在调用hero的“walk”方法,在第一次出起“walk”之后,后来的“direction”已经毫无意义了(因为hero已经在walk了,根本不处理)。
我想了想,ActionSprite归根结底只是个“动作精灵”,它只要管好自己动作的相关动画和“角色属性”就好了,至于怎么调用,怎么改变,它的生杀大权还是交给GameLayer吧!
首先要做一个小小的重构,就是删除所有ActionSprite相关的“动作参数”,一律改为void action(void),ActionSprite.h修改如下:
这样应该就没问题了,不过现在只会原地踏步,接下来进一步实现hero真正的移动。
hero在地图上移动是一个持续的过程(当玩家手指滑动后,只要不离开屏幕,hero就不会停下walk的脚步!),处理持续的动作时常常会用到cocos2dx的一个接口update
这个接口为在每帧渲染的时候都被调用一次(所以update的实现务必要快准狠,不然浪费资源)。那么在update中需要做些什么呢?目前为止其实只需要做一点,递增/减hero的坐标值。那这个增量的标准是什么?其实就是hero的移动速度。那移动速度从何而来?想象物理界是如何定义速度的:速度是描述物体运动快慢和方向的物理量,定义为位移与发生这个位移所用的时间之比,看前半句,速度无非要两个东西“快慢”和“方向”,再来看看onWalk
吧,它是带有两个参数的direction
、distance
,顾名思义“方向”“偏移量”,我们姑且把偏移量看做快慢的标准,偏移量越大速度越快;再来看看后半句的定义,“位移”与“时间”之比。真实世界里描述速度一般都是xx米/秒或者xx公里/小时,游戏里则可以用xx像素/帧来衡量,而update每帧都会调用。所以重新回到最开始的问题,update中要做些什么?其实就是把hero当前坐标加上一个速度量。
一切都搞清楚了,具体来实现吧。
GameLayer.h中声明update
函数,并再声明一个_heroVelocity
给update用:
确实可以了,不过总感觉哪里不对呀??
。。。。。。。。。
问章有点长了,剩下的部分留到下一篇写,主要包括完善hero的移动,整个GameLayer的移动,另外就是robot的人工智能部分,最后是碰撞检测。
声明:
- 本文主要参考《如何制作一个横版格斗过关游戏 Cocos2d-x 2.0.4》和《How To Make A Side-Scrolling Beat Em Up Game Like Scott Pilgrim with Cocos2D》
- 本文游戏与上述两篇文章游戏实现上出入较大(拒绝复制粘贴),如有不对的地方还望指正,以免误导!
- 美术素材下载:?下载地址