转载请注明,原文地址: http://www.benmutou.com/archives/29
文章来源:笨木头与游戏开发

Cocos2d-x游戏实例-《跑跑跑》制作教程(第三篇)——让主角跑

 

笨木头花心贡献,啥?花心?不呢,是用心~ 转载请注明,原文地址http://www.benmutou.com/archives/29 

正文:

 

注:本文使用到的资源请到这里下载:http://download.csdn.net/detail/musicvs/4769412

终于进入我们的游戏的主题了——跑!

来,我们开始让主角跑起来~

 

1. 修改一下糟糕的代码

我们要给主角加一个动画,不断地播放跑步动作。我们来打开熟悉的TollgateScene.cpp文件,糟糕,我发现这个文件的init函数有点庞大了。

于是,我做了一个艰难的决定,把创建玩家精灵的工作移到Player类的init函数里:

[cce_cpp]//Player.h文件

#ifndef __PLAYER_H__ #define __PLAYER_H__

#include "Entity.h"

class Player : public Entity { public: bool initWithTiledMap(CCTMXTiledMap* map);

static Player* createWithTiledMap(CCTMXTiledMap* map); };

#endif [/cce_cpp]




[cce_cpp]//Player.cpp文件

#include "Player.h"

Player* Player::createWithTiledMap( CCTMXTiledMap* map ) { Player* mPlayer = new Player();

if(mPlayer && mPlayer->initWithTiledMap(map)) { } else { CC_SAFE_DELETE(mPlayer); }

return mPlayer; }

bool Player::initWithTiledMap( CCTMXTiledMap* map ) { /* 加载对象层 */ CCTMXObjectGroup* objGroup = map->objectGroupNamed("objects");

/* 加载玩家坐标对象 */ CCDictionary* playerPointDic = objGroup->objectNamed("PlayerPoint"); float playerX = playerPointDic->valueForKey("x")->floatValue(); float playerY = playerPointDic->valueForKey("y")->floatValue();

/* -------------- 加载玩家 --------------- */ CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize(); CCSprite* playerSprite = CCSprite::create("sprite/player1.png"); playerSprite->setPosition(ccp(playerX, playerY));

/* 精灵添加到地图 */ map->addChild(playerSprite);

/* 绑定精灵对象 */ setSprite(playerSprite); return true; } [/cce_cpp]

Player类改得有点多,把创建和初始化的函数加了一个参数:地图对象。这样我们就可以直接在playerinit函数中帮精灵对象添加到地图了。

然后现在TollgateScene.cppinit函数就好看多了:

[cce_cpp]bool TollgateScene::init() { /* 加载地图 */ CCTMXTiledMap* map = CCTMXTiledMap::create("map/level01.tmx"); this->addChild(map);

/* 创建玩家 */ Player* mPlayer = Player::createWithTiledMap(map);

return true; } [/cce_cpp]

 

2.主角跑动动画

现在开始真正给主角加一个动画了,我们先给Player类新增一个函数:

[cce_cpp]void Player::run() { CCArray* framesList = CCArray::create();

framesList->addObject(CCSpriteFrame::create("sprite/player1.png", CCRectMake(0, 0, 77, 134))); framesList->addObject(CCSpriteFrame::create("sprite/player2.png", CCRectMake(0, 0, 66, 129))); framesList->addObject(CCSpriteFrame::create("sprite/player3.png", CCRectMake(0, 0, 99, 132))); framesList->addObject(CCSpriteFrame::create("sprite/player4.png", CCRectMake(0, 0, 111, 135))); framesList->addObject(CCSpriteFrame::create("sprite/player5.png", CCRectMake(0, 0, 94, 132))); framesList->addObject(CCSpriteFrame::create("sprite/player6.png", CCRectMake(0, 0, 64, 128))); framesList->addObject(CCSpriteFrame::create("sprite/player7.png", CCRectMake(0, 0, 96, 133))); framesList->addObject(CCSpriteFrame::create("sprite/player8.png", CCRectMake(0, 0, 103, 138)));

CCAnimation* animation = CCAnimation::createWithSpriteFrames(framesList, 0.2f); animation->setLoops(-1);

mSprite->runAction(CCAnimate::create(animation)); } [/cce_cpp]

我为了尽量减少教程里出现的代码量,以及尽量让素材简单,我采用最直接的方式来创建CCSpriteFrame对象,如果大家不喜欢的话,可以用CCSpriteFrameCache的方式来创建,这里就不多说了。创建动画的方式也不说了,我默认大家有了解过。

  好吧,还是简单说一下,先创建一组CCSpriteFrame对象,然后用这组对象创建一个CCAnimation对象,这个对象还不能用来形成动画,还必须创建一个CCAnimate对象,然后精灵类通过runAction方法来执行动画。setLoops(-1)是为了循环播放动画。

  然后,继续打开我们熟悉的TollgateScene.cppinit函数,加上一句代码:

  OK,编译运行,精灵已经在跑动了!当然,只是原地跑。

