制作横版游戏KillBear第2课:加入英雄

2015年03月19日 13:08 0 点赞 0 评论 更新于 2025-11-21 17:44

在第1课中,我们介绍了如何添加地图,尚未掌握的读者可以回去复习一下。在这一课中,我们将实现生成一个基础的 Role 类,让 Hero 类继承 Role 类(后续敌人 Enemy 类也会继承该类),最后将其放置到我们的 GameLayer 中。

开发环境

  • Win64 : vs2010
  • Cocos2d-x v3.4Final
  • TexturePackerGUI
  • MapEdit

代码构建

角色 Role

基础类 Role

Role 是一个基础类,所有的角色都将继承该类。由于篇幅限制,下面仅贴出部分代码,并添加详细注释。

.h 文件

typedef struct _BoundingBox
{
Rect actual;
Rect original;
} BoundingBox;

typedef enum {
ACTION_STATE_NONE = 0,
ACTION_STATE_IDLE,
ACTION_STATE_WALK,
// 其他状态可按需添加
ACTION_STATE_DEAD
} ActionState;

// 基础角色类,主角和NPC都需要继承它
// 单纯的控制播放动画,何时播放它在具体的类里面设置
class Role : public Sprite
{
public:
Role(void);
~Role(void);

/* 角色状态设定,初始化角色状态等 */
CC_SYNTHESIZE(std::string, Name, Name); // 角色名称
CC_SYNTHESIZE(float, curtLifevalue, CurtLifeValue); // 角色当前生命值
// 其他属性可按需添加

/* 需要用creatNomalAnimation创建,只有普通动画,有受伤判定,无攻击判定 */
CC_SYNTHESIZE_RETAIN(Action*, m_pidleaction, IdleAction); // 角色空闲时序列
CC_SYNTHESIZE_RETAIN(Action*, m_pwalkaction, WalkAction); // 角色移动时动画帧序列
// 其他动作序列可按需添加

void callBackAction(Node* pSender); // 动画执行完毕的通用回调处理

/* 下面是所有的动画回调函数,用来执行此动画 */
virtual void runIdleAction(); // 执行闲置动画
virtual void runWalkAction(); // 执行移动行走动画
// 其他动画执行函数可按需添加

BoundingBox createBoundingBox(Vec2 origin, Size size);
CC_SYNTHESIZE(BoundingBox, m_bodyBox, BodyBox); // 身体碰撞盒子
CC_SYNTHESIZE(BoundingBox, m_hitBox, HitBox); // 攻击碰撞盒子

virtual void setPosition(const Vec2 &position);
void updateBoxes();

protected:
static Animation* createNomalAnimation(const char* formatStr, int frameCount, int fps);
static Animation* createAttackAnimation(const char* formatStr, int frameCountBegan, int frameCountEnd, int fps);

private:
bool changeState(ActionState actionState);
ActionState proActionState;
};

CC_SYNTHESIZE(float, curtLifevalue, CurtLifeValue); 是一个宏定义。我们可以通过 target->getCurtLifeValue() 获得 targetcurtLifevalue,也可以通过 set 方法改变 curtLifevalue 的值。curtLifevalueRole 内部的一个私有成员。其他宏的功能类似。

.cpp 文件

// 各种动作
void Role::runIdleAction()
{
// 通过changeState判断是否可以变更状态,之后执行动作
if (changeState(ACTION_STATE_IDLE))
{
this->runAction(m_pidleaction);
}
}

// 其他动作函数可按需添加

Animation* Role::createAttackAnimation(const char* formatStr, int frameCountBegan, int frameCountEnd, int fps)
{
// 通过frameCountBegan, frameCountEnd从SpriteFrameCache中创建一个动作
Vector<SpriteFrame*> pFrames;
for (int i = frameCountBegan; i < frameCountEnd; i++)
{
const char* imgName = String::createWithFormat(formatStr, i)->getCString();
SpriteFrame *pFrame = SpriteFrameCache::getInstance()->getSpriteFrameByName(imgName);
pFrames.insert(i - frameCountBegan, pFrame);
}
return Animation::createWithSpriteFrames(pFrames, 1.0f / fps);
}

