实验3 二维游戏动画合成(侠客行)

说明:

课程教材《计算机游戏程序设计》(基础篇)(第3版) 提供示例代码,而课程实验在示例代码的基础上提出更高的实验要求。除此之外,本人也会额外加入些个人创意,希望同学们在参考之余也能加入自己的想法。

(这次实验对应的书本示例代码bug超级多,所以报告里发的牢骚和说的废话也多了……)

 

教材示例:

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

实现效果:

 

实验报告:

一、实验目的与要求

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

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

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

二、实验内容与方法

1.完成游戏编译(70分)

成功编译并运行教材P128“游戏动画实例-侠客行”。

 

2.完成方案一 (15分)

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

 

3.完成方案二 (15分)

修改游戏代码,实现方案二,即增加英雄defend动作,记录成功/失败次数,增加计分板功能。

 

三、实验步骤与过程

记录关键步骤/设计过程/设计结果的截图

1.完成游戏编译(70分)

    老套路运行游戏例程,运行结果如下:

图 1

 

2.完成方案一 (15分)

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

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

实验3 二维游戏动画合成(侠客行)_第1张图片

图 2

实验3 二维游戏动画合成(侠客行)_第2张图片

图 3

 

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

实验3 二维游戏动画合成(侠客行)_第3张图片

图 4

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

实验3 二维游戏动画合成(侠客行)_第4张图片

图 5

 

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

实验3 二维游戏动画合成(侠客行)_第5张图片

图 6

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

实验3 二维游戏动画合成(侠客行)_第6张图片

图 7

 

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

实验3 二维游戏动画合成(侠客行)_第7张图片

图 8

 

3.完成方案二 (15分)

修改游戏代码,实现方案二,即增加英雄defend动作,记录成功/失败次数,增加计分板功能。

 

如第2部分“完成方案一”中已制作了“防御”defend的动画,接下来只要模仿攻击按钮及动作相关代码写一遍就行。如下为AnimationScene.cpp文件中按钮代码:

图 9

图 10

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

图 11

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

实验3 二维游戏动画合成(侠客行)_第8张图片

图 12

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

 

增加计分板:

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

SavaScore.h

图 13

SavaScore.cpp

图 14

 

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

计分板代码:

实验3 二维游戏动画合成(侠客行)_第9张图片

图 15

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

实验3 二维游戏动画合成(侠客行)_第10张图片

图 16

实验3 二维游戏动画合成(侠客行)_第11张图片

图 17

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

restart代码:

图 18

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

 

4云朵移动

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

图 19

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

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

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

图 20

运行效果图:

实验3 二维游戏动画合成(侠客行)_第12张图片

图 21

 

5BUG修正

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

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

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

 

下面修正上述bug:

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

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

实验3 二维游戏动画合成(侠客行)_第13张图片

图 22

 

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

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

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

实验3 二维游戏动画合成(侠客行)_第14张图片

图 23

  1. Hero.cpp中的play函数:

实验3 二维游戏动画合成(侠客行)_第15张图片

图 24

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

图 25

实验3 二维游戏动画合成(侠客行)_第16张图片

图 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允许被赋值。

修改相关代码:

实验3 二维游戏动画合成(侠客行)_第17张图片

图 27

实验3 二维游戏动画合成(侠客行)_第18张图片

图 28

 

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

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

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

模仿着更改代码:

图 29

实验3 二维游戏动画合成(侠客行)_第19张图片

图 30

 

    同理修改Enemy的代码即可。

 

 

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

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

实验3 二维游戏动画合成(侠客行)_第20张图片

图 31

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

实验3 二维游戏动画合成(侠客行)_第21张图片

图 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为例,关键代码为:

实验3 二维游戏动画合成(侠客行)_第22张图片

图 33

   

 Hero的update函数中:

实验3 二维游戏动画合成(侠客行)_第23张图片

图 34

 

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

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

实验3 二维游戏动画合成(侠客行)_第24张图片

图 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

 

6防御机制

防御机制设定为:

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

值显示

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

 

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

Hero中的update:

实验3 二维游戏动画合成(侠客行)_第25张图片

图 40

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

图 41

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

实验3 二维游戏动画合成(侠客行)_第26张图片

图 42

实验3 二维游戏动画合成(侠客行)_第27张图片

图 43

运行图:

实验3 二维游戏动画合成(侠客行)_第28张图片

图 44

7.AI设计

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

图 45

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

 

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

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

实验3 二维游戏动画合成(侠客行)_第29张图片

图 46

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

 

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

修改后的代码为:

实验3 二维游戏动画合成(侠客行)_第30张图片

图 47

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

实验3 二维游戏动画合成(侠客行)_第31张图片

图 48

AI的随机方案:

图 49

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

8.其余Label显示细节

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

实验3 二维游戏动画合成(侠客行)_第32张图片

图 50

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

实验3 二维游戏动画合成(侠客行)_第33张图片

图 51

③ 场景有部落格特效过渡

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

实验3 二维游戏动画合成(侠客行)_第34张图片

图 52

你可能感兴趣的:(游戏,cocos2dx,学生作业)