[cce_cpp]/* 创建玩家 */ Player* mPlayer = Player::createWithTiledMap(map);

/* 让玩家跑起来 */ mPlayer->run(); [/cce_cpp]

 

接下来要新增的代码有点多,慢慢来。

3. 控制器

让主角向前跑,我想采用组合来实现,把向前跑作为一个功能单独写成一个类,主角只要增加一个成员变量(向前跑的类),就能实现向前跑的动作。而这个类,就是控制器。

因为考虑到不止一个控制器,我们来稍微写多几行代码,以便以后扩展。先来实现控制器的父类(在实体文件夹新建Controller.hController.cpp文件)

[cce_cpp]//Controller.h文件

#ifndef __CONTROLLER_H__ #define __CONTROLLER_H__

#include "cocos2d.h" #include "ControllerListener.h"

using namespace cocos2d;

class Controller : public CCNode { public: /* 设置监听对象 */ void setControllerListener(ControllerListener* mControllerListener);

protected: ControllerListener* mControllerListener; };

#endif [/cce_cpp]




[cce_cpp]//Controller.cpp文件

#include "Controller.h"

void Controller::setControllerListener( ControllerListener* mControllerListener ) { this->mControllerListener = mControllerListener; } [/cce_cpp]

很简单的一个类,只有一个变量和一个方法,我们来看看ControllerListener是做什么用的。ControllerListener就是将要被控制的对象,比如我们主角,只要继承了ControllerListener接口,就能够被控制器控制。很方便吧~针对接口编程,使得代码稍微没有那么糟糕。

看看ControllerListener的代码:

[cce_cpp]//ControllerListener.h文件

#ifndef __CONTROLLER_LISTENER_H__ #define __CONTROLLER_LISTENER_H__

#include "cocos2d.h"

using namespace cocos2d;

class ControllerListener { public: virtual void setSimplePosition(int x, int y) = 0; virtual CCPoint getCurPosition() = 0; };

#endif [/cce_cpp]

 也很简单,只有头文件,定义了两个虚函数,用来设置和获取被控制对象的坐标。我不太喜欢C++的编码,有点繁琐,我对Java中毒很深,嘻嘻,Java要定义一个接口的话就简单多了,啊喂,跑题了~

4. 简单移动控制器

我们来实现我们的第一个控制器,控制物体只往前移动的控制器,看代码:

[cce_cpp]//SimpleMoveController.h文件

#ifndef __SIMPLE_MOVE_CONTROLL_H__ #define __SIMPLE_MOVE_CONTROLL_H__

#include "cocos2d.h" #include "Controller.h"

using namespace cocos2d;

class SimpleMoveControll : public Controller { public: CREATE_FUNC(SimpleMoveControll); virtual bool init(); virtual void update(float dt);

/* 设置移动速度 */ void setiSpeed(int iSpeed);

private: int iSpeed; };

#endif</pre> <pre class="cpp">// SimpleMoveControll.cpp文件

#include "SimpleMoveControll.h"

bool SimpleMoveControll::init() { this->iSpeed = 0;

/* 每一帧都要调用update函数,所以要这样设置 */ this->scheduleUpdate();

return true; }

void SimpleMoveControll::update( float dt ) { if(mControllerListener == NULL) { return; } CCPoint pos = mControllerListener->getCurPosition(); pos.x += iSpeed; mControllerListener->setSimplePosition(pos.x, pos.y); }

void SimpleMoveControll::setiSpeed( int iSpeed ) { this->iSpeed = iSpeed; } [/cce_cpp]

SimplerMoveController继承了Controller类,也很简单,拥有一个成员变量iSpeed,用来设置移动速度。

  这里简单介绍一下update(float dt)函数,update函数是CCNode节点的函数,有什么用呢?很强大的。我们都知道,游戏的画面是一帧帧的绘制,从而形成丰富多彩的世界。而程序只需要在每一帧里执行操作,绘制图形。

  Update函数提供了一个入口,让我们可以在游戏的每一帧里执行我们自己想要做的事情。是的,就算你只是在里面放一个屁都是允许的(你不会当真了吧?= =)。

  但是咯,不可能每个CCNode对象都执行一次update函数吧?(不是每个人都需要update不是么?),所以,默认情况下update函数是不会被调用的,需要对象通过scheduleUpdate方法来注册被调用的权限。

然后float dt参数是什么呢?我们都知道CPU是一个很忙的孩子(虽然它很聪明),CPU不可能让所有update函数同时进行的,只能是一个个执行,所以总有人先调用有人后调用,float dt参数就记录了某个update函数从最后一次被调用到本次调用时经过了多少毫秒。那这又有什么用呢?很有用,但是我们先不管~

然后,SimpleMoveControllerupdate函数里做了一件很伟大的事情,那就是改变被控制者的X坐标,使得被控制者往前移动了一段距离。

 

5. 给主角绑定一个控制器吧

