大家好,我是红孩儿.上一节我们学习了使用红孩儿工具箱开发打地鼠游戏.这一节我们继续学习使用红孩儿工具箱来开发坦克大战游戏.
坦克大战这个游戏大家小时候都玩过,它给了我们儿时一个实现英雄梦的机会.现在每每想起和儿时的小伙伴们一起玩FC时的情景,都会感觉无限美好.唉,人一长大,各种烦闷都来了,不说这个了,下面来介绍一下本次坦克大战的设计规划.
我们这是个单机游戏,玩家可以控制一辆坦克在游戏中对敌方进攻的坦克进行回击,保卫自已的大本营.将敌方坦克都消灭了,当前关卡就胜利结束了.如果被敌方坦克消灭,那当前关卡就失败了.
首先,我们要制做一个开始界面,有一个背景图和两个按钮-开始游戏和退出游戏.这个界面的作用是给玩家一个宣明的主题,并让玩家选择是否游戏.需要使用工具箱的界面编辑器.
之后,我们做一个战争的剧情动画,做这个是为了让玩家能够迅速的进入到主题,有一种身临其境的感受.为了更好的把战争的气氛渲染出来,我们这里使用打字机效果来说明剧情背景故事并配备一个老照片的动画更好的对人的心理进行一个触动.这里需要使用工具箱的字体编辑器来生成字图,以及使用动画编辑器来制做动画.
然后我们进入到关卡界面,本次我们设计了十五个关卡,另外要求下次玩时要保存已玩过的关卡.这样就不必每次都从第一关开始玩了.这里当然还是要使用工具箱的界面编辑器.
再继续就是游戏的主场景了,核心的玩法都在这里面了,场景由工具箱的地图编辑器进行制作,玩法逻辑则在代码中实现.
打完一仗后有一个关卡结束的界面,对胜利,失败和击毁的坦克有一个结算处理.让玩家稍适休息,继续战斗.
所有十五个关卡都结束会进入到通关界面,通过这个界面中的动画将战争结束的激动心情更好的烘托出来.玩到这里,相信很多小伙伴会把悬着的一颗心放下,感叹一下幸好没生活在战争时代,幸好,幸好.
好了,我们现在开始第一步:开始界面制作.
一.开始界面制作
首先我们需要从www.game2z.com 网站,在左上角的 下载红孩儿工具箱最新版 链接处下载最新的0.2.3版本。下载后我们将其解压到自定义的目录。打开后我们可以看到工具箱EXE程序和一个dx9的dll.还有一个资源目录Resource,一个测试设备图片存放目录Dev以及一个配套的SDK目录.红孩儿工具箱只需要这个EXE和对应的这个dll就可以运行在Windows操作系统了.本次的更新重点是多了一个工具箱配套库目录-HHR,这里面有两个子目录include和Lib ,分别为包含头文件和静态Lib库.同时又分为基础库HHRLib和高级特效库EffectLib.
在工具箱所在目录的资源目录Resource中有一个坦克大战的资源目录TankWar。为了方便学习和使用,我把坦克大战所需的资源和做好的场景地图,动画源文件都放在这里了.
启动工具箱,这时我们可以看到红孩儿工具箱的登录界面,我们一般查看可以选择离线登录,但离线登录不能导出动画和场景,主要是用于查看和编辑。如果是想导出,那就最好到www.game2z.com论坛注册一个正式用户的账号。在这里我们输入注册账号和密码后,点击“登录”,稍等片刻完成验证后即可进入工具箱主界面。
我们首先要做的,是先设置一下我们的工程资源目录,点击“设置工程资源目录”按钮,选择我们的资源目录即可,一旦设置完成,工具箱就会开始遍历和登记工程目录下所有的文件资源,这样我们就可以使用工程资源目录下的图片了.下次再使用工具箱时也会自动的设置好资源目录.
进入工具箱后,我们来作做一下开始界面.选择”界面编辑”选项卡,进入到界面编辑器.当前版本的界面编辑仍是未完全版,有很多高级控件还未实现,不过对于一些简单的界面也基本够用了.
界面编辑器分为四大区域,顶部主要是界面文件打开,保存,导出按钮与控件选取区域,左边是控件关系树.右边是属性值设置区域,中间是界面的主视图区域.
我们要制作一个界面,首先要考虑这个界面都需要哪些控件.在我们这个开始界面中,主要是有一个背景图和两个按钮,所以我们至少需要两种控件,分别为面板控件和按钮控件.我们现在来做一下.
在左边的控件树的Root项下我们右键单击,在弹出的菜单中选择”增加子结点-面板”.
单击后,我们可以看到在中央视窗区域会生成一个面板.同时在右边的属性编辑区域出现相应的属性值.
我们看到此时这个界面是处于被选中状态的,因为它的顶点和边界中点有拖动块,我们可以通过鼠标直接点击这个面板并移动它,也可以通过鼠标点击小拖动块来改变他的大小.如果觉得不好控制,还可以直接在右边的属性框中输入数据值来进行修改.另外我们可以通过鼠标中健或滚轮按下后移动鼠标来方便我们改变编辑时我们观察点.
我们希望这个面板填充背景图片,这时我们来看一下面板控件的属性都有什么.
Visible:是否可见
Enable:是否有效,无效的话就不能响应屏幕触屏控制.
PosX,PosY,PosZ,具体的位置X,Y,Z
Width,Height:控件宽高.
Normal_Image:所要填充的图片
ImageShowType:图片缩放方式,有按控件大小,按图片大小和平铺填充.
在这里我们点击Normal_Image编辑框会有一个按钮.点击后弹出选择显示资源的对话框.这时我们可以选择控件要显示的图片或动画.
如果当前选择显示资源的对话框中没有我们需要的素材,我们可以点击“导入资源文件到当前资源库中”.并查找相应的图片或动画文件加入到这里.
找到start.png后,我们点击"打开",这时start.png就被加入到资源库中了.我们点击左边对应的树项,这里可以在右边看到它,这里显示会有点大,我们可以点击"限制大小"来进行界面大小重置.
看着没什么问题,我们就可以点击"应用此图片"了.之后我们的面板就会填充相应的图片.然后再点击"关闭选择窗口"还进入到界面编辑器.
这时候我们的面板位置,大小还不对,我们需要修改一下属性,既然图片是800x600大小,我们这里就让控件大小按照图片大小就可以了。选择ImageShowType为图片大小,之后将PosY设为-600。因为在Cocos2d-x中使用的是Opengl的坐标系,所以这里调整为-600。基准点0,0点以上显示界面。
这样背景图就做好了,我们继续来做一下两个按钮。我们在左边的控件树的面板控件树项上右键单击,在弹出菜单中点选“增加子控件-按钮”,这时候在面板的左上角会产生出一个按钮控件。
然后我们把创建出来的按钮控件放到合适的地方,然后看一下属性编辑区域。
这里除了和面板一件的基本属性外,又多了一些控件特有的属性
MoveUp_Image:这里是说鼠标移上时的图片资源,适用于PC游戏的开发,如果是触屏游戏就没这个需求了。
LButtonDown_Image:鼠标按下或触屏响应时的图片资源。
按下响应函数:这个是对应界面的Lua响应处理函数名称。在界面编辑器保存导出后,会生成一个对应的Lua文件,如果是使用Lua来处理界面逻辑,则可以在这里进行函数名的设置,当然,工具箱本身已经给他默认了一个基于控件名称的响应函数.
我们点击Normal_Image,MoveUp_Image,LButtonDown_Image分别在选择图片资源对话框中将"开始游戏"图片加入.
将这几种状态的图片资源都选中之后我们再设置控件也按图片大小进行显示,并拖放到合适的位置,这时候我们把鼠标移上或点击时就可以看到相应状态时的图片了.
"开始游戏"的按钮做好了,我们在左边它对应树项上右键单击,在弹出菜单中点击"复制当前控件",然后在面板控件项上右键再单击,在弹出菜单中点击"粘贴到此结点下"这样就会又复制出一个新的按钮.我们用鼠标可以将它拖到合适的位置,并按照刚才设置
开始游戏"按钮的办法,将另外的"退出游戏"的按钮也做出来.
我相信大家一定可以自已做到,这里我就不再赘述了.
这个界面基本功能控件算做完了,下面我们保存一下界面start.xml
这样界面只有静态图构成,稍单调了一点,现在我们来为他增加一些动画元素.在标题处增加两个旋转闪动的星星.我们点击工具箱的编辑器选项页的“动画编辑”,进入到动画编辑器.
在动画编辑器左边的结点树区域,在根结点Root上右键,在弹出的菜单中点击"增加图片结点".
我们在图片上双击,会弹出选择图片资源的对话框.然后我们将事先准备好的两个星星的图片加载进来.
应用此图片后,原来显示Cocos2d-x图标的结点会变成星星图片.
我们在帧显示区域的60帧处右键单击,会弹出一个菜单,点击"加入关键帧".
之后我们可以看到帧显示区域在60帧处出现了关键帧的标记.然后我们在图片结点上点击右键.在弹出菜单里找到"调整自身旋转".
点击之后,会弹出一个"调整图片自身的旋转"输入对话框.在这时我们希望图片绕Z轴旋转一圈.所以我们在z处输入角度360.即旋转一周.
确定后我们可以再在30帧处增加一个新的关键帧.
然后我们点击右上角的"属性编辑"对话框.这时候我们可以看到结点的属性编辑面板会显示出来.
在这个编辑面板里.我们可以调整四个顶点的透明度都调整为0.这样可以让图片完全透明.
这样这个动画就做好了,我们点击"播放动画"可以来查看一下.
最后我们保存一下动画.这里需要在弹出的"请设置动画名称"中输入动作名称.
现在动画文件star.ani就做好了.我们继续把另一个星星的闪烁旋转的动画做好并保存之后,我们再加到界面编辑器中,在面板上新加入两个子控件面板.并设置其图片资源为我们刚才做好的两个动画文件.
选中相应的动画后,应用为面板所对应的动画资源即可.这样我们就把界面上的动画元素做好了.
保存一下,然后导出ui_start.ui待用.
二.剧情动画制作

