解说Cocos2d-x 自定义按钮类控制精灵攻击—-之游戏开发《赵云要格斗》

2015年03月16日 13:34 0 点赞 0 评论 更新于 2025-11-21 17:18

在《赵云要格斗》这款游戏开发中,我们之前探讨了Cocos2d-x虚拟摇杆控制精灵上下左右运动。若你需要素材和项目代码,可留下邮箱,由于项目还在持续改进中,后续会不断完善。

在游戏里,精灵的攻击表现为一个动画,且该动画仅播放一次。这类似于在界面添加一个按钮,玩家点击一次按钮,精灵就播放一次攻击动画。

开发环境信息

  • Cocos2d-x版本:2.2.5
  • 工程环境:Windows 7 + VS2010

文章目录

  1. 自定义按钮类
  2. 精灵攻击动画和结束判断
  3. 自定义按钮控制精灵

一、自定义按钮类

虽然COCOS2D - X自带了按钮控件,但为了方便开发,我们自行封装了一个按钮类 ControlButton。在这个类中,我们添加了一个按钮控件变量 CCControlButton* controlBtn,并为其添加相应的回调事件,以此实现自定义按钮类。

以下是 ControlButton.h 文件的代码:

#ifndef __ControlButton_H__
#define __ControlButton_H__

#include "cocos2d.h"
#include "cocos-ext.h"

USING_NS_CC;
USING_NS_CC_EXT;

// 用于标识当前按钮的状态
typedef enum {
touch_begin,
touch_down,
touch_up,
} tagForTouch;

class ControlButton : public CCNode {
public:
ControlButton();
~ControlButton();
CREATE_FUNC(ControlButton);

// 创建按钮,其中name_png为按钮的背景图片,button_title为按钮图片上要显示的文字,num为文字的透明度0 - 100,0为透明
void CreateButton(const char* name_png, const char* button_title = "0", unsigned int num = 0);

// 绑定按钮事件
void BindButtonEven();

// 当鼠标处于按下并曾经点中按钮时,则触发一次
void touchDownAction(CCObject* pSender, CCControlEvent event);

// 当鼠标处于按下并曾经点中按钮的状态下,鼠标进入按钮范围,则触发一次
void touchDragEnter(CCObject* pSender, CCControlEvent event);

// 当鼠标处于按下并曾经点中按钮的状态下,鼠标离开按钮范围,则触发一次
void touchDragExit(CCObject* pSender, CCControlEvent event);

// 当鼠标处于按下并曾经点中按钮的状态下,鼠标进入按钮范围,则触发,只要达到条件,就不断触发
void touchDragInside(CCObject* pSender, CCControlEvent event);

// 当鼠标处于按下并曾经点中按钮的状态下,鼠标离开按钮范围,则触发,只要达到条件,就不断触发
void touchDragOutside(CCObject* pSender, CCControlEvent event);

// 当鼠标处于按下并曾经点中按钮的状态下,鼠标松开且在按钮范围内,则触发一次
void touchUpInside(CCObject* pSender, CCControlEvent event);

// 当鼠标处于按下并曾经点中按钮的状态下,鼠标松开且在按钮范围外,则触发一次
void touchUpOutside(CCObject* pSender, CCControlEvent event);

// 暂时没有发现能用鼠标触发这个事件的操作,看了注释,应该是由其它事件中断按钮事件而触发的
void touchCancel(CCObject* pSender, CCControlEvent event);

// 是否按下按钮
bool isTouch;

private:
// 按钮控件变量
CCControlButton* controlBtn;
};

#endif

以下是 ControlButton.cpp 文件的代码:

#include "ControlButton.h"

ControlButton::ControlButton() : controlBtn(NULL), isTouch(false) {}

ControlButton::~ControlButton() {}