整了这么多代码,主角都还没有跑起来,太累了,我不写了!啊,才怪呢,现在写,现在写~

有个不幸的消息,我们需要稍微改改Entity类(好吧,不要恨我,代码总是逐步优化的嘛T^T:

[cce_cpp]//Entity.h文件 #ifndef __ENTITY_H__ #define __ENTITY_H__

#include "cocos2d.h" #include "Controller.h" #include "ControllerListener.h"

using namespace cocos2d;

class Entity : public CCNode, public ControllerListener { public: void setSprite(CCSprite* mSprite); void setController(Controller* controller);

/* 实现SimpleMoveListener接口的方法 */ virtual void setSimplePosition(int x, int y); virtual CCPoint getCurPosition(); protected: CCSprite* mSprite; Controller* mController; };

#endif</pre> <span style="font-size: 14px;">我们给<span style="font-family: Calibri;">Entity</span>加了一个父类,那就是<span style="font-family: Calibri;">ControllerListener</span>,大家都知道为什么了,因为我们的角色需要被当做一个被控制器控制的对象。继承了<span style="font-family: Calibri;">ControllerListener</span>之后,当然就要实现它的方法了。</span>

<span style="font-size: 14px;">另外,我还为<span style="font-family: Calibri;">Entity</span>新增了一个方法,那就是<span style="font-family: Calibri;">setController</span>,当然了,因为我们需要绑定一个控制器。</span>

<span style="font-size: 14px;">三个方法的实现如下:</span>

<span style="font-family: Calibri; font-size: 14px;"> </span> <pre class="cpp">[cce_cpp]//Entity.cpp的部分代码

void Entity::setController( Controller* controller ) { this->mController = controller; controller->setControllerListener(this); }

void Entity::setSimplePosition( int x, int y ) { if(mSprite) { mSprite->setPosition(ccp(x, y)); } }

cocos2d::CCPoint Entity::getCurPosition() { if(mSprite) { return mSprite->getPosition(); }

return CCPoint::CCPoint(0, 0); } [/cce_cpp]

好了,好了,最后了,大家坚持住,我们打开TollgateScene.cppinit函数吧,我们要开始跑喇~init函数最后加上:

[cce_cpp]/* ------------ 创建玩家简单移动控制器 -------------- */ SimpleMoveControll* mSMoveControll = SimpleMoveControll::create(); mSMoveControll->setiSpeed(1);

/* 控制器要添加到场景中才能获得update事件 */ this->addChild(mSMoveControll);

mPlayer->setController(mSMoveControll); [/cce_cpp]

简单说明一下哈,创建一个移动控制器,设置移动速度为1,然后把控制器添加到场景中(这样它才能获得update函数的调用),最后把控制器添加到主角身上。

来,编译运行,看主角疯狂地跑动吧!

太帅了,简直就是一个流氓冲着良民狂奔啊喂~= =



 

下一篇我们将会让地图也动起来,我们的地图可是很长的不是么,不能浪费啊喂~
16 条评论
  • 百合花 2013-10-29 18:16:58

    player * mplayer = new player()这块 好像有点问题 我是初学者 但是还是感觉做的这个教程太好了 顶你!
    0回复
  • 曹建伟 2013-07-10 14:33:11

    player * mplayer = new player()还是那个问题。。它问题是:Player是抽象类 不能实例化抽象类是因为父类Entity里实现了ControllerListener里的抽象类 而Player里没有实现吗??这该怎么写啊???
    0回复
  • txxm520 2013-01-03 19:10:00

    幽默啊,哈哈
    0回复
  • musicvs 2013-01-04 08:16:00

    [reply]txxm520[/reply]
    嘿嘿~笑着学习~
    0回复
  • start530 2013-01-09 15:08:00

    菜鸟实习生看完第三篇表示蛋有点痛。
    0回复
  • musicvs 2013-01-10 08:38:00

    [reply]start530[/reply]
    也许是我的表达能力还是不够强,我会继续努力的,嘿嘿~
    0回复
  • start530 2013-01-10 09:06:00

    博主Q多少啊?我们线上密切交流下。
    0回复
  • musicvs 2013-01-10 12:08:00

    [reply]start530[/reply]
    噗,密切~你加这个群:107952708 虽然现在还没有人在。。。
    0回复
  • start530 2013-01-10 14:10:00

    从上午到现在按照你的博客一路做到这一步。也不知道这样按部就班,照抄他人的方法到底能不能让我学到多少,不过有点是可以肯定的:c++ 还可以这么用?!一层嵌套一层。我之前写程序都是一个init()里塞的满满的。。
    0回复
  • musicvs 2013-01-10 18:25:00

    [reply]start530[/reply]
    哈哈,照抄是第一步,第二步就是自己动手重写一遍,不然就无法深刻理解(我就是因为这样所以常常写教程,让自己更深刻地理解)。
    init里塞满满,好有意思,呵呵~
    0回复
发表评论
粤ICP备16043700号

本博客基于 BlazorAnt Design Blazor 开发