目录
Bug4:第二回合开始时,按下攻击按钮英雄不执行攻击动作了。
Bug5:连续点击攻击按钮时,攻击动作没有执行完毕就开始执行新的攻击动作。
Bug6:英雄和敌人的攻击击中对方时,攻击动画未结束就进行下一个动作。
Bug9:攻击动作的碰撞检测出现问题,有时候会出现没有打到对方但掉血的现象
前言
这门课的所有实验都挺简单的,但是我估摸着混了个A+,不写白不写,本次实验可能会有我没有发现的错误或者仍需完善的内容,但整体上是没有问题的,得分也不低。
一、实验目的与要求
1.了解二维游戏动画合成原理。
2.熟悉Cocos2d-x中的用户交互、触摸事件、碰撞检测机制。
3.熟悉CocoStudio动画编辑器的使用,了解骨骼动画。
二、实验内容与方法
1.完成游戏编译
成功编译并运行教材P128“游戏动画实例-侠客行”。
2.增加计分板功能和回合制
记录成功/失败次数,增加计分板功能;将游戏改为回合制游戏。
3.增加英雄Defend动作
利用CocoStudio设计英雄Defend动作,并将此功能在游戏中加载。
4.修改游戏bug
修改游戏中出现的明显bug。
5. 游戏优化升级
自行发挥想象力,优化游戏功能。
三、实验步骤与过程
1. 完成游戏编译
创建游戏项目,将窗口大小改为960x640,详细步骤见我的实验1的文章。
2. 增加计分板功能,并将游戏改为回合制游戏
(1)在AnimationScene.h中新声明两个变量。分别记录英雄和敌人赢的回合数。
//得分
int goal_hero=0;
int goal_enemy=0;
(2)在AnimationScene.cpp的init函数中,使用createWithBMFont函数创建Label的两个实例,分别用于显示第几回合和比分信息,回合数的值等于英雄赢的回合数goal_hero与敌人赢的回合数goal_enemy的和再加上1,用Value的asString函数将整数转化为字符串显示。
//背景文字round
auto tips_round = Label::createWithBMFont("fonts/futura-48.fnt",
"Round "+Value(goal_hero+ goal_enemy+1).asString());
tips_round->setPosition(Vec2(visibleSize.width / 2, visibleSize.height - 50));
this->addChild(tips_round, 5);
// 背景文字VS和比分
auto tips_vs = Label::createWithBMFont("fonts/futura-48.fnt",
Value(goal_hero).asString()+" VS "+ Value(goal_enemy).asString());
tips_vs->setPosition(Vec2(visibleSize.width/2,visibleSize.height - 120));
this->addChild(tips_vs, 5);
(3)死亡动画播放需要时间,需要等待其播放结束才能进入下一回合,因此我添加了一个计时器,角色死亡3s后,才开始下一回合。先在AnimationScene.h中声明开始计时的标志is_countdown、时间countdown_time变量,以及计时函数countdown。
//计时器
bool is_countdown = false;
void countdown(float dt);
int countdown_time = 0;
(4)计时函数简单地让countdown_time自增1即可。
//计时
void AnimationScene::countdown(float dt)
{
countdown_time++;
}
(5)定义nextRound函数,用于开始下一回合。在该函数中,先判断是否有角色死亡,如果有角色死亡,则根据is_countdown标志判断是否增加计时器,定义一个schedule,每一秒更新一次,目标函数countdown,这样countdown_time就会每秒增加1了。
如果经过3秒,则调用英雄和敌人的isDeath函数判断哪方得分,然后关闭并重置计时,将其在下一回合能够重新使用,最后调用removeAllChildren函数将所有子节点都移除,并重新调用init函数初始化游戏界面,这样就进入了下一回合。
//进入下一回合
void AnimationScene::nextRound()
{
if (m_player->isDeath()|| m_enemy->isDeath())
{
if (!is_countdown)
{
//计时器计时3s
this->schedule(schedule_selector(AnimationScene::countdown), 1.0f);
is_countdown = true;
}
if (countdown_time == 3)
{
//得分
if(m_player->isDeath())
goal_enemy++;
else if (m_enemy->isDeath())
{
goal_hero++;
}
//重置计时器
is_countdown = false;
countdown_time = 0;
this->unschedule(schedule_selector(AnimationScene::countdown));
//移除所有结点,重新初始化
this->removeAllChildren();
this->init();
}
}
}
(6)尝试运行程序,上方区域能够显示当前为第几回合和比分的信息,当角色死亡后,死亡动画播放完毕才会进入下一回合。
3. 设计英雄Defend动作动画
(1)在cocostudio的Animation Editor中新建一个项目,然后将英雄各个部位的图片素材添加到Resources文件夹中。向画布中添加图片素材,为每个身体部位添加骨骼,并且将骨骼和对应的身体部位绑定,接着绑定骨骼之间的父子关系。
(2)在对象结构窗口中可以调整图层的覆盖关系,调整身体部位的图层并拼接好每一部分。
(3)切换到动画模式,在该项目中添加一个动画defend,在动画帧窗口中添加关键帧,设计防御动作。
(4)防御动作如下图所示,前5帧从loading状态到防御状态,防御状态保持25帧,最后5帧从防御状态恢复到loading状态。
(5)在最后一帧的关键帧中添加帧事件“defend_end”,这样在cocos2d-x中可以监听该事件,从而判断防御动画是否结束。
(6)导出动画,并将导出的文件添加到“侠客行”项目的Resources文件夹中。
4. 将动画添加到游戏中
(1)在Hero.cpp的init函数中,通过ArmatureManager的addArmatureFileInfo加载导出的动画的json文件,然后创建Armature的实例,这样动画就被封装到一个Armature对象中了。
播放的第一个动画是待机动画“loading”,接着调用了setFrameEventCallFunc函数添加帧动画监听器。
ArmatureDataManager::getInstance()->addArmatureFileInfo("Chapter06/AnimationScene/animation/Hero/Hero.ExportJson");
m_armature = Armature::create("Hero");
if (m_armature == NULL)
{
CCLOG("hero load error!");
return false;
}
m_armature->setPosition(Vec2::ZERO);
m_armature->getAnimation()->play("loading");
m_armature->getAnimation()->setFrameEventCallFunc(CC_CALLBACK_0(Hero::onFrameEvent, this,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
this->addChild(m_armature);
this->setPosition(position);
(2)新创建的动画和以前的动画人物位置不一样,因此要重设动画在场景中的位置。
// hero
m_player = Hero::create(Vec2(visibleSize.width/4, 20));
this->addChild(m_player, 3);
(3)在config_set.h的变量State中添加一项值“DEFEND”,表示角色处于防御状态,接着在Hero.h中声明两个布尔型变量m_isdefend和m_isdefend_end,还定义了isDefend_end函数获取m_isdefend_end变量的值。
enum State
{
STAND,
MOVELEFT,
MOVERIGHT,
ATTACK,
DEATH,
SMITTEN,
DEFEND
};
bool m_isdefend;//防御
bool m_isdefend_end;
bool isDefend_end() { return m_isdefend_end; }
(4)在Hero类的init函数中添加一个执行防御动作的按钮,通过Create函数创建Button的实例,然后为其添加触摸事件监听器,当触摸事件结束时,调用Hero类的play函数,传入参数“DEFEND”,最后为按钮设置位置并添加到场景中。
//防御
Button* Defendbutton = Button::create("Chapter06/AnimationScene/animation/control/d-btn.png",
"Chapter06/AnimationScene/animation/control/d-btn.png");
Defendbutton->addTouchEventListener([&](Ref* sender, Widget::TouchEventType type)
{
switch (type)
{
case cocos2d::ui::Widget::TouchEventType::BEGAN:
break;
case cocos2d::ui::Widget::TouchEventType::MOVED:
break;
case cocos2d::ui::Widget::TouchEventType::ENDED:
m_player->play(DEFEND);
break;
}
});
Defendbutton->setScale(1.3);
Defendbutton->setPosition(Vec2(visibleSize.width - 180, 100));
this->addChild(Defendbutton,5);
(5)修改Hero类的play函数,当传入参数为“DEFEND”且m_isdefend_end为false时,才执行如下图所示的语句,变量m_isdefend值改为true,表示可以播放防御动画。
变量m_isdefend_end用于表示防御动画是否结束,因为上一次的防御动画进行时不能开始新的防御动画,m_isdefend_end值为false时表示防御动画播放完毕。
void Hero::play(State state)
{
if (state == SMITTEN) // 控制被击中时颤抖动画只播放一次
{
m_ishurt = true;
}
//上一次的攻击动画结束后才开始新的动画
else if (state == ATTACK&& m_isAttack_end=&