// 其他动画创建函数可按需添加

bool Role::changeState(ActionState actionState)
{
// 允许改变状态,死了和状态不变不会再接受其他状态改变指令
if (proActionState == ACTION_STATE_DEAD || proActionState == actionState)
{
return false;
}
this->stopAllActions();
this->proActionState = actionState;
return true;
}

// 其他状态改变函数可按需添加

BoundingBox Role::createBoundingBox(Vec2 origin, Size size)
{
// 创建一个BoundingBox,用于设置身体碰撞和攻击碰撞检测
BoundingBox boundingBox;
boundingBox.original.origin = origin;
boundingBox.original.size = size;
boundingBox.actual.origin = this->getPosition() + boundingBox.original.origin;
boundingBox.actual.size = size;
return boundingBox;
}

void Role::updateBoxes()
{
// 通过方向刷新盒子位置
bool isFlippedX = this->isFlippedX();
float x = 0.0f;
if (isFlippedX)
{
x = this->getPosition().x - m_hitBox.original.origin.x;
}
else
{
x = this->getPosition().x + m_hitBox.original.origin.x;
}
m_hitBox.actual.origin = Vec2(x, this->getPosition().y + m_hitBox.original.origin.y);
m_bodyBox.actual.origin = this->getPosition() + m_bodyBox.original.origin;
}

void Role::setPosition(const Vec2 &position)
{
// 每次重新设定坐标的时候刷新盒子
Sprite::setPosition(position);
this->updateBoxes();
}

英雄 Hero

主角类

代码较长,下面重点关注注释。.h 文件中只有 init 方法,这里不再赘述。

.cpp 文件

this->initWithSpriteFrameName("boy_idle_00.png");
// 站立时播放动画
Animation *idleAnim = this->createNomalAnimation("boy_idle_d.png", 3, 5);
this->setIdleAction(RepeatForever::create(Animate::create(idleAnim)));

// 其他动画设置可按需添加

// 普通攻击A,分出招和收招,期间夹杂攻击判定。自己可以通过调节fps控制出招速度之类的
Animation *attackAnima1 = this->createAttackAnimation("boy_attack_00_d.png", 0, 4, 10);
Animation *attackAnima2 = this->createAttackAnimation("boy_attack_00_d.png", 4, 8, 15);
this->setNomalAttackA(Sequence::create(
Animate::create(attackAnima1),
// CallFuncN::create(CC_CALLBACK_1(Hero::attackCallBackAction, this)),
Animate::create(attackAnima2),
Role::createIdleCallbackFunc(),
NULL));

// 其他攻击动画设置可按需添加

// 碰撞盒子设定
this->m_bodyBox = this->createBoundingBox(Vec2(0, 30), Size(30, 60));
this->m_hitBox = this->createBoundingBox(Vec2(35, 50), Size(80, 90));

游戏 Game

GameLayer

.cpp 文件

init 方法中:

this->addHero();

实现方法如下:

void GameLayer::addHero()
{
m_pHero = Hero::create();
m_pHero->setPosition(_visibleOrigin + Vec2(100, 50));
m_pHero->runIdleAction();
m_pHero->setLocalZOrder(_visibleSize.height - m_pHero->getPositionY());
this->addChild(m_pHero);
}

效果

至此,我们的第二步已经完成。运行程序后,可以看到在窗口中有一个英雄站立在那里(帧数不同可能是录制问题)。

结语

基础的 Role 类一次性构建完成,后续可直接调用。后续我们将通过其他方式深入理解 BoundingBox。下一篇文章,我们将实现摇杆的添加和对 Hero 的控制。

作者信息

boke

boke

共发布了 3994 篇文章