学习3 二维游戏动画合成(侠客行)

说明:

<学习>系列所有的源代码均由《计算机游戏程序设计》提供。本人会在这些代码中融入自己的想法,对其进行迭代优化,旨在个人学习。

 

教材源代码示例:

示例代码的迷之bug贼多,怪物AI也只有一套固定的行动模式

优化后的效果:

 

学习目标:

1.了解二维游戏动画合成原理。

2.熟悉Cocos2d-x中的用户交互、触摸事件、碰撞检测机制。

3.熟悉CocoStudio动画编辑器的使用,了解骨骼动画。

主要修改内容:

1.利用cocostudio修改人物骨骼,并将修改结果在游戏中读取,从而改变人物外形,动作等,实现自定义人物骨骼动画效果。

2.增加英雄defend动作,记录成功/失败次数,增加计分板功能。

3.修复bug,使游戏能正常运行。

4.怪物AI设计。

 

步骤与过程:

1.利用cocostudio修改人物骨骼,并将修改结果在游戏中读取,从而改变人物外形,动作等,实现自定义人物骨骼动画效果。

 

CocoStudio Animation导入新的部位的图片,如下图的帽子和斧子:

图 1

图 2

 

创建新骨骼并对图片进行绑定,再绑定到相应父骨骼上,得到新的角色外形:

图 3

之后更新英雄角色每个动作的角色外貌,如下图奔跑动画:

图 4

 

添加自制防御动画,如下图所示:

图 5

最后给每个动作的最后一帧中的某个部位层添加一个“动作名_end”的帧事件。如下图添加攻击动作的帧事件:

图 6

 

导出文件后替换掉源代码中英雄文件,运行程序,发现英雄外貌成功改变了:

图 7

 

2.增加英雄defend动作,记录成功/失败次数,增加计分板功能。

 

在cocostudio中已制作了“防御”defend的动画,接下来只要将防御按钮和动作加到游戏中去就行。如下为AnimationScene.cpp文件中按钮代码:

图 9

图 10

为区分攻击和防御键,另其分别使用两张不同的按钮照片。其中,攻击按钮为红色,防御按钮为蓝色:

图 11

枚举类型中添加了防御DEFEND后,英雄类文件Hero中,简单模仿攻击方式的代码写一个防御方式,使点击防御按钮后实现防御功能,如下图所示:

图 12

关于防御功能的代码及其相关完善,我将在后文的第5部分再详细说明。

 

增加计分板:

重新新建一个文件SaveScore (.h和.cpp):

SavaScore.h

图 13

SavaScore.cpp

图 14

 

把SaveScore.h头文件导入AnimationScene.h文件中被其引用。 SaveScore文件的功能为存储英雄和怪物的比分,heroScore为英雄得分,enemyScore为敌人得分,刚开始都为0。这种做法好处在于,可在AnimationScene文件中直接对heroScore和enemyScore进行操作,在AnimationScene场景被刷新后,计分值也不会改变,直接调用即可。

计分板代码:

图 15

update函数中不断调用judge函数,根据角色的血量来判断输赢,代码为:

图 16

图 17

当每轮游戏结束时(还没到最终输赢),需要刷新当前场景,这里用scheduleOnce的方式调用了restart函数,该函数里执行的代码可刷新场景。由于一轮游戏结束后场景不会被马上刷新,而是在等待几秒中后才刷新,所以这里的scheduleOnce对选择器选择的函数的执行是在3秒后。

restart代码:

图 18

其中,场景过渡使用了部落格特效,持续1.2s 。

 

3.云朵移动

寻找或自行扣掉一张没有背景的云朵的png图片,替换掉资源原有的cloud.png图片 。

图 19

AnimationScene.cpp初始化时创建3朵云,位置设置为不同,代码较简单,不显示。

再在update函数中不断修正其x轴方向位置,每朵云的位移距离不同。当云朵移动到镜头的一端看不见时,在修正其位置到屏幕另一端,这样就能得到循环播放的3朵云。

以1号云朵的位移代码为例,其余类似:

图 20

运行效果图:

图 21

 

4.BUG修正

教材提供的示例代码有许许多多的bug,主要原因是代码逻辑写得不好,这一部分花了大量的时间去Debug来修正。终于解决了游戏中存在的目前能找到的所有Bug,令游戏能正确且流畅的运行起来。

由于游戏bug太多,下面根据解决方法的不同,总结为4类Bug:

  • 按攻击键除了能控制英雄的攻击动作,也能控制怪物的攻击动作。
  • SMITTEN颤抖/硬直动作没有执行、ATTACK动作的动画可被打断、角色被击中后会原地卡在STAND或RUN动作中执行不了任何操作  等一系列稀奇古怪的动作。
  • 莫名其妙连续多次扣血(不是指暴击),在解决②的bug后,容易发现当角色砍中另一角色时,会继续不断使用attack。
  • 角色在奇怪的地方被砍中,或砍不到角色。

 

