如何制作一个基于Tile的游戏 cocos2d x下载-x 2.0.4

5112人阅读
CCCallFunc CCCallFuncN CCCallFuncND的区别和使用
CCCallFunc CCCallFuncN CCCallFuncND都用来创建带有回调函数的动作,区别主要在于回调函数是否带有参数
CCCallFunc
CCCallFunc是执行对应的回调函数,其中回调函数不可带参数。一般使用静态成员函数create创建实例,create声明如下:
static CCCallFunc* create ( CCObject *
pSelectorTarget,
SEL_CallFunc
回调函数通过execute方法执行,CCCallFunc中的execute的实现如下:
void CCCallFunc::execute() {
if (m_pCallFunc) {
(m_pSelectorTarget-&*m_pCallFunc)();
if (m_nScriptHandler) {
CCScriptEngineManager::sharedManager()-&getScriptEngine()-&executeCallFuncActionEvent(this);
通过(m_pSelectorTarget-&*m_pCallFunc)();可以看到回调函数不包含参数
CCCallFuncN
CCCallFuncN也是执行对应的回调函数,其中回调函数带一个参数。一般使用静态成员函数create创建实例,create声明如下:
static CCCallFuncN* create ( CCObject *
pSelectorTarget,
SEL_CallFuncN
回调函数通过execute方法执行,CCCallFuncN中的execute的实现如下:
void CCCallFuncN::execute() {
if (m_pCallFuncN) {
(m_pSelectorTarget-&*m_pCallFuncN)(m_pTarget);
if (m_nScriptHandler) {
CCScriptEngineManager::sharedManager()-&getScriptEngine()-&executeCallFuncActionEvent(this, m_pTarget);
通过(m_pSelectorTarget-&*m_pCallFuncN)(m_pTarget);可以看到回调函数包含一个参数。
CCCallFuncND
CCCallFuncND也是执行对应的回调函数,其中回调函数可带两个参数。一般使用静态成员函数create创建实例,create声明如下:
static CCCallFuncND* create ( CCObject *
pSelectorTarget,
SEL_CallFuncND
回调函数通过execute方法执行,CCCallFuncND中的execute的实现如下:
void CCCallFuncND::execute() {
if (m_pCallFuncND) {
(m_pSelectorTarget-&*m_pCallFuncND)(m_pTarget, m_pData);
通过(m_pSelectorTarget-&*m_pCallFuncND)(m_pTarget, m_pData);可以看到回调函数包含两个参数。
CCCallFunc CCCallFuncN CCCallFuncND实例对比
testCallFunc.h中代码
class testCallFunc : public CCLayer
protected:
virtual void onEnter();
void callback1();
void callback2(CCNode* sender);
void callback3(CCNode* sender, void* data);
testCallFunc.cpp中代码
void testCallFunc::onEnter()
//CCCallFunc的使用
CCFiniteTimeAction*
action = CCSequence::create(
CCMoveBy::create(2, ccp(200,0)),
CCCallFunc::create(this, callfunc_selector(testCallFunc::callback1)),
//CCCallFuncN的使用
CCFiniteTimeAction*
action2 = CCSequence::create(
CCScaleBy::create(2 ,
CCFadeOut::create(2),
CCCallFuncN::create(this, callfuncN_selector(testCallFunc::callback2)),
//CCCallFuncNC的使用
CCFiniteTimeAction*
action3 = CCSequence::create(
CCRotateBy::create(3 , 360),
CCFadeOut::create(2),
CCCallFuncND::create(this, callfuncND_selector(testCallFunc::callback3), (void*)0xbebabeba),
sprite1-&runAction(action);
sprite2-&runAction(action2);
sprite3-&runAction(action3);
void testCallFunc::callback1()
CCSize s = CCDirector::sharedDirector()-&getWinSize();
CCLabelTTF *label = CCLabelTTF::create(&callback 1 called&, &Marker Felt&, 16);
label-&setPosition(ccp( s.width/4*1,s.height/2));
addChild(label);
void testCallFunc::callback2(CCNode* pSender)
CCSize s = CCDirector::sharedDirector()-&getWinSize();
CCLabelTTF *label = CCLabelTTF::create(&callback 2 called&, &Marker Felt&, 16);
label-&setPosition(ccp( s.width/4*2,s.height/2));
addChild(label);
void testCallFunc::callback3(CCNode* pTarget, void* data)
CCSize s = CCDirector::sharedDirector()-&getWinSize();
CCLabelTTF *label = CCLabelTTF::create(&callback 3 called&, &Marker Felt&, 16);
label-&setPosition(ccp( s.width/4*3,s.height/2));
addChild(label);
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:142193次
积分:1900
积分:1900
排名:第8260名
原创:22篇
转载:87篇
评论:135条
(1)(1)(1)(1)(2)(1)(8)(74)(1)(5)(5)(1)(3)(1)(1)(3)移动开发_如何制作一个基于Tile的游戏(2) Cocos2d_软件世界网
如何制作一个基于Tile的游戏(2) Cocos2d
在第一篇《如何制作一个基于Tile的游戏》基础上,增加碰撞和拾取功能,原文《Collisions
and Collectables: How To Make a Tile-Based Game with Cocos2D Part 2》,在这里继续以Cocos2d-x进行实现。有关源码、资源等在文章下面给出了地址。
步骤如下:
1.使用上一篇的工程;
2.打开Tiled Map Editor工具,菜单栏→&图层&→&添加图层&,命名为&Meta&。这个层,我们将放入一些假的tile来代表&特殊tile&。菜单栏→&地图&→&新图块&,点击&浏览&,选择&Resources&目录下的meta_tiles.png文件,边距和间距设置成1像素点,点击&确定&。可以看到在&图块&窗口新增了一页,里面有红色和绿色两种tile,如下图所示:
3.确认&Meta&层被选中,选择工具栏上&图章刷&,选择红色tile,绘制可碰撞区域,完成之后,大概如下图所示:
需要给这个tile设置属性来标识它,这样才能知道该tile具有碰撞属性。在&图块&窗口,右键红色tile,选择&图块属性&,新建一个属性,名称为&Collidable&,其值为&true&,如下图所示:
点击&确定&。保存地图。
4.打开HelloWorldScene.h文件,添加如下声明:
CC_SYNTHESIZE_RETAIN(TMXLayer*, _meta, Meta);在HelloWorldScene.cpp文件构造函数里,添加代码:
_meta = NULL;在init函数里,添加背景之后,添加如下代码:
this-&setMeta(_tilemap-&getLayer(&Meta&));
_meta-&setVisible(false);这里把Meta层隐藏起来了,因为这只是作为阻挡的,不是真实可见的。添加一个新的方法,代码如下:
Point HelloWorld::tileCoordForPosition(Point position)
int x = position.x / _tilemap-&getTileSize().
int y = ((_tilemap-&getMapSize().height * _tilemap-&getTileSize().height) - position.y) / _tilemap-&getTileSize().
return Point(x, y);
}这个方法将坐标转换成tile坐标,tile坐标系如下图所示:
修改setPlayerPosition函数,代码如下:
void HelloWorld::setPlayerPosition(Point position)
Point tileCoord = this-&tileCoordForPosition(position);
int tileGid = _meta-&getTileGIDAt(tileCoord);
if (tileGid) {
Dictionary* properties = _tilemap-&getPropertiesForGID(tileGid);
if (properties) {
const String* collision = properties-&valueForKey(&Collidable&);
if (collision && collision-&compare(&true&) == 0) {
_player-&setPosition(position);
}在这里,我们将坐标转成tile坐标,获得这个tile坐标上的GID,再根据GID得到的属性字典,查找是否&Collidable&属性为&true&,如果是则直接返回。
5.编译运行,可以看到忍者不能穿过红色区域了。如下图所示:
6.动态修改Tiled地图。我们为忍者增加可以吃的东西,比如这里的西瓜。创建一个可拾取的前景层,当忍者从tile拾取东西时,就把这个tile从前景层中移除。菜单栏→&图层&→&添加图层&,命名为&Foreground&。注意,若是之前有在&Background&层绘制过西瓜的,需要用底图块,比如这里的沙漠块填充覆盖,以免达不到吃西瓜的效果。然后,选中&Foreground&层,选择西瓜tile块,在地图上进行绘制。如下图所示:
为西瓜标识可拾取。选择&Meta&层,图块切换到&meta_tiles&页,选择绿色tile,绘制到地图上西瓜tile区域。需要先把&Meta&层前置,点击菜单栏→&图层&→&前置图层&,确保&Meta&层在最上层。如下图所示:
在&图块&窗口,右键绿色tile块,选择&图块属性&,新建一个属性,名称为&Collectable&,其值为&true&。点击&确定&。保存地图。
7.打开HelloWorldScene.h文件,添加如下声明:
CC_SYNTHESIZE_RETAIN(TMXLayer*, _foreground, Foreground);在HelloWorldScene.cpp文件构造函数里,添加代码:
_foreground = NULL;在init函数里,添加背景之后,添加如下代码:
this-&setForeground(_tilemap-&getLayer(&Foreground&));在setPlayerPosition函数检测&Collidable&属性之后,添加检测&Collectable&属性,代码如下:
const String* collectable = properties-&valueForKey(&Collectable&);
if (collectable && collectable-&compare(&true&) == 0) {
_meta-&removeTileAt(tileCoord);
_foreground-&removeTileAt(tileCoord);
}8.编译运行,可以看到忍者把西瓜吃掉了,如下图所示:
9.创建计分器。为忍者记录所吃西瓜的数量。我们创建一个新层HelloWorldHud来显示分数。在HelloWorldScene.h文件中,添加如下代码:
#include &cocos2d.h&
USING_NS_CC;
class HelloWorldHud:public Layer {
virtual bool init();
CREATE_FUNC(HelloWorldHud);
void numCollectedChanged(int numCollected);
LabelTTF* _
在HelloWorldScene.cpp文件中,进行实现,代码如下:
bool HelloWorldHud::init()
bool bRet =
CC_BREAK_IF(!Layer::init());
Size winSize = Director::getInstance()-&getWinSize();
_label = LabelTTF::create(&0&, &Verdana-Bold&, 18.0, Size(50, 20), TextHAlignment::RIGHT);
_label-&setColor(Color3B(0, 0, 0));
int margin = 10;
_label-&setPosition(Point(winSize.width - (_label-&getContentSize().width / 2) - margin, _label-&getContentSize().height / 2 + margin));
this-&addChild(_label);
} while (0);
void HelloWorldHud::numCollectedChanged(int numCollected)
_label-&setString(String::createWithFormat(&%d&,numCollected)-&getCString());
}接下去在HelloWorld类,添加HelloWorldHud层指针,在HelloWorldScene.h文件中HelloWorld类里,添加如下代码:
CC_SYNTHESIZE(int, _numCollected, NumCollected);
CC_SYNTHESIZE_RETAIN(HelloWorldHud*, _hud, Hud);在HelloWorldScene.cpp文件HelloWorld类构造函数里,添加代码:
_numCollected = 0;
_hud = NULL;在createScene()函数里,添加如下代码:
HelloWorldHud* hud = HelloWorldHud::create();
scene-&addChild(hud);
layer-&setHud(hud);在setPlayerPosition函数,检测到&Collectable&属性为&true&时,添加如下代码:
_numCollected++;
_hud-&numCollectedChanged(_numCollected);10.编译运行,现在可以看到右下角有一个西瓜计分器,如下图所示:
参考资料:
1.Collisions and Collectables: How To Make a Tile-Based Game with Cocos2D Part 2/1186/collisions-and-collectables-how-to-make-a-tile-based-game-with-cocos2d-part-2
2.碰撞与拾取:如何使用Cocos2D制作一款基于tile的游戏第2部分/zh-hans/19250/%E7%A2%B0%E6%92%9E%E4%B8%8E%E6%8B%BE%E5%8F%96%EF%BC%9A%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8cocos2d%E5%88%B6%E4%BD%9C%E4%B8%80%E6%AC%BE%E5%9F%BA%E4%BA%8Etile%E7%9A%84%E6%B8%B8%E6%88%8F%E7%AC%AC2
3.(译)碰撞检测和收集物品:如何使用cocos2d制作基于tiled地图的游戏:第二部分/zilongshanren/archive//2033620.html
iOS版下载地址:&/share/link?shareid=&uk=
如文章存在错误之处,欢迎指出,以便改正。
&此文从网络中自动搜索生成,不代表本网站赞成被搜索网站的内容或立场
软件世界网- &2014 蜀ICP备号 三峰网旗下网站如何使用cocos2d-x3.0和物理引擎来制作一个Breakout游戏:第一部分
在这个教程中,我们将一步一步创建一个简单的breakout游戏,完成碰撞检测,篮球反弹物理效果,通过touch拖动paddle(就是上图的白色矩形),以及胜利/失败的场景。
如果你还不了解cocos2d-x和其封装的物理引擎,你可能先要读一读以及。
好了,是时候制作breakout了!
一个永远反弹的球
首先创建一个空项目。
接下来,在HelloWorldScene.h中添加以下成员变量:
Sprite* ball;
Sprite* paddle;
Sprite* edgeSp;
PhysicsWorld* m_world;
void setPhyWorld(PhysicsWorld* world){ m_world = world; };
然后,在init方法中加入下列代码:
auto visibleSize = Director::getInstance()-&getVisibleSize();
auto origin = Director::getInstance()-&getVisibleOrigin();
edgeSp = Sprite::create();
auto boundBody = PhysicsBody::createEdgeBox(visibleSize, PHYSICSBODY_MATERIAL_DEFAULT, 3);
boundBody-&getShape(0)-&setRestitution(1.0f);
boundBody-&getShape(0)-&setFriction(0.0f);
boundBody-&getShape(0)-&setDensity(1.0f);
edgeSp-&setPosition(Point(visibleSize.width / 2, visibleSize.height / 2));
edgeSp-&setPhysicsBody(boundBody);
this-&addChild(edgeSp);
edgeSp-&setTag(0);
好,这个代码和我们上一个教程中,为整个屏幕创建一个盒子边界差不多。然后,这一次,我们把重力设置为0,因为,在我们的breakout游戏中,我们并不需要重力!
让我们往场景里面添加一个精灵并为其创建body。紧接着上面的代码,加入下面的代码片段:
ball = Sprite::create(&Ball.png&, Rect(0, 0, 52, 52));
ball-&setPosition(100, 100);
auto ballBody = PhysicsBody::createCircle(ball-&getContentSize().width / 2.);
ballBody-&getShape(0)-&setRestitution(1.0f);
ballBody-&getShape(0)-&setFriction(0.0f);
ballBody-&getShape(0)-&setDensity(1.0f);
ballBody-&setGravityEnable(false);
Vect force = Vect(f, f);
ballBody-&applyImpulse(force);
ball-&setPhysicsBody(ballBody);
ball-&setTag(1);
this-&addChild(ball);
注意,我们设置这些参数有一点点不一样了:我们把回复力(restitution)设置为1.0,这意味着,我们的球在碰撞的时候,将会是完全弹性碰撞。
注意,我们也把球的摩擦力设置为0.这样可以防止球在碰撞的时候,由于摩擦损失能量,导致来回碰撞的过程中会有一点点偏差。
Vect force = Vect(f, f);
ballBody-&applyImpulse(force);
这里往球上面施加了一个冲力(impulse),这样可以让它初始化的时候朝一个特定的方向运动。
好了,让我们试一下吧。编译并运行工程,你将会看到一个球无限地在屏幕里面来回弹!----很酷吧!
增加 Paddle
如果没有一个paddle的话,那么就不可能称其为一个breakout游戏。
在init方法中构建paddle body:
paddle = Sprite::create(&Paddle.png&);
auto paddleBody = PhysicsBody::createBox(paddle-&getContentSize(), PHYSICSBODY_MATERIAL_DEFAULT);
paddleBody-&getShape(0)-&setRestitution(1.0f);
paddleBody-&getShape(0)-&setFriction(0.0f);
paddleBody-&getShape(0)-&setDensity(10.0f);
paddleBody-&setGravityEnable(false);
paddleBody-&setDynamic(false);
paddle-&setPosition(visibleSize.width / 2, 50);
paddle-&setPhysicsBody(paddleBody);
ball-&setTag(2);
this-&addChild(paddle);
如果你编译并运行的话,你将会看到屏幕中间有一个paddle,而且球碰到它将会反弹。
移动Paddle
移动paddle需要touch事件,所以先在onEnter方法中允许touch和监听碰撞事件(后面会用到):
void HelloWorld::onEnter()
Layer::onEnter();
auto listener = EventListenerTouchOneByOne::create();
listener-&setSwallowTouches(true);
listener-&onTouchMoved = CC_CALLBACK_2(HelloWorld::onTouchMoved, this);
auto contactListener = EventListenerPhysicsContact::create();
contactListener-&onContactBegin = CC_CALLBACK_2(HelloWorld::onContactBegin, this);
auto dispatcher = Director::getInstance()-&getEventDispatcher();
dispatcher-&addEventListenerWithSceneGraphPriority(listener, this);
dispatcher-&addEventListenerWithSceneGraphPriority(contactListener, this);
现在,让我们实现touch方法!首先是onTouchMoved:
void HelloWorld::onTouchMoved(Touch* touch, Event* event)
Point touchLocation = this-&convertToWorldSpace(this-&convertTouchToNodeSpace(touch));
paddle-&setPositionX(touchLocation.x);
限制Paddle的移动
只需要改变paddle的X值就限制了paddle的移动
paddle-&setPositionX(touchLocation.x);
编译并运行,你只能沿关x轴方向移动paddle,这正是我们想要的,不是吧?
给我源代码!
  这里是本教程的
  这里是本教程的完整源代码。这只是一部分,第二部分的教程会包含一个完整的breakout的源码。
接下来呢?
  目前为止,我们已经有一个篮球在屏幕四周来回反弹了,同时还有一个paddle可以用鼠标来控制其移动。在下个教程中,我们将创建一些方块,当球碰到它们的时候,方块就会消失。当然,还有游戏胜利和失败的逻辑!)。这只是一部分,第二部分的教程会包含一个完整的breakout的源码。
接下来呢?
目前为止,我们已经有一个篮球在屏幕四周来回反弹了,同时还有一个paddle可以用鼠标来控制其移动。在下个教程中,我们将创建一些方块,当球碰到它们的时候,方块就会消失。当然,还有游戏胜利和失败的逻辑!
Follow us:
& 本文实践自 Allen Tan 的文章《》,文中使用Cocos2D,我在这里使用Cocos2D-x 2.0.4进行学习和移植。在这篇文章,将会学习到如何制作一个简单的横版格斗过关游戏。在这当中,学习如何跟踪动画状态、碰撞盒、添加方向键、添加简单敌人AI和更多其它的。
步骤如下:
1.新建Cocos2d-win32工程,工程名为&PompaDroid&,去除&Box2D&选项,勾选&Simple Audio Engine in Cocos Denshion&选项;
2.添加游戏场景类GameScene,派生自CCScene类。添加GameLayer类和HudLayer类,派生自CCLayer类。删除HelloWorldScene.h和HelloWorldScene.cpp文件。
3.文件GameScene.h代码如下:
#pragma&once
#include&&cocos2d.h&
#include&&GameLayer.h&
#include&&HudLayer.h&
class&GameScene&:&public&cocos2d::CCScene
&&&&GameScene(void);
&&&&~GameScene(void);
&&&&virtual&bool&init();
&&&&CREATE_FUNC(GameScene);
&&&&CC_SYNTHESIZE(GameLayer*,&_gameLayer,&GameLayer);
&&&&CC_SYNTHESIZE(HudLayer*,&_hudLayer,&HudLayer);
文件GameScene.cpp代码如下:
#include&&GameScene.h&
using&namespace&cocos2d;
GameScene::GameScene(void)
&&&&_gameLayer&=&NULL;
&&&&_hudLayer&=&NULL;
GameScene::~GameScene(void)
bool&GameScene::init()
&&&&bool&bRet&=&false;
&&&&&&&&&CC_BREAK_IF(!CCScene::init());
&&&&&&&&&_gameLayer&=&GameLayer::create();
&&&&&&&&&this-&addChild(_gameLayer,&0);
&&&&&&&&&_hudLayer&=&HudLayer::create();
&&&&&&&&&this-&addChild(_hudLayer,&1);
&&&&&&&&&bRet&=&true;
&&&&}&while&(0);
&&&&return&bR
4.HudLayer类增加一个方法:
CREATE_FUNC(HudLayer);
和GameLayer类增加一个方法:
CREATE_FUNC(GameLayer);
5.修改AppDelegate.cpp文件,代码如下:
//#include&&HelloWorldScene.h&
#include&&GameScene.h&
bool&AppDelegate::applicationDidFinishLaunching()
&&&&//&create&a&scene.&it's&an&autorelease&object
&&&&//CCScene&*pScene&=&HelloWorld::scene();
&&&&CCScene&*pScene&=&GameScene::create();
6.编译运行,此时只是空空的界面。
7.下载本游戏所需资源,将资源放置&Resources&目录下;
8.用Tiled工具打开pd_tilemap.tmx,就可以看到游戏的整个地图:
地图上有两个图层:Wall和Floor,即墙和地板。去掉每个图层前的打钩,可以查看层的组成。你会发现下数第四行是由两个图层一起组成的。每个tile都是32x32大小。可行走的地板tile位于下数三行。
9.打开GameLayer.h文件,添加如下代码:
bool&init();
void&initTileMap();
cocos2d::CCTMXTiledMap&*_tileM
打开GameLayer.cpp,在构造函数,添加如下代码:
_tileMap&=&NULL;
添加如下代码:
bool&GameLayer::init()
&&&&bool&bRet&=&false;
&&&&&&&&CC_BREAK_IF(!CCLayer::init());
&&&&&&&&this-&initTileMap();
&&&&&&&&bRet&=&true;
&&&&}&while&(0);
&&&&return&bR
void&GameLayer::initTileMap()
&&&&_tileMap&=&CCTMXTiledMap::create(&pd_tilemap.tmx&);
&&&&CCObject&*pObject&=&NULL;
&&&&CCARRAY_FOREACH(_tileMap-&getChildren(),&pObject)
&&&&&&&&CCTMXLayer&*child&=&(CCTMXLayer*)pO
&&&&&&&&child-&getTexture()-&setAliasTexParameters();
&&&&this-&addChild(_tileMap,&-6);
对所有图层进行setAliasTexParameters设置,该方法是关闭抗锯齿功能,这样就能保持像素风格。
10.编译运行,可以看到地图显示在屏幕上,如下图所示:
11.创建英雄。在大多数2D横版游戏中,角色有不同的动画代表不同类型的动作。我们需要知道什么时候播放哪个动画。这里采用状态机来解决这个问题。状态机就是某种通过切换状态来改变行为的东西。单一状态机在同一时间只能有一个状态,但可以从一种状态过渡到另一种状态。在这个游戏中,角色共有五种状态,空闲、行走、出拳、受伤、死亡,如下图所示:
为了有一个完整的状态流,每个状态应该有一个必要条件和结果。例如:行走状态不能突然转变到死亡状态,因为你的英雄在死亡前必须先受伤。
12.添加ActionSprite类,派生自CCSprite类,ActionSprite.h文件代码如下:
#pragma&once
#include&&cocos2d.h&
#include&&Defines.h&
class&ActionSprite&:&public&cocos2d::CCSprite
&&&&ActionSprite(void);
&&&&~ActionSprite(void);
&&&&//action&methods
&&&&void&idle();
&&&&void&attack();
&&&&void&hurtWithDamage(float&damage);
&&&&void&knockout();
&&&&void&walkWithDirection(cocos2d::CCPoint&direction);
&&&&//scheduled&methods
&&&&void&update(float&dt);
&&&&//actions
&&&&CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*,&_idleAction,&IdleAction);
&&&&CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*,&_attackAction,&AttackAction);
&&&&CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*,&_walkAction,&WalkAction);
&&&&CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*,&_hurtAction,&HurtAction);
&&&&CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*,&_knockedOutAction,&KnockedOutAction);
&&&&//states
&&&&CC_SYNTHESIZE(ActionState,&_actionState,&ActionState);
&&&&//attributes
&&&&CC_SYNTHESIZE(float,&_walkSpeed,&WalkSpeed);
&&&&CC_SYNTHESIZE(float,&_hitPoints,&HitPoints);
&&&&CC_SYNTHESIZE(float,&_damage,&Damage);
&&&&//movement
&&&&CC_SYNTHESIZE(cocos2d::CCPoint,&_velocity,&Velocity);
&&&&CC_SYNTHESIZE(cocos2d::CCPoint,&_desiredPosition,&DesiredPosition);
&&&&//measurements
&&&&CC_SYNTHESIZE(float,&_centerToSides,&CenterToSides);
&&&&CC_SYNTHESIZE(float,&_centerToBottom,&CenterToBottom);
打开ActionSprite.cpp文件,构造函数如下:
ActionSprite::ActionSprite(void)
&&&&_idleAction&=&NULL;
&&&&_attackAction&=&NULL;
&&&&_walkAction&=&NULL;
&&&&_hurtAction&=&NULL;
&&&&_knockedOutAction&=&NULL;
各个方法实现暂时为空。以上代码声明了基本变量和方法,可以分为以下几类:
Actions:这些是每种状态要执行的动作。这些动作是当角色切换状态时,执行精灵动画和其他触发的事件。
States:保存精灵的当前动作/状态,使用ActionState类型,这个类型待会我们将会进行定义。
Attributes:包含精灵行走速度值,受伤时减少生命点值,攻击伤害值。
Movement:用于计算精灵如何沿着地图移动。
Measurements:保存对精灵的实际图像有用的测量值。需要这些值,是因为你将要使用的这些精灵画布大小是远远大于内部包含的图像。
Action methods:不直接调用动作,而是使用这些方法触发每种状态。
Scheduled methods:任何事需要在一定的时间间隔进行运行,比如精灵位置和速度的更新,等等。
新建一个头文件Defines.h,代码如下:
#pragma&once
#include&&cocos2d.h&
//&1&-&convenience&measurements
#define&SCREEN&CCDirector::sharedDirector()-&getWinSize()
#define&CENTER&ccp(SCREEN.width&/&2,&SCREEN.height&/&2)
#define&CURTIME&do&{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&\
&&&&timeval&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&\
&&&&gettimeofday(&time,&NULL);&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&\
&&&&unsigned&long&millisecs&=&(time.tv_sec&*&1000)&+&(time.tv_usec&/&1000);&\
&&&&return&(float)&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&\
}&while&(0)
//&2&-&convenience&functions
#define&random_range(low,&high)&(rand()&%&(high&-&low&+&1))&+&low
#define&frandom&(float)rand()&/&UINT64_C(0x)
#define&frandom_range(low,&high)&((high&-&low)&*&frandom)&+&low
//&3&-&enumerations
typedef&enum&_ActionState&{
&&&&kActionStateNone&=&0,
&&&&kActionStateIdle,
&&&&kActionStateAttack,
&&&&kActionStateWalk,
&&&&kActionStateHurt,
&&&&kActionStateKnockedOut
//&4&-&structures
typedef&struct&_BoundingBox&{
&&&&cocos2d::CCRect&
&&&&cocos2d::CCRect&
}&BoundingB
简要说明下:
①.定义了一些便利的宏,如直接使用SCREEN获取屏幕大小;
②.定义了一些便利的函数,随机返回整型或者浮点型;
③.定义ActionState类型,这个是ActionSprite可能处在不同状态的类型枚举;
④.定义BoundingBox结构体,将用于碰撞检测。
打开GameLayer.h文件,添加如下代码:
cocos2d::CCSpriteBatchNode&*_
打开GameLayer.cpp文件,在init函数里面添加如下代码:
CCSpriteFrameCache::sharedSpriteFrameCache()-&addSpriteFramesWithFile(&pd_sprites.plist&);
_actors&=&CCSpriteBatchNode::create(&pd_sprites.pvr.ccz&);
_actors-&getTexture()-&setAliasTexParameters();
this-&addChild(_actors,&-5);
加载精灵表单,创建一个CCSpriteBatchNode。这个精灵表单包含我们的所有精灵。它的z值高于CCTMXTiledMap对象,这样才能出现在地图前。
添加Hero类,派生自ActionSprite类,添加如下代码:
CREATE_FUNC(Hero);
bool&init();
Hero类的init函数的实现如下所示:
bool&Hero::init()
&&&&bool&bRet&=&false;
&&&&&&&&CC_BREAK_IF(!ActionSprite::initWithSpriteFrameName(&hero_idle_00.png&));
&&&&&&&&int&i;
&&&&&&&&//idle&animation
&&&&&&&&CCArray&*idleFrames&=&CCArray::createWithCapacity(6);
&&&&&&&&for&(i&=&0;&i&&&6;&i++)
&&&&&&&&&&&&CCSpriteFrame&*frame&=&CCSpriteFrameCache::sharedSpriteFrameCache()-&spriteFrameByName(CCString::createWithFormat(&hero_idle_%02d.png&,&i)-&getCString());
&&&&&&&&&&&&idleFrames-&addObject(frame);
&&&&&&&&CCAnimation&*idleAnimation&=&CCAnimation::createWithSpriteFrames(idleFrames,&1.0&/&12.0);
&&&&&&&&this-&setIdleAction(CCRepeatForever::create(CCAnimate::create(idleAnimation)));
&&&&&&&&this-&setCenterToBottom(39.0);
&&&&&&&&this-&setCenterToSides(29.0);
&&&&&&&&this-&setHitPoints(100.0);
&&&&&&&&this-&setDamage(20.0);
&&&&&&&&this-&setWalkSpeed(80.0);
&&&&&&&&bRet&=&true;
&&&&}&while&(0);
&&&&return&bR
我们用初始空闲精灵帧创建了英雄角色,配备了一个CCArray数组包含所有的属于空闲动画的精灵帧,然后创建一个CCAction动作播放来这个动画。以每秒12帧的速率进行播放。接下去,为英雄设置初始属性,包括精灵中心到边到底部的值。如下图所示:
英雄的每个精灵帧都在280x150像素大小的画布上创建,但实际上英雄精灵只占据这个空间的一部分。所以需要两个测量值,以便更好的设置精灵的位置。需要额外的空间,是因为每个动画精灵绘制的方式是不同的,而有些就需要更多的空间。
打开GameLayer.h文件,添加头文件声明:
#include&&Hero.h&
GameLayer类添加如下代码:
打开GameLayer.cpp文件,在构造函数添加如下代码:
_hero&=&NULL;
在init函数this-&addChild(_actors, -5);后面添加如下代码:
this-&initHero();
添加initHero方法,代码如下:
void&GameLayer::initHero()
&&&&_hero&=&Hero::create();
&&&&_actors-&addChild(_hero);
&&&&_hero-&setPosition(ccp(_hero-&getCenterToSides(),&80));
&&&&_hero-&setDesiredPosition(_hero-&getPosition());
&&&&_hero-&idle();
创建了一个英雄实例,添加到了精灵表单,并设置了设置。调用idle方法,让其处于空闲状态,运行空闲动画。返回到ActionSprite.cpp文件,实现idle方法,代码如下:
void&ActionSprite::idle()
&&&&if&(_actionState&!=&kActionStateIdle)
&&&&&&&&this-&stopAllActions();
&&&&&&&&this-&runAction(_idleAction);
&&&&&&&&_actionState&=&kActionStateI
&&&&&&&&_velocity&=&CCPointZ
这个idle方法只有当ActionSprite不处于空闲状态才能调用。当它触发时,它会执行空闲动作,改变当前状态到kActionStateIdle,并且把速度置零。
13.编译运行,可以看到英雄处于空闲状态。如下图所示:
14.出拳动作。打开Hero.cpp文件,在init函数idle animation后面,添加如下代码:
//attack&animation
CCArray&*attackFrames&=&CCArray::createWithCapacity(3);
for&(i&=&0;&i&&&3;&i++)
&&&&CCSpriteFrame&*frame&=&CCSpriteFrameCache::sharedSpriteFrameCache()-&spriteFrameByName(CCString::createWithFormat(&hero_attack_00_%02d.png&,&i)-&getCString());
&&&&attackFrames-&addObject(frame);
CCAnimation&*attackAnimation&=&CCAnimation::createWithSpriteFrames(attackFrames,&1.0&/&24.0);
this-&setAttackAction(CCSequence::create(CCAnimate::create(attackAnimation),&CCCallFunc::create(this,&callfunc_selector(Hero::idle)),&NULL));
打开ActionSprite.cpp文件,实现attack方法,代码如下:
void&ActionSprite::attack()
&&&&if&(_actionState&==&kActionStateIdle&||&_actionState&==&kActionStateAttack&||&_actionState&==&kActionStateWalk)
&&&&&&&&this-&stopAllActions();
&&&&&&&&this-&runAction(_attackAction);
&&&&&&&&_actionState&=&kActionStateA
英雄只有在空闲、攻击、行走状态才能进行出拳。确保英雄正在受伤时、或者死亡时不能进行攻击。为了触发attack方法,打开GameLayer.cpp文件,在init函数添加如下代码:
this-&setTouchEnabled(true);
重载ccTouchesBegan方法,代码如下:
void&GameLayer::ccTouchesBegan(CCSet&*pTouches,&CCEvent&*pEvent)
&&&&_hero-&attack();
15.编译运行,点击屏幕进行出拳,如下图所示:
16.创建8个方向的方向键。我们需要创建虚拟的8个方向的方向键来让英雄在地图上进行移动。添加SimpleDPad类,派生自CCSprite类,SimpleDPad.h文件代码如下:
#pragma&once
#include&&cocos2d.h&
class&SimpleDP
class&SimpleDPadDelegate
&&&&virtual&void&didChangeDirectionTo(SimpleDPad&*simpleDPad,&cocos2d::CCPoint&direction)&=&0;
&&&&virtual&void&isHoldingDirection(SimpleDPad&*simpleDPad,&cocos2d::CCPoint&direction)&=&0;
&&&&virtual&void&simpleDPadTouchEnded(SimpleDPad&*simpleDPad)&=&0;
class&SimpleDPad&:&public&cocos2d::CCSprite,&public&cocos2d::CCTargetedTouchDelegate
&&&&SimpleDPad(void);
&&&&~SimpleDPad(void);
&&&&static&SimpleDPad*&dPadWithFile(cocos2d::CCString&*fileName,&float&radius);
&&&&bool&initWithFile(cocos2d::CCString&*filename,&float&radius);
&&&&void&onEnterTransitionDidFinish();
&&&&void&onExit();
&&&&void&update(float&dt);
&&&&virtual&bool&ccTouchBegan(cocos2d::CCTouch&*pTouch,&cocos2d::CCEvent&*pEvent);
&&&&virtual&void&ccTouchMoved(cocos2d::CCTouch&*pTouch,&cocos2d::CCEvent&*pEvent);
&&&&virtual&void&ccTouchEnded(cocos2d::CCTouch&*pTouch,&cocos2d::CCEvent&*pEvent);
&&&&void&updateDirectionForTouchLocation(cocos2d::CCPoint&location);
&&&&CC_SYNTHESIZE(SimpleDPadDelegate*,&_delegate,&Delegate);
&&&&CC_SYNTHESIZE(bool,&_isHeld,&IsHeld);
protected:
&&&&float&_
&&&&cocos2d::CCPoint&_
对以上的一些声明,解释如下:
radius:圆形方向键的半径。
direction:当前所按下的方向。这是一个矢量,(-1.0, -1.0)是左下方向,(1.0, 1.0)是右上方向。
delegate:方向键的委托,后续进行介绍。
isHeld:布尔值表示玩家触摸着方向键。
对于SimpleDPad类,使用了委托模式。意味着一个委托类(并非SimpleDPad),将会处理由被委托类(SimpleDPad)启动的任务。在某些你指定的点上,主要是当涉及到处理任何游戏相关的东西,SimpleDPad将会将职责传递给委托类。这使得SimpleDPad无需知道任何游戏逻辑,从而允许你在开发任何其他游戏时,可以进行重用。如下图所示:
当SimpleDPad检测到在方向键内的触摸,它会计算触摸的方向,然后发送消息到委托类指明方向。在这之后的任何事情都不是SimpleDPad所关心的了。为了实施这个模式,SimpleDPad需要至少了解其委托的有关信息,特别是将触摸方向传递给委托的方法。这是另一种设计模式:协议。可以看到SimpleDPad的委托定义了所需的方法,在这种方式中,SimpleDPad强制其委托有三个指定的方法,以便确保每当它想传递东西放到委托中时,它能调用这些方法中的任何一种。事实上,SimpleDPad也遵循一种协议,即CCTargetedTouchDelegate。当SimpleDPad被触摸时,进行处理触摸事件,而GameLayer将不会得到触摸。否则的话,在触摸方向键的时候,英雄就会出拳攻击,显然,这不是希望看到的。打开SimpleDPad.cpp文件,添加如下代码:
#include&&SimpleDPad.h&
using&namespace&cocos2d;
SimpleDPad::SimpleDPad(void)
&&&&_delegate&=&NULL;
SimpleDPad::~SimpleDPad(void)
SimpleDPad*&SimpleDPad::dPadWithFile(CCString&*fileName,&float&radius)
&&&&SimpleDPad&*pRet&=&new&SimpleDPad();
&&&&if&(pRet&&&&pRet-&initWithFile(fileName,&radius))
&&&&&&&&return&pR
&&&&&&&&delete&pR
&&&&&&&&pRet&=&NULL;
&&&&&&&&return&NULL;
bool&SimpleDPad::initWithFile(CCString&*filename,&float&radius)
&&&&bool&bRet&=&false;
&&&&&&&&CC_BREAK_IF(!CCSprite::initWithFile(filename-&getCString()));
&&&&&&&&_radius&=&
&&&&&&&&_direction&=&CCPointZ
&&&&&&&&_isHeld&=&false;
&&&&&&&&this-&scheduleUpdate();
&&&&&&&&bRet&=&true;
&&&&}&while&(0);
&&&&return&bR
void&SimpleDPad::onEnterTransitionDidFinish()
&&&&CCDirector::sharedDirector()-&getTouchDispatcher()-&addTargetedDelegate(this,&1,&true);
void&SimpleDPad::onExit()
&&&&CCDirector::sharedDirector()-&getTouchDispatcher()-&removeDelegate(this);
void&SimpleDPad::update(float&dt)
&&&&if&(_isHeld)
&&&&&&&&_delegate-&isHoldingDirection(this,&_direction);
bool&SimpleDPad::ccTouchBegan(CCTouch&*pTouch,&CCEvent&*pEvent)
&&&&CCPoint&location&=&pTouch-&getLocation();
&&&&float&distanceSQ&=&ccpDistanceSQ(location,&this-&getPosition());
&&&&if&(distanceSQ&&=&_radius&*&_radius)
&&&&&&&&this-&updateDirectionForTouchLocation(location);
&&&&&&&&_isHeld&=&true;
&&&&&&&&return&true;
&&&&return&false;
void&SimpleDPad::ccTouchMoved(CCTouch&*pTouch,&CCEvent&*pEvent)
&&&&CCPoint&location&=&pTouch-&getLocation();
&&&&this-&updateDirectionForTouchLocation(location);
void&SimpleDPad::ccTouchEnded(CCTouch&*pTouch,&CCEvent&*pEvent)
&&&&_direction&=&CCPointZ
&&&&_isHeld&=&false;
&&&&_delegate-&simpleDPadTouchEnded(this);
void&SimpleDPad::updateDirectionForTouchLocation(CCPoint&location)
&&&&float&radians&=&ccpToAngle(ccpSub(location,&this-&getPosition()));
&&&&float&degrees&=&-1&*&CC_RADIANS_TO_DEGREES(radians);
&&&&if&(degrees&&=&22.5&&&&degrees&&=&-22.5)&
&&&&&&&&//right
&&&&&&&&_direction&=&ccp(1.0,&0.0);
&&&&else&if&(degrees&&&22.5&&&&degrees&&&67.5)
&&&&&&&&//bottomright
&&&&&&&&_direction&=&ccp(1.0,&-1.0);
&&&&else&if&(degrees&&=&67.5&&&&degrees&&=&112.5)
&&&&&&&&//bottom
&&&&&&&&_direction&=&ccp(0.0,&-1.0);
&&&&else&if&(degrees&&&112.5&&&&degrees&&&157.5)
&&&&&&&&//bottomleft
&&&&&&&&_direction&=&ccp(-1.0,&-1.0);
&&&&else&if&(degrees&&=&157.5&||&degrees&&=&-157.5)
&&&&&&&&//left
&&&&&&&&_direction&=&ccp(-1.0,&0.0);
&&&&else&if&(degrees&&&-22.5&&&&degrees&&&-67.5)
&&&&&&&&//topright
&&&&&&&&_direction&=&ccp(1.0,&1.0);
&&&&else&if&(degrees&&=&-67.5&&&&degrees&&=&-112.5)
&&&&&&&&//top
&&&&&&&&_direction&=&ccp(0.0,&1.0);
&&&&else&if&(degrees&&&-112.5&&&&degrees&&&-157.5)
&&&&&&&&//topleft
&&&&&&&&_direction&=&ccp(-1.0,&1.0);
&&&&_delegate-&didChangeDirectionTo(this,&_direction);
以上方法中,onEnterTransitionDidFinish注册SimpleDPad委托类,onExit移除SimpleDPad委托类,update方法是当方向键被触摸时,传递方向值到委托类。ccTouchBegan方法检测触摸位置是否在方向键圆内,如果是,则将isHeld置为true,并更新方向值,返回true以拥有触摸事件优先权。ccTouchMoved当触摸点移动时,更新方向值。ccTouchEnded将isHeld置为false,重置方向,并通知委托触摸结束。updateDirectionForTouchLocation方法计算触摸点到方向键中心距离值,转换成角度,得到正确的方向值,然后传递值到委托。
打开HudLayer.h文件,添加头文件声明:
#include&&SimpleDPad.h&
添加如下代码:
bool&init();
CC_SYNTHESIZE(SimpleDPad*,&_dPad,&DPad);
打开HudLayer.cpp文件,添加如下代码:
HudLayer::HudLayer(void)
&&&&_dPad&=&NULL;
bool&HudLayer::init()
&&&&bool&bRet&=&false;
&&&&&&&&CC_BREAK_IF(!CCLayer::init());
&&&&&&&&_dPad&=&SimpleDPad::dPadWithFile(CCString::create(&pd_dpad.png&),&64);
&&&&&&&&_dPad-&setPosition(ccp(64.0,&64.0));
&&&&&&&&_dPad-&setOpacity(100);
&&&&&&&&this-&addChild(_dPad);
&&&&&&&&bRet&=&true;
&&&&}&while&(0);
&&&&return&bR
以上代码实例化SimpleDPad,并且添加到HudLayer上。现在GameScene同时控制GameLayer和HudLayer,但有时候想直接通过HudLayer访问GameLayer。打开GameLayer.h文件,添加头文件声明:
#include&&SimpleDPad.h&
#include&&HudLayer.h&
将GameLayer类声明修改成如下:
class&GameLayer&:&public&cocos2d::CCLayer,&public&SimpleDPadDelegate
并添加以下声明:
virtual&void&didChangeDirectionTo(SimpleDPad&*simpleDPad,&cocos2d::CCPoint&direction);
virtual&void&isHoldingDirection(SimpleDPad&*simpleDPad,&cocos2d::CCPoint&direction);
virtual&void&simpleDPadTouchEnded(SimpleDPad&*simpleDPad);
CC_SYNTHESIZE(HudLayer*,&_hud,&Hud);
以上方法的实现暂时为空。这样我们就在GameLayer中添加了HudLayer的引用,同时还让GameLayer遵循SimpleDPad所创建的协议。打开GameScene.cpp文件,在init函数this-&addChild(_hudLayer,
1);后面,添加如下代码:
_hudLayer-&getDPad()-&setDelegate(_gameLayer);
_gameLayer-&setHud(_hudLayer);
17.编译运行,可以看到左下角的虚拟方向键,如下图所示:
别试着压下方向键,英雄不会有任何反应,因为还未实现协议方法,这在将完成。
参考资料:
1.How To Make A Side-Scrolling Beat ‘Em Up Game Like Scott Pilgrim with Cocos2D – Part 1
2.如何使用cocos2d制作类似Scott Pilgrim的2D横版格斗过关游戏part1(翻译)
3.如何使用Cocos2d-x做一DNF类的游戏-part1
非常感谢以上资料,本例子源代码附加资源下载地址:
如文章存在错误之处,欢迎指出,以便改正。
在第一篇《》基础上,增加角色运动、碰撞、敌人、AI和音乐音效,原文《》,在这里继续以Cocos2d-x进行实现。有关源码、资源等在文章下面给出了地址。
步骤如下:
1.使用上一篇的工程;
2.移动英雄。在第一部分我们创建了虚拟方向键,但是还未实现按下方向键移动英雄,现在让我们进行实现。打开Hero.cpp文件,在init函数attack animation后面,添加如下代码:
//walk&animation
CCArray&*walkFrames&=&CCArray::createWithCapacity(8);
for&(i&=&0;&i&&&8;&i++)
&&&&CCSpriteFrame&*frame&=&CCSpriteFrameCache::sharedSpriteFrameCache()-&spriteFrameByName(CCString::createWithFormat(&hero_walk_%02d.png&,&i)-&getCString());
&&&&walkFrames-&addObject(frame);
CCAnimation&*walkAnimation&=&CCAnimation::createWithSpriteFrames(walkFrames,&float(1.0&/&12.0));
this-&setWalkAction(CCRepeatForever::create(CCAnimate::create(walkAnimation)));
打开ActionSprite.cpp文件,实现walkWithDirection方法,代码如下:
void&ActionSprite::walkWithDirection(CCPoint&direction)
&&&&if&(_actionState&==&kActionStateIdle)
&&&&&&&&this-&stopAllActions();
&&&&&&&&this-&runAction(_walkAction);
&&&&&&&&_actionState&=&kActionStateW
&&&&if&(_actionState&==&kActionStateWalk)
&&&&&&&&_velocity&=&ccp(direction.x&*&_walkSpeed,&direction.y&*&_walkSpeed);
&&&&&&&&if&(_velocity.x&&=&0)
&&&&&&&&&&&&this-&setScaleX(1.0);
&&&&&&&&}&
&&&&&&&&else
&&&&&&&&&&&&this-&setScaleX(-1.0);
这段代码,检查前置动作状态是否空闲,若是的话切换动作到行走。在行走状态时,根据_walkSpeed值改变精灵速度。同时检查精灵的左右方向,并通过将精灵scaleX设置为1或-1来翻转精灵。要让英雄的行走动作跟方向键联系起来,需要借助方向键的委托:GameLayer类。打开GameLayer.cpp文件,实现如下方法:
void&GameLayer::didChangeDirectionTo(SimpleDPad&*simpleDPad,&CCPoint&direction)
&&&&_hero-&walkWithDirection(direction);
void&GameLayer::isHoldingDirection(SimpleDPad&*simpleDPad,&CCPoint&direction)
&&&&_hero-&walkWithDirection(direction);
void&GameLayer::simpleDPadTouchEnded(SimpleDPad&*simpleDPad)
&&&&if&(_hero-&getActionState()&==&kActionStateWalk)
&&&&&&&&_hero-&idle();
此时,编译运行程序的话,通过方向键移动英雄,发现英雄只是原地踏步。改变英雄的位置是ActionSprite和GameLayer共同的责任。一个ActionSprite永远不会知道它在地图上的位置。因此,它并不知道已经到达了地图的边缘,它只知道它想去哪里。而GameLayer的责任就是将它的期望位置转换成实际的位置。打开ActionSprite.cpp文件,实现以下方法:
void&ActionSprite::update(float&dt)
&&&&if&(_actionState&==&kActionStateWalk)
&&&&&&&&_desiredPosition&=&ccpAdd(this-&getPosition(),&ccpMult(_velocity,&dt));
这个方法在每次游戏更新场景的时候都会进行调用,当精灵处于行走状态时,它更新精灵的期望位置。位置+速度*时间,实际上就是意味着每秒移动X和Y点。打开GameLayer.cpp文件,在init函数this-&initTileMap();后面添加如下代码:
this-&scheduleUpdate();
在析构函数,添加如下代码:
GameLayer::~GameLayer(void)
&&&&this-&unscheduleUpdate();
增加如下两个方法:
void&GameLayer::update(float&dt)
&&&&_hero-&update(dt);
&&&&this-&updatePositions();
void&GameLayer::updatePositions()
&&&&float&posX&=&MIN(_tileMap-&getMapSize().width&*&_tileMap-&getTileSize().width&-&_hero-&getCenterToSides(),
&&&&&&&&MAX(_hero-&getCenterToSides(),&_hero-&getDesiredPosition().x));
&&&&float&posY&=&MIN(3&*&_tileMap-&getTileSize().height&+&_hero-&getCenterToBottom(),
&&&&&&&&MAX(_hero-&getCenterToBottom(),&_hero-&getDesiredPosition().y));
&&&&_hero-&setPosition(ccp(posX,&posY));
设定GameLayer的更新方法,每次循环时,GameLayer让英雄更新它的期望位置,然后通过以下这些值,将期望位置进行检查是否在地图地板的范围内:
mapSize:地图tile数量。总共有10x100个tile,但只有3x100属于地板。
tileSize:每个tile的尺寸,在这里是32x32像素。
GameLayer还使用到了ActionSprite的两个测量值,centerToSides和centerToBottom,因为ActionSprite要想保持在场景内,它的位置不能超过实际的精灵边界。假如ActionSprite的位置在已经设置的边界内,则GameLayer让英雄达到期望位置,否则GameLayer会让英雄留停在原地。
3.编译运行,此时点击方向键,移动英雄,如下图所示:
但是,很快你就会发现英雄可以走出地图的右边界,然后就这样从屏幕上消失了。
4.以上的问题,可以通过基于英雄的位置进行滚动地图,这个方法在文章《》中有描述过。打开GameLayer.cpp文件,在update函数里最后添加如下代码:
this-&setViewpointCenter(_hero-&getPosition());
添加如下方法:
void&GameLayer::setViewpointCenter(CCPoint&position)
&&&&CCSize&winSize&=&CCDirector::sharedDirector()-&getWinSize();
&&&&int&x&=&MAX(position.x,&winSize.width&/&2);
&&&&int&y&=&MAX(position.y,&winSize.height&/&2);
&&&&x&=&MIN(x,&(_tileMap-&getMapSize().width&*&_tileMap-&getTileSize().width)&-&winSize.width&/&2);
&&&&y&=&MIN(y,&(_tileMap-&getMapSize().height&*&_tileMap-&getTileSize().height)&-&winSize.height&/&2);
&&&&CCPoint&actualPosition&=&ccp(x,&y);
&&&&CCPoint&centerOfView&=&ccp(winSize.width&/&2,&winSize.height&/&2);
&&&&CCPoint&viewPoint&=&ccpSub(centerOfView,&actualPosition);
&&&&this-&setPosition(viewPoint);
以上代码让英雄处于屏幕中心位置,当然,英雄在地图边界时的情况除外。编译运行,效果如下图所示:
5.创建机器人。我们已经创建了精灵的基本模型:ActionSprite。我们可以重用它来创建游戏中电脑控制的角色。新建Robot类,派生自ActionSprite类,增加如下方法:
CREATE_FUNC(Robot);
bool&init();
打开Robot.cpp文件,init函数代码如下:
bool&Robot::init()
&&&&bool&bRet&=&false;
&&&&&&&&CC_BREAK_IF(!ActionSprite::initWithSpriteFrameName(&robot_idle_00.png&));
&&&&&&&&int&i;
&&&&&&&&//idle&animation
&&&&&&&&CCArray&*idleFrames&=&CCArray::createWithCapacity(5);
&&&&&&&&for&(i&=&0;&i&&&5;&i++)
&&&&&&&&&&&&CCSpriteFrame&*frame&=&CCSpriteFrameCache::sharedSpriteFrameCache()-&spriteFrameByName(
&&&&&&&&&&&&&&&&CCString::createWithFormat(&robot_idle_%02d.png&,&i)-&getCString());
&&&&&&&&&&&&idleFrames-&addObject(frame);
&&&&&&&&CCAnimation&*idleAnimation&=&CCAnimation::createWithSpriteFrames(idleFrames,&float(1.0&/&12.0));
&&&&&&&&this-&setIdleAction(CCRepeatForever::create(CCAnimate::create(idleAnimation)));
&&&&&&&&//attack&animation
&&&&&&&&CCArray&*attackFrames&=&CCArray::createWithCapacity(5);
&&&&&&&&for&(i&=&0;&i&&&5;&i++)
&&&&&&&&&&&&CCSpriteFrame&*frame&=&CCSpriteFrameCache::sharedSpriteFrameCache()-&spriteFrameByName(
&&&&&&&&&&&&&&&&CCString::createWithFormat(&robot_attack_%02d.png&,&i)-&getCString());
&&&&&&&&&&&&attackFrames-&addObject(frame);
&&&&&&&&CCAnimation&*attackAnimation&=&CCAnimation::createWithSpriteFrames(attackFrames,&float(1.0&/&24.0));
&&&&&&&&this-&setAttackAction(CCSequence::create(CCAnimate::create(attackAnimation),&CCCallFunc::create(this,&callfunc_selector(Robot::idle)),&NULL));
&&&&&&&&//walk&animation
&&&&&&&&CCArray&*walkFrames&=&CCArray::createWithCapacity(6);
&&&&&&&&for&(i&=&0;&i&&&6;&i++)
&&&&&&&&&&&&CCSpriteFrame&*frame&=&CCSpriteFrameCache::sharedSpriteFrameCache()-&spriteFrameByName(
&&&&&&&&&&&&&&&&CCString::createWithFormat(&robot_walk_%02d.png&,&i)-&getCString());
&&&&&&&&&&&&walkFrames-&addObject(frame);
&&&&&&&&CCAnimation&*walkAnimation&=&CCAnimation::createWithSpriteFrames(walkFrames,&float(1.0&/&12.0));
&&&&&&&&this-&setWalkAction(CCRepeatForever::create(CCAnimate::create(walkAnimation)));
&&&&&&&&this-&setWalkSpeed(80.0);
&&&&&&&&this-&setCenterToBottom(39.0);
&&&&&&&&this-&setCenterToSides(29.0);
&&&&&&&&this-&setHitPoints(100.0);
&&&&&&&&this-&setDamage(10.0);
&&&&&&&&bRet&=&true;
&&&&}&while&(0);
&&&&return&bR
跟英雄一样,以上代码创建一个带有3个动作的机器人:空闲、出拳、行走。它也有两个测量值:centerToBottom和centerToSides。注意到机器人的属性比英雄低一点,这是合乎逻辑的,不然英雄永远打不赢机器人。让我们开始添加一些机器人到游戏中去。打开GameLayer.h文件,添加如下代码:
CC_SYNTHESIZE_RETAIN(cocos2d::CCArray*,&_robots,&Robots);
打开GameLayer.cpp文件,添加头文件如下:
#include&&Robot.h&
在构造函数里,添加如下代码:
_robots&=&NULL;
在init函数this-&initTileMap();的后面添加如下代码:
this-&initRobots();
添加如下方法:
void&GameLayer::initRobots()
&&&&int&robotCount&=&50;
&&&&this-&setRobots(CCArray::createWithCapacity(robotCount));
&&&&for&(int&i&=&0;&i&&&robotC&i++)
&&&&&&&&Robot&*robot&=&Robot::create();
&&&&&&&&_actors-&addChild(robot);
&&&&&&&&_robots-&addObject(robot);
&&&&&&&&int&minX&=&SCREEN.width&+&robot-&getCenterToSides();
&&&&&&&&int&maxX&=&_tileMap-&getMapSize().width&*&_tileMap-&getTileSize().width&-&robot-&getCenterToSides();
&&&&&&&&int&minY&=&robot-&getCenterToBottom();
&&&&&&&&int&maxY&=&3&*&_tileMap-&getTileSize().height&+&robot-&getCenterToBottom();
&&&&&&&&robot-&setScaleX(-1);
&&&&&&&&robot-&setPosition(ccp(random_range(minX,&maxX),&random_range(minY,&maxY)));
&&&&&&&&robot-&setDesiredPosition(robot-&getPosition());
&&&&&&&&robot-&idle();
这些代码做了以下事情:
创建一个包含50个机器人的数组,并把它们添加到精灵表单中。使用Defines.h里面的随机函数随机放置50个机器人到地图地板上。同时,让最小随机值大于屏幕宽度,以确保不会有任何机器人出现在起点处。让每个机器人都处于空闲状态。
编译运行,让英雄向前走,直到看到地图上的机器人,如下图所示:
试着走到机器人区域中,你会发现机器人的绘制有些不对。如果英雄是在机器人的下面,那么他应该被绘制在机器人的前面,而不是在后面。我们需要明确的告诉游戏,哪个对象先绘制,这就是Z轴来进行控制的。添加英雄和机器人时,并没有明确指定其Z轴,默认下,后面添加的对象会比前面的对象Z轴值高,这就是为什么机器人挡住了英雄。为了解决这个问题,我们需要动态的处理Z轴顺序。每当精灵在屏幕上垂直移动时,它的Z轴值应该有所改变。屏幕上越高的精灵,其Z轴值应越低。打开GameLayer.cpp文件,添加如下方法:
void&GameLayer::reorderActors()
&&&&CCObject&*pObject&=&NULL;
&&&&CCARRAY_FOREACH(_actors-&getChildren(),&pObject)
&&&&&&&&ActionSprite&*sprite&=&(ActionSprite*)pO
&&&&&&&&_actors-&reorderChild(sprite,&(_tileMap-&getMapSize().height&*&_tileMap-&getTileSize().height)&-&sprite-&getPosition().y);
然后在update函数this-&updatePositions();的后面,添加如下代码:
this-&reorderActors();
每当精灵的位置更新,这个方法会让CCSpriteBatchNode重新设置它的每个子节点Z轴值,其根据子节点离地图底部的距离,当子节点离底部更高时,其Z轴值就会下降。编译运行,可以看到正确的绘制顺序,如下图所示:
6.出拳猛击机器人,碰撞检测。为了让英雄能够出拳,并且能够实际上打在了机器人身上,需要实现一种方式的碰撞检测。在这篇文章中,我们使用矩形创建一个非常简单的碰撞检测系统。在这个系统中,我们为每个角色定义两种矩形/盒子:
Hit box:代表精灵的身体Attack box:代表精灵的手
假如某个ActionSprite的Attack box碰撞到另一个ActionSprite的Hit box,那么这就是一次碰撞发生。这两个矩形之间的区别,将帮助我们知道谁打了谁。Defines.h文件中的BoundingBox定义,包含两种矩形:实际的,和原始的:
①原始矩形,每个精灵的基本矩形,一旦设置后就不会改变。
②实际矩形,这是位于世界空间中的矩形,当精灵移动时,实际的矩形也跟着变动。
打开ActionSprite.h文件,添加如下代码:
CC_SYNTHESIZE(BoundingBox,&_hitBox,&Hitbox);
CC_SYNTHESIZE(BoundingBox,&_attackBox,&AttackBox);
BoundingBox&createBoundingBoxWithOrigin(cocos2d::CCPoint&origin,&cocos2d::CCSize&size);
以上创建了ActionSprite的两个包围盒:Hit box和Attack box。还定义了一个方法,用于根据给定的原点和大小来创建一个BoundingBox结构体。打开ActionSprite.cpp文件,添加如下方法:
BoundingBox&ActionSprite::createBoundingBoxWithOrigin(CCPoint&origin,&CCSize&size)
&&&&BoundingBox&boundingB
&&&&boundingBox.original.origin&=&
&&&&boundingBox.original.size&=&
&&&&boundingBox.actual.origin&=&ccpAdd(this-&getPosition(),&ccp(boundingBox.original.origin.x,&boundingBox.original.origin.y));
&&&&boundingBox.actual.size&=&
&&&&return&boundingB
void&ActionSprite::transformBoxes()
&&&&_hitBox.actual.origin&=&ccpAdd(this-&getPosition(),&ccp(_hitBox.original.origin.x,&_hitBox.original.origin.y));
&&&&_attackBox.actual.origin&=&ccpAdd(this-&getPosition(),&ccp(_attackBox.original.origin.x&+&
&&&&&&&&(this-&getScaleX()&==&-1&?&(-&_attackBox.original.size.width&-&_hitBox.original.size.width)&:&0),
&&&&&&&&_attackBox.original.origin.y));
void&ActionSprite::setPosition(CCPoint&position)
&&&&CCSprite::setPosition(position);
&&&&this-&transformBoxes();
第一个方法创建一个新的包围盒,这有助于ActionSprite的子类创建属于它们自己的包围盒。第二个方法,基于精灵的位置、比例因子,和包围盒原本的原点和大小来更新每个包围盒实际测量的原点和大小。之所以要用到比例因子,是因为它决定着精灵的方向。位于精灵右侧的盒子,当比例因子设置为-1时,将会翻转到左侧。打开Hero.cpp文件,在init函数后面添加如下代码:
this-&setHitbox(this-&createBoundingBoxWithOrigin(ccp(-this-&getCenterToSides(),&-this-&getCenterToBottom()),
&&&&CCSizeMake(this-&getCenterToSides()&*&2,&this-&getCenterToBottom()&*&2)));
this-&setAttackBox(this-&createBoundingBoxWithOrigin(ccp(this-&getCenterToSides(),&-10),&CCSizeMake(20,&20)));
打开Robot.cpp文件,在init函数后面添加如下代码:
this-&setHitbox(this-&createBoundingBoxWithOrigin(ccp(-this-&getCenterToSides(),&-this-&getCenterToBottom()),
&&&&CCSizeMake(this-&getCenterToSides()&*&2,&this-&getCenterToBottom()&*&2)));
this-&setAttackBox(this-&createBoundingBoxWithOrigin(ccp(this-&getCenterToSides(),&-5),&CCSizeMake(25,&20)));
现在我们已经有了英雄和机器人各自的Hit box和Attack box。如果是可视化的箱子,它们会像下面这样:
无论何时,当一个attack box(红色)跟一个hit box(蓝色)交叉,即一次碰撞发生。在开始编写代码,检测包围盒交叉前,需要确保ActionSprite能够对被击中有所反应。我们已经添加了空闲、出拳、行走动作,但还未创建受伤和死亡动作。打开ActionSprite.cpp文件,实现如下方法:
void&ActionSprite::hurtWithDamage(float&damage)
&&&&if&(_actionState&!=&kActionStateKnockedOut)
&&&&&&&&this-&stopAllActions();
&&&&&&&&this-&runAction(_hurtAction);
&&&&&&&&_actionState&=&kActionStateH
&&&&&&&&_hitPoints&-=&
&&&&&&&&if&(_hitPoints&&=&0)
&&&&&&&&&&&&this-&knockout();
void&ActionSprite::knockout()
&&&&this-&stopAllActions();
&&&&this-&runAction(_knockedOutAction);
&&&&_hitPoints&=&0;
&&&&_actionState&=&kActionStateKnockedO
只要精灵还未死亡,被击中时状态将会切换到受伤状态,执行受伤动画,并且精灵的生命值将会减去相应的伤害值。如果生命值少于0,那么死亡的动作将会触发。为了完成这两个动作,我们还需更改Hero类和Robot类。打开Hero.cpp文件,在init函数walk
animation后面添加如下代码:
//hurt&animation
CCArray&*hurtFrames&=&CCArray::createWithCapacity(3);
for&(i&=&0;&i&&&3;&i++)
&&&&CCSpriteFrame&*frame&=&CCSpriteFrameCache::sharedSpriteFrameCache()-&spriteFrameByName(CCString::createWithFormat(&hero_hurt_%02d.png&,&i)-&getCString());
&&&&hurtFrames-&addObject(frame);
CCAnimation&*hurtAnimation&=&CCAnimation::createWithSpriteFrames(hurtFrames,&float(1.0&/&12.0));
this-&setHurtAction(CCSequence::create(CCAnimate::create(hurtAnimation),&CCCallFunc::create(this,&callfunc_selector(Hero::idle)),&NULL));
//knocked&out&animation
CCArray&*knockedOutFrames&=&CCArray::createWithCapacity(5);
for&(i&=&0;&i&&&5;&i++)
&&&&CCSpriteFrame&*frame&=&CCSpriteFrameCache::sharedSpriteFrameCache()-&spriteFrameByName(CCString::createWithFormat(&hero_knockout_%02d.png&,&i)-&getCString());
&&&&knockedOutFrames-&addObject(frame);
CCAnimation&*knockedOutAnimation&=&CCAnimation::createWithSpriteFrames(knockedOutFrames,&float(1.0&/&12.0));
this-&setKnockedOutAction(CCSequence::create(CCAnimate::create(knockedOutAnimation),&CCBlink::create(2.0,&10.0),&NULL));
打开Robot.cpp文件,在init函数walk animation后面添加如下代码:
//hurt&animation
CCArray&*hurtFrames&=&CCArray::createWithCapacity(3);
for&(i&=&0;&i&&&3;&i++)
&&&&CCSpriteFrame&*frame&=&CCSpriteFrameCache::sharedSpriteFrameCache()-&spriteFrameByName(CCString::createWithFormat(&robot_hurt_%02d.png&,&i)-&getCString());
&&&&hurtFrames-&addObject(frame);
CCAnimation&*hurtAnimation&=&CCAnimation::createWithSpriteFrames(hurtFrames,&float(1.0&/&12.0));
this-&setHurtAction(CCSequence::create(CCAnimate::create(hurtAnimation),&CCCallFunc::create(this,&callfunc_selector(Robot::idle)),&NULL));
//knocked&out&animation
CCArray&*knockedOutFrames&=&CCArray::createWithCapacity(5);
for&(i&=&0;&i&&&5;&i++)
&&&&CCSpriteFrame&*frame&=&CCSpriteFrameCache::sharedSpriteFrameCache()-&spriteFrameByName(CCString::createWithFormat(&robot_knockout_%02d.png&,&i)-&getCString());
&&&&knockedOutFrames-&addObject(frame);
CCAnimation&*knockedOutAnimation&=&CCAnimation::createWithSpriteFrames(knockedOutFrames,&float(1.0&/&12.0));
this-&setKnockedOutAction(CCSequence::create(CCAnimate::create(knockedOutAnimation),&CCBlink::create(2.0,&10.0),&NULL));
以上代码应该不陌生了。我们用创建其他动作同样的方式创建了受伤和死亡动作。受伤动作结束时,会切换到空闲状态。死亡动作结束时,精灵进行闪烁。打开GameLayer.cpp文件,添加碰撞处理,在ccTouchesBegan函数后面添加如下代码:
if&(_hero-&getActionState()&==&kActionStateAttack)
&&&&CCObject&*pObject&=&NULL;
&&&&CCARRAY_FOREACH(_robots,&pObject)
&&&&&&&&Robot&*robot&=&(Robot*)pO
&&&&&&&&if&(robot-&getActionState()&!=&kActionStateKnockedOut)
&&&&&&&&&&&&if&(fabsf(_hero-&getPosition().y&-&robot-&getPosition().y)&&&10)
&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&if&(_hero-&getAttackBox().actual.intersectsRect(robot-&getHitbox().actual))
&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&robot-&hurtWithDamage(_hero-&getDamage());
&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&}
&&&&}&&&&&&&
以上代码通过三个简单步骤来检测碰撞:
①.检测英雄是否处于攻击状态,以及机器人是否处于非死亡状态。
②.检测英雄的位置和机器人的位置垂直相距在10个点以内。这表明它们在同一平面上站立。
③.检测英雄的attack box是否与机器人的hit box进行交叉。
如果这些条件都成立,那么则一次碰撞发生,机器人执行受伤动作。英雄的伤害值作为参数进行传递,这样该方法就会知道需要减去多少生命值。
7.编译运行,出拳攻击机器人吧,效果如下图所示:
8.简单机器人AI的实现。为了使机器人能够移动,并且能够使用我们为它们所创建的动作,就需要开发一个简单的AI(人工智能)系统。这个AI系统基于决策机制。在特定的时间间隔里,我们给每个机器人一个机会来决定接下来该做什么。它们需要知道的第一件事情就是何时做出选择。打开Robot.h文件,添加如下代码:
CC_SYNTHESIZE(float,&_nextDecisionTime,&NextDecisionTime);
打开Robot.cpp文件,在init函数后面,添加如下代码:
_nextDecisionTime&=&0;
这个属性保存下一次机器人可以作出决定的时间。打开Defines.h文件,修改成如下代码:
#pragma&once
#include&&cocos2d.h&
//&1&-&convenience&measurements
#define&SCREEN&CCDirector::sharedDirector()-&getWinSize()
#define&CENTER&ccp(SCREEN.width&/&2,&SCREEN.height&/&2)
#define&CURTIME&GetCurTime()
//&2&-&convenience&functions
#ifndef&UINT64_C
#define&UINT64_C(val)&val##ui64
#define&random_range(low,&high)&(rand()&%&(high&-&low&+&1))&+&low
#define&frandom&(float)rand()&/&UINT64_C(0x)
#define&frandom_range(low,&high)&((high&-&low)&*&frandom)&+&low
//&3&-&enumerations
typedef&enum&_ActionState&{
&&&&kActionStateNone&=&0,
&&&&kActionStateIdle,
&&&&kActionStateAttack,
&&&&kActionStateWalk,
&&&&kActionStateHurt,
&&&&kActionStateKnockedOut
//&4&-&structures
typedef&struct&_BoundingBox&{
&&&&cocos2d::CCRect&
&&&&cocos2d::CCRect&
}&BoundingB
inline&float&GetCurTime(){
&&&&timeval&
&&&&gettimeofday(&time,&NULL);
&&&&unsigned&long&millisecs&=&(time.tv_sec&*&1000)&+&(time.tv_usec&/&1000);
&&&&return&(float)
打开GameLayer.cpp文件,添加如下方法:
void&GameLayer::updateRobots(float&dt)
&&&&int&alive&=&0;
&&&&float&distanceSQ;
&&&&int&randomChoice&=&0;
&&&&CCObject&*pObject&=&NULL;
&&&&CCARRAY_FOREACH(_robots,&pObject)
&&&&&&&&Robot&*robot&=&(Robot*)pO
&&&&&&&&robot-&update(dt);
&&&&&&&&if&(robot-&getActionState()&!=&kActionStateKnockedOut)
&&&&&&&&&&&&//1
&&&&&&&&&&&&alive++;
&&&&&&&&&&&&
&&&&&&&&&&&&//2
&&&&&&&&&&&&if&(CURTIME&&&robot-&getNextDecisionTime())
&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&distanceSQ&=&ccpDistanceSQ(robot-&getPosition(),&_hero-&getPosition());
&&&&&&&&&&&&&&&&//3
&&&&&&&&&&&&&&&&if&(distanceSQ&&=&50&*&50)
&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&robot-&setNextDecisionTime(CURTIME&+&frandom_range(0.1,&0.5)&*&1000);
&&&&&&&&&&&&&&&&&&&&randomChoice&=&random_range(0,&1);
&&&&&&&&&&&&&&&&&&&&if&(randomChoice&==&0)
&&&&&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&if&(_hero-&getPosition().x&&&robot-&getPosition().x)
&&&&&&&&&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&&&&&robot-&setScaleX(1.0);
&&&&&&&&&&&&&&&&&&&&&&&&}&
&&&&&&&&&&&&&&&&&&&&&&&&else
&&&&&&&&&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&&&&&robot-&setScaleX(-1.0);
&&&&&&&&&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&&&&&&&&&//4
&&&&&&&&&&&&&&&&&&&&&&&&robot-&setNextDecisionTime(robot-&getNextDecisionTime()&+&frandom_range(0.1,&0.5)&*&2000);
&&&&&&&&&&&&&&&&&&&&&&&&robot-&attack();&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&if&(robot-&getActionState()&==&kActionStateAttack)
&&&&&&&&&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&&&&&if&(fabsf(_hero-&getPosition().y&-&robot-&getPosition().y)&&&10)
&&&&&&&&&&&&&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&if&(_hero-&getHitbox().actual.intersectsRect(robot-&getAttackBox().actual))
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&_hero-&hurtWithDamage(robot-&getDamage());
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&//end&game&checker&here
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&&&&&else
&&&&&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&robot-&idle();
&&&&&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&else&if&(distanceSQ&&=&SCREEN.width&*&SCREEN.width)
&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&//5
&&&&&&&&&&&&&&&&&&&&robot-&setNextDecisionTime(CURTIME&+&frandom_range(0.5,&1.0)&*&1000);
&&&&&&&&&&&&&&&&&&&&randomChoice&=&random_range(0,&2);
&&&&&&&&&&&&&&&&&&&&if&(randomChoice&==&0)
&&&&&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&CCPoint&moveDirection&=&ccpNormalize(ccpSub(_hero-&getPosition(),&robot-&getPosition()));
&&&&&&&&&&&&&&&&&&&&&&&&robot-&walkWithDirection(moveDirection);
&&&&&&&&&&&&&&&&&&&&}&
&&&&&&&&&&&&&&&&&&&&else
&&&&&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&robot-&idle();
&&&&&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&}
&&&&//end&game&checker&here
这是一个漫长的代码片段。将代码分解为一段段。对于游戏中的每个机器人:
①.使用一个计数来保存仍然存活着的机器人数量。一个机器人只要不是死亡状态,就被认为仍然存活着。这将用于判断游戏是否应该结束。
②.检查当前应用程序时间的推移是否超过了机器人的下一次决定时间。如果超过了,意味着机器人需要作出一个新的决定。
③.检查机器人是否足够接近英雄,以便于有机会出拳攻击落在英雄身上。如果接近英雄了,那么就进行一个随机选择,看是要朝着英雄出拳,还是要继续空闲着。
④.假如机器人决定攻击,我们就用之前检测英雄攻击时相同的方式来进行检测碰撞。只是这一次,英雄和机器人的角色互换了。
⑤.如果机器人和英雄之间的距离小于屏幕宽度,那么机器人将作出决定,要么朝着英雄移动,要么继续空闲。机器人的移动基于英雄位置和机器人位置产生的法向量。
每当机器人作出决定,它的下一个决定的时间被设定为在未来的一个随机时间。在此期间,它将继续执行上次作出决定时所做出的动作。接着在update函数里,this-&updatePositions();前添加如下代码:
this-&updateRobots(dt);
在updatePositions函数后面,添加如下代码:
CCObject&*pObject&=&NULL;
CCARRAY_FOREACH(_robots,&pObject)
&&&&Robot&*robot&=&(Robot*)pO
&&&&posX&=&MIN(_tileMap-&getMapSize().width&*&_tileMap-&getTileSize().width&-&robot-&getCenterToSides(),
&&&&&&&&MAX(robot-&getCenterToSides(),&robot-&getDesiredPosition().x));
&&&&posY&=&MIN(3&*&_tileMap-&getTileSize().height&+&robot-&getCenterToBottom(),
&&&&&&&&MAX(robot-&getCenterToBottom(),&robot-&getDesiredPosition().y));
&&&&robot-&setPosition(ccp(posX,&posY));
确保每次游戏循环时,机器人AI方法都被调用。遍历每个机器人,并让它们朝着期望的位置进行移动。
9.编译运行,将会看到沿着走廊过来的机器人。效果如下图所示:
10.为游戏添加重新开始的按钮。打开GameLayer.cpp文件,添加头文件引用:
#include&&GameScene.h&
添加如下方法:
void&GameLayer::endGame()
&&&&CCLabelTTF&*restartLabel&=&CCLabelTTF::create(&RESTART&,&&Arial&,&30);
&&&&CCMenuItemLabel&*restartItem&=&CCMenuItemLabel::create(restartLabel,&this,&menu_selector(GameLayer::restartGame));
&&&&CCMenu&*menu&=&CCMenu::create(restartItem,&NULL);
&&&&menu-&setPosition(CENTER);
&&&&menu-&setTag(5);
&&&&_hud-&addChild(menu,&5);
void&GameLayer::restartGame(CCObject*&pSender)
&&&&CCDirector::sharedDirector()-&replaceScene(GameScene::create());
第一个方法创建显示一个重新开始的按钮,当按下它时,触发第二个方法。后者只是命令导演用新的GameScene实例替换当前场景。接着在updateRobots函数里面,在第一个end game checker here注释后面,添加如下代码:
if&(_hero-&getActionState()&==&kActionStateKnockedOut&&&&_hud-&getChildByTag(5)&==&NULL)
&&&&this-&endGame();
在第二个end game checker here注释后面,添加如下代码:
if&(alive&==&0&&&&_hud-&getChildByTag(5)&==&NULL)
&&&&this-&endGame();
这些语句都是检测游戏结束的条件。第一个检测英雄被机器人攻击后,是否还存活着。如果英雄死亡了,那么游戏就结束了。第二个检测是否所有的机器人都死亡了。如果都死亡了,那么游戏也结束了。另外,在endGame方法里,可以看到游戏结束菜单的tag值为5。因为检测是在循环里面,需要确保游戏结束菜单之前没被创建过。否则的话,将会一直创建游戏结束菜单。
11.编译运行,可以看到游戏结束时的样子,如下图所示:
12.音乐和音效。打开GameLayer.cpp文件,添加头文件引用:
#include&&SimpleAudioEngine.h&
在init函数里,CC_BREAK_IF(!CCLayer::init());后面添加如下代码:
//&Load&audio
CocosDenshion::SimpleAudioEngine::sharedEngine()-&preloadBackgroundMusic(&latin_industries.aifc&);
CocosDenshion::SimpleAudioEngine::sharedEngine()-&playBackgroundMusic(&latin_industries.aifc&);
CocosDenshion::SimpleAudioEngine::sharedEngine()-&preloadEffect(&pd_hit0.wav&);
CocosDenshion::SimpleAudioEngine::sharedEngine()-&preloadEffect(&pd_hit1.wav&);
CocosDenshion::SimpleAudioEngine::sharedEngine()-&preloadEffect(&pd_herodeath.wav&);
CocosDenshion::SimpleAudioEngine::sharedEngine()-&preloadEffect(&pd_botdeath.wav&);
打开ActionSprite.cpp文件,添加头文件引用:
#include&&SimpleAudioEngine.h&
在hurtWithDamage函数,第一个条件语句里添加如下代码:
int&randomSound&=&random_range(0,&1);
CocosDenshion::SimpleAudioEngine::sharedEngine()-&playEffect(CCString::createWithFormat(&pd_hit%d.wav&,&randomSound)-&getCString());
打开ActionSprite.h文件,将knockout方法声明修改如下:
virtual&void&knockout();
打开Hero.cpp文件,添加头文件引用:
#include&&SimpleAudioEngine.h&
添加如下方法:
void&Hero::knockout()
&&&&ActionSprite::knockout();
&&&&CocosDenshion::SimpleAudioEngine::sharedEngine()-&playEffect(&pd_herodeath.wav&);
打开Robot.cpp文件,添加头文件引用:
#include&&SimpleAudioEngine.h&
添加如下方法:
void&Robot::knockout()
&&&&ActionSprite::knockout();
&&&&CocosDenshion::SimpleAudioEngine::sharedEngine()-&playEffect(&pd_botdeath.wav&);
13.编译运行,现在游戏将有配乐,效果图:
参考资料:
1.How To Make A Side-Scrolling Beat ‘Em Up Game Like Scott Pilgrim with Cocos2D – Part 2
2.如何使用cocos2d制作类似Scott Pilgrim的2D横版格斗过关游戏part2(翻译)
非常感谢以上资料,本例子源代码附加资源下载地址:
如文章存在错误之处,欢迎指出,以便改正
对此示例的内存泄露修正说明:《》
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:740594次
积分:11234
积分:11234
排名:第384名
原创:361篇
转载:168篇
评论:220条
(1)(9)(11)(8)(3)(28)(10)(29)(8)(3)(3)(1)(1)(1)(1)(2)(5)(6)(17)(12)(1)(3)(2)(4)(2)(2)(28)(8)(13)(4)(1)(1)(18)(1)(5)(51)(21)(4)(3)(5)(5)(2)(3)(10)(11)(14)(24)(31)(36)(15)(50)(26)(1)(7)

我要回帖

更多关于 cocos2d x下载 的文章

 

随机推荐