cocos2d-x横版格斗教程

2015年01月26日 11:05 0 点赞 0 评论 更新于 2025-11-21 15:29

本文将分享一个cocos2d-x横版格斗游戏教程,涵盖打怪、人物移动等内容。我使用的是最新版cocos2d-x-3.0alpha1,开发环境为win7 + vs2012。这个游戏demo源自网络,网上已有不少相关文章和示例,但大多基于cocos2d-x较早版本实现,本文将使用cocos2d-x 3.0重新实现。cocos2d-x 3.0更新了部分API,并加入了C++ 11特性。我们将学习精灵动作切换与保存、碰撞检测、简单的机器人AI以及3.0新功能的使用等,最终效果如下:

1. 创建项目

首先,切换到tools\project-creator目录下,按住Shift键并右键,打开命令提示符窗口,输入以下命令创建项目:

python create_project.py -p PompaDroid -k com.alexzhou.pompadroid -l cpp

项目目录如下所示:

双击cocos2d-x-3.0\projects\PompaDroid\proj.win32\PompaDroid.sln,即可打开项目。

2. 下载游戏所需资源文件

可从这里下载游戏所需资源文件。使用Tiled打开pd_tilemap.tmx,就能看到游戏的整个地图。

根据地图,我们可以获取以下信息:

  • 地图分为两层:Wall(墙)和Floor(地板),可通过关闭每个层的透明度查看每层的构成。
  • 每个瓦片的大小为32×32,地图瓦片数为100×10(宽×高)。
  • 从下往上数,前三行瓦片是可以行走的。

3. 添加地图

创建GameLayer类,代码如下:

GameLayer.h

class GameLayer : public cocos2d::Layer
{
public:
GameLayer();
~GameLayer();
virtual bool init();
CREATE_FUNC(GameLayer);

private:
cocos2d::TMXTiledMap *m_pTiledMap;
};

GameLayer.cpp

GameLayer::GameLayer()
: m_pTiledMap(NULL)
{
}

GameLayer::~GameLayer()
{
}

bool GameLayer::init()
{
bool ret = false;
do {
CC_BREAK_IF(!Layer::init());
m_pTiledMap = TMXTiledMap::create("pd_tilemap.tmx");
this->addChild(m_pTiledMap, -10);
ret = true;
} while (0);
return ret;
}

这里将z-order的值设置为 -10,也可设置为其他值。由于地图通常显示在最底层,因此添加其他元素时,可将z-order的值设置为大于该值。

接着创建场景类GameScene

GameScene.h

class GameScene
{
public:
static cocos2d::Scene* createScene();
};

GameScene.cpp

Scene* GameScene::createScene()
{
auto scene = Scene::create();
auto gameLayer = GameLayer::create();
scene->addChild(gameLayer, 0);
return scene;
}

然后修改AppDelegate.cppapplicationDidFinishLaunching函数:

bool AppDelegate::applicationDidFinishLaunching() {
// 初始化导演
auto director = Director::getInstance();
auto eglView = EGLView::getInstance();
director->setOpenGLView(eglView);
eglView->setDesignResolutionSize(480, 320, ResolutionPolicy::SHOW_ALL);
// 关闭显示FPS
director->setDisplayStats(false);
// 设置FPS,默认值为1.0/60
director->setAnimationInterval(1.0 / 60);
// 创建场景
auto scene = GameScene::createScene();
// 运行场景
director->runWithScene(scene);
return true;
}

setDesignResolutionSize用于设置资源分辨率尺寸,以适配多个分辨率。由于这里的资源分辨率是480×320,因此在main.cpp中设置宽高为480×320效果最佳。

编译运行项目,即可看到地图已显示出来。

目前,这个cocos2d-x横版格斗游戏的地图上什么都没有,显得过于单调,接下来我们将创建一个英雄。

4. 添加英雄

英雄的资源文件是pd_sprites.pvr.ccz,可使用TexturePacker打开。