这里我们要做一个打字机效果来显示剧情文字.并在结束后显示一个老照片动画.我们这里可以使用工具箱的文字字图编辑器来制作字图.我们点击工具箱的"字体编辑"按钮进入到字体编辑器.
首先我们将所用到的文字都放到一个新建的txt文档中.
然后我们在字体编辑器中加载相应的文档文件.之后在文字编辑框中会显示出文字.
然后我们点击"选择字体",在弹出的字体对话框中选择一种字体使用.
然后我们点击"开始生成",在等待几秒后,字图被生成出来.
点击"编辑文字效果"后可以进入到文字效果编辑面板.这时我们可以对颜色,描边以及纹理做一些调整.
调好了之后就可以点击"导出字图"来将文字导出fnt和png了.这样我们的剧情介绍字图就制作完成了.
三.剧情动画制作
在剧情文字介绍的打字机效果动画结束后,我们还需要再做一个老照片动画,现在我们点击“动画编辑”进入到动画编辑器.
首先我们在左边的结点树控件区域右健,增加一个图片结点,然后我们按照之前的方法设置其对应的图片为老照片.
之后我们在下部帧显示区域第50帧区间内右键单击,在弹出菜单里选择新增一个关键帧,同样也在51帧区间内增加一个关键帧,然后我们点击"属性编辑"
在展开的属性编辑面板里我们点击"隐藏精灵"复选框.使照片不显示.
使用同样的方法,我们在53,55,57,58帧上增加关键帧,并设置显示或隐藏精灵,以达到动画播放时的快速照片闪现动画.
在58帧显示设置之后,我们在下部的帧显示区域滑动条"当前显示起始帧"处调整一下帧显示区域的起始帧,这样我们可以设置区域显示不到的帧信息.我们在130帧处再增加一个关键帧.
在130帧处我们通过属性设置来设计照片的四个顶点颜色改变.这样就可以实现变色动画了.
然后我们保存为aboutwar.ani并导出aboutwar.gan.以供使用.
四.选择关卡界面制作
在看完战争的剧情动画后,我们将会进入到关卡界面,关卡界面主要是为了显示可以玩的关卡,它主要由一些按钮组成.在我们学会了制作开始界面后,这个关卡界面就很容易了.仍然是一样的方法.通过创建面板增加背景图,然后在上面加上一系列按钮并为它们选择相应状态图.在这里就不再赘述了.相信大家可以通过自已的实践来完成它.
五.游戏场景制作
游戏场景是游戏中最重要的部分了,我们终于开始进入到游戏场景的制作了,相信大家都很期待.我们现在就开始.
点击"场景编辑"后进入到场景编辑器.
在场景编辑器的左上角位置,点击按钮"创建",在弹出的对话框中输入场景的宽780,高度600,点击确定后在场景显示区域出现红色区域方框,即场景的大小.
我们用鼠标点击场景编辑器的中间分隔部分向右拖动,调整到合适位置以方便我们观察和编辑.
我们在右下部分的"场景层元素区域"中右键单击,在弹出的菜单中点击"增加新图层".这是一个背景图的格子图层,用于显示地表.
我们在弹出的"创建新图层"对话框中输入图层名称,格子大小.之后确定.
确定之后,会在层元素显示区域出现一个树控件,这样这一层就建好了,我们可以在这一层里加入一些元素.在这里面的空白区域右键单击,在弹出菜单上点选"创建新图类",输入BK后点击"确定".
这样这个图类就创建好了.我们在相应的图类树项上右健点击,在弹出菜单中点选"增加新图片",然后在弹出的文件对话框中将地表图片加进来.
这样我们就完成了地表格子元素的导入.
点击左上角的"网络显示"后,我们可以看到场景区域会出现网格,并吸选中的图片元素会自动吸咐在网格中.
这时候我们就可以进行场景的第一层绘制了,在鼠标按下的状态下可以在场景中刷格子,如果按着Ctrl从左上向下拖动,则对角线所在矩形区域都会被刷上相同的元素.
然后我们来创建第二层,在第二层中,我们要进行建筑的一些布局和事件点的设置.我们在图层显示区域BK选项卡右边空白处右键,在弹出的菜单中点击"增加新图层".
在弹出的对话框中输入与第一层基本相同的设置.
在这一层中我们导入一些砖墙,树,金属墙,木桶等图片元素.
待元素增加完之后,我们现在也可以开始尝试在场景中刷一些建筑
是不是很犀利?现在我们还要为这些元素设置一下相应的阻挡属性,我们在砖墙,金属墙,木桶等有阻挡的图片树项上右键点击,在弹出的菜单中点击"修改图片属性",在弹出的对话框中点选"是否阻挡".之后确定.
设置完相应的阻挡后,场景会有红色圈来标识出物体所在的阻挡格子.
我们用动画编辑器再制作一个鹰的变色动画放在场景的基地中.这个动画的制作很简单,学到这一步大家应该都会了,就是做关键帧,并在相应关键帧上调整元件的顶点色.
做好后保存为home.ani供场景使用.并导出home.gan在Cocos2d-x中调用.然后我们加到场景编辑器.在第二层中按之前同样的方法增加图片资源,只是在文件类型中选择动画文件ani.找到home.ani加入进来.并选中它放在基地中间.
越来越像回事了,不是么?下面我们来为场景中增加一些坦克出生点,这个可以用事件格来做.我们在图层显示区域的Build树项上右键,在弹出菜单中点击“增加空事件格”,这时就可以看到在当前层分类下有一个新的子项“空事件格”。
我们选中它后在场景中的当前层相应格子中点击,就可以增加事件格了。
这些事件格都有一个ID值,默认为-1,我们可以自行设置它的ID,并在编写代码时进行访问和区分事件格,这里我们设置敌方坦克出生点的事件为0,我方坦克出生点主位为1,副位为2。我们只要在场景显示区域中事件点右键,点击弹出菜单的“设置当前的事件点ID”项,就可以为事件点设置ID了。
在这个场景中,我们将上边的事件点设置为0,基本左右方的事件点设置为1和2。
这样这个场景就做好了。我们保存为第一关即可。
用同样的方法,我们可以迅速的做出十五个关卡.
六.角色动作管理
工具箱提供了对于分类角色的动作管理,通过对于角色动作的分类管理,我们可以更加清晰方便的在Cocos2d-x中对于角色的动作进行播放,而不必在代码中手动编写相应的动画文件名称或PLIST。
点击“角色编辑”面板,我们可以看到与动画编辑器类似的界面,在最左边的区域是分类角色与动作的一个树控件。我们在这里对游戏中的所有角色和动作进行统一管理。下面我们来做一下。
首先我们要确定都有哪些角色.
列举一下,应该有5个角色。
<1>我方主坦克
<2>敌方普通坦克
<3>敌方快速战车
<4>敌方重型坦克
<5>子弹
为什么把子弹也作为一个角色了呢?因为在这个游戏里,子弹是在场景中运动的,另外它有两种,普通子弹和穿甲弹。我们可以把这两种子弹作为子弹角色的两个动作。当然,在没有好的美工辅助的情况下,我只能用单帧图片来做这样的动作。
我们在动画编辑器中创建出相应的图片结点并一一为其设置相应的图片资源后保存为各自的动作文件player.ani,enemy1,an,enemy2.ani,enem3.ani,bullet.ani,bullet3.ani。
在弹出的对话框里输入类名称为“Tank”,然后确定,这样就有了一个新分类,我们在其对应树项上右键单击,在弹出菜单中点选“增加新角色”。
之后在弹出的对话框里输入角色名称“Player”,点击确定,这样在其树项下就又生成出一个角色树项,继续~,我们为这个角色导入动画player.ani。
按照这种办法,我们继续将所有的角色和动作文件一一导入,只是子弹导入bullet.ani和bullet3.ani两个动作文件。
这样我们保存Tank.rol并导出Tank.gro。在Cocos2d-x中只需要根据角色名称就可以创建相应角色,通过动作名称就可以播放相应动画了。虽然这里只是一帧的单图。
八.爆炸粒子效果制作
在这个游戏中,爆炸的粒子效果有两种,一种是基础粒子效果,在工具箱的粒子编辑面板里制作,另一种是高级粒子效果,在工具箱的效果编辑面板里制作。我们先来看一下基础粒子效果的制作,点击“粒子编辑”切换到粒子编辑器,从粒子模版库中选择“Fire”,然后更换图片为fire.png。之后设置一些属性,主要是角度要设成360度随机。
之后就可以看到我们需要的爆炸粒子特效了,
保存为explode.plist供游戏中使用。
然后我们来看一下高级爆炸效果的制作。点击“效果编辑”进入到效果编辑器。
在主视窗区域右键单击,在弹出菜单里选择“添加粒子系统效果”,然后在材质属性上替换图片。
点击粒子发射器参数,修改粒子总数目,每秒发射粒子数目和修改Z轴发散角度为180,注意这个Z轴发散角度的意思是说(-180~180)。并修改生命变化为0.5,速度变化为50,宽度为10,高度为10,宽度变化为5,高度变化为5,经过粒子参数的设置,这个时候粒子的参数增加了很多随机性。
点击粒子宽高缩放关键帧,这时会弹出关键帧编辑界面
在生命周期时间帧上起始帧,0.4生命周期帧,和结束帧上都双击,做一些大小的调整。
比如在开始帧处宽高为1.0,0.4生命周期处宽高为2.0,结束时宽高为3.0
按同样的方法我们修改粒子颜色关健帧。
并修改粒子速度缩放关键帧,修改初始帧和结束帧的速度缩放值.
这样这个粒子运动速度就实现了由慢变快的效果.最后,我们修改下粒子系统的喷射时间,点击基础属性,修改生命时间为0.16,修改名字为爆炸火星.
我们做好这个爆炸粒子效果后,按照同样的方式增加几个其它的粒子.
闪动火焰:修改纹理名字为huangguang.png,粒子总数目为1,每秒喷射粒子数目为1,生命变化为0,宽度为64,宽度变化为0,高度64,高度变化为0,粒子重力为0,速度为0.速度变化为0.设置粒子宽高缩放关键帧为
设置粒子颜色关键帧为
粒子速度缩放关键帧不设置.
扩散光圈:纹理名字为qilang_mogu.png,粒子生命时间为0.12,总数目为1,每秒喷射粒子数目为1,生命为0.5,宽度为256,宽度变化为0,高度256,高度变化为0,粒子重力为0,速度为0.速度变化为0,设置粒子宽高缩放关键帧为
设置粒子颜色关键帧为
粒子速度缩放关键帧不设置
扩散光圈2:纹理名字为ring_huangquan.png,粒子生命时间为1,总数目为1,每秒喷射粒子数目为1,生命为1.0,宽度为256,宽度变化为0,高度256,高度变化为0,粒子重力为0,速度为0.速度变化为0,设置粒子宽高缩放关键帧为:
设置粒子颜色关键帧为
粒子速度缩放关键帧不设置.
最后我们点击下左测的效果元素列表,
将效果保存.
七.代码编写
现在终于进入到代码编写的阶段了,是不是手早就痒痒了。我们将HelloCpp拷出一份进行改造就可以了,将其工程名改为TankWar。
现在我们要进行一下设计规划。首先我们再确定一下游戏的流程。
首先是初始化HelloWorld层时加载开始界面和预载入一些音声文件。开始界面ui_start.ui中的按钮可以通过Lua来进行界面显示隐藏的逻辑处理,并通过回调函数来进行对于游戏界面切换时的一些逻辑进行处理。
点击“开始游戏”按钮后,在回调函数中创建打字机效果来展示剧情文字。在所有的文字展示完之后播放一个老照片的显示动画。在动画结束帧处增加回调函数,进入到选关卡界面ui_chosemap.ui。
在ui_chosemap.ui创建后根据存档信息来设置相应的关卡按钮可以被点选及其相应的回调函数,在这个回调函数中创建出场景并初始化游戏敌我双方的坦克设置。
为场景中的敌我双方的坦克做逻辑处理。这个可以通过重载场景库中的现有NPC类来做到。通过其NPC私有属性结构的重载来进行私有属性的设计,在其每帧update函数的重载中做一些逻辑判断。比如键盘的按下响应或位置的更新,在这里我们按下攻击键时,要产生出一个子弹。子弹也可以通过重载场景库中的现有NPC类来做到,在重载其每帧的update函数中进行与坦克和阻挡物的碰撞判断,如果与坦克碰撞上,减少坦克的HP,如果与阻挡物碰撞上,击毁阻挡物并同时创建粒子爆炸效果。
在坦克被击中减血到0时,统计击中的坦克数量,如果所有坦克被击毁了,那就释放场景。并显示胜利统计界面。当然,如果我方坦克或基地之鹰被击毁,那则显示失败统计界面。在点击Go!!!按钮后会如果是胜利就开始下一关卡,如果是失败就重新开始当前关卡。
所有场景都结束之后,要显示一下通关的老照片动画。游戏回到开始界面。
上面的流程确定了,下面我们来认识一下工具箱的配套库。
工具箱的配套库暂时由两个模块来构成,一个是基础模块HHRLib,一个是高级特效模块EffectLib.基础模块HHRLib中包括动画解析与播放,界面解析与播放,场景解析与播放,角色列表解析,资源加密解密等功能。高级特效模块EffectLib中主要是特效与技能的解析与播放。当然,这也都只是当前版本的功能,未来肯定还会不断的扩展和强大。
我们将这两个目录拷到坦克大战的目录下,并在TankWar工程设置里加入包含目录与库目录。
如上图示,将HHRLib_d.lib,EffectLib_d.lib放在debug模式下工程设置的引用库中。如果是Release模式,则换成HHRLib.lib和EffectLib.lib。
现在工具箱的配套库和头文件就引入到我们的工程中了,我们就可以解析和显示工具箱导出的界面和场景等内容了。
这个游戏规模很小,我们的所有游戏界面处理主要放在HelloWorldScene所在的中就行了。下面我们开始流程的第一步:加载和显示开始界面。
首先我们这个游戏画面大小约定为800x600像素,我们修改一下main.cpp中的大小设置。
USING_NS_CC;
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// create the application instance
AppDelegate app;
CCEGLView* eglView = CCEGLView::sharedOpenGLView();
eglView->setViewName(&34;HelloCpp&34;);
eglView->setFrameSize(800, 600);
// The resolution of ipad3 is very large. In general, PC&39;s resolution is smaller than it.
// So we need to invoke &39;setFrameZoomFactor&39;(only valid on desktop(win32, mac, linux)) to make the window smaller.
eglView->setFrameZoomFactor(1.0f);
return CCApplication::sharedApplication()->run();
}
然后我们打开AppMacros.h,将ipad的大小修改成800x600,注意:这样做只针对于win32平台的这个DEMO,如果你是真的为ipad写游戏,那还是要根据相应的设备大小进行游戏的开发。
在完成了WIN32模式下窗口大小的修改后,我们在HelloWorld.h中定义一个枚举,用来标识所要用到的界面ID。这是一个好的习惯,方便后面的ID获取,如果用数字的话,往往容易忘记。
enumNodeID
{
NID_UI_START = 1, //开始界面
NID_ANI_WAR = 2, //战争介绍动画
NID_UI_CHOSEMAP = 3, //选关卡界面
NID_UI_FXP = 4, //方向与攻击控制界面
NID_UI_FINISH = 5, //关卡结束界面
NID_UI_WIN = 6 //通关界面
};
然后我们可以在HelloWorld的init函数中加入创建开始界面的代码:
//开始界面
CGameUI* pNewUI = new CGameUI();
pNewUI->autorelease();
pNewUI->LoadUITree(&34;ui_start.ui&34;);
//设置界面的位置
pNewUI->setOffset(0, 0);
this->addChild(pNewUI,1,NID_UI_START);
这样我们把做好的ui_start.ui和相应的图片资源拷到工程的Resource下的ipad目录中。我们就可以运行一下,看到相应的界面了,但是这个界面还不能响应点击,我们可以来处理一下:
首先我们把ui_start.lua也拷到ipad中。并修改其中代码:
我们在“开始游戏”的按钮对应的LUA函数中加入了对于开始界面的隐藏处理。
在“结束游戏”的按钮对应的LUA函数中加入了退出程序的处理。
在完成后,大家可以运行下测试一下。在点击“开始游戏”的按钮后,我们还希望能够启动后面的剧情文字介绍。这时候我们用LUA做就没必要了,可以直接对按钮增加回调函数:
//通过控件名称获取到相应的控件
CUICtrl* pUICtrl = pNewUI->QueryUICtrl(&34;UICtrl2_Button&34;);
if(pUICtrl)
{
//转化为按钮
CUIButton* pUIButton = dynamic_cast<CUIButton*>(pUICtrl);
if(pUIButton)
{
//设置在按下时响应的回调函数
pUIButton->setClickEndCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::CreateAboutUI), (void*)0xbebabeba));
}
}
在这个CreateAboutUI的回调函数中,我们将创建相应的文字行并设定一个定时回调的函数CreateAboutPrintAni函数来显示打字机动画。因为文字是通过我们工具箱创建的字图来生成的,所以我们要创建出每一行的CCLabelBMFont。打字机效果的实现通过每次调用定时回调函数时对这一行的文字要显示的字数加一就可以实现了,这样我们就需要统计出当前行的字数和总共的字数,我们在HelloWorld类中增加相应的成员。
private:
//第一行
int m_nPrintCharSize1_Total;
int m_nPrintCharSize1_Curr;
string m_strPrintText1;
CCLabelBMFont* m_pAboutTextLable1;
//第二行
int m_nPrintCharSize2_Total;
int m_nPrintCharSize2_Curr;
string m_strPrintText2;
CCLabelBMFont* m_pAboutTextLable2;
//第三行
int m_nPrintCharSize3_Total;
int m_nPrintCharSize3_Curr;
string m_strPrintText3;
CCLabelBMFont* m_pAboutTextLable3;
//第四行
int m_nPrintCharSize4_Total;
int m_nPrintCharSize4_Curr;
string m_strPrintText4;
CCLabelBMFont* m_pAboutTextLable4;
//第五行
int m_nPrintCharSize5_Total;
int m_nPrintCharSize5_Curr;
string m_strPrintText5;
CCLabelBMFont* m_pAboutTextLable5;
因为我们使用到中文,所以我们按照之前做打地鼠的经验,可以将这些文字放到PLIST中再读取。
并在CPP中增加CreateAboutUI函数:
//开始介绍战争
void HelloWorld::CreateAboutUI(CCNode* pSender, void* data)
{
if(m_pAboutTextLable1)
{
CGameUI* pNewUI
= dynamic_cast<CGameUI*>(getChildByTag(NID_UI_START));
if(pNewUI)
{
pNewUI->setVisible(false);
}
return ;
}
//因为我们要显示的文字是汉字,所以为了避免乱码,我们在这里将文字存入到XML中,然后在Cocos2d-x中读取。
CCDictionary *strings = CCDictionary::createWithContentsOfFile(&34;string.plist&34;);
//第一行
m_strPrintText1 = ((CCString*)strings->objectForKey(&34;line1&34;))->m_sString;
m_nPrintCharSize1_Total = m_strPrintText1.size();
m_nPrintCharSize1_Curr = 0;
//第二行
m_strPrintText2 = ((CCString*)strings->objectForKey(&34;line2&34;))->m_sString;
m_nPrintCharSize2_Total = m_strPrintText2.size();
m_nPrintCharSize2_Curr = 0;
//第三行
m_strPrintText3 = ((CCString*)strings->objectForKey(&34;line3&34;))->m_sString;
m_nPrintCharSize3_Total = m_strPrintText3.size();
m_nPrintCharSize3_Curr = 0;
//第四行
m_strPrintText4 = ((CCString*)strings->objectForKey(&34;line4&34;))->m_sString;
m_nPrintCharSize4_Total = m_strPrintText4.size();
m_nPrintCharSize4_Curr = 0;
//第五行
m_strPrintText5 = ((CCString*)strings->objectForKey(&34;line5&34;))->m_sString;
m_nPrintCharSize5_Total = m_strPrintText5.size();
m_nPrintCharSize5_Curr = 0;
//创建相应的文字标签
CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize();
m_pAboutTextLable1 = CCLabelBMFont::create(&34;&34;, &34;card_0_text.fnt&34;);
m_pAboutTextLable1->setPosition(ccp(20, visibleSize.height - 100));
m_pAboutTextLable1->setAlignment(kCCTextAlignmentLeft);
m_pAboutTextLable1->setAnchorPoint(ccp(0,0));
this->addChild(m_pAboutTextLable1,3);
m_pAboutTextLable2 = CCLabelBMFont::create(&34;&34;, &34;card_0_text.fnt&34;);
m_pAboutTextLable2->setPosition(ccp(20, visibleSize.height - 150));
m_pAboutTextLable2->setAlignment(kCCTextAlignmentLeft);
m_pAboutTextLable2->setAnchorPoint(ccp(0,0));
this->addChild(m_pAboutTextLable2,3);
m_pAboutTextLable3 = CCLabelBMFont::create(&34;&34;, &34;card_0_text.fnt&34;);
m_pAboutTextLable3->setPosition(ccp(20, visibleSize.height - 200));
m_pAboutTextLable3->setAlignment(kCCTextAlignmentLeft);
m_pAboutTextLable3->setAnchorPoint(ccp(0,0));
this->addChild(m_pAboutTextLable3,3);
m_pAboutTextLable4 = CCLabelBMFont::create(&34;&34;, &34;card_0_text.fnt&34;);
m_pAboutTextLable4->setPosition(ccp(20, visibleSize.height - 250));
m_pAboutTextLable4->setAlignment(kCCTextAlignmentLeft);
m_pAboutTextLable4->setAnchorPoint(ccp(0,0));
this->addChild(m_pAboutTextLable4,3);
m_pAboutTextLable5 = CCLabelBMFont::create(&34;&34;, &34;card_0_text.fnt&34;);
m_pAboutTextLable5->setPosition(ccp(20, visibleSize.height - 300));
m_pAboutTextLable5->setAlignment(kCCTextAlignmentLeft);
m_pAboutTextLable5->setAnchorPoint(ccp(0,0));
this->addChild(m_pAboutTextLable5,3);
//每0.06秒出现一个字
schedule(schedule_selector(HelloWorld::CreateAboutPrintAni), 0.06f);
//播放打字机的音效
m_nSoundEffectID = SimpleAudioEngine::sharedEngine()->playEffect(&34;1943.mp3&34;);
m_bShowAboutTextAndAni = true;
m_nAwardTotal = 0;
}
在这个函数里只是初始化了每一行的文字标签,真正的打字机效果定时更新文字是在每0.06秒定时调用的CreateAboutPrintAni函数中。
//开始战争介绍-显示打字机动画效果
void HelloWorld::CreateAboutPrintAni(float dt)
{
char tTemp[1024];
memset(tTemp,0,sizeof(tTemp));
m_nPrintCharSize1_Curr++;
if(m_nPrintCharSize1_Curr >= m_nPrintCharSize1_Total)
{
m_nPrintCharSize2_Curr++;
if(m_nPrintCharSize2_Curr >= m_nPrintCharSize2_Total)
{
m_nPrintCharSize3_Curr++;
if(m_nPrintCharSize3_Curr >= m_nPrintCharSize3_Total)
{
m_nPrintCharSize4_Curr++;
if(m_nPrintCharSize4_Curr >= m_nPrintCharSize4_Total)
{
m_nPrintCharSize5_Curr++;
if(m_nPrintCharSize5_Curr >= m_nPrintCharSize5_Total)
{
m_nPrintCharSize5_Curr++;
unschedule(schedule_selector(HelloWorld::CreateAboutPrintAni));
//停止音效ID
SimpleAudioEngine::sharedEngine()->pauseEffect(m_nSoundEffectID);
//文字隐藏
removeChild(m_pAboutTextLable1);
removeChild(m_pAboutTextLable2);
removeChild(m_pAboutTextLable3);
removeChild(m_pAboutTextLable4);
removeChild(m_pAboutTextLable5);
//播放动画
scheduleOnce(schedule_selector(HelloWorld:: CreateAboutPictureAni), 1.0f);
}
else
{
strcpy(tTemp,m_strPrintText5.c_str());
memset(tTemp+m_nPrintCharSize5_Curr,0,1024-m_nPrintCharSize5_Curr);
m_pAboutTextLable5->setCString(tTemp);
}
}
else
{
strcpy(tTemp,m_strPrintText4.c_str());
memset(tTemp+m_nPrintCharSize4_Curr,0,1024-m_nPrintCharSize4_Curr);
m_pAboutTextLable4->setCString(tTemp);
}
}
else
{
strcpy(tTemp,m_strPrintText3.c_str());
memset(tTemp+m_nPrintCharSize3_Curr,0,1024-m_nPrintCharSize3_Curr);
m_pAboutTextLable3->setCString(tTemp);
}
}
else
{
strcpy(tTemp,m_strPrintText2.c_str());
memset(tTemp+m_nPrintCharSize2_Curr,0,1024-m_nPrintCharSize2_Curr);
m_pAboutTextLable2->setCString(tTemp);
}
}
else
{
strcpy(tTemp,m_strPrintText1.c_str());
memset(tTemp+m_nPrintCharSize1_Curr,0,1024-m_nPrintCharSize1_Curr);
m_pAboutTextLable1->setCString(tTemp);
}
}
这样就实现了带感的打字机介绍剧情的动画效果。在所有文字都介绍完之后,我们调用了停止音效的函数并注销了定时回调函数,并调用一个CreateAboutPictureAni函数来显示老照片动画。这个函数很简单,加载工具箱制做的老照片动画并播放就OK了。
//显示老照片动画 void HelloWorld::CreateAboutPictureAni(float dt) { //创建动画 C2DSkinAni* pNewSkinAni = new C2DSkinAni(); if(pNewSkinAni) { if(true == pNewSkinAni->ReadAllSubBodyFromBin(&34;card_0_about.gan&34;)) { CCSize tBoundingBox = pNewSkinAni->GetBoundingBoxSize(); pNewSkinAni->autorelease(); pNewSkinAni->Play(1); this->addChild(pNewSkinAni,4,NID_ANI_WAR); CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize(); pNewSkinAni->SetOffset(visibleSize.width/2,visibleSize.height/2 ); //最后一帧加一个函数调用 pNewSkinAni->SetEndFrameCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::AboutUIEndFrameCallBack), (void*)0xbebabeba)); } }
我们在动画的最后一帧加入了一个函数调用AboutUIEndFrameCallBack。我们将在这个函数里做一下稍后调用显示选关卡界面的处理:
//老照片动画显示结束的回调
void HelloWorld::AboutUIEndFrameCallBack(CCNode* pSender, void* data)
{
//停留一秒后调用显示选关卡函数ShowCardUI。
scheduleOnce(schedule_selector(HelloWorld::ShowCardUI), 1.0f);
}
增加一个ShowCardUI函数,在这个函数里,我们将加载选关卡的界面,并为相应的函数增加回调函数:
//选择关卡界面
void HelloWorld::ShowCardUI(float dt)
{
//显示剧情动画结束,将动画设为不显示并移除
m_bShowAboutTextAndAni = false;
C2DSkinAni* pNewSkinAni = dynamic_cast<C2DSkinAni*>(getChildByTag(NID_ANI_WAR));
if(pNewSkinAni)
{
pNewSkinAni->setVisible(false);
removeChild(pNewSkinAni);
//pNewSkinAni->release();
}
//取得选关卡的界面.如果存在,则直接显示,如果不存在则新建并加载.这一步是因为有个后退按钮可以退到开始界面,再次点击&34;开始游戏&34;按钮时就不需要重复创建了.
CGameUI* pChoseChardUI = dynamic_cast<CGameUI*>(getChildByTag(NID_UI_CHOSEMAP));
if(pChoseChardUI)
{
pChoseChardUI->setVisible(true);
}
else
{
//选关卡界面
CGameUI* pNewUI = new CGameUI();
pNewUI->autorelease();
pNewUI->LoadUITree(&34;ui_chosecard.ui&34;);
//设置界面的位置
pNewUI->setOffset(0, 0);
this->addChild(pNewUI,1,NID_UI_CHOSEMAP);
//设置按钮的回调,只有玩过的能选择。
char szCtrlName[64];
for(int i = 0 ; i < 15 ; i++)
{
sprintf(szCtrlName,&34;UICtrl%d_Button&34;,i+3);
CUICtrl* pUICtrl = pNewUI->QueryUICtrl(szCtrlName);
if(pUICtrl)
{
CUIButton* pUIButton = dynamic_cast<CUIButton*>(pUICtrl);
if(pUIButton)
{
//如果关卡能玩,则设置回调函数响应按钮,否则不能响应并置为灰色处理
if( i <= m_nLastPlayMapID )
{
//设置在按下时响应的回调函数,可带自定义参数
pUIButton->setClickBeginCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::ChoseCardBtnCallBack), (void*)i));
//关卡上的文字
char szText[10];
sprintf(szText,&34;%d&34;,i+1);
m_pTankTextLabel[i] = CCLabelBMFont::create(szText, &34;number.fnt&34;);
POINT tPoint = pUICtrl->GetWorldPos();
int nWidth = pUICtrl->getWidth();
int nHeight = pUICtrl->getHeight();
if(i >= 10)
{
m_pTankTextLabel[i]->setPosition(ccp(tPoint.x+nWidth/4,tPoint.y-nHeight/2));
}
else
{
m_pTankTextLabel[i]->setPosition(ccp(tPoint.x+nWidth/3,tPoint.y-nHeight/2));
}
m_pTankTextLabel[i]->setAlignment(kCCTextAlignmentLeft);
m_pTankTextLabel[i]->setAnchorPoint(ccp(0,0));
pNewUI->addChild(m_pTankTextLabel[i],1);
}
else
{
//设置按钮灰色
CCSprite* pScrpit = pUIButton->getSprite();
if( pScrpit )
{
pScrpit->setColor(ccc3(128,128,128));
}
}
}
}
}
}
}
增加一个空函数,用于在后面编写创建关卡的处理
//开始第N关
void HelloWorld::ChoseCardBtnCallBack(CCNode* pSender, void* data)
{
}
在这些可以选择关卡的按钮上我们设定了相应的点击回调处理函数,这个函数将实现相应关卡的场景加载与敌我坦克的设置,到这里,我们将开始游戏场景的处理.
首先我们要熟悉一下工具箱的场景类.我们打开HHRLib的HHR_SceneManage.h看一下,这个CGameScene类是由CCLayer派生出来的。它的主要功能是解析场景文件并显示。
它的头文件里有一个类CGameSceneNpc,这是一个NPC的基类,在CGameScene里会有相应的容器来存放游戏场景中需要的NPC实例,而我们只要由CGameSceneNpc派生出我们需要的NPC并调用CGameScene的成员函数AddNew2DActiveObj就可以把我们的NPC实体放到场景中了。我们的NPC的逻辑可以在其重载基类的update中进行处理。
比如,现在我们加入我们玩家的主坦克类。
新建文件CMyTank.h/cpp,由CGameSceneNpc类派生出CMyTank类。
ifndef _CMYTANK_H define _CMYTANK_H //=========================================================== //Auto:火云红孩儿 QQ:285421210 //Date:2013-10-11 //Desc:玩家的坦克 //=========================================================== include &34;Header.h&34; include &34;HHR_Character.h&34; include &34;HHR_SceneManage.h&34; //当前玩家的坦克 class CMyTank :public CGameSceneNPC { public: CMyTank(); ~CMyTank(); public: //初始化 virtual void Init(); //每帧调用的更新 virtual void update(float fDelaySecond); //每帧调用的渲染 virtual void Render(); public: //设置当前触屏按键 void SetDirKey(enuDirKey vDir); //设置开炮 void SetAttackKey(bool bAttack); //设置无敌状态 void setNBState(); //是否无敌状态 bool IsNBState(); //设置发射穿甲弹 void setNBBulletState(); //是否发射穿甲弹 bool IsNBBulletState(); //坦克血量 VAR_SETGET(int,m_nHP,HP); private: //上次的攻击时间
include &34;CMyTank.h&34; CMyTank::CMyTank() { m_dwLastAttackTime = 0; m_nDir = DK_UP; m_nDirKey = DK_NONE; m_bAttackKey = false; m_nHP = 10; m_bNB = false; m_NBTime = 0; m_bNBBullet = false; m_NBBulletTime = 0; } CMyTank::~CMyTank() { } //初始化 void CMyTank::Init() { } //每帧调用的更新 Void CMyTank::update(float fDelaySecond) { if( m_pCharacter && CGameSceneNPC::m_pCurrGameMap) { //无敌状态时间计算 if(m_bNB) { m_NBTime += fDelaySecond ; if(m_NBTime > 10000) { m_bNB = false; m_NBTime = 0; } } //穿甲弹状态时间计算 if(m_bNBBullet) { m_NBBulletTime += fDelaySecond ; if(m_NBBulletTime > 10000) { m_bNBBullet = false; m_NBBulletTime = 0; } } fDelaySecond = min(fDelaySecond,0.2); //取得场景信息 stSceneHeader* tpSceneHeader = CGameSceneNPC::m_pCurrGameMap->GetSceneHeader(); CCPoint tScenePos_Old; CCPoint tScenePos; tScenePos_Old = tScenePos = m_pCharacter->GetScenePos(); CCSize tBoundingBox = m_pCharacter->GetBoundingBoxSize(); enuDirKey nDir = m_nDirKey; //按下左键 if ((::GetKeyState(&39;A&39;))&0x8000) { nDir = DK_LEFT; } //按下右键 if ((::GetKeyState(&39;D&39;))&0x8000) { nDir = DK_RIGHT; } //按下上键 if ((::GetKeyState(&39;W&39;))&0x8000) { nDir = DK_UP; } //按下下键 if ((::GetKeyState(&39;S&39;))&0x8000) { nDir = DK_DOWN; } //按下下键 if (m_bAttackKey || (::GetKeyState(&39;J&39;))&0x8000) { struct timeval tv; gettimeofday(&tv,NULL); double dwCurrTime = tv.tv_sec*1000.0+tv.tv_usec* 0.001; //开炮时间 if(dwCurrTime - m_dwLastAttackTime > 1000) { //创建一个子弹角色 //这里待加入创建子弹的代码 m_dwLastAttackTime = dwCurrTime ; } } if(nDir<0)return; m_nDir = nDir; float fHalfWidth = tBoundingBox.width * 0.5 ; float fHalfHeight = tBoundingBox.height * 0.5 ; float fMoveSpeed = 80; bool bCollResult = false; int nLayer = 1; //当前所在格子 POINT tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x,tScenePos.y,false); //取得格子对应的中心点 CCPoint tTile_Center = CGameSceneNPC::m_pCurrGameMap->GetTileCenterPoint(nLayer,tTile.x,tTile.y,false); //设置图片的旋转方向 m_pCharacter->GetCurrBody()->setRotateZ(90 * nDir); switch(nDir) { case DK_LEFT: { //左 tScenePos.x -= fMoveSpeed*fDelaySecond ; //取得相应层的相应格子数据 POINT tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x-fHalfWidth,tScenePos.y,false); stBlockRenderInfo* tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y); if(tpBlockRenderInfo) { if(tpBlockRenderInfo->m_bIsObstruct) { bCollResult = true; tScenePos = tScenePos_Old ; } } if(false == bCollResult) { tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x-fHalfWidth,tScenePos.y - fHalfHeight * 0.6,false); tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y); if(tpBlockRenderInfo) { if(tpBlockRenderInfo->m_bIsObstruct) { bCollResult = true; tScenePos = tScenePos_Old ; } } } if(false == bCollResult) { tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x-fHalfWidth,tScenePos.y + fHalfHeight * 0.6,false); tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y); if(tpBlockRenderInfo) { if(tpBlockRenderInfo->m_bIsObstruct) { bCollResult = true; tScenePos = tScenePos_Old ; } } } } break; case DK_UP: { //上 tScenePos.y += fMoveSpeed*fDelaySecond ; //取得相应层的相应格子数据 POINT tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x,tScenePos.y+fHalfHeight,false); stBlockRenderInfo* tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y); if(tpBlockRenderInfo) { if(tpBlockRenderInfo->m_bIsObstruct) { bCollResult = true; tScenePos = tScenePos_Old ; } } if(false == bCollResult) { tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x-fHalfWidth * 0.6,tScenePos.y + fHalfHeight,false); tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y); if(tpBlockRenderInfo) { if(tpBlockRenderInfo->m_bIsObstruct) { bCollResult = true; tScenePos = tScenePos_Old ; } } } if(false == bCollResult) { tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x+fHalfWidth* 0.6,tScenePos.y + fHalfHeight ,false); tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y); if(tpBlockRenderInfo) { if(tpBlockRenderInfo->m_bIsObstruct) { bCollResult = true; tScenePos = tScenePos_Old ; } } } } break; case DK_RIGHT: { //右 tScenePos.x += fMoveSpeed*fDelaySecond ; //取得相应层的相应格子数据 POINT tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x+fHalfWidth,tScenePos.y,false); stBlockRenderInfo* tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y); if(tpBlockRenderInfo) { if(tpBlockRenderInfo->m_bIsObstruct) { bCollResult = true; tScenePos = tScenePos_Old ; } } if(false == bCollResult) { tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x+fHalfWidth,tScenePos.y - fHalfHeight * 0.6,false); tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y); if(tpBlockRenderInfo) { if(tpBlockRenderInfo->m_bIsObstruct) { bCollResult = true; tScenePos = tScenePos_Old ; } } } if(false == bCollResult) { tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x+fHalfWidth,tScenePos.y + fHalfHeight * 0.6,false); tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y); if(tpBlockRenderInfo) { if(tpBlockRenderInfo->m_bIsObstruct) { bCollResult = true; tScenePos = tScenePos_Old ; } } } } break; case DK_DOWN: { //下 tScenePos.y -= fMoveSpeed*fDelaySecond ; //取得相应层的相应格子数据 POINT tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x,tScenePos.y-fHalfHeight,false); stBlockRenderInfo* tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y); if(tpBlockRenderInfo) { if(tpBlockRenderInfo->m_bIsObstruct) { bCollResult = true; tScenePos = tScenePos_Old ; } } if(false == bCollResult) { tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x-fHalfWidth * 0.6,tScenePos.y - fHalfHeight,false); tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y); if(tpBlockRenderInfo) { if(tpBlockRenderInfo->m_bIsObstruct) { bCollResult = true; tScenePos = tScenePos_Old ; } } } if(false == bCollResult) { tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x+fHalfWidth* 0.6,tScenePos.y - fHalfHeight ,false); tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y); if(tpBlockRenderInfo) { if(tpBlockRenderInfo->m_bIsObstruct) { bCollResult = true; tScenePos = tScenePos_Old ; } } } } break; } if(false == bCollResult) { if(tScenePos.x < tBoundingBox.width*0.5) { tScenePos.x = tBoundingBox.width*0.5; bCollResult = true; } else if(tScenePos.x > (tpSceneHeader->m_nSceneWidth-tBoundingBox.width*0.5)) { tScenePos.x = tpSceneHeader->m_nSceneWidth - tBoundingBox.width*0.5; bCollResult = true; } else if(tScenePos.y < tBoundingBox.height*0.5) { tScenePos.y = tBoundingBox.height*0.5; bCollResult = true; } else if(tScenePos.y > (tpSceneHeader->m_nSceneHeight-tBoundingBox.height*0.5)) { tScenePos.y = (tpSceneHeader->m_nSceneHeight-tBoundingBox.height*0.5); bCollResult = true; } } m_pCharacter->SetScenePos(tScenePos); //接到物品 tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x,tScenePos.y,false); //取得相应层的相应格子数据 stBlockRenderInfo* tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(2,tTile.x,tTile.y); if(tpBlockRenderInfo) { if(RET_IMAGE == tpBlockRenderInfo->m_sRenderElementType) { //HP if(0==strcmp(tpBlockRenderInfo->m_strNpcName.c_str(),&34;eff3.png&34;)) { m_nHP = 10; CGameSceneNPC::m_pCurrGameMap->SetTileRenderInfo(2,tTile.x,tTile.y,-1); } //无敌 if(0==strcmp(tpBlockRenderInfo->m_strNpcName.c_str(),&34;eff2.png&34;)) { setNBState(); CGameSceneNPC::m_pCurrGameMap->SetTileRenderInfo(2,tTile.x,tTile.y,-1); } //穿甲弹 if(0==strcmp(tpBlockRenderInfo->m_strNpcName.c_str(),&34;eff1.png&34;)) { setNBBulletState(); CGameSceneNPC::m_pCurrGameMap->SetTileRenderInfo(2,tTile.x,tTile.y,-1); } } } } } //每帧调用的渲染 Void CMyTank::Render() { CGameSceneNPC::Render(); //显示血条 C2DCharacter* tpCharacter = getCharInfo(); if( tpCharacter ) { CCPoint tCenetrPos = tpCharacter->GetScenePos(); CCSize tBoundingBox = tpCharacter->GetBoundingBoxSize(); CCPoint tScreenPos = CGameSceneNPC::m_pCurrGameMap->GetScreenPosition(tCenetrPos.x,tCenetrPos.y); int nHPBorder = 4; ccDrawColor4B(255, 255, 255, 255); ccDrawRect(ccp(tScreenPos.x-tBoundingBox.width*0.5,tScreenPos.y+tBoundingBox.height*0.5),ccp(tScreenPos.x+tBoundingBox.width*0.5,tScreenPos.y+tBoundingBox.height*0.5-nHPBorder)); //普通 int nLeft = tBoundingBox.width - tBoundingBox.width * m_nHP / 10 ; ccDrawSolidRect(ccp(tScreenPos.x-tBoundingBox.width*0.5+nLeft+1,tScreenPos.y+tBoundingBox.height*0.5-1),ccp(tScreenPos.x+tBoundingBox.width*0.5-1,tScreenPos.y+tBoundingBox.height*0.5-nHPBorder+1),ccc4f(1.0,0,0,1.0)); } } //设置当前触屏按键 void CMyTank::SetDirKey(enuDirKey vDir) { m_nDirKey = vDir; } //设置开炮 void CMyTank::SetAttackKey(bool bAttack) { m_bAttackKey = bAttack; } //设置无敌状态 Void CMyTank::setNBState() { m_bNB = true; m_NBTime = 0; } //是否无敌状态 bool CMyTank::IsNBState() { return m_bNB; } //设置发射穿甲弹 void CMyTank::setNBBulletState() { m_bNBBullet = true; m_NBBulletTime = 0; } //是否发射穿甲弹 bool CMyTank::IsNBBulletState() { return m_bNBBullet; }
double m_dwLastAttackTime;//当前的方向int m_nDir;//触屏按键enuDirKey m_nDirKey;//触屏开炮bool m_bAttackKey;//无敌状态bool m_bNB;//无敌的起始时间float m_NBTime;//是否发射穿甲弹bool m_bNBBullet;//发射穿甲弹的起始时间float m_NBBulletTime;};endif
这样,我们的主坦克就实现了。在这个类里我们实现了坦克的控制与行走,发射子弹等处理。接下来我们来一下由AI来控制的敌方坦克, 我们新建文件AI_Tank.h/cpp,在红孩儿工具箱的场景中,CGameSceneNPC类是可以挂接私有属性结构CGameSceneNPCAttrib和AI处理功能接口类CGameSceneNPCAI。这样可以方便的进行扩展,每一个NPC类实例有单独的属性结构CGameSceneNPCAttrib的实例。而CGameSceneNPCAI则可以实现所有同类的NPC都具有的相同处理方法。
ifndef _AI_TANK_H
define _AI_TANK_H
//===========================================================
//Auto:火云红孩儿 QQ:285421210
//Date:2013-10-11
//Desc:敌人的坦克
//===========================================================
include &34;Header.h&34;
include &34;HHR_Character.h&34;
include &34;HHR_SceneManage.h&34;
//坦克类
class CEnemyTank :public CGameSceneNPC
{
public:
//每帧调用的渲染
virtual void Render();
}
;
//坦克的私有属性
class CAttrib_Tank :public CGameSceneNPCAttrib
{
public:
//构造与析构
CAttrib_Tank();
~CAttrib_Tank();
public:
//坦克类型
VAR_SETGET(int,m_nTankType,TankType);
//坦克血量
VAR_SETGET(int,m_nHP,HP);
//运动速度
VAR_SETGET(float,m_fMoveSpeed,MoveSpeed);
//方向 0左 1上 2右 3下
VAR_SETGET(int,m_nDir,Dir);
//设置上次换方向的时间
VAR_SETGET(double,m_dwLastChangeDirTime,LastChangeDirTime);
//改变方向的机率
VAR_SETGET(int,m_nChangeDirProbability,ChangeDirProbability);
//设置上次开炮的时间
VAR_SETGET(double,m_dwLastAttackTime,LastAttackTime);
public:
//按照相应机率取得是否改变方向的判定
bool GetRandChangeDirValue();
//设置无敌状态
void setNBState(bool bNB = true);
//是否无敌状态
bool IsNBState();
//
//设置发射穿甲弹
void setNBBulletState(bool bNBBullet = true);
//是否发射穿甲弹
bool IsNBBulletState();
//设置上次开炮的时间
VAR_SETGET(float,m_NBTime,NBTime);
//设置上次开炮的时间
VAR_SETGET(float,m_NBBulletTime,NBBulletTime);
private:
//无敌状态
bool m_bNB;
//是否发射穿甲弹
bool m_bNBBullet;
}
;
//坦克的AI
class CAi_Tank :public CGameSceneNPCAI
{
public:
//构造与析构
CAi_Tank();
~CAi_Tank();
public:
//设置NPC所用的私有属性
void registerDestNPC(CGameSceneNPC* pNPC);
//每帧调用的更新
virtual void update(CGameSceneNPC* pNPC,float delay);
private:
//取得一个随机可移动的方向
int GetRandDir(int vLayer,int vTileX,int vTileY);
};
endif
对应的CPP:
include &34;AI_Tank.h&34;
//每帧调用的渲染
void CEnemyTank::Render()
{
CGameSceneNPC::Render();
//显示血条
C2DCharacter* tpCharacter = getCharInfo();
if( tpCharacter )
{
CCPoint tCenetrPos = tpCharacter->GetScenePos();
CCSize tBoundingBox = tpCharacter->GetBoundingBoxSize();
CCPoint tScreenPos = CGameSceneNPC::m_pCurrGameMap->GetScreenPosition(tCenetrPos.x,tCenetrPos.y);
//取得所用的属性
CGameSceneNPCAttrib* pTankAttrib = getNpcAttrib();
CAttrib_Tank* pEnemyTankAttrib = dynamic_cast<CAttrib_Tank*>(pTankAttrib);
if(pEnemyTankAttrib)
{
int nHP = pEnemyTankAttrib->getHP();
int nHPBorder = 4;
ccDrawColor4B(255, 255, 255, 255);
ccDrawRect(ccp(tScreenPos.x-tBoundingBox.width*0.5,tScreenPos.y+tBoundingBox.height*0.5),ccp(tScreenPos.x+tBoundingBox.width*0.5,tScreenPos.y+tBoundingBox.height*0.5-nHPBorder));
int nType = pEnemyTankAttrib->getTankType();
switch(nType)
{
case 0:
{
//普通
int nLeft = tBoundingBox.width - tBoundingBox.width * nHP / 2 ;
ccDrawSolidRect(ccp(tScreenPos.x-tBoundingBox.width*0.5+nLeft+1,tScreenPos.y+tBoundingBox.height*0.5-1),ccp(tScreenPos.x+tBoundingBox.width*0.5-1,tScreenPos.y+tBoundingBox.height*0.5-nHPBorder+1),ccc4f(1.0,0,0,1.0));
}
break;
case 1:
{
//快速
int nLeft = tBoundingBox.width - tBoundingBox.width * nHP ;
ccDrawSolidRect(ccp(tScreenPos.x-tBoundingBox.width*0.5+nLeft+1,tScreenPos.y+tBoundingBox.height*0.5-1),ccp(tScreenPos.x+tBoundingBox.width*0.5-1,tScreenPos.y+tBoundingBox.height*0.5-nHPBorder+1),ccc4f(1.0,0,0,1.0));
}
break;
case 2:
{
//重型
int nLeft = tBoundingBox.width - tBoundingBox.width * nHP / 4;
ccDrawSolidRect(ccp(tScreenPos.x-tBoundingBox.width*0.5+nLeft+1,tScreenPos.y+tBoundingBox.height*0.5-1),ccp(tScreenPos.x+tBoundingBox.width*0.5-1,tScreenPos.y+tBoundingBox.height*0.5-nHPBorder+1),ccc4f(1.0,0,0,1.0));
}
break;
}
}
}
}
//构造与析构
CAttrib_Tank::CAttrib_Tank()
{
m_nTankType = 0;
m_nHP = 100;
m_fMoveSpeed = 50;
m_nDir = 3;
m_dwLastChangeDirTime = 0;
m_nChangeDirProbability = 30;
m_dwLastAttackTime = 0;
m_bNB = false;
m_NBTime = 0;
m_bNBBullet = false;
m_NBBulletTime = 0;
}
CAttrib_Tank::~CAttrib_Tank()
{
}
//按照相应机率取得是否改变方向的判定
bool CAttrib_Tank::GetRandChangeDirValue()
{
int nRand = rand() % 100 ;
return nRand < m_nChangeDirProbability ;
}
//设置无敌状态
void CAttrib_Tank::setNBState(bool bNB)
{
m_bNB = bNB;
m_NBTime = 0;
}
//是否无敌状态
bool CAttrib_Tank::IsNBState()
{
return m_bNB;
}
//设置发射穿甲弹
void CAttrib_Tank::setNBBulletState(bool bNBBullet)
{
m_bNBBullet = bNBBullet;
m_NBBulletTime = 0;
}
//是否发射穿甲弹
bool CAttrib_Tank::IsNBBulletState()
{
return m_bNBBullet;
}
//构造
CAi_Tank::CAi_Tank()
{
}
CAi_Tank::~CAi_Tank()
{
}
//设置NPC所用的私有属性
void CAi_Tank::registerDestNPC(CGameSceneNPC* pNPC)
{
if(pNPC)
{
pNPC->setNpcAttrib(new CAttrib_Tank());
}
}
//每帧调用的更新
void CAi_Tank::update(CGameSceneNPC* pNPC,float fDelaySecond)
{
if(!pNPC)return ;
fDelaySecond = min(fDelaySecond,0.2);
C2DCharacter* pCharacter = pNPC->getCharInfo();
CAttrib_Tank* pAttrib_Tank = dynamic_cast<CAttrib_Tank*>(pNPC->getNpcAttrib());
if(pCharacter && pAttrib_Tank && CGameSceneNPC::m_pCurrGameMap)
{
//无敌状态时间计算
if(pAttrib_Tank->IsNBState())
{
float fNBTime = pAttrib_Tank->getNBTime();
fNBTime += fDelaySecond ;
if(fNBTime > 10000)
{
pAttrib_Tank->setNBState(false);
pAttrib_Tank->setNBTime(0);
}
}
//穿甲弹状态时间计算
if(pAttrib_Tank->IsNBBulletState())
{
float fNBBulletTime = pAttrib_Tank->getNBBulletTime();
fNBBulletTime += fDelaySecond ;
if(fNBBulletTime > 10000)
{
pAttrib_Tank->setNBBulletState(false);
pAttrib_Tank->setNBBulletTime(0);
}
}
//播放判断
struct timeval tv;
gettimeofday(&tv,NULL);
double dwCurrTime = tv.tv_sec*1000.0+tv.tv_usec* 0.001;
//坦克类型
int nTankType = pAttrib_Tank->getTankType();
//取得场景信息
stSceneHeader* tpSceneHeader = CGameSceneNPC::m_pCurrGameMap->GetSceneHeader();
CCPoint tScenePos_Old;
CCPoint tScenePos;
tScenePos_Old = tScenePos = pCharacter->GetScenePos();
CCSize tBoundingBox = pCharacter->GetBoundingBoxSize();
float fHalfWidth = tBoundingBox.width * 0.5 ;
float fHalfHeight = tBoundingBox.height * 0.5 ;
float fMoveSpeed = pAttrib_Tank->getMoveSpeed();
bool bChangeDir = false;
int nDir = pAttrib_Tank->getDir();
int nLayer = 1;
//当前所在格子
POINT tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x,tScenePos.y,false);
//取得格子对应的中心点
CCPoint tTile_Center = CGameSceneNPC::m_pCurrGameMap->GetTileCenterPoint(nLayer,tTile.x,tTile.y,false);
//是否进入格子中央
bool bInTileCenter = false;
//设置图片的旋转方向
pCharacter->GetCurrBody()->setRotateZ(90 * nDir);
switch(nDir)
{
case DK_UP:
{
//上
float fPosY = tScenePos.y + fMoveSpeed*fDelaySecond ;
if(tScenePos.y < tTile_Center.y && fPosY >= tTile_Center.y)
{
bInTileCenter = true;
}
tScenePos.y = fPosY;
//取得相应层的相应格子数据
POINT tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x,tScenePos.y+fHalfHeight,false);
stBlockRenderInfo* tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y);
if(tpBlockRenderInfo)
{
if(tpBlockRenderInfo->m_bIsObstruct)
{
bChangeDir = true;
tScenePos = tScenePos_Old ;
}
}
}
break;
case DK_RIGHT:
{
//右
float fPosX = tScenePos.x + fMoveSpeed*fDelaySecond ;
if(tScenePos.x < tTile_Center.x && fPosX >= tTile_Center.x)
{
bInTileCenter = true;
}
tScenePos.x = fPosX;
//取得相应层的相应格子数据
POINT tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x+fHalfWidth,tScenePos.y,false);
stBlockRenderInfo* tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y);
if(tpBlockRenderInfo)
{
if(tpBlockRenderInfo->m_bIsObstruct)
{
bChangeDir = true;
tScenePos = tScenePos_Old ;
}
}
}
break;
case DK_DOWN:
{
//下
float fPosY = tScenePos.y - fMoveSpeed*fDelaySecond ;
if(tScenePos.y > tTile_Center.y && fPosY <= tTile_Center.y)
{
bInTileCenter = true;
}
tScenePos.y = fPosY;
//取得相应层的相应格子数据
POINT tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x,tScenePos.y-fHalfHeight,false);
stBlockRenderInfo* tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y);
if(tpBlockRenderInfo)
{
if(tpBlockRenderInfo->m_bIsObstruct)
{
bChangeDir = true;
tScenePos = tScenePos_Old ;
}
}
}
break;
case DK_LEFT:
{
//左
float fPosX = tScenePos.x - fMoveSpeed*fDelaySecond ;
if(tScenePos.x > tTile_Center.x && fPosX <= tTile_Center.x)
{
bInTileCenter = true;
}
tScenePos.x = fPosX;
//取得相应层的相应格子数据
POINT tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x-fHalfWidth,tScenePos.y,false);
stBlockRenderInfo* tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y);
if(tpBlockRenderInfo)
{
if(tpBlockRenderInfo->m_bIsObstruct)
{
bChangeDir = true;
tScenePos = tScenePos_Old ;
}
}
}
break;
}
if(false == bChangeDir)
{
if(tScenePos.x < tBoundingBox.width*0.5)
{
tScenePos.x = tBoundingBox.width*0.5;
bChangeDir = true;
}
else if(tScenePos.x > (tpSceneHeader->m_nSceneWidth-tBoundingBox.width*0.5))
{
tScenePos.x = tpSceneHeader->m_nSceneWidth - tBoundingBox.width*0.5;
bChangeDir = true;
}
else if(tScenePos.y < tBoundingBox.height*0.5)
{
tScenePos.y = tBoundingBox.height*0.5;
bChangeDir = true;
}
else if(tScenePos.y > (tpSceneHeader->m_nSceneHeight-tBoundingBox.height*0.5))
{
tScenePos.y = (tpSceneHeader->m_nSceneHeight-tBoundingBox.height*0.5);
bChangeDir = true;
}
}
pCharacter->SetScenePos(tScenePos);
//接到物品
POINT tNewTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayer,tScenePos.x,tScenePos.y,false);
//取得相应层的相应格子数据
stBlockRenderInfo* tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tNewTile.x,tNewTile.y);
if(tpBlockRenderInfo)
{
if(RET_IMAGE == tpBlockRenderInfo->m_sRenderElementType)
{
//穿甲弹
if(0==strcmp(tpBlockRenderInfo->m_strNpcName.c_str(),&34;eff1.png&34;))
{
pAttrib_Tank->setNBBulletState();
}
//无敌
if(0==strcmp(tpBlockRenderInfo->m_strNpcName.c_str(),&34;eff2.png&34;))
{
pAttrib_Tank->setNBState();
}
//HP
if(0==strcmp(tpBlockRenderInfo->m_strNpcName.c_str(),&34;eff3.png&34;))
{
switch(nTankType)
{
case 0:
{
//普通
pAttrib_Tank->setHP(2);
}
break;
case 1:
{
//快速
pAttrib_Tank->setHP(1);
}
break;
case 2:
{
//重型
pAttrib_Tank->setHP(4);
}
break;
}
}
CGameSceneNPC::m_pCurrGameMap->SetTileRenderInfo(nLayer,tNewTile.x,tNewTile.y,-1);
}
}
//开炮时间
double dwLastAttackTime = pAttrib_Tank->getLastAttackTime() ;
if(dwCurrTime - dwLastAttackTime > 4000)
{
//发射子弹的代码
pAttrib_Tank->setLastAttackTime(dwCurrTime) ;
}
//如果方向改变
if(bChangeDir)
{
//取得相应层的相应格子数据
int nNextDir = GetRandDir(nLayer,tNewTile.x,tNewTile.y);
pAttrib_Tank->setDir(nNextDir);
//设置图片的旋转方向
pCharacter->GetCurrBody()->setRotateZ(90 * nNextDir);
}
else
{
if(bInTileCenter)
{
//放在中心点
pCharacter->SetScenePos(tTile_Center);
switch(nDir)
{
case DK_LEFT:
case DK_RIGHT:
{
//取得当前层的格子信息
stEditLayerInfo* tpLayerInfo = CGameSceneNPC::m_pCurrGameMap->GetCurrLayerInfo(nLayer);
//左
//tScenePos.x -= fMoveSpeed*fDelaySecond ;
//取得相应层的相应格子数据
vector<int> nRandDirVec;
stBlockRenderInfo* tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y-1);
if(tpBlockRenderInfo)
{
if(false == tpBlockRenderInfo->m_bIsObstruct)
{
nRandDirVec.push_back(1);
}
}
else if(tTile.y > 0)
{
nRandDirVec.push_back(1);
}
tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x,tTile.y+1);
if(tpBlockRenderInfo)
{
if(false == tpBlockRenderInfo->m_bIsObstruct)
{
nRandDirVec.push_back(3);
}
}
else if(tTile.y < tpLayerInfo->m_nTileRows)
{
nRandDirVec.push_back(3);
}
int nNextDirNum = nRandDirVec.size();
bool bCheckChangeDir = pAttrib_Tank->GetRandChangeDirValue();
if( nNextDirNum > 0 && bCheckChangeDir)
{
double dwLastChangeDirTime = pAttrib_Tank->getLastChangeDirTime() ;
if(dwCurrTime - dwLastChangeDirTime > 5000)
{
int nNextDirIndex = rand()%nNextDirNum;
int nNextDir = nRandDirVec[nNextDirIndex];
pAttrib_Tank->setLastChangeDirTime(dwCurrTime);
pAttrib_Tank->setDir(nNextDir);
}
}
}
break;
case DK_UP:
case DK_DOWN:
{
stEditLayerInfo* tpLayerInfo = CGameSceneNPC::m_pCurrGameMap->GetCurrLayerInfo(nLayer);
//下
//tScenePos.y -= fMoveSpeed*fDelaySecond ;
vector<int> nRandDirVec;
stBlockRenderInfo* tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x-1,tTile.y);
if(tpBlockRenderInfo)
{
if(false == tpBlockRenderInfo->m_bIsObstruct)
{
nRandDirVec.push_back(0);
}
}
else if(tTile.x > 0)
{
nRandDirVec.push_back(0);
}
tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayer,tTile.x+1,tTile.y);
if(tpBlockRenderInfo)
{
if(false == tpBlockRenderInfo->m_bIsObstruct)
{
nRandDirVec.push_back(2);
}
}
else if(tTile.x < tpLayerInfo->m_nTileCows)
{
nRandDirVec.push_back(2);
}
int nNextDirNum = nRandDirVec.size();
bool bCheckChangeDir = pAttrib_Tank->GetRandChangeDirValue();
if( nNextDirNum > 0 && bCheckChangeDir)
{
double dwLastChangeDirTime = pAttrib_Tank->getLastChangeDirTime() ;
if(dwCurrTime - dwLastChangeDirTime > 5000)
{
int nNextDirIndex = rand()%nNextDirNum;
int nNextDir = nRandDirVec[nNextDirIndex];
pAttrib_Tank->setLastChangeDirTime(dwCurrTime);
pAttrib_Tank->setDir(nNextDir);
}
}
}
break;
}
}
}
}
}
//取得一个随机可移动的方向
int CAi_Tank::GetRandDir(int vLayer,int vTileX,int vTileY)
{
//取得相应层的相应格子数据
vector<int> nRandDirVec;
stBlockRenderInfo* tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(vLayer,vTileX,vTileY-1);
if(tpBlockRenderInfo)
{
if(false == tpBlockRenderInfo->m_bIsObstruct)
{
nRandDirVec.push_back(1);
}
}
else
{
nRandDirVec.push_back(1);
}
tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(vLayer,vTileX,vTileY+1);
if(tpBlockRenderInfo)
{
if(false == tpBlockRenderInfo->m_bIsObstruct)
{
nRandDirVec.push_back(3);
}
}
else
{
nRandDirVec.push_back(3);
}
tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(vLayer,vTileX-1,vTileY);
if(tpBlockRenderInfo)
{
if(false == tpBlockRenderInfo->m_bIsObstruct)
{
nRandDirVec.push_back(0);
}
}
else
{
nRandDirVec.push_back(0);
}
tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(vLayer,vTileX+1,vTileY);
if(tpBlockRenderInfo)
{
if(false == tpBlockRenderInfo->m_bIsObstruct)
{
nRandDirVec.push_back(2);
}
}
else
{
nRandDirVec.push_back(2);
}
int nNextDirNum = nRandDirVec.size();
if( nNextDirNum > 0 )
{
int nNextDirIndex = rand()%nNextDirNum;
return nRandDirVec[nNextDirIndex];
}
return -1;
}
通过这些代码的编写,我们给每一个坦克定义了一个单独的属性结构CAttrib_Tank以及对应的AI处理接口类CAi_Tank。
我们可以看到,在发射子弹的代码处没有做处理,我们下面来完成子弹的实现,创建CBullet.h/cpp:
ifndef _CBULLET_H
define _CBULLET_H
//===========================================================
//Auto:火云红孩儿 QQ:285421210
//Date:2013-10-11
//Desc:子弹类
//===========================================================
include &34;Header.h&34;
include &34;HHR_Character.h&34;
include &34;HHR_SceneManage.h&34;
include &34;EffectManager.h&34;
include &34;EffectNode.h&34;
//子弹
class CBullet :public CGameSceneNPC
{
public:
CBullet();
~CBullet();
public:
//初始化
virtual void Init();
//每帧调用的更新
virtual void update(float fDelaySecond);
public:
//敌方还是多方
VAR_SETGET(int,m_bIsEnemy,Enemy);
//运动速度
VAR_SETGET(float,m_fMoveSpeed,MoveSpeed);
//方向 0左 1上 2右 3下
VAR_SETGET(int,m_nDir,Dir);
//是否能打穿铁皮
VAR_SETIS(bool,m_bNB,NB);
};
endif
对应CPP:
include &34;CBullet.h&34;
include &34;HelloWorldScene.h&34;
include &34;AI_Tank.h&34;
include &34;CMyTank.h&34;
include &34;SimpleAudioEngine.h&34;
using namespace CocosDenshion;
CBullet::CBullet()
{
m_fMoveSpeed = 100;
m_nDir = -1;
m_bIsEnemy = true;
m_bNB = false;
}
CBullet::~CBullet()
{
}
//初始化
void CBullet::Init()
{
}
//每帧调用的更新
void CBullet::update(float fDelaySecond)
{
if( m_pCharacter && CGameSceneNPC::m_pCurrGameMap)
{
fDelaySecond = min(fDelaySecond,0.2);
//取得场景信息
CCPoint tScenePos = m_pCharacter->GetScenePos();
//取得绑定盒大小
CCSize tBoundingBox = m_pCharacter->GetBoundingBoxSize();
//取得场景信息
stSceneHeader* tpSceneHeader = CGameSceneNPC::m_pCurrGameMap->GetSceneHeader();
//设置图片的旋转方向
m_pCharacter->GetCurrBody()->setRotateZ(90 * m_nDir);
switch(m_nDir)
{
case DK_UP:
{
//上
tScenePos.y += m_fMoveSpeed*fDelaySecond ;
}
break;
case DK_RIGHT:
{
//右
tScenePos.x += m_fMoveSpeed*fDelaySecond ;
}
break;
case DK_DOWN:
{
//下
tScenePos.y -= m_fMoveSpeed*fDelaySecond ;
}
break;
case DK_LEFT:
{
//左
tScenePos.x -= m_fMoveSpeed*fDelaySecond ;
}
break;
}
m_pCharacter->SetScenePos(tScenePos);
int nBulletLayerIndex = 2;
int nLayerIndex = 1;
POINT tTile = CGameSceneNPC::m_pCurrGameMap->GetTile(nLayerIndex,tScenePos.x,tScenePos.y,false);
//取得相应层的相应格子数据
stBlockRenderInfo* tpBlockRenderInfo = CGameSceneNPC::m_pCurrGameMap->GetTileRenderInfo(nLayerIndex,tTile.x,tTile.y);
if(tpBlockRenderInfo)
{
if(RET_IMAGE == tpBlockRenderInfo->m_sRenderElementType)
{
if(0==strcmp(tpBlockRenderInfo->m_strNpcName.c_str(),&34;wall2.png&34;))
{
//破损墙
CCParticleSystemQuad* pFireSystem = new CCParticleSystemQuad();
pFireSystem->initWithFile(&34;explode.plist&34;);
CCPoint tScreenPos = CGameSceneNPC::m_pCurrGameMap->GetScreenPosition(tScenePos.x,tScenePos.y);
pFireSystem->setPosition(tScreenPos.x,tScreenPos.y);
CGameSceneNPC::m_pCurrGameMap->AddNew2DParticleSystem(nBulletLayerIndex,pFireSystem);
CGameSceneNPC::m_pCurrGameMap->Del2DActiveObj(nBulletLayerIndex,this);
CGameSceneNPC::m_pCurrGameMap->SetTileRenderInfo(nLayerIndex,tTile.x,tTile.y,-1);
SimpleAudioEngine::sharedEngine()->playEffect(&34;hurt.mp3&34;);
}
if(0==strcmp(tpBlockRenderInfo->m_strNpcName.c_str(),&34;wall.png&34;))
{
//完整墙
CCParticleSystemQuad* pFireSystem = new CCParticleSystemQuad();
pFireSystem->initWithFile(&34;explode.plist&34;);
CCPoint tScreenPos = CGameSceneNPC::m_pCurrGameMap->GetScreenPosition(tScenePos.x,tScenePos.y);
pFireSystem->setPosition(tScreenPos.x,tScreenPos.y);
CGameSceneNPC::m_pCurrGameMap->AddNew2DParticleSystem(nBulletLayerIndex,pFireSystem);
CGameSceneNPC::m_pCurrGameMap->Del2DActiveObj(nBulletLayerIndex,this);
//将其变为破损墙
int nWall2ResID = CGameSceneNPC::m_pCurrGameMap->GetRenderInfoID(RET_IMAGE,&34;wall2.png&34;,&34;&34;);
CGameSceneNPC::m_pCurrGameMap->SetTileRenderInfo(nLayerIndex,tTile.x,tTile.y,nWall2ResID);
SimpleAudioEngine::sharedEngine()->playEffect(&34;hurt.mp3&34;);
}
if(0==strcmp(tpBlockRenderInfo->m_strNpcName.c_str(),&34;tone.png&34;))
{
//木桶
CCParticleSystemQuad* pFireSystem = new CCParticleSystemQuad();
pFireSystem->initWithFile(&34;explode.plist&34;);
CCPoint tScreenPos = CGameSceneNPC::m_pCurrGameMap->GetScreenPosition(tScenePos.x,tScenePos.y);
pFireSystem->setPosition(tScreenPos.x,tScreenPos.y);
CGameSceneNPC::m_pCurrGameMap->AddNew2DParticleSystem(nBulletLayerIndex,pFireSystem);
CGameSceneNPC::m_pCurrGameMap->Del2DActiveObj(nBulletLayerIndex,this);
SimpleAudioEngine::sharedEngine()->playEffect(&34;hurt.mp3&34;);
//随机产生
int randvalue = rand()%3;
switch(randvalue)
{
case 0:
{
//穿甲弹
int nEffectIndex = CGameSceneNPC::m_pCurrGameMap->GetRenderInfoID(RET_IMAGE,&34;eff1.png&34;,&34;&34;);
CGameSceneNPC::m_pCurrGameMap->SetTileRenderInfo(nLayerIndex,tTile.x,tTile.y,nEffectIndex);
}
break;
case 1:
{
//HP
int nEffectIndex = CGameSceneNPC::m_pCurrGameMap->GetRenderInfoID(RET_IMAGE,&34;eff2.png&34;,&34;&34;);
CGameSceneNPC::m_pCurrGameMap->SetTileRenderInfo(nLayerIndex,tTile.x,tTile.y,nEffectIndex);
}
break;
case 2:
{
//无敌
int nEffectIndex = CGameSceneNPC::m_pCurrGameMap->GetRenderInfoID(RET_IMAGE,&34;eff3.png&34;,&34;&34;);
CGameSceneNPC::m_pCurrGameMap->SetTileRenderInfo(nLayerIndex,tTile.x,tTile.y,nEffectIndex);
}
break;
}
}
if(0==strcmp(tpBlockRenderInfo->m_strNpcName.c_str(),&34;metal.png&34;))
{
//金属块打不动
CCParticleSystemQuad* pFireSystem = new CCParticleSystemQuad();
pFireSystem->initWithFile(&34;explode.plist&34;);
CCPoint tScreenPos = CGameSceneNPC::m_pCurrGameMap->GetScreenPosition(tScenePos.x,tScenePos.y);
pFireSystem->setPosition(tScreenPos.x,tScreenPos.y);
CGameSceneNPC::m_pCurrGameMap->AddNew2DParticleSystem(nBulletLayerIndex,pFireSystem);
CGameSceneNPC::m_pCurrGameMap->Del2DActiveObj(nBulletLayerIndex,this);
SimpleAudioEngine::sharedEngine()->playEffect(&34;hurt.mp3&34;);
if(m_bNB)
{
//如果够牛逼,就能击穿
CGameSceneNPC::m_pCurrGameMap->SetTileRenderInfo(nLayerIndex,tTile.x,tTile.y,-1);
}
}
}
if(RET_ANI == tpBlockRenderInfo->m_sRenderElementType)
{
if(0==strcmp(tpBlockRenderInfo->m_strNpcName.c_str(),&34;home.gan&34;))
{
//基地被炸,结束了
CGameSceneNPC::m_pCurrGameMap->Del2DActiveObj(nBulletLayerIndex,this);
CGameSceneNPC::m_pCurrGameMap->SetTileRenderInfo(nLayerIndex,tTile.x,tTile.y,-1);
//击毁一个敌方坦克
HelloWorld* tpHelloWorld = dynamic_cast<HelloWorld*>(CGameSceneNPC::m_pCurrGameMap->getParent());
if(tpHelloWorld)
{
tpHelloWorld->KillMyTank();
CCPoint tScreenPos = CGameSceneNPC::m_pCurrGameMap->GetScreenPosition(tScenePos.x,tScenePos.y);
//加载爆炸效果
EffectNode* gpEffect = EffectManager::GetInstance()->LoadEffect(&34;101&34;);
MyVector3 vPos;
vPos.x = tScreenPos.x;
vPos.y = tScreenPos.y;
vPos.z = 10;
//设置效果位置
gpEffect->SetPos(vPos);
MyVector3 vScale;
vScale.x=0.5;
vScale.y=0.5;
vScale.z=0;
//设置效果缩放
gpEffect->SetScale(vScale);
//播放效果
gpEffect->PlayEffect();
}
return ;
}
}
}
//取得坦克数量
int nTankNum = CGameSceneNPC::m_pCurrGameMap->Get2DActiveObjNum(nLayerIndex);
for(int t = 0 ; t < nTankNum ; t++)
{
CGameSceneNPC* tpTank = CGameSceneNPC::m_pCurrGameMap->Get2DActiveObj(nLayerIndex,t);
if( tpTank )
{
bool bEnemyTank = false;
//取得所用的属性
CGameSceneNPCAttrib* pTankAttrib = tpTank->getNpcAttrib();
CAttrib_Tank* pEnemyTankAttrib = dynamic_cast<CAttrib_Tank*>(pTankAttrib);
if(pEnemyTankAttrib)
{
bEnemyTank = true;
}
//敌方子弹只攻击我方
if(m_bIsEnemy && bEnemyTank)
{
continue;
}
//我方子弹只攻击敌方
if(!m_bIsEnemy && !bEnemyTank)
{
continue;
}
//取得角色信息
C2DCharacter* tpCharacter = tpTank->getCharInfo();
if( tpCharacter )
{
CCPoint tCenetrPos = tpCharacter->GetScenePos();
CCSize tBoundingBox = tpCharacter->GetBoundingBoxSize();
if(tScenePos.x < (tCenetrPos.x - tBoundingBox.width*0.5))continue ;
if(tScenePos.y < (tCenetrPos.y - tBoundingBox.height*0.5))continue ;
if(tScenePos.x > (tCenetrPos.x + tBoundingBox.width*0.5))continue ;
if(tScenePos.y > (tCenetrPos.y + tBoundingBox.height*0.5))continue ;
CCParticleSystemQuad* pFireSystem = new CCParticleSystemQuad();
pFireSystem->initWithFile(&34;explode.plist&34;);
CCPoint tScreenPos = CGameSceneNPC::m_pCurrGameMap->GetScreenPosition(tScenePos.x,tScenePos.y);
pFireSystem->setPosition(tScreenPos.x,tScreenPos.y);
CGameSceneNPC::m_pCurrGameMap->AddNew2DParticleSystem(nBulletLayerIndex,pFireSystem);
CGameSceneNPC::m_pCurrGameMap->Del2DActiveObj(nBulletLayerIndex,this);
SimpleAudioEngine::sharedEngine()->playEffect(&34;hurt.mp3&34;);
if(true == bEnemyTank)
{
CAttrib_Tank* tpTankAttrib = dynamic_cast<CAttrib_Tank*>(tpTank->getNpcAttrib());
if(tpTankAttrib&&tpTankAttrib->IsNBState() == false)
{
int tHP = tpTankAttrib->getHP();
if(m_bNB)
{
tHP-=2;
}
else
{
tHP--;
}
tpTankAttrib->setHP(tHP);
if(tHP <= 0)
{
CGameSceneNPC::m_pCurrGameMap->Del2DActiveObj(nLayerIndex,tpTank);
//击毁一个敌方坦克
HelloWorld* tpHelloWorld = dynamic_cast<HelloWorld*>(CGameSceneNPC::m_pCurrGameMap->getParent());
if(tpHelloWorld)
{
tpHelloWorld->KillEmenyTank(tpTankAttrib->getTankType());
CCPoint tScreenPos = CGameSceneNPC::m_pCurrGameMap->GetScreenPosition(tScenePos.x,tScenePos.y);
//加载爆炸效果
EffectNode* gpEffect = EffectManager::GetInstance()->LoadEffect(&34;101&34;);
MyVector3 vPos;
vPos.x = tScreenPos.x;
vPos.y = tScreenPos.y;
vPos.z = 10;
//设置效果位置
gpEffect->SetPos(vPos);
MyVector3 vScale;
vScale.x=0.5;
vScale.y=0.5;
vScale.z=0;
//设置效果缩放
gpEffect->SetScale(vScale);
//播放效果
gpEffect->PlayEffect();
}
}
}
}
else
{
CMyTank* tpMyTank = dynamic_cast<CMyTank*>(tpTank);
if(tpMyTank&&tpMyTank->IsNBState() == false)
{
int tHP = tpMyTank->getHP();
if(m_bNB)
{
tHP-=2;
}
else
{
tHP--;
}
tpMyTank->setHP(tHP);
if(tHP <= 0)
{
CGameSceneNPC::m_pCurrGameMap->Del2DActiveObj(1,tpTank);
//击毁一个敌方坦克
HelloWorld* tpHelloWorld = dynamic_cast<HelloWorld*>(CGameSceneNPC::m_pCurrGameMap->getParent());
if(tpHelloWorld)
{
tpHelloWorld->KillMyTank();
CCPoint tScreenPos = CGameSceneNPC::m_pCurrGameMap->GetScreenPosition(tScenePos.x,tScenePos.y);
//加载爆炸效果
EffectNode* gpEffect = EffectManager::GetInstance()->LoadEffect(&34;101&34;);
MyVector3 vPos;
vPos.x = tScreenPos.x;
vPos.y = tScreenPos.y;
vPos.z = 10;
//设置效果位置
gpEffect->SetPos(vPos);
MyVector3 vScale;
vScale.x=0.5;
vScale.y=0.5;
vScale.z=0;
//设置效果缩放
gpEffect->SetScale(vScale);
//播放效果
gpEffect->PlayEffect();
}
}
}
}
return ;
}
}
}
//删除
if(tScenePos.x < tBoundingBox.width*0.5)
{
CGameSceneNPC::m_pCurrGameMap->Del2DActiveObj(nBulletLayerIndex,this);
}
else if(tScenePos.x > (tpSceneHeader->m_nSceneWidth-tBoundingBox.width*0.5))
{
CGameSceneNPC::m_pCurrGameMap->Del2DActiveObj(nBulletLayerIndex,this);
}
else if(tScenePos.y < tBoundingBox.height*0.5)
{
CGameSceneNPC::m_pCurrGameMap->Del2DActiveObj(nBulletLayerIndex,this);
}
else if(tScenePos.y > (tpSceneHeader->m_nSceneHeight-tBoundingBox.height*0.5))
{
CGameSceneNPC::m_pCurrGameMap->Del2DActiveObj(nBulletLayerIndex,this);
}
}
}
这个子弹类的重点,是在每次update更新位置时对所在格子里的物体进行判断并在击中相应的物体后创建粒子爆炸效果,删除子弹及相应物体。
字弹类完成后,我们在敌我坦克的update函数中的发射子弹处理中加入相应的代码.
CMyTank.cpp:
//按下下键
if (m_bAttackKey || (::GetKeyState(&39;J&39;))&0x8000)
{
struct timeval tv;
gettimeofday(&tv,NULL);
double dwCurrTime = tv.tv_sec*1000.0+tv.tv_usec* 0.001;
//开炮时间
if(dwCurrTime - m_dwLastAttackTime > 1000)
{
//创建一个子弹角色
C2DCharacter* tpPlayerChar = g_CharacterManage.CreateNewCharacter(&34;Bullet&34;);
if(tpPlayerChar)
{
//如果是穿甲弹,则播放穿甲弹对应的角色动作
if(m_bNBBullet)
{
tpPlayerChar->PlayAction(&34;run_nb&34;,-1);
}
else
{
tpPlayerChar->PlayAction(&34;run&34;,-1);
}
//产生炮弹
tpPlayerChar->SetScenePos(tScenePos);
tpPlayerChar->GetCurrBody()->setZ(3);
CBullet* pNewBullet = new CBullet();
pNewBullet->setCharInfo(tpPlayerChar);
pNewBullet->setEnemy(false);
pNewBullet->setDir(m_nDir);
pNewBullet->setNB(m_bNBBullet);
pNewBullet->setMoveSpeed(250);
pNewBullet->Init();
int nBulletLayerIndex = 2;
CGameSceneNPC::m_pCurrGameMap->AddNew2DActiveObj(nBulletLayerIndex,pNewBullet);
m_dwLastAttackTime = dwCurrTime ;
}
}
}
AI_Tank.cpp:
//开炮时间
double dwLastAttackTime = pAttrib_Tank->getLastAttackTime() ;
if(dwCurrTime - dwLastAttackTime > 4000)
{
C2DCharacter* tpPlayerChar = g_CharacterManage.CreateNewCharacter(&34;bullet&34;);
if(tpPlayerChar)
{
if(2 == nTankType || pAttrib_Tank->IsNBBulletState())
{
//穿甲弹
tpPlayerChar->PlayAction(&34;run_nb&34;,-1);
}
else
{
//普通子弹
tpPlayerChar->PlayAction(&34;run&34;,-1);
}
//产生炮弹
tpPlayerChar->SetScenePos(tScenePos);
tpPlayerChar->GetCurrBody()->setZ(3);
CBullet* pNewBullet = new CBullet();
pNewBullet->setCharInfo(tpPlayerChar);
pNewBullet->setEnemy(true);
pNewBullet->setDir(nDir);
pNewBullet->setNB(pAttrib_Tank->IsNBBulletState());
pNewBullet->setMoveSpeed(250);
pNewBullet->Init();
CGameSceneNPC::m_pCurrGameMap->AddNew2DActiveObj(2,pNewBullet);
pAttrib_Tank->setLastAttackTime(dwCurrTime) ;
}
}
这样运行起来就可以发射子弹了.到这时为止,游戏场景,坦克,攻击逻辑和效果都算做完了.我们回到HelloWorldScene. Cpp,在点选关卡按钮函数中加入相应的处理:
//开始第N关
void HelloWorld::ChoseCardBtnCallBack(CCNode* pSender, void* data)
{
//关闭选关卡的界面
CGameUI* pChoseChardUI = dynamic_cast<CGameUI*>(getChildByTag(NID_UI_CHOSEMAP));
if(pChoseChardUI)
{
pChoseChardUI->setVisible(false);
}
//如果没有创建场景,在这里创建出来.
if(NULL == m_pGameScene)
{
m_pGameScene = new CGameScene();
m_pGameScene->autorelease();
m_pGameScene->SetCameraPosition(0,0);
m_pGameScene->setTouchEnabled(false);
//pNewScene->ShowTileInfo(true);
this->addChild(m_pGameScene,1,101);
}
//取得关卡ID并初始化一些变量
int nCardIndex = (int)data;
m_nCurrMapID = nCardIndex+1 ;
m_EnemyTankVec.clear();
//坦克总数
m_EnemyDeadTotal = 0;
//每关卡坦克数量递增以增加难度
m_EnemyTankTotal = 4 + m_nCurrMapID ;
m_EnenyTankCount = 0;
m_nEnemyTank1_DeadCount = m_nEnemyTank2_DeadCount = m_nEnemyTank3_DeadCount = 0;
m_nEnemyTank1_AwardCount = m_nEnemyTank2_AwardCount = m_nEnemyTank3_AwardCount = 0;
char szCardName[100];
sprintf(szCardName,&34;card%d.gsc&34;,m_nCurrMapID++);
if(true == m_pGameScene->LoadSceneFile_Bin(szCardName))
{
//为场景增加一些图片资源,分别为穿甲弹,无敌药瓶和回血瓶.末参数设置为不阻挡,
m_pGameScene->AddRenderInfo(RET_IMAGE,&34;eff1.png&34;,&34;&34;,false);
m_pGameScene->AddRenderInfo(RET_IMAGE,&34;eff2.png&34;,&34;&34;,false);
m_pGameScene->AddRenderInfo(RET_IMAGE,&34;eff3.png&34;,&34;&34;,false);
//增加第二种墙的资源,即被击中后的半击毁墙体。末参数设为阻挡。
m_pGameScene->AddRenderInfo(RET_IMAGE,&34;wall2.png&34;,&34;&34;,true);
//增加一层用于子弹显示
m_pGameScene->AddNewLayer();
//附加效果管理器到当前层。效果库是一个单件。在这里加入一下。
m_pGameScene->addChild(EffectManager::GetInstance(), 10);
//加载角色包。
if(true == g_CharacterManage.LoadRolTreeFromBin(&34;tank.gro&34;))
{
//创建玩家角色
C2DCharacter* tpPlayerChar = g_CharacterManage.CreateNewCharacter(&34;Player&34;);
if(tpPlayerChar)
{
//调用其动作
tpPlayerChar->PlayAction(&34;run&34;,-1);
//取得事件点数量,
int nLayerIndex = 1;
int nEventTileNum = m_pGameScene->GetEventTileNum(nLayerIndex);
//取得对应层的指定事件点
for(int n = 0 ; n < nEventTileNum ; n++)
{
stEventTile* tpEventTile = m_pGameScene->GetEventTile(nLayerIndex,n);
if(tpEventTile)
{
//如果事件点ID为1,设置为我方主坦克位。
if(tpEventTile->m_nEventID == 1)
{
//取得相应格子的实际位置点。
CCPoint tBorthPt = m_pGameScene->GetTileCenterPoint(nLayerIndex,tpEventTile->m_sTile.x,tpEventTile->m_sTile.y,false);
//设置我方主坦克在这个位置上。
tpPlayerChar->SetScenePos(tBorthPt);
//创建一个逻辑NPC类。
m_pMyTank = new CMyTank();
m_pMyTank->setCharInfo(tpPlayerChar);
m_pMyTank->Init();
//将NPC放入到场景中。
m_pGameScene->AddNew2DActiveObj(1,m_pMyTank);
}
else if(tpEventTile->m_nEventID == 0)
{
//记录敌人坦克出生点
POINT tPt;
tPt.x = tpEventTile->m_sTile.x;
tPt.y = tpEventTile->m_sTile.y;
m_EnemyTankVec.push_back(tPt);
}
}
}
//创建出敌人坦克AI
m_pEnemyTankAI = new CAi_Tank();
//每2秒出现一个坦克
schedule(schedule_selector(HelloWorld::EnemyTankBorth), 2.0f);
}
}
}
}
}
在初始化场景时,这里会遍历所有的事件点,根据不同的事件点做不同的处理,比如在事件点ID为0处创建敌方坦克,在事件点ID为1处创建我方坦克.因为创建敌方坦克是个持续的过程,这里通过每2秒调用的回调函数EnemyTankBorth来进行处理.
void HelloWorld::EnemyTankBorth(float dt)
{
if(m_EnenyTankCount < m_EnemyTankTotal)
{
m_EnenyTankCount++;
int nBorthTileIndex = m_EnenyTankCount % m_EnemyTankVec.size() ;
//创建一个2D动画角色
char szRandEnemy[64];
int nRandValue = rand()%3;
sprintf(szRandEnemy,&34;Enemy%d&34;,nRandValue+1);
C2DCharacter* tpEnemyChar = g_CharacterManage.CreateNewCharacter(szRandEnemy);
if(tpEnemyChar)
{
//播放相应的动画名称
tpEnemyChar->PlayAction(&34;run&34;,-1);
int nLayerIndex = 1;
CCPoint tBorthPt = m_pGameScene->GetTileCenterPoint(nLayerIndex,m_EnemyTankVec[nBorthTileIndex].x,m_EnemyTankVec[nBorthTileIndex].y,false);
tpEnemyChar->SetScenePos(tBorthPt);
//创建敌人坦克
CEnemyTank* m_pEnemyTank = new CEnemyTank();
m_pEnemyTank->setCharInfo(tpEnemyChar);
m_pEnemyTank->Init();
//设置坦克AI
m_pEnemyTank->setNpcAI(m_pEnemyTankAI);
m_pGameScene->AddNew2DActiveObj(1,m_pEnemyTank);
//设置速度
CAttrib_Tank* tpTankAttrib = dynamic_cast<CAttrib_Tank*>(m_pEnemyTank->getNpcAttrib());
if(tpTankAttrib)
{
tpTankAttrib->setTankType(nRandValue);
switch(nRandValue)
{
case 0:
//普通
tpTankAttrib->setHP(2);
tpTankAttrib->setMoveSpeed(50);
break;
case 1:
//快速
tpTankAttrib->setHP(1);
tpTankAttrib->setMoveSpeed(70);
break;
case 2:
//重型
tpTankAttrib->setHP(4);
tpTankAttrib->setMoveSpeed(30);
break;
}
}
}
}
else
{
//注销当前定时触发的回调函数
unschedule(schedule_selector(HelloWorld::EnemyTankBorth));
}
}
这样刷敌人坦克的处理也就做好了.下面我们还要处理一下击毁敌方坦克和我方坦克或基地被击毁的函数.
//击毁一个敌人坦克
void HelloWorld::KillEmenyTank(int vTankType)
{
m_EnemyDeadTotal++;
//统计每种坦克被击毁的数量
switch(vTankType)
{
case 0:
m_nEnemyTank1_DeadCount++;
break;
case 1:
m_nEnemyTank2_DeadCount++;
break;
case 2:
m_nEnemyTank3_DeadCount++;
break;
}
//如果所有敌坦克被灭,则设置本关卡完成
if(m_EnemyDeadTotal == m_EnemyTankTotal)
{
m_bWinThisMap = true;
}
}
//击毁我方坦克
void HelloWorld::KillMyTank()
{
//我方被毁,也要进入到关卡结束界面,所以可以在这里设置一下变量以进入关卡结束界面.
m_EnemyDeadTotal = m_EnemyTankTotal ;
//判定失败
m_bWinThisMap = false;
//因为关卡开始时m_nCurrMapID++,所以这里要还原一下.
m_nCurrMapID--;
}
重载一下HelloWorld的darw函数,判断是否结束当前关卡.
//渲染
void HelloWorld::draw(void)
{
//如果敌坦克被灭,进入到关卡结束界面.
if(m_EnemyTankTotal >0 && m_EnemyDeadTotal == m_EnemyTankTotal)
{
FinishThisMap();
}
CCLayer::draw();
}
FinishThisMap函数中我们将需要显示一个统计界面.使用工具箱制作一个界面,然后将数字也用工具箱转成需要的字图以便显示统计数字.
做完需要的界面和字图后将其保存为ui_finish.ui和number.fnt,我们继续完成FinishThisMap函数.
//关卡结束界面
void HelloWorld::FinishThisMap()
{
if(m_pGameScene)
{
//隐藏场景与控制界面
m_pGameScene->setVisible(false);
CGameUI* pFXPUI = dynamic_cast<CGameUI*>(getChildByTag(NID_UI_FXP));
if(pFXPUI)
{
pFXPUI->setVisible(false);
}
}
//方向盘与攻击按键界面
CGameUI* pFinishUI = dynamic_cast<CGameUI*>(getChildByTag(NID_UI_FINISH));
if(pFinishUI)
{
pFinishUI->setVisible(true);
CUICtrl* pUICtrl = pFinishUI->QueryUICtrl(&34;UICtrl6_Rect&34;);
if(pUICtrl)
{
pUICtrl->setVisible(false);
}
pUICtrl = pFinishUI->QueryUICtrl(&34;UICtrl7_Rect&34;);
if(pUICtrl)
{
pUICtrl->setVisible(false);
}
}
else
{
pFinishUI = new CGameUI();
pFinishUI->autorelease();
pFinishUI->LoadUITree(&34;ui_finish.ui&34;);
//设置界面的位置
pFinishUI->setOffset(0, 0);
this->addChild(pFinishUI,2,NID_UI_FINISH);
m_pTankTextLabel1 = CCLabelBMFont::create(&34;&34;,&34;number.fnt&34;);
m_pTankTextLabel1->setPosition(ccp(170, 380));
m_pTankTextLabel1->setAlignment(kCCTextAlignmentLeft);
m_pTankTextLabel1->setAnchorPoint(ccp(0,0));
pFinishUI->addChild(m_pTankTextLabel1,1);
m_pTankTextLabel2 = CCLabelBMFont::create(&34;&34;,&34;number.fnt&34;);
m_pTankTextLabel2->setPosition(ccp(170, 300));
m_pTankTextLabel2->setAlignment(kCCTextAlignmentLeft);
m_pTankTextLabel2->setAnchorPoint(ccp(0,0));
pFinishUI->addChild(m_pTankTextLabel2,1);
m_pTankTextLabel3 = CCLabelBMFont::create(&34;&34;,&34;number.fnt&34;);
m_pTankTextLabel3->setPosition(ccp(170, 220));
m_pTankTextLabel3->setAlignment(kCCTextAlignmentLeft);
m_pTankTextLabel3->setAnchorPoint(ccp(0,0));
pFinishUI->addChild(m_pTankTextLabel3,1);
m_pWardTotalLabel = CCLabelBMFont::create(&34;&34;,&34;number.fnt&34;);
m_pWardTotalLabel->setPosition(ccp(230, 140));
m_pWardTotalLabel->setAlignment(kCCTextAlignmentLeft);
m_pWardTotalLabel->setAnchorPoint(ccp(0,0));
pFinishUI->addChild(m_pWardTotalLabel,1);
}
//显示数量
char tTemp[1024];
sprintf(tTemp,&34;x %d = %d&34;, m_nEnemyTank1_DeadCount,0);
m_pTankTextLabel1->setCString(tTemp);
sprintf(tTemp,&34;x %d = %d&34;, m_nEnemyTank2_DeadCount,0);
m_pTankTextLabel2->setCString(tTemp);
sprintf(tTemp,&34;x %d = %d&34;, m_nEnemyTank3_DeadCount,0);
m_pTankTextLabel3->setCString(tTemp);
sprintf(tTemp,&34;%d&34;, m_nAwardTotal);
m_pWardTotalLabel->setCString(tTemp);
//设置攻击按钮的回调
CUICtrl* pUICtrl = pFinishUI->QueryUICtrl(&34;UICtrl6_Button&34;);
if(pUICtrl)
{
CUIButton* pUIButton = dynamic_cast<CUIButton*>(pUICtrl);
if(pUIButton)
{
//设置在按下时响应的回调函数,可带自定义参数
pUIButton->setClickBeginCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::NextMapBtnCallBack), (void*)0));
}
}
//胜利还是失败的显示
if(m_bWinThisMap)
{
CUICtrl* pUICtrl = pFinishUI->QueryUICtrl(&34;UICtrl7_Rect&34;);
if(pUICtrl)
{
CUIRect* pUIRect = dynamic_cast<CUIRect*>(pUICtrl);
if(pUIRect)
{
pUIRect->setVisible(true);
C2DSkinAni* pSkinAni = pUIRect->getSkinAni();
if(pSkinAni)
{
pSkinAni->Play(1);
}
}
}
}
else
{
CUICtrl* pUICtrl = pFinishUI->QueryUICtrl(&34;UICtrl8_Rect&34;);
if(pUICtrl)
{
CUIRect* pUIRect = dynamic_cast<CUIRect*>(pUICtrl);
if(pUIRect)
{
pUIRect->setVisible(true);
C2DSkinAni* pSkinAni = pUIRect->getSkinAni();
if(pSkinAni)
{
pSkinAni->Play(1);
}
}
}
}
//快速更新统计数字的效果,使用每0.01秒的回调函数来更新数字从0到指定数字的变化。
schedule(schedule_selector(HelloWorld::ShowEnemyTankCountText), 0.01f);
m_EnemyTankTotal= 0;
}
//敌人坦克被击毁统计
void HelloWorld::ShowEnemyTankCountText(floatdt)
{
if(m_pTankTextLabel1&&m_pTankTextLabel2&&m_pTankTextLabel3)
{
boolbFinish = true;
if(m_nEnemyTank1_AwardCount< m_nEnemyTank1_DeadCount*10)
{
m_nEnemyTank1_AwardCount++;
bFinish= false;
}
if(m_nEnemyTank2_AwardCount< m_nEnemyTank2_DeadCount*8)
{
m_nEnemyTank2_AwardCount++;
bFinish= false;
}
if(m_nEnemyTank3_AwardCount< m_nEnemyTank3_DeadCount*30)
{
m_nEnemyTank3_AwardCount++;
bFinish= false;
}
m_nAwardTotal= m_nEnemyTank1_AwardCount + m_nEnemyTank2_AwardCount +m_nEnemyTank3_AwardCount;
//显示数量
char tTemp[1024];
sprintf(tTemp,&34;x%d=%d&34;,m_nEnemyTank1_DeadCount,m_nEnemyTank1_AwardCount);
m_pTankTextLabel1->setString(tTemp,true);
sprintf(tTemp,&34;x%d=%d&34;,m_nEnemyTank2_DeadCount,m_nEnemyTank2_AwardCount);
m_pTankTextLabel2->setString(tTemp,true);
sprintf(tTemp,&34;x%d=%d&34;,m_nEnemyTank3_DeadCount,m_nEnemyTank3_AwardCount);
m_pTankTextLabel3->setString(tTemp,true);
sprintf(tTemp,&34;%d&34;, m_nAwardTotal);
m_pWardTotalLabel->setString(tTemp,true);
if(bFinish)
{
//注销显示敌方坦克数量的函数.
unschedule(schedule_selector(HelloWorld::ShowEnemyTankCountText));
}
}
}
在ui_finish.ui中我们为继续游戏按钮增加了一个点击响应的回函数.
//下一关按钮回调
void HelloWorld::NextMapBtnCallBack(CCNode* pSender, void* data)
{
LoadNextMap();
}
//开始下一关
void HelloWorld::LoadNextMap()
{
CGameUI* pChoseChardUI = dynamic_cast<CGameUI*>(getChildByTag(NID_UI_FINISH));
if(pChoseChardUI)
{
pChoseChardUI->setVisible(false);
}
if(m_pGameScene)
{
m_EnemyTankVec.clear();
//坦克总数
m_EnemyDeadTotal = 0;
m_EnemyTankTotal = 4 + m_nCurrMapID ;
m_EnenyTankCount = 0;
m_nEnemyTank1_DeadCount = m_nEnemyTank2_DeadCount = m_nEnemyTank3_DeadCount = 0;
m_nEnemyTank1_AwardCount = m_nEnemyTank2_AwardCount = m_nEnemyTank3_AwardCount = 0;
//保存关卡
if(m_nLastPlayMapID < m_nCurrMapID-1)
{
m_nLastPlayMapID = m_nCurrMapID-1;
CCUserDefault::sharedUserDefault()->setIntegerForKey(&34;MapID&34;, m_nLastPlayMapID);
CCUserDefault::sharedUserDefault()->flush();
}
//根据是否胜利进入关卡
char szCardName[100];
if(m_bWinThisMap)
{
sprintf(szCardName,&34;card%d.gsc&34;,m_nCurrMapID++);
}
else
{
sprintf(szCardName,&34;card%d.gsc&34;,m_nCurrMapID);
}
if(m_nCurrMapID > 15)
{
//播放通关动画
scheduleOnce(schedule_selector(HelloWorld::CreatWinnerUI), 0.0f);
}
else
{
if(true == m_pGameScene->LoadSceneFile_Bin(szCardName))
{
//增加一些物件资源
m_pGameScene->AddRenderInfo(RET_IMAGE,&34;eff1.png&34;,&34;&34;,false);
m_pGameScene->AddRenderInfo(RET_IMAGE,&34;eff2.png&34;,&34;&34;,false);
m_pGameScene->AddRenderInfo(RET_IMAGE,&34;eff3.png&34;,&34;&34;,false);
//增加第二种墙的资源
m_pGameScene->AddRenderInfo(RET_IMAGE,&34;wall2.png&34;,&34;&34;,true);
//增加一层用于子弹显示
m_pGameScene->AddNewLayer();
//设置显示
m_pGameScene->setVisible(true);
if(true == g_CharacterManage.LoadRolTreeFromBin(&34;tank.gro&34;))
{
C2DCharacter* tpPlayerChar = g_CharacterManage.CreateNewCharacter(&34;Player&34;);
if(tpPlayerChar)
{
tpPlayerChar->PlayAction(&34;run&34;,-1);
//取得事件点数量
int nLayerIndex = 1;
int nEventTileNum = m_pGameScene->GetEventTileNum(nLayerIndex);
//取得对应层的指定事件点
for(int n = 0 ; n < nEventTileNum ; n++)
{
stEventTile* tpEventTile = m_pGameScene->GetEventTile(nLayerIndex,n);
if(tpEventTile)
{
if(tpEventTile->m_nEventID == 1)
{
//产生我方坦克
CCPoint tBorthPt = m_pGameScene->GetTileCenterPoint(nLayerIndex,tpEventTile->m_sTile.x,tpEventTile->m_sTile.y,false);
tpPlayerChar->SetScenePos(tBorthPt);
m_pMyTank = new CMyTank();
m_pMyTank->setCharInfo(tpPlayerChar);
m_pMyTank->Init();
m_pGameScene->AddNew2DActiveObj(1,m_pMyTank);
}
else if(tpEventTile->m_nEventID == 0)
{
//记录敌人坦克出生点
POINT tPt;
tPt.x = tpEventTile->m_sTile.x;
tPt.y = tpEventTile->m_sTile.y;
m_EnemyTankVec.push_back(tPt);
}
}
}
//创建出敌人坦克AI
m_pEnemyTankAI = new CAi_Tank();
//每2秒出现一个坦克
schedule(schedule_selector(HelloWorld::EnemyTankBorth), 2.0f);
}
}
}
}
}
}
播放通关动画的处理在函数CreatWinnerUI中,当然,我们先用工具箱制作好通过的胜利动画,这里简单的做了一个老照片的渐渐显示的动画,与开始时的老照片动画类似,不多做讲述.
做好后保存为sl.ani并导出sl.gan供调用.
//通关的界面
void HelloWorld::CreatWinnerUI(float dt)
{
//过场动画
C2DSkinAni* pNewSkinAni = new C2DSkinAni();
if(pNewSkinAni)
{
pNewSkinAni->ReadAllSubBodyFromBin(&34;sl.gan&34;);
CCSize tBoundingBox = pNewSkinAni->GetBoundingBoxSize();
pNewSkinAni->autorelease();
pNewSkinAni->Play(1);
this->addChild(pNewSkinAni,4,NID_ANI_WAR);
CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize();
pNewSkinAni->SetOffset(visibleSize.width/2,visibleSize.height/2 );
//最后一帧加一个动画调用
pNewSkinAni->SetEndFrameCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::WinnerEndFrameCallBack), (void*)0xbebabeba));
}
}
//通关的动画显示结束的回调,再次回到开始界面.
void HelloWorld::WinnerEndFrameCallBack(CCNode* pSender, void* data)
{
C2DSkinAni* pNewSkinAni = dynamic_cast<C2DSkinAni*>(getChildByTag(NID_ANI_WAR));
if(pNewSkinAni)
{
pNewSkinAni->setVisible(false);
removeChild(pNewSkinAni);
//pNewSkinAni->release();
}
CGameUI* pNewUI = dynamic_cast<CGameUI*>(getChildByTag(NID_UI_START));
if(pNewUI)
{
pNewUI->setVisible(true);
}
}
到现在这一步,基本逻辑就算完成了.最后我们再为它增加一个触屏控制界面,以使其可以在移动设备上进行控制.
导出ui_fxp.ui.在场景初始化完成时加入代码:
//方向盘与攻击按键界面
CGameUI* pFXPUI = new CGameUI();
pFXPUI->autorelease();
pFXPUI->LoadUITree(&34;ui_fxp.ui&34;);
//设置界面的位置
pFXPUI->setOffset(0, 0);
this->addChild(pFXPUI,4,NID_UI_FXP);
//设置方向盘按钮的回调
CUICtrl* pUICtrl = pFXPUI->QueryUICtrl(&34;UICtrl6_Button&34;);
if(pUICtrl)
{
CUIButton* pUIButton = dynamic_cast<CUIButton*>(pUICtrl);
if(pUIButton)
{
//设置在按下时响应的回调函数,可带自定义参数
pUIButton->setClickBeginCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::FXPBtnCallBack), (void*)0));
pUIButton->setClickEndCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::FXPBtnCallBack), (void*)-1));
}
}
pUICtrl = pFXPUI->QueryUICtrl(&34;UICtrl7_Button&34;);
if(pUICtrl)
{
CUIButton* pUIButton = dynamic_cast<CUIButton*>(pUICtrl);
if(pUIButton)
{
//设置在按下时响应的回调函数,可带自定义参数
pUIButton->setClickBeginCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::FXPBtnCallBack), (void*)1));
pUIButton->setClickEndCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::FXPBtnCallBack), (void*)-1));
}
}
pUICtrl = pFXPUI->QueryUICtrl(&34;UICtrl8_Button&34;);
if(pUICtrl)
{
CUIButton* pUIButton = dynamic_cast<CUIButton*>(pUICtrl);
if(pUIButton)
{
//设置在按下时响应的回调函数,可带自定义参数
pUIButton->setClickBeginCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::FXPBtnCallBack), (void*)2));
pUIButton->setClickEndCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::FXPBtnCallBack), (void*)-1));
}
}
pUICtrl = pFXPUI->QueryUICtrl(&34;UICtrl9_Button&34;);
if(pUICtrl)
{
CUIButton* pUIButton = dynamic_cast<CUIButton*>(pUICtrl);
if(pUIButton)
{
//设置在按下时响应的回调函数,可带自定义参数
pUIButton->setClickBeginCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::FXPBtnCallBack), (void*)3));
pUIButton->setClickEndCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::FXPBtnCallBack), (void*)-1));
}
}
//设置攻击按钮的回调
pUICtrl = pFXPUI->QueryUICtrl(&34;UICtrl21_Button&34;);
if(pUICtrl)
{
CUIButton* pUIButton = dynamic_cast<CUIButton*>(pUICtrl);
if(pUIButton)
{
//设置在按下时响应的回调函数,可带自定义参数
pUIButton->setClickBeginCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::AttackBtnCallBack), (void*)1));
pUIButton->setClickEndCallFuncND(CCCallFuncND::create(this, callfuncND_selector(HelloWorld::AttackBtnCallBack), (void*)0));
}
}
增加两个回调函数:
//方向盘按钮回调
void HelloWorld::FXPBtnCallBack(CCNode* pSender, void* data)
{
if(m_pMyTank)
{
int nValue = (int)data;
switch(nValue)
{
case 0:
{//up
m_pMyTank->SetDirKey(DK_UP);
}
break;
case 1:
{//right
m_pMyTank->SetDirKey(DK_RIGHT);
}
break;
case 2:
{//down
m_pMyTank->SetDirKey(DK_DOWN);
}
break;
case 3:
{//left
m_pMyTank->SetDirKey(DK_LEFT);
}
break;
default:
{
m_pMyTank->SetDirKey(DK_NONE);
}
break;
}
}
}
//攻击按钮回调
void HelloWorld::AttackBtnCallBack(CCNode* pSender, void* data)
{
if(m_pMyTank)
{
if( 0 == data )
{
m_pMyTank->SetAttackKey(false);
}
else
{
m_pMyTank->SetAttackKey(true);
}
}
}
这样这个坦克大战就算基本完成了.我们再找一些背景音乐与音效加入到其中播放,最后编译运行一下,看着坦克勇猛的向前冲击,是否很带感呢?
谢谢大家的学习,下一节超级玛丽再见!