void ControlButton::CreateButton(const char* name_png, const char* button_title, unsigned int num) {
// 得到按钮图片的大小
CCScale9Sprite* btn = CCScale9Sprite::create(name_png);
CCLOG("%f", btn->getContentSize().width);
CCLOG("%f", btn->getContentSize().height);
int png_height = static_cast<int>(btn->getContentSize().height);
int png_width = static_cast<int>(btn->getContentSize().width);
btn->release();

// 要显示的图片大小
CCRect rect = CCRectMake(0, 0, png_width, png_height); // 图片的大小
CCRect rectInsets = CCRectMake(1, 1, 1, 1); // left,right,width,height

// 按钮标题,Marker Felt为字体类型,png_height为字体高度
CCLabelTTF *title = CCLabelTTF::create(button_title, "Marker Felt", png_height - 10);
title->setOpacity(num); // 设置可见度

// 正常状态下的按钮图片
CCScale9Sprite *btnNormal = CCScale9Sprite::create(name_png, rect, rectInsets);

// 创建按钮
controlBtn = CCControlButton::create(title, btnNormal);
this->addChild(controlBtn);

// 绑定事件
BindButtonEven();
}

void ControlButton::BindButtonEven() {
if (!controlBtn)
return;

controlBtn->addTargetWithActionForControlEvents(this, cccontrol_selector(ControlButton::touchDownAction), CCControlEventTouchDown);
controlBtn->addTargetWithActionForControlEvents(this, cccontrol_selector(ControlButton::touchDragEnter), CCControlEventTouchDragEnter);
controlBtn->addTargetWithActionForControlEvents(this, cccontrol_selector(ControlButton::touchDragExit), CCControlEventTouchDragExit);
controlBtn->addTargetWithActionForControlEvents(this, cccontrol_selector(ControlButton::touchDragInside), CCControlEventTouchDragInside);
controlBtn->addTargetWithActionForControlEvents(this, cccontrol_selector(ControlButton::touchDragOutside), CCControlEventTouchDragOutside);
controlBtn->addTargetWithActionForControlEvents(this, cccontrol_selector(ControlButton::touchUpInside), CCControlEventTouchUpInside);
controlBtn->addTargetWithActionForControlEvents(this, cccontrol_selector(ControlButton::touchUpOutside), CCControlEventTouchUpOutside);
controlBtn->addTargetWithActionForControlEvents(this, cccontrol_selector(ControlButton::touchCancel), CCControlEventTouchCancel);
}

// 当鼠标处于按下并曾经点中按钮时,则触发一次
void ControlButton::touchDownAction(CCObject* pSender, CCControlEvent event) {
isTouch = true;
}

// 当鼠标处于按下并曾经点中按钮的状态下,鼠标进入按钮范围,则触发一次
void ControlButton::touchDragEnter(CCObject* pSender, CCControlEvent event) {}

// 当鼠标处于按下并曾经点中按钮的状态下,鼠标离开按钮范围,则触发一次
void ControlButton::touchDragExit(CCObject* pSender, CCControlEvent event) {}

// 当鼠标处于按下并曾经点中按钮的状态下,鼠标进入按钮范围,则触发,只要达到条件,就不断触发
void ControlButton::touchDragInside(CCObject* pSender, CCControlEvent event) {}

// 当鼠标处于按下并曾经点中按钮的状态下,鼠标离开按钮范围,则触发,只要达到条件,就不断触发
void ControlButton::touchDragOutside(CCObject* pSender, CCControlEvent event) {}

// 当鼠标处于按下并曾经点中按钮的状态下,鼠标松开且在按钮范围内,则触发一次
void ControlButton::touchUpInside(CCObject* pSender, CCControlEvent event) {
isTouch = false;
}

// 当鼠标处于按下并曾经点中按钮的状态下,鼠标松开且在按钮范围外,则触发一次
void ControlButton::touchUpOutside(CCObject* pSender, CCControlEvent event) {}

// 暂时没有发现能用鼠标触发这个事件的操作,看了注释,应该是由其它事件中断按钮事件而触发的
void ControlButton::touchCancel(CCObject* pSender, CCControlEvent event) {}

使用方法

在需要使用该按钮类的地方,添加头文件 #include "ControlButton.h",并定义成员变量 ControlButton* btn。在 bool HelloWorld::init() 函数里添加以下代码:

