转自:http://philon.cn/post/cocos2d-x-3.0-zhi-zuo-heng-ban-ge-dou-you-xi

cocos2d-x: v3.0-alpha-pre
Windows环境: Windows8 + Visual Studio 2012
Linux环境: Ubuntu12.04 + gcc 4.7.2
Android环境: Android Studio v0.1 + Mi2 + MIUIv5

老早知道cocos2d-x出3.0的预览版了,据说变化很大(更牛x了),但对于我这个初学者而言依然努力保持一颗蛋定的心,目前手上拥有的是2.0.3的版本并已经完成了PompaDroid(在英文教程中名为《PompaDroid》不过网上有人发布了的应用叫旋风小子)的开发和适配工作,而那也是一个月之前的事了。。。。今天脑子一热跑去cocos2d-x的官网上了解了一下3.0alpha版的内容,果然很牛逼,于是果断下载并彻头彻尾的改造了手上的《PompaDroid》代码,一来为了熟悉cocos2d-x3.0,二来巩固一下之前的学习并做个学习笔记,温故而知新嘛~

先来看个之前做出来的效果:

闲聊一下,其实这次3.0版本吸引我的地方除了各种性能提升的传说之外,主要有两点:

  • 取消了各种类、宏的CC前缀,例如CCSprite变为了Sprite,老实说我很不喜欢之前那种类似匈牙利的命名方式,总觉得既然有cocos2d这个namespace了干嘛还要画蛇添足的在每个类前面加个CC呢?我不知道是不是cocos2d-iPhone的传统(懒得研究),但真心不喜欢这个CC前缀。

  • 加入了2.5D的支持,这个纯属个人喜好,因为在我脑子了已经勾勒出了一两个游戏,而且都是2.5d才能更好的表达游戏的效果(3d人物+2d场景的效果很炫,目前正苦逼的练习手绘+photoshop)

扯远了,进入正题,横版格斗游戏《PompaDroid》的制作过程

新建工程

使用create-multi-platform-projects.py

纳尼!!!之前的install-templates-msvc.bat呢,没有模版怎么在vs里新建工程??查阅了一番文档后发现其实从2.1.4开始,所有cocos2dx项目都用create-multi-platform-projects.py脚本来创建了(唉,落伍啊)。于是乎:

$ python create-multi-platform-projects.py -p PompaDroid -k cn.philon.pompadroid -l cpp

完成之后得到这样一个目录:

目录 说明
Classes 用于存放游戏的各种类各种实现各种算法(总之我想说的是.h和.cpp)
proj.xxx 即是各个平台的相应工程项目
Resources 游戏中所用到的资源全在这里

Wonderful!!我很喜欢这样的目录结构,简单明了。

接下来先从Windows平台开始吧,进入proj.win32并打开PompaDroid.sln

在解决方案中有6个项目:

项目 说明
PompaDroid 很明显,需要我们实现的游戏项目
libBox2D 物理引擎(这里用不到,不管它)
libchipmunk 貌似和box2d一样是物理引擎
libcocos2d 这个就不用解释了
libCocosDenshion 音频引擎(我没在游戏里加入音效)
libExtensions 扩展库,具体参考这里

依照惯例,cocos2dx新工程里会有个HelloWorld实例,展开cyclone-kid\Classes就可以看到了,果断编译运行,一切ok!

好了,删除HelloWorld.h和HelloWorld.cpp文件,开始实现CycloneKid的代码吧!

首先点击这里下载所需的美术资源,解压放到Resources目录下。

GameScene

一款游戏中会有很多个场景(Scene):主菜单、游戏、游戏结束、过场动画等等,由于只是学习,我仅做了主游戏场景(GameScene),该场景包涵两个图层(Layer):游戏层和操作层。

顾名思义,游戏层主要负责游戏的内容(地图的渲染,各个精灵的调度等);而操作层负责响应触摸(前进、后退、攻击等操作)。

因此根据上图所示,我们需要建立三个类,GameScene GameLayer OptionLayer

务必把类文件建立在Classes目录下,方便之后夸平台编译

GameScene.h

class GameScene : public cocos2d::Scene{public:    GameScene();    ~GameScene();    virtual bool init();    CREATE_FUNC(GameScene);    CC_SYNTHESIZE(GameLayer*, _gameLayer, GameLayer);    CC_SYNTHESIZE(OptionLayer*, _optionLayer, OptionLayer);};

GameLayer.h

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

OptionLayer.h

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

1. GameLayer

GameLayer主要有背景地图(map)主角(hero)机器人若干(robots)人组成。

1.1 瓦片地图

Resources/pd_tilemap.tmx便是背景地图资源,tile即瓦片的意思,整个背景地图有若干个32*32像素的图片组成,像瓦片一样,cocos2dx是支持.tmx文件的,因此在GameLayer.h文件中声明一个私有变量_map,用于游戏的背景地图。

GameLayer.h

private:    cocos2d::TMXTileMap *_map;

新建GameLayer.cpp文件,实现游戏层

GameLayer.cpp