下面修正上述bug:

  • 攻击键也能控制怪物攻击。

    注释掉或删掉AnimationScene.cpp文件中的attackCallback函数中的红框中代码即可:

图 22

 

  • SMITTEN动作不执行,被砍后不能操作,attack动作被打断 等奇怪举动的bug。

这个bug为该程序中最主要的bug,以英雄Hero为例,问题主要出现在下面三个地方:

  1. AnimationScene.cpp中的update函数中的:

图 23

  1. Hero.cpp中的play函数:

图 24

  1. Hero.cpp中的整个update函数(代码太长,截取一部分)

图 25

图 26

 

分析bug原因:

可见,1)根据摇杆的操作情况,调用2)的Hero中play函数,而3)能检测Hero的状态,执行动作。

因为1)在update函数中;2)被1)调用;3)是update函数。 所以理论上1)2)3)都是一直在运作中的,没有固定的先后顺序。因此,由于不能确定运作顺序(除了 2)会在1)后运行),导致程序容易出错。

 

举例:

当hero被攻击,Hero的play函数会被传入枚举类型SMITTEN作为参数,根据play代码可知,此时受伤状态变量m_ishurt会变为true,当前角色状态m_state会被赋值为SMITTEN。  

理论上来说,下一步该执行Hero中的update函数,判断并执行SMITTEN动作了才对。 然而,这里也有可能在执行Hero的update函数之前,先执行了AnimationScene的update函数。

如果先执行了AnimationScene的update函数,那么1)的“控制角色移动”的代码段就先被执行了,如果此时没有动摇杆,那么枚举类型STAND将作为Hero的play函数的参数传进去,之后就会重新赋值给m_state,SMITTEN状态就会被STAND状态给覆盖掉了。

此时,m_state的值为STAND,而m_ishurt的状态依然是true(因为SMITTEN动作没有被执行)。 再进入Hero的update函数时,由于m_ishurt也是各个动作是否该执行的判断依据,当m_ishurt == true时,这些动作都不会被执行。

因此,当hero被攻击后,hero的操作都将失效。

 

 

根据上面分析,想要解决这个bug必须要保证两个前提(暂不讨论DEFEND防御)

  1. 状态m_state一旦被改变后,只有执行完了该动作(ATTACK和SMITTEN)后,才能再次改变状态m_state。
  2. ATTACK和SMITTEN对应动作在执行中时,一定要运行到最后一帧,不能被打断。

解决上述1)和2):

    1)在Hero.h中声明新的布尔类型私有变量actionFlag,其作用为  当m_state被赋值为ATTACK或SMITTEN时,actionFlag被赋值为true,当其为true时,m_state不能被再改变,只有在ATTACK和SMITTEN动画运行到最后一帧时,actionFlag变为false,此时m_state允许被赋值。

修改相关代码:

图 27

图 28

 

    2)研究源代码中attack相关函数,发现Hero中的m_isAttack的作用为判断hero是否“正在攻击”,这里指的是“动作”而不是“状态”。通过这一变量,在动作执行时(动画播放时)才赋值为true,在最后一帧播放完了再赋值为false。把该变量作为动作执行的判断依据,能有效地控制并防止动作的被打断以及持续进行(譬如一直点击攻击键,攻击动作不断被打断并重新执行,只播放前几帧);

模仿m_isAttack变量,把m_ishurt变量的意义从原来的“受伤状态”更改为“受伤动作”。

同时,在监听帧事件的函数中,要在动作执行完后加入play(STAND)的代码,防止其带着原来的m_state先执行Hero的update函数又引起什么奇怪的操作。

模仿着更改代码:

图 29

图 30

 

    同理修改Enemy的代码即可。

 

 

  • 莫名其妙连续多次扣血(不是指暴击),在解决②的bug后,容易发现当角色砍中另一角色时,会继续不断使用attack。

查看碰撞检测文件MyContactListener.cpp,查看其update函数:

图 31

以敌人攻击英雄为例,关键代码部分放大↓:

图 32

分析:

由上面代码可知,当其他条件满足的前提下,Enemy的m_isAttack变量为true时,表示此时敌人正在执行攻击动作,if满足条件,执行Hero的hurt函数,hero受伤掉血。然后Enemy执行setAttack(false)把其m_isAttack置为false。

然而,当m_isAttack置为false后,在Enemy中,会把其视为攻击动作已经结束,在m_state还是ATTACK时,会把m_isAttack==false作为再次执行攻击动作的判断依据。而检测碰撞文件的update函数又会很快的被再次执行,m_enemy->isAttack()又会被视为true……如此地连续执行,可能会造成角色的连续多次掉血,或者角色一旦攻击到另一角色时,会不断地执行攻击动作。

 