英雄有5种状态:

  1. 空闲(站着不动时)
  2. 行走
  3. 攻击
  4. 被攻击
  5. 死亡

每个状态都对应一组动画。由于英雄不可能同时处于多种状态,因此我们需要考虑如何实现状态切换,并播放对应状态的动画。当英雄处于空闲状态时,只能切换到行走、攻击、受伤状态,不会在未受伤的情况下死亡;当英雄死亡后,就不能再切换到其他四种状态。

创建BaseSprite类,作为精灵的基类:

BaseSprite.h

typedef enum {
ACTION_STATE_NONE = 0,
ACTION_STATE_IDLE,
ACTION_STATE_WALK,
ACTION_STATE_ATTACK,
ACTION_STATE_HURT,
ACTION_STATE_DEAD
} ActionState;

class BaseSprite : public cocos2d::Sprite
{
public:
BaseSprite();
~BaseSprite();
void runIdleAction();
void runWalkAction();
void runAttackAction();
void runHurtAction();
void runDeadAction();
CC_SYNTHESIZE_RETAIN(cocos2d::Action*, m_pIdleAction, IdleAction);
CC_SYNTHESIZE_RETAIN(cocos2d::Action*, m_pWalkAction, WalkAction);
CC_SYNTHESIZE_RETAIN(cocos2d::Action*, m_pAttackAction, AttackAction);
CC_SYNTHESIZE_RETAIN(cocos2d::Action*, m_pHurtAction, HurtAction);
CC_SYNTHESIZE_RETAIN(cocos2d::Action*, m_pDeadAction, DeadAction);
CC_SYNTHESIZE(ActionState, m_currActionState, CurrActionState);
cocos2d::CallFunc* createIdleCallbackFunc();

protected:
static cocos2d::Animation* createAnimation(const char* formatStr, int frameCount, int fps);

private:
bool changeState(ActionState actionState);
};

BaseSprite.cpp

BaseSprite::BaseSprite()
: m_pIdleAction(NULL),
m_pWalkAction(NULL),
m_pAttackAction(NULL),
m_pHurtAction(NULL),
m_pDeadAction(NULL),
m_currActionState(ACTION_STATE_NONE)
{
}

BaseSprite::~BaseSprite()
{
CC_SAFE_RELEASE_NULL(m_pIdleAction);
CC_SAFE_RELEASE_NULL(m_pWalkAction);
CC_SAFE_RELEASE_NULL(m_pAttackAction);
CC_SAFE_RELEASE_NULL(m_pHurtAction);
CC_SAFE_RELEASE_NULL(m_pDeadAction);
}

void BaseSprite::runIdleAction()
{
if (changeState(ACTION_STATE_IDLE))
{
this->runAction(m_pIdleAction);
}
}

void BaseSprite::runWalkAction()
{
if (changeState(ACTION_STATE_WALK))
{
this->runAction(m_pWalkAction);
}
}

void BaseSprite::runAttackAction()
{
if (changeState(ACTION_STATE_ATTACK))
{
this->runAction(m_pAttackAction);
}
}

void BaseSprite::runHurtAction()
{
if (changeState(ACTION_STATE_HURT))
{
this->runAction(m_pHurtAction);
}
}

void BaseSprite::runDeadAction()
{
if (changeState(ACTION_STATE_DEAD))
{
this->runAction(m_pDeadAction);
}
}

Animation* BaseSprite::createAnimation(const char* formatStr, int frameCount, int fps)
{
Array *pFrames = Array::createWithCapacity(frameCount);
for (int i = 0; i < frameCount; ++i)
{
const char* imgName = String::createWithFormat(formatStr, i)->getCString();
SpriteFrame *pFrame = SpriteFrameCache::getInstance()->getSpriteFrameByName(imgName);
pFrames->addObject(pFrame);
}
return Animation::createWithSpriteFrames(pFrames, 1.0f / fps);
}