using namespace cocos2d;GameLayer::GameLayer(){}GameLayer::~GameLayer(){}bool GameLayer::init(){    bool ret = false;    do {        CC_BREAK_IF(!Layer::init());        _map = TMXTiledMap::create("pd_tilemap.tmx");        this->addChild(_map);        ret = true;    } while(0);    return ret;}

游戏层已经有地图了,现在只差把游戏层添加到场景中,新建GameScene.cpp,实现游戏场景的内容:

GameScene.cpp

GameScene::GameScene(){}GameScene::~GameScene(){}bool GameScene::init(){    bool ret = false;    do {        CC_BREAK_IF(!Scene::init());        // 游戏层初始化        _gameLayer = GameLayer::create();        this->addChild(_gameLayer, 0);        ret = true;    } while (0);    return ret;}

编译并运行一下!

在编译中我的环境会出现两个警告:

  • 现默认库“LIBCMT”与其他库的使用冲突;请使用 /NODEFAULTLIB:library 在属性-->配置属性-->链接器-->输入-->忽略特定库:增加LIBCMT即可;
  • 忽略“/EDITANDCONTINUE”(由于“/SAFESEH”规范) 在属性-->配置属性-->链接器-->高级-->映像具有安全异常处理程序:改为“否”即可。

1.2 动作精灵

主角和机器人的资源包涵在pd_sprites.plistpd_sprites.pvr.ccz当中,打开pd_sprites.plist文件可以看到很多hero_xx_xx.pngrobot_xx_xx.png,这些便是主角英雄和机器人的动作序列图片。从plist文件中可以清楚的看到,不论是“hero”还是“robot”都具备了idle空闲、attack攻击、walk行走、knockout(被)击倒、hurt受伤这五个动作,因此需要建立一个动作类ActionSprite来统一实现动作播放。

如上图所示,我们需要新建三个类ActionSpriteHeroRobot,其中ActionSprite既然是“动作精灵”所以需要继承cocos2d的Sprite类。

原本我想ActionSprite类只负责调用精灵每个动作的动画,精灵的“攻击力”、“生命值”、“移动”等属性方法放到另一个新的类当中管理,但为了省事我还是决定把这些内容完全塞到ActionSprite当中(尽管我知道这是一种灾难,但是。。。让bug来得更猛烈些吧!谁让我懒呢)。

首先,新建三个类ActionSpriteHeroRobot

ActionSprite.h

// 根据pd_sprites.plist得到,动作精灵有五种状态typedef enum {    ACTION_STATE_NONE = 0,    ACTION_STATE_IDLE,    ACTION_STATE_WALK,    ACTION_STATE_ATTACK,    ACTION_STATE_HURT,    ACTION_STATE_KNOCKOUT,} ActionState;class ActionSprite : public cocos2d::Sprite{public:    ActionSprite();    ~ActionSprite();    void idle();    void walk(cocos2d::Point direction);    void attack();    void hurt(int damage);    void knockout();    // 定义每个状态动作的get/set方法    CC_SYNTHESIZE_RETAIN(cocos2d::Action*, _idleAction, IdleAction);    CC_SYNTHESIZE_RETAIN(cocos2d::Action*, _attackAction, AttackAction);    CC_SYNTHESIZE_RETAIN(cocos2d::Action*, _hurtAction, HurtAction);    CC_SYNTHESIZE_RETAIN(cocos2d::Action*, _knockoutAction, KnockoutAction);    CC_SYNTHESIZE_RETAIN(cocos2d::Action*, _walkAction, WalkAction);    // 精灵的当前状态    CC_SYNTHESIZE(ActionState, _currentState, ActionState);    CC_SYNTHESIZE(float, _velocity, Velocity); // 移动速度    CC_SYNTHESIZE(cocos2d::Point, _direction, Direction); // 移动方向(向量)    CC_SYNTHESIZE(unsigned int, _hp, HP); // 生命值    CC_SYNTHESIZE(unsigned int, _atk, ATK); // 攻击力protected:    // 定义一个创建状态动画的方法    // fmt   - 状态的图片名格式(查看pd_sprites.plist,每种状态格式都类似hero_idle_xx.png)    // count - 状态图片序列的数量    // fps   - 动画的播放帧率    static cocos2d::Animation *createAnimation(const char *fmt, int count, float fps);private:    // 切换演员的当前状态    bool _changeState(ActionState state);};

然后是具体ActionSprite的实现,要注意动作切换时的基本逻辑关系,例如某个角色已经挂了,就不能在执行其他动作了!还有一点要非常小心,尤其是用惯了cocos2d-x以前的版本,SpriteFrameCache::sharedSpriteFrameCache这个函数已经不能用了(尽管编译时能通过)如果沿用以前的代码,会有大麻烦!!所以用SpriteFrameCache::getInstance()代替。ccp这个坐标宏也不能用了,我现在用Point(x, y)代替。

  • ActionSprite.cpp ```cpp ActionSprite::ActionSprite() { _idleAction = NULL; _walkAction = NULL; _attackAction = NULL; _hurtAction = NULL; _knockoutAction = NULL; }

ActionSprite::~ActionSprite()
{}

void ActionSprite::idle()
{
if (_changeState(ACTION_STATE_IDLE)) {
runAction(_idleAction);
_velocity = Point(0, 0);
}
}

void ActionSprite::attack()
{
if (_changeState(ACTION_STATE_ATTACK)) {
runAction(_attackAction);
}

}

void ActionSprite::walk(Point direction)
{
if (_changeState(ACTION_STATE_WALK)) {
runAction(_walkAction);
_direction = direction;
// 根据精灵的x向量,来判断精灵的正面“朝向”
_direction.x > 0 ? setFlipX(true) : setFlipX(false);
}
}

void ActionSprite::hurt(int damage)
{
if (_changeState(ACTION_STATE_HURT)) {
runAction(_hurtAction);
_hp -= damage;
if (_hp <= 0) {
knockout();
}
}
}

void ActionSprite::knockout()
{
if (_changeState(ACTION_STATE_KNOCKOUT)) {
runAction(_knockoutAction);
}
}

bool ActionSprite::_changeState(ActionState state)
{
bool ret = false;

// 精灵已经被击倒(Game Over),就不能再出发其他动作了!if (_currentState == ACTION_STATE_KNOCKOUT) {    goto change_state_failed;}// 精灵已经处于要改变的状态,就没必要在改变了!if (_currentState == state) {    goto change_state_failed;}// 改变动作之前,先停止所有动作this->stopAllActions();_currentState = state;ret = true;

change_state_failed:
return ret;
}

Animation *ActionSprite::createAnimation(const char *fmt, int count, float fps)
{
Array *frames = Array::createWithCapacity(count);
int i = 0;

for (i = 0; i < count; i++) {    const char *png = String::createWithFormat(fmt, i)->getCString();    SpriteFrame *frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(png);    frames->addObject(frame);}return Animation::createWithSpriteFrames(frames, 1 / fps);

}

### 1.2.1    英雄![] [pic-hero-actions]Hero部分相对简单,只需要把具体的动作图片序列的加载部分实现,以及“攻击力”啊、“生命值”什么的随便写一个(但是别写负数哦,一个英雄的生命为负。。。这是要闹哪样!)。不过同样有一点需要注意,我在写动作回调函数的时候发现`CallFunc::create(obj, selector)`这个函数也被废弃了,改用c++11特性的`CallFunc::create(std::function<void()> &cb)`,之前没怎么接触c++11,这个改动把我坑惨了(主要是一开始不知道怎么使用)!!- Hero.h```cppclass Hero : public ActionSprite{public:    Hero();    ~Hero();    bool init();    CREATE_FUNC(Hero);};
  • Hero.cpp
Hero::Hero(){}Hero::~Hero(){}bool Hero::init(){    bool ret = false;    do {        CC_BREAK_IF(!ActionSprite::initWithSpriteFrameName("hero_idle_00.png"));        // 之前的CallFunc::create(this, callfunc_selector(Hero::idle))已经被废弃额        // 改用c++11特性来回调        CallFunc *callbackIdle = CallFunc::create(std::bind(&Hero::idle, this));        // 创建idle(空闲)动画,调用后反复播放        Animation *idle = createAnimation("hero_idle_%02d.png", 6, 12);        setIdleAction(RepeatForever::create(Animate::create(idle)));        // 创建walk(行走)动画,同样调用后反复播放        Animation *walk = createAnimation("hero_walk_%02d.png", 7, 24);        setWalkAction(RepeatForever::create(Animate::create(walk)));        // 创建attact(攻击)动画,播放后回调到idle动画        Animation *attack = createAnimation("hero_attack_00_%02d.png", 3, 12);        setAttackAction(Sequence::create(Animate::create(attack), callbackIdle, NULL));        // 创建hurt(受伤)动画,播放后回调到idle动画        Animation *hurt = createAnimation("hero_hurt_%02d.png", 3, 12);        setHurtAction(Sequence::create(Animate::create(hurt), callbackIdle, NULL));        // 创建knockout(被击倒)动画,播放后不做任何调用        Animation *knockout = createAnimation("hero_knockout_%02d.png", 5, 12);        setKnockoutAction(Sequence::create(Animate::create(knockout), NULL));        setATK(20); // 攻击力        setHP(100); // 生命值        setVelocity(1); // 移动速度        setDirection(Point::ZERO); // 移动方向        ret = true;    } while (0);    return ret;}

Hero部分的代码基本实现,已经迫不及待想要看看成效了,其实现在万事俱备只欠东风,无非就是在GameLayer中增加hero的调用即可。因此:

  • 尽管之前一直在调用plist文件中的png图片资源,不过实际上它还没被游戏加载进来,因此添加动作图片序列的资源pd_sprites.plist和pd_sprites.pvr.ccz
  • 在GameLayer.h中声明hero和robots等精灵角色(robots是多个,所以声明为数组)
  • 后期除了hero外还有无数个robots,为了提高渲染效率,这里需要增加一个SpriteBatchNode对象_actors(演员列表),把所有动作精灵都放到这个_actors中进行批量渲染。
  • 最后把hero对象create出来,编译运行!

GameLayer.h(增加三个私有成员变量):

private:    ActionSprite *_hero;    cocos2d::Array *robots;    cocos2d::SpriteBatchNode *_actors;

GameLayer.cpp中的init做如下改变:

bool GameLayer::init(){    bool ret = false;    do {        CC_BREAK_IF(!Layer::init());        _map = TMXTiledMap::create("pd_tilemap.tmx");        this->addChild(_map);        // 加载精灵的动作图片序列资源        // 注意,2.x版本中的sharedSpriteFrameCache以弃用!!!        SpriteFrameCache::getInstance()->addSpriteFramesWithFile("pd_sprites.plist");        _actors = SpriteBatchNode::create("pd_sprites.pvr.ccz");        this->addChild(_actors);        _hero = Hero::create();        _hero->setPosition(Point(80, 80));        _hero->idle();        _actors->addChild(_hero);        ret = true;    } while(0);    return ret;}

好了,hero终于出现了!

1.2.2 机器人

机器人与hero一样是动作精灵,不考虑AI(人工智能,之后实现),它的实现过程几乎和Hero一模一样,根据plist文件把相关的五个动作动画实现就行了。

Robot.h

class Robot : public ActionSprite{public:    Robot();    ~Robot();    bool init();    CREATE_FUNC(Robot);};

Robot.cpp其他没什么好说的,主要就是init函数的实现:

bool Robot::init(){    bool ret = false;    do {        CC_BREAK_IF(!ActionSprite::initWithSpriteFrameName("robot_idle_00.png"));        // 之前的CallFunc::create(this, callfunc_selector(Robot::idle))已经被废弃额        // 改用c++11特性来回调        CallFunc *callbackIdle = CallFunc::create(std::bind(&Robot::idle, this));        // 创建idle(空闲)动画,调用后反复播放        Animation *idle = createAnimation("robot_idle_%02d.png", 5, 12);        setIdleAction(RepeatForever::create(Animate::create(idle)));        // 创建walk(行走)动画,同样调用后反复播放        Animation *walk = createAnimation("robot_walk_%02d.png", 6, 20);        setWalkAction(RepeatForever::create(Animate::create(walk)));        // 创建attact(攻击)动画,播放后回调到idle动画        Animation *attack = createAnimation("robot_attack_%02d.png", 5, 12);        setAttackAction(Sequence::create(Animate::create(attack), callbackIdle, NULL));        // 创建hurt(受伤)动画,播放后回调到idle动画        Animation *hurt = createAnimation("robot_hurt_%02d.png", 3, 12);        setHurtAction(Sequence::create(Animate::create(hurt), callbackIdle, NULL));        // 创建knockout(被击倒)动画,播放后不做任何调用        Animation *knockout = createAnimation("robot_knockout_%02d.png", 5, 12);        setKnockoutAction(Sequence::create(Animate::create(knockout), NULL));        setATK(20); // 攻击力        setHP(100); // 生命值        setVelocity(1); // 移动速度        setDirection(Point::ZERO); // 移动方向        ret = true;    } while (0);    return ret;}

同样的,在GameLayer.cpp中,把robots数组创建出来,并create几个robot对象,就能看到效果了。

GameLayer.cpp的init中增加以下几行,这只是测试,增加5个机器人,并随意放置:

_robots = Array::createWithCapacity(5);        for (int i = 0; i < 5; i++) {            Robot *robot = Robot::create();            // 随机的放到地图的任何地方            robot->setPosition(Point(CCRANDOM_0_1() * 400, CCRANDOM_0_1() * 100));            robot->idle();            _robots->addObject(robot);            _actors->addChild(robot);        }

运行效果如下

2. 操作层

在上面一节,hero和robots都已经出现了,不过只是一直出于idle状态,现在首先让hero“动”起来,具体实现OptionLayer,这个类负责响应触控操作。其实GameLayer与OptionLayer一样都继承于Layer,完全可以在GameLayer中实现这些功能,只不过我认为把“游戏”和“操作”解耦之后更有利于把这个“操作”复用到不同的“游戏”中,或者让这个“游戏”使用多种不同的“操作”方式(例如纯触控、虚拟按钮等)。原文教程就是用虚拟按钮的方式实现控制的!

But,我比较反感在触屏上设置虚拟按钮的方式进行操控,主要的问题在于,用这种方式不仅占用了游戏空间,而且玩家在操作之间必须先把注意力集中到寻找按钮的位置上,这是中相当糟糕的体验,所以我更喜欢发挥出现代智能手机的潜力,直接用触控手势的方式取代虚拟按钮。回顾上面的内容可以看到,玩家能控制hero的动作也就只有attackwalk两种,那么可以把手机屏幕中间“一分为二”变为左屏右屏,左半部分交给玩家的左手用来控制hero的walk,右半部分交给玩家的右手用来控制hero的attack。既然是左右屏,就有左右屏同时操作的可能,所以必须使用多点触控。

2.1 触摸响应

cocos2dx3.0中的多点触控接口貌似就这四个:

接口 说明
ccTouchesBegan 触控开始事件,手指碰到屏幕
ccTouchesMoved 触控移动事件,手指在屏幕上滑动
ccTouchesEnded 停止触控时间, 手指离开屏幕
ccTouchesCancelled 触控被取消事件,例如手指画出屏幕外

真心觉得很悲催啊,如果cocos2dx能加入手势识别不是会方便很多,例如“拖拽”、“滑动”、“点击”什么的。。。。唉,算了,还是老老实实先把这个游戏的操作层实现吧。除了最后一个Cancelled不需要外(大部分场合都用不到它),其他三个我们都需要,思路基本是这样:

  • 左屏为一个隐藏的“摇杆”,响应玩家左手的“拖拽”手势,ccTouchesBegan记录按下的屏幕坐标为起点并激活“摇杆”,当ccTouchesMoved的时候根据新坐标与起点的偏移量获取拖拽的向量和距离,来控制hero移动的方向和速度;
  • 右屏为一个看不见的按钮,响应玩家右手的“点击”手势,触发hero的攻击动作;

基本思路出来了,开始动工吧!新建OptionLayer类(如果没有的话),根据前面的分析,OptionLayer中大概需要这么几个东西:

  • 响应触摸的beganmovedended这三个事件
  • 一个“摇杆”,游戏中为joystick及它的相关函数(激活、更新什么的)
  • 委托OptionDelegate类,定义onWalkonAttackonStop接口(由GameLayer实现)

OptionLayer.h:

class OptionDelegate{public:    // 移动,direction为向量,distance是与起点的直线距离    virtual void onWalk(cocos2d::Point direction, float distance) = 0;    // 攻击    virtual void onAttack() = 0;    // 停止移动    virtual void onStop() = 0;};class OptionLayer : public cocos2d::Layer{public:    OptionLayer();    ~OptionLayer();    virtual bool init();    CREATE_FUNC(OptionLayer);    // 触控的三个事件函数重载    void ccTouchesBegan(cocos2d::Set *ts, cocos2d::Event *e);    void ccTouchesMoved(cocos2d::Set *ts, cocos2d::Event *e);    void ccTouchesEnded(cocos2d::Set *ts, cocos2d::Event *e);    // 委托者    CC_SYNTHESIZE(OptionDelegate*, _delegator, Delegator);private:    // 摇杆,分为“摇杆”、“摇杆基座”两个部分    cocos2d::Sprite *_joystick;    cocos2d::Sprite *_joystick_bg;    // 激活“摇杆精灵”,并更新其坐标    void _activityJoystick(cocos2d::Point position);    // 隐藏“摇杆精灵”,并将“摇杆”置于“摇杆基座”中心    void _inactivityJoystick();    // 刷新“摇杆”相对于“摇杆基座”的位置(根据触控手势)    void _updateJoystick(cocos2d::Point direction, float distance);};

我把OptionLayer和OptionDelegate两个类放在一起声明了(理由是:我很懒!)

2.2 隐藏的摇杆

需要说明的一点,这里的joystick“虚拟摇杆”是我自己画的,无非就是两个“同心圆”,大圆看做“摇杆基座”,小圆看做“摇杆”,当触控操作发生时,“虚拟摇杆”会被激活(平时未激活是看不到的),“摇杆”会在“摇杆基座”内部发生位移,就像真实世界中的摇杆做的推拉操作一样!!

接下来是具体实现OptionLayer.cpp,步骤是:

  • 初始化joystick精灵、开启多点触控,在init函数中实现

    bool OptionLayer::init(){bool ret = false;do {    CC_BREAK_IF(!Layer::init());    _joystick = Sprite::create("joystick.png");    _joystick_bg = Sprite::create("joystick_bg.png");    this->addChild(_joystick_bg);    this->addChild(_joystick);    _inactivityJoystick();    setTouchEnabled(true);    ret = true;} while(0);return ret;}

  • 实现“摇杆”的“激活”“刷新”等方法 激活/停止很容易,就是把joystick设置为可/不可见,并设置或复位其坐标就可以了,重点是“刷新”这个部分,这里要模拟真实世界中的摇杆的推拉操作,“摇杆”会随着操作发生位移,但不管怎么位移,“摇杆”的坐标是不会超出“基座”的(想象一下我们小时候玩过的街机,或者xbox,再想想圆规画圆的道理!!)

    void OptionLayer::_activityJoystick(Point position){_joystick->setPosition(position);_joystick_bg->setPosition(position);_joystick->setVisible(true);_joystick_bg->setVisible(true);}

void OptionLayer::_inactivityJoystick()
{
_joystick->setPosition(_joystick_bg->getPosition());
_joystick->setVisible(false);
_joystick_bg->setVisible(false);
}

// direction是方向(向量,偏移点与起始点行程的向量)
void OptionLayer::_updateJoystick(Point direction, float distance)
{
// 以“摇杆基座”的圆心为触控起点参考,“摇杆”做相应的便宜
Point start = _joystick_bg->getPosition();

if (distance < 32) {    // 如果移动未超出“摇杆基座”,“摇杆”在“基座”做偏移    _joystick->setPosition(start + (direction * distance));} else if (distance > 96) {    // 如果移动超出“摇杆基座”,“摇杆”圆心始终在“基座”边缘做偏移    _joystick->setPosition(start + (direction * 64));} else {    // 如果移动超出“摇杆基座”,“摇杆”边缘始终在“基座”边缘做偏移    _joystick->setPosition(start + (direction * 32));}

}

- 重载多点触控的“开始”、“结束”和“移动”三个函数  上面的部分已经把“摇杆”实现的差不多了,不过看起来总是云里雾里,`direction`从字面上看知道是方向,但是从何而来,`distance`这个偏移量又是怎么计算出来的,看看下面代码就明白了。```cppvoid OptionLayer::ccTouchesBegan(Set *ts, Event *e){    Size winSize = Director::getInstance()->getWinSize();    SetIterator iter = ts->begin();    while (iter != ts->end()) {        Touch *t = (Touch*)(*iter);        Point p = t->getLocation();        // left,当触控操作的起点小于屏幕宽度的一半,说明触控发生在左屏        if (p.x <= winSize.width / 2) {            _activityJoystick(p);        } else {            // right,否则发生在右屏,会产生“攻击”信号        }        iter++;    }}void OptionLayer::ccTouchesMoved(Set *ts, Event *e){    Size winSize = Director::getInstance()->getWinSize();    SetIterator iter = ts->begin();    Touch *t = (Touch*)(*iter);    Point start = t->getStartLocation();    // 如果该触控的起点是右屏产生的,则不做“滑动”处理    if (start.x > winSize.width / 2) {        return;    }    Point p = t->getLocation();    // 获取位移点与起始点的偏移量(直线距离)    float distance = start.getDistance(p);    // 获取起始点到位移点的向量(单位为1的坐标)    Point direction = (p - start).normalize();    _updateJoystick(direction, distance);}void OptionLayer::ccTouchesEnded(Set *ts, Event *e){    _inactivityJoystick();}

到这里,已经能够看到“虚拟摇杆”了,编译运行一下,如果没错误的话可以看到类似下图的结果(点击和移动左屏才有效,代码中可以看到,右屏的响应还没写呢!)

2.3 来控制hero吧

目前为止,控制hero的部分其实已经完成了,因为思路和上一节的“摇杆”是一样的,现在无非是调用一下“委托”通知GameLayer层,让它改变hero的相关属性,所以首先完善一下手上的代码。

OptionLayer.cpp中

  • ccTouchesBegan函数的right右屏部分发起“攻击”信号,“左屏”部分暂时不用写什么(该写的前面都已经写了);
  • ccTouchesMoved中发起hero的“行走”信号,就之前的思路而言,只有“左屏”会产生“滑动”的手势;
  • ccTouchesEnded中发起“停止移动”信号,让hero恢复为“idle”状态。
void OptionLayer::ccTouchesBegan(Set *ts, Event *e){...        } else {            // right,否则发生在右屏,会产生“攻击”信号            _delegator->onAttack();        }    }}void OptionLayer::ccTouchesMoved(Set *ts, Event *e){...    _delegator->onWalk(direction, distance);}void OptionLayer::ccTouchesEnded(Set *ts, Event *e){...    _delegator->onStop();}

OptionLayer的代码可以说全部完成了,但如果现在编译运行的话程序肯定会出错!很简单,那个_delegator还没有一个具体的值呢,而之前也说了这个“委托者”其实就是GameLayer,“游戏层”懒得响应玩家的操控才委托给了OptionLayer,现在OptionLayer的工作完成了,就该轮到GameLayer了:

  • 在GameLayer中具体实现onWalk onStop onAttack
    GameLayer.h中继承OptionDelegate,并声明相应的成员函数: cpp class GameLayer : public cocos2d::Layer, public OptionDelegate { public: ... // 实现OptionDelegate的几个方法 void onWalk(cocos2d::Point direction, float distance); void onAttack(); void onStop(); ... };

GameLayer.cpp:

void GameLayer::onAttack(){    _hero->attack();}void GameLayer::onWalk(Point direction, float distance){    _hero->walk(direction);}void GameLayer::onStop(){    _hero->idle();}

  • 还记得GameLayer和OptionLayer是在哪里创建的吗?GameScene!,所以在GameScene中,把GameLayer赋值给_delegator。

    GameScene.cpp

    bool GameScene::init(){...    _gameLayer = GameLayer::create();    _optionLayer = OptionLayer::create();    _optionLayer->setDelegator(_gameLayer);...}

编译运行!看看结果:

-。-!!!逻辑不对呀!有几个问题:

  1. 摇杆往左,hero往右,摇杆往右,hero往左。。。
  2. walk的帧率貌似太高了,而attack的动画帧率又太低了
  3. 摇杆刚开始在左,移动到右的时候,hero的朝向却没有跟着取反(这是个大bug)
    赶紧改改!!

回忆下之前的ActionSprite类的实现,我参考教程把walkattack两个方法都添加了参数,其目的就是当“角色”行走的时候会根据参数direction判断并修改朝向(问题1的错误也在此),当“角色”受伤的时候根据damage参数,减少“角色”的hp,当hp为0后自动出发角色“死亡”,这种小聪明走到现在发现还真有点画蛇添足!“操作层”发来的onWalk是持续的,意味着那个“direction”参数随时都在改变,可只要“操作层”没有发送onStop信号时hero将会一直处于ACTION_STATE_WALK状态,在ActionSprite类实现中有一点很重要:只要精灵已经处于要改变的状态时,就不会在做改变的处理,所以尽管“direction”一直在变,也一直在调用hero的“walk”方法,在第一次出起“walk”之后,后来的“direction”已经毫无意义了(因为hero已经在walk了,根本不处理)。

我想了想,ActionSprite归根结底只是个“动作精灵”,它只要管好自己动作的相关动画和“角色属性”就好了,至于怎么调用,怎么改变,它的生杀大权还是交给GameLayer吧!

首先要做一个小小的重构,就是删除所有ActionSprite相关的“动作参数”,一律改为void action(void),ActionSprite.h修改如下:

class ActionSprite : public Sprite{...    void idle();    void walk();    void attack();    void hurt();    void knockout();...};

对应的ActionSprite.cpp中walk和hurt函数也要修改:

void ActionSprite::walk(){    if (_changeState(ACTION_STATE_WALK)) {        runAction(_walkAction);    }}void ActionSprite::hurt(){    if (_changeState(ACTION_STATE_HURT)) {        runAction(_hurtAction);    }}

还有Hero.cpp与Robot.cpp, 顺便把帧率的问题也改了吧:

bool Hero::init(){...        // 创建walk(行走)动画,同样调用后反复播放        //Animation *walk = createAnimation("hero_walk_%02d.png", 7, 24);        Animation *walk = createAnimation("hero_walk_%02d.png", 7, 14);        setWalkAction(RepeatForever::create(Animate::create(walk)));        // 创建attact(攻击)动画,播放后回调到idle动画        //Animation *attack = createAnimation("hero_attack_00_%02d.png", 3, 12);        Animation *attack = createAnimation("hero_attack_00_%02d.png", 3, 20);        setAttackAction(Sequence::create(Animate::create(attack), callbackIdle, NULL));...        setATK(20); // 攻击力        setHP(100); // 生命值        //setVelocity(1); // 移动速度        //setDirection(Point::ZERO); // 移动方向...}

修改完成了,现在hero的walk和hurt的具体参数控制交由GameLayer管理,所以修改GameLayer.cpp的onWalk:

void GameLayer::onWalk(Point direction, float distance){    // 根据精灵的x向量,来判断精灵的正面“朝向”    _hero->setFlipX(direction.x < 0 ? true : false);    _hero->walk();}

再来看看新的效果

这样应该就没问题了,不过现在只会原地踏步,接下来进一步实现hero真正的移动。

hero在地图上移动是一个持续的过程(当玩家手指滑动后,只要不离开屏幕,hero就不会停下walk的脚步!),处理持续的动作时常常会用到cocos2dx的一个接口update这个接口为在每帧渲染的时候都被调用一次(所以update的实现务必要快准狠,不然浪费资源)。那么在update中需要做些什么呢?目前为止其实只需要做一点,递增/减hero的坐标值。那这个增量的标准是什么?其实就是hero的移动速度。那移动速度从何而来?想象物理界是如何定义速度的:速度是描述物体运动快慢和方向的物理量,定义为位移与发生这个位移所用的时间之比,看前半句,速度无非要两个东西“快慢”和“方向”,再来看看onWalk吧,它是带有两个参数的directiondistance,顾名思义“方向”“偏移量”,我们姑且把偏移量看做快慢的标准,偏移量越大速度越快;再来看看后半句的定义,“位移”与“时间”之比。真实世界里描述速度一般都是xx米/秒或者xx公里/小时,游戏里则可以用xx像素/帧来衡量,而update每帧都会调用。所以重新回到最开始的问题,update中要做些什么?其实就是把hero当前坐标加上一个速度量。

一切都搞清楚了,具体来实现吧。

GameLayer.h中声明update函数,并再声明一个_heroVelocity给update用:

class GameLayer : public cocos2d::Layer, public OptionDelegate{public:...    void update (float dt);...private:    cocos2d::Point _heroVelocity;...};

GameLayer.cpp中具体实现,这里要注意我让hero的速度分两个档次

void GameLayer::onWalk(Point direction, float distance){...    // 根据偏移量的大小,确定速度在1档还是2档    _heroVelocity = direction * (distance < 96 ? 1 : 2);}void GameLayer::update(float dt){    // 如果hero处于Walk,则刷新其坐标    if (_hero->getActionState() == ACTION_STATE_WALK) {        _hero->setPosition(_hero->getPosition() + _heroVelocity);    }}

这样hero就可以在地图上移动了,来看看效果:

确实可以了,不过总感觉哪里不对呀??

  1. 肿么hero可以跑到墙上,不科学啊
  2. hero都走出屏幕了这是要闹哪样
  3. robot肿么把hero给挡住了,他可是男主角啊!

。。。。。。。。。

问章有点长了,剩下的部分留到下一篇写,主要包括完善hero的移动,整个GameLayer的移动,另外就是robot的人工智能部分,最后是碰撞检测。

再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

cocos2d-x 3 0 制作横版格斗游戏相关推荐

  1. Cocos2d-x 3.0 制作横版格斗游戏2

    转载:https://blog.csdn.net/bridge001/article/details/18882575 git:https://github.com/pj2933/fight2d co ...

  2. cocos2d-x 3.0 制作横版格斗游戏

    cocos2d-x: v3.0-alpha-pre Windows环境: Windows8 + Visual Studio 2012 Linux环境: Ubuntu12.04 + gcc 4.7.2 ...

  3. java 横版游戏开发_用MyEclipse的Java Project开发仿DNF横版格斗游戏

    这些天,我正在用MyEclipse的Java Project开发一款仿DNF 横版格斗游戏. http://v.youku.com/v_show/id_XMTI5MTE0NDg4MA==.html 这 ...

  4. Beat #39;Em Up Game Starter Kit (横版格斗游戏) cocos2d-x游戏源代码

    浓缩精华.专注战斗! 游戏的本质是什么?界面?养成?NoNo!    游戏来源于对实战和比赛的模拟,所以它的本源就是对抗.就是战斗! 是挥洒热血的一种方式! 一个游戏最复杂最难做的是什么?UI?商城? ...

  5. Beat 'Em Up Game Starter Kit (横版格斗游戏) cocos2d-x游戏源码

    浓缩精华,专注战斗!    游戏的本质是什么?界面?养成?NoNo!    游戏来源于对实战和比赛的模拟,所以它的本源就是对抗!就是战斗!是挥洒热血的一种方式!    一个游戏最复杂最难做的是什么?U ...

  6. 横版java_Project4 自己用java写的横版格斗游戏 功能还不是很复杂 可以作为参考~ Other Games 其他 238万源代码下载- www.pudn.com...

    文件名称: Project4下载 收藏√  [ 5  4  3  2  1 ] 开发工具: Java 文件大小: 5963 KB 上传时间: 2013-07-17 下载次数: 4 提 供 者: lyk ...

  7. Cocos2D来制作横版过关游戏1

    本文实践自 Allen Tan 的文章< How To Make A Side-Scrolling Beat 'Em Up Game Like Scott Pilgrim with Cocos2 ...

  8. CocosCreator横版格斗游戏Demo(一):创建游戏地图和主角

    小时候在小霸王游戏机上玩过很多种横版格斗类游戏(比如:快打旋风),自从进入游戏开发行业,一直都有个梦想就是自己写一个(虽然工作都是做休闲类.消除类.棋牌类游戏,但是此梦想从未放弃过!!!).本系列课程 ...

  9. 《Genesis-3D开源游戏引擎--横版格斗游戏制作教程03:碰撞检测》

    2019独角兽企业重金招聘Python工程师标准>>> 3.碰撞检测 碰撞检测的概述: 碰撞在物理学中表现为两粒子或物体间极端的相互作用.而在游戏世界中,游戏对象在游戏世界自身并不受 ...

最新文章

  1. 用Rhino V7建造机甲学习教程 Building a Mecha using Rhino V7
  2. pandas对dataframe进行排序:单数据列排序、多数据列排序、NA值排序位置、排序算法
  3. 服务器连接超时不响应怎么回事,勇者斗恶龙x服务器没有响应连接超时解决方法...
  4. 报名 | AI Time :论道AI安全与伦理
  5. Rxjava Demo
  6. Eclipse快捷键指南
  7. 计算机系中学生的自我鉴定,中学生毕业自我鉴定
  8. leetcode 797. All Paths From Source to Target | 797. 所有可能的路径(回溯法)
  9. 食疗去除头屑的小偏方 不错!
  10. 使用Gradle消除开发重新部署
  11. 配置CDI对话的超时
  12. 详细介绍CISCO IOS命名规则
  13. 9月30号后新版CCNA考试要点
  14. hdoj小数转化为分数
  15. FFmpeg 硬件加速介绍
  16. 腾讯云函数转华为云函数
  17. favicon.ico在线转换网站
  18. ENVI下Landsat8大气校正法反演地表温度
  19. java线程状态和状态切换
  20. HDOJ 4696 Answers 乱搞

热门文章

  1. Android 微信扫码登陆
  2. aect17定义_教育技术学AECT历史定义大全
  3. 线性代数---之正交向量
  4. 筑智建——重庆发布《房屋建筑和市政基础设施工程竣工联合验收管理办法》
  5. Linux下硬盘安装fedora,Fedora硬盘安装
  6. Go语言自学系列 | golang结构体指针
  7. 图片格式怎么转换成png?怎么改变图片格式?
  8. 过滤器(Filter)的简单概述
  9. 如何开始接手一个项目
  10. 基于微信小程序的电影交流平台小程序