制作横版游戏KillBear第2课:加入英雄
在第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() 获得 target 的 curtLifevalue,也可以通过 set 方法改变 curtLifevalue 的值。curtLifevalue 是 Role 内部的一个私有成员。其他宏的功能类似。
.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 的控制。