bool BaseSprite::changeState(ActionState actionState)
{
if (m_currActionState == ACTION_STATE_DEAD || m_currActionState == actionState)
{
return false;
}
this->stopAllActions();
this->m_currActionState = actionState;
return true;
}

CallFunc* BaseSprite::createIdleCallbackFunc()
{
return CallFunc::create(CC_CALLBACK_0(BaseSprite::runIdleAction, this));
}

ActionState枚举表示精灵的各种状态,runXXXAction用于切换状态并播放对应状态的动画。m_currActionState变量表示当前精灵的状态。createIdleCallbackFunc函数在后续会用到,用于在精灵切换到攻击或受伤状态后立即回到空闲状态。createAnimation函数是一个工具函数,可根据图片路径、帧数和每秒显示的帧数创建动画。changeState函数用于实现状态切换,当精灵死亡时,游戏结束。

现在创建英雄类Hero

Hero.h

class Hero : public BaseSprite
{
public:
Hero();
~Hero();
bool init();
CREATE_FUNC(Hero);
};

Hero.cpp

Hero::Hero()
{
}

Hero::~Hero()
{
}

bool Hero::init()
{
bool ret = false;
do {
CC_BREAK_IF(!this->initWithSpriteFrameName("hero_idle_00.png"));
Animation *pIdleAnim = this->createAnimation("hero_idle_%02d.png", 6, 12);
this->setIdleAction(RepeatForever::create(Animate::create(pIdleAnim)));
Animation *pWalkAnim = this->createAnimation("hero_walk_%02d.png", 7, 14);
this->setWalkAction(RepeatForever::create(Animate::create(pWalkAnim)));
Animation *pAttackAnim = this->createAnimation("hero_attack_00_%02d.png", 3, 20);
this->setAttackAction(Sequence::create(Animate::create(pAttackAnim), BaseSprite::createIdleCallbackFunc(), NULL));
Animation *pHurtAnim = this->createAnimation("hero_hurt_%02d.png", 3, 12);
this->setHurtAction(Sequence::create(Animate::create(pHurtAnim), BaseSprite::createIdleCallbackFunc(), NULL));
Animation *pDeadAnim = this->createAnimation("hero_knockout_%02d.png", 5, 12);
this->setDeadAction(Sequence::create(Animate::create(pDeadAnim), Blink::create(3, 9), NULL));
ret = true;
} while (0);
return ret;
}

Hero类较为简单,主要用于初始化各种动作。

接下来修改GameLayer类,加载精灵图片所需的资源。

GameLayer.h中添加:

CC_SYNTHESIZE_READONLY(Hero*, m_pHero, Hero);
float m_fScreenWidth;
float m_fScreenHeight;
cocos2d::Point m_origin;
cocos2d::SpriteBatchNode *m_pSpriteNodes;

GameLayer.cppinit函数中添加:

auto visibleSize = Director::getInstance()->getVisibleSize();
this->m_origin = Director::getInstance()->getVisibleOrigin();
this->m_fScreenWidth = visibleSize.width;
this->m_fScreenHeight = visibleSize.height;
SpriteFrameCache::getInstance()->addSpriteFramesWithFile("pd_sprites.plist");
m_pSpriteNodes = SpriteBatchNode::create("pd_sprites.pvr.ccz");
this->addChild(m_pSpriteNodes);
m_pHero = Hero::create();
m_pHero->setPosition(m_origin + Point(100, 100));
m_pHero->runIdleAction();
m_pHero->setZOrder(m_fScreenHeight - m_pHero->getPositionY());
m_pSpriteNodes->addChild(m_pHero);

这里添加了一个英雄,并将其切换到空闲状态,同时设置ZOrder值与Y坐标相关,以便后期解决英雄和怪物的遮挡问题。关于SpriteFrameCacheSpriteBatchNode的详细信息,可查看这里

现在编译运行项目,就能看到一个英雄在屏幕上显示出来。

至此,这个cocos2d-x横版格斗教程就介绍完了。