解决:

通过上述分析,我们了解到,解决问题的关键点在于不能在检测碰撞中执行m_enemy->setAttack(false)来改变破坏Enemy的攻击动作。

综上,我们保留其思想,但是不改变m_isAttack的值,为角色引入一个新的私有变量attackHurtFlag,表示被攻击伤到伤害的标志,增加set和get方法。以enemy攻击hero为例,关键代码为:

图 33

   

 Hero的update函数中:

图 34

 

  • 角色在奇怪的地方被砍中,或砍不到角色。

    观察碰撞检测文件MyContactListener.cpp,查看其update函数中enemy攻击hero部分:

图 35

分析:

发现其碰撞检测的基本原理为:为enemy的ax层(即enemy的斧子部件)添加2个检测点,再根据hero的位置创建一个矩形。当enemy为攻击状态,并且其斧子的2个检测点在hero的矩形范围内时,即为实现碰撞。

因此,这里该如何创建矩形成为关键。

分析Rect方法的参数,其第1个参数为矩形左下角的x坐标,第2个参数为矩形左下角的y坐标,第3个参数为矩形的宽,第4个参数为矩形的高。

结合游戏运行图来分析:

假设在使用cocoStudio Animation时,角色的中心点在身体的中心点,随意创建一个矩形,则有:

图 36

假设还是同一程序,当hero转身后,其矩形不会根据角色的转身而左右颠倒,如下图所示:

图 37

由上面两张图可知道,创建矩形时,宽(即x轴)的中间位置的x坐标最好落在角色中心点的x坐标上。只有这样,hero无论转身与否,其前后的被攻击的判定范围都是一样的,这样才不会出现奇怪的“有时能砍到,有时又砍不到”的奇怪现象。

最后只要不断调整矩形的宽度即可(即调整第1个参数和第3个参数)。而矩形的高度只要足以涵盖住角色即可(即调整第2个参数和第4个参数)。

 

最终矩形参数修改为:

图 38

图 39

 

5.防御机制

防御机制设定为:

  • 点击蓝色按钮进入防御状态
  • 防御状态下,角色最后会保持防御动画的最后一帧
  • 防御状态下能减少一段暴击及暴击伤害,受到的伤害值以蓝色数

值显示

  • 防御状态下,操作摇杆,点击攻击按钮,能打断防御状态,并执行其他相应动作
  • 防御状态下,再点击一次防御按钮可以取消防御状态

 

根据以上设定,编写代码:

Hero中的update:

图 40

并为每个动作的执行加上m_isDefend=false,以STAND站立动作为例:

图 41

Hero的showBloodTips函数 “减少暴击数和暴击伤害”以及“防御状态下伤害值为蓝色”:

图 42

图 43

运行图:

图 44

6.AI设计

观察AI文件AIManager的原代码,发现怪物AI仅仅是根据一套固有的动作反复执行而已,关键代码为:

图 45

分析上面代码,可知这个AI并不智能。并且根据上面的执行结果,可知moveLeft的动作持续最久,因此游戏中的后半段,敌人emeny会一直往左边界“推墙”,moveRight的持续时间太短,因此无法往右半边回来。

 

因此,重新编写一个AI代码文件,使其能够根据hero的位置,实现自动跟踪,在适宜的位置进行攻击的功能。

编写后的关键代码为(以hero在enemy的左方为例):

图 46

hero在enemy的右方时也同理可得,而当hero和enemy位置相同时,enemy直接攻击即可。

 

由上述得到了敌人AI的最佳方案,但是如果直接把bestAI函数放到update函数不断调用的话,会发现游戏会变得非常困难,几乎没有赢的可能性。并且,敌人enemy的行动模式不够随机也反而显得不是那么的“智能”。因此,在此基础上减少bestAI的执行次数,插入随机行动模式,并适当地减少攻击频率,让游戏变得更简单,令AI变得更随机些。

修改后的代码为:

图 47

bestAI()中以hero在enemy的左方为例:

图 48

AI的随机方案:

图 49

通过上述操作后,敌人AI能够保持在最佳行动方案的基础上,也进行些许随机行动了。

7.其余Label显示细节

   ① 每轮游戏开始时都会出现“Round X”,X表示游戏的第几轮,1.5s后消失(移除)

图 50

   ② 每一小轮游戏结束后,都会在对应角色的血条下方显示“win”字样

图 51

③ 场景有部落格特效过渡

④ 游戏结束后会显示玩家的输赢,赢了显示“YOU WIN!”;输了显示“YOU LOSE!”

图 52

  • 12
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值