// 添加攻击按钮
btn = ControlButton::create();
btn->CreateButton("bt.png");
btn->setPosition(ccp(visibleSize.width - 50, 50));
this->addChild(btn, 2);

二、精灵攻击动画和结束判断

有了按钮之后,接下来要考虑如何控制精灵攻击的动画。精灵攻击动画的逻辑是:按下按钮,精灵播放一次攻击动画。但需要注意的是,如果玩家连续快速点击按钮,精灵的动作会显得不真实。因此,每次按下按钮前,需要先判断上次的攻击动画是否结束。若动画已结束且玩家按下按钮,则播放攻击动画;若上次动画未结束,则不再播放。

为了实现这个逻辑,我们在 Hero.h(上篇中定义)里定义了一个 bool IsAttack 成员变量,默认值为 false。当精灵进行攻击时,将其设为 true,在攻击动画结束后再将其设为 false。同时,增加两个函数:

// 攻击动画
void AttackAnimation(const char *name_plist, const char *name_png, const char *name_each, const unsigned int num, bool run_directon);

// 攻击动画结束
void AttackEnd();

以下是这两个函数的实现代码:

void Hero::AttackAnimation(const char *name_plist, const char *name_png, const char *name_each, const unsigned int num, bool run_directon) {
if (IsAttack)
return;

// 将图片加载到精灵帧缓存池
m_frameCache = CCSpriteFrameCache::sharedSpriteFrameCache();
m_frameCache->addSpriteFramesWithFile(name_plist, name_png);

CCArray* frameArray = CCArray::createWithCapacity(num);
for (unsigned int i = 1; i <= num; i++) {
CCSpriteFrame* frame = m_frameCache->spriteFrameByName(CCString::createWithFormat("%s%d.png", name_each, i)->getCString());
frameArray->addObject(frame);
}

// 使用列表创建动画对象
CCAnimation* animation = CCAnimation::createWithSpriteFrames(frameArray);

if (HeroDirecton != run_directon) {
HeroDirecton = run_directon;
}

animation->setLoops(1); // 表示循环播放1次
animation->setDelayPerUnit(0.1f); // 每两张图片的时间间隔,图片数目越少,间隔最小就越小

// 将动画包装成一个动作
CCAnimate* act = CCAnimate::create(animation);

// 创建回调动作,攻击结束后调用AttackEnd()
CCCallFunc* callFunc = CCCallFunc::create(this, callfunc_selector(Hero::AttackEnd));

// 创建连续动作
CCActionInterval* attackact = CCSequence::create(act, callFunc, NULL);

IsAttack = true;
m_HeroSprite->runAction(attackact);
}

void Hero::AttackEnd() {
// 恢复精灵原来的初始化贴图
this->removeChild(m_HeroSprite, TRUE); // 把原来的精灵删除掉
m_HeroSprite = CCSprite::create(Hero_name); // 恢复精灵原来的贴图样子
m_HeroSprite->setFlipX(HeroDirecton);
this->addChild(m_HeroSprite);

IsAttack = false;
}

创建连续动作是本次的关键点,每次攻击动画 AttackAnimation(...) 结束后,会调用 AttackEnd() 函数,这样我们就能判断精灵是否正在进行攻击动画。

三、自定义按钮控制精灵

在前面我们已经添加了 ControlButton* btn 按钮控件变量。只需在 void HelloWorld::update(float delta)(可看上一篇)函数中增加以下代码:

if (btn->isTouch) {
hero->AttackAnimation("attack1_animation.plist", "attack1_animation.png", "attack_", 6, rocker->rocketRun);
}

这里的 attack_ 表示图片中的公共名称部分,即 attack1_animation.png 中有 6 张图片,命名分别为 attack_1.pngattack_2.png …… attack_6.png

效果说明

运行程序后,我们可以看到精灵的攻击效果。由于攻击动画的图片大小不一致,精灵攻击时的位置和原来的位置会有一些移动。这可以通过调整图片来解决,但由于图片资源获取困难,目前先将就使用,后续找到合适的图片后再进行替换。

作者信息

menghao

menghao

共发布了 3994 篇文章