如何制作一个横版格斗过关游戏 Cocos2d x 2 0 4

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

               

      本文实践自 Allen Tan 的文章《How To Make A Side-Scrolling Beat ‘Em Up Game Like Scott Pilgrim with Cocos2D – Part 1》,文中使用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.hHelloWorldScene.cpp文件。
3.文件GameScene.h代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#pragma once
#include  "cocos2d.h"
#include  "GameLayer.h"
#include  "HudLayer.h"

class GameScene :  public cocos2d::CCScene
{
public:
    GameScene( void);
    ~GameScene( void);

     virtual  bool init();
    CREATE_FUNC(GameScene);

    CC_SYNTHESIZE(GameLayer*, _gameLayer, GameLayer);
    CC_SYNTHESIZE(HudLayer*, _hudLayer, HudLayer);
};

文件GameScene.cpp代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include  "GameScene.h"
using  namespace cocos2d;

GameScene::GameScene( void)
{
    _gameLayer =  NULL;
    _hudLayer =  NULL;
}

GameScene::~GameScene( void)
{
}

bool GameScene::init()
{
     bool bRet =  false;
     do 
    {
         CC_BREAK_IF(!CCScene::init());
         
         _gameLayer = GameLayer::create();
          this->addChild(_gameLayer,  0);
         _hudLayer = HudLayer::create();
          this->addChild(_hudLayer,  1);
         
         bRet =  true;
    }  while ( 0);

     return bRet;
}

4.HudLayer类增加一个方法:

1
CREATE_FUNC(HudLayer);
GameLayer类增加一个方法:
1
CREATE_FUNC(GameLayer);
5.修改 AppDelegate.cpp文件,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
//#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文件,添加如下代码:

1
2
3
4
bool init();
void initTileMap();

cocos2d::CCTMXTiledMap *_tileMap;

打开GameLayer.cpp,在构造函数,添加如下代码:

1
_tileMap =  NULL;
添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
bool GameLayer::init()
{
     bool bRet =  false;
     do 
    {
        CC_BREAK_IF(!CCLayer::init());

         this->initTileMap();
        
        bRet =  true;
    }  while ( 0);

     return bRet;
}

void GameLayer::initTileMap()
{
    _tileMap = CCTMXTiledMap::create( "pd_tilemap.tmx");
    CCObject *pObject =  NULL;
    CCARRAY_FOREACH(_tileMap->getChildren(), pObject)
    {
        CCTMXLayer *child = (CCTMXLayer*)pObject;
        child->getTexture()->setAliasTexParameters();
    }
     this->addChild(_tileMap, - 6);
}

对所有图层进行setAliasTexParameters设置,该方法是关闭抗锯齿功能,这样就能保持像素风格。
10.编译运行,可以看到地图显示在屏幕上,如下图所示:

11.创建英雄。在大多数2D横版游戏中,角色有不同的动画代表不同类型的动作。我们需要知道什么时候播放哪个动画。这里采用状态机来解决这个问题。状态机就是某种通过切换状态来改变行为的东西。单一状态机在同一时间只能有一个状态,但可以从一种状态过渡到另一种状态。在这个游戏中,角色共有五种状态,空闲、行走、出拳、受伤、死亡,如下图所示:

为了有一个完整的状态流,每个状态应该有一个必要条件和结果。例如:行走状态不能突然转变到死亡状态,因为你的英雄在死亡前必须先受伤。
12.添加ActionSprite类,派生自CCSprite类,ActionSprite.h文件代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#pragma once
#include  "cocos2d.h"
#include  "Defines.h"

class ActionSprite :  public cocos2d::CCSprite
{
public:
    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文件,构造函数如下:

1
2
3
4
5
6
7
8
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,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#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 time;                                                           \
    gettimeofday(&time,  NULL);                                              \
     unsigned  long millisecs = (time.tv_sec *  1000) + (time.tv_usec /  1000); \
     return ( float)millisecs;                                                \
while ( 0)

// 2 - convenience functions
#define random_range(low, high) (rand() % (high - low +  1)) + low
#define frandom ( float)rand() / UINT64_C(0x100000000)
#define frandom_range(low, high) ((high - low) * frandom) + low

// 3 - enumerations
typedef  enum _ActionState {
    kActionStateNone =  0,
    kActionStateIdle,
    kActionStateAttack,
    kActionStateWalk,
    kActionStateHurt,
    kActionStateKnockedOut
} ActionState;

// 4 - structures
typedef  struct _BoundingBox {
    cocos2d::CCRect actual;
    cocos2d::CCRect original;
} BoundingBox;
简要说明下:
①.定义了一些便利的宏,如直接使用SCREEN获取屏幕大小;
②.定义了一些便利的函数,随机返回整型或者浮点型;
③.定义ActionState类型,这个是ActionSprite可能处在不同状态的类型枚举;
④.定义BoundingBox结构体,将用于碰撞检测。
打开 GameLayer.h文件,添加如下代码:
1
cocos2d::CCSpriteBatchNode *_actors;
打开 GameLayer.cpp文件,在 init函数里面添加如下代码:
1
2
3
4
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类,添加如下代码:

1
2
CREATE_FUNC(Hero);
bool init();
Hero类的 init函数的实现如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
bool Hero::init()
{
     bool bRet =  false;
     do 
    {
        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 bRet;
}

我们用初始空闲精灵帧创建了英雄角色,配备了一个CCArray数组包含所有的属于空闲动画的精灵帧,然后创建一个CCAction动作播放来这个动画。以每秒12帧的速率进行播放。接下去,为英雄设置初始属性,包括精灵中心到边到底部的值。如下图所示:

英雄的每个精灵帧都在280x150像素大小的画布上创建,但实际上英雄精灵只占据这个空间的一部分。所以需要两个测量值,以便更好的设置精灵的位置。需要额外的空间,是因为每个动画精灵绘制的方式是不同的,而有些就需要更多的空间。
打开GameLayer.h文件,添加头文件声明:

1
#include  "Hero.h"
GameLayer类添加如下代码:
1
Hero *_hero;
打开 GameLayer.cpp文件,在构造函数添加如下代码:
1
_hero =  NULL;
init函数 this->addChild(_actors, -5);后面添加如下代码:
1
this->initHero();
添加 initHero方法,代码如下:
1
2
3
4
5
6
7
8
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方法,代码如下:
1
2
3
4
5
6
7
8
9
10
void ActionSprite::idle()
{
     if (_actionState != kActionStateIdle)
    {
         this->stopAllActions();
         this->runAction(_idleAction);
        _actionState = kActionStateIdle;
        _velocity = CCPointZero;
    }
}
这个 idle方法只有当ActionSprite不处于空闲状态才能调用。当它触发时,它会执行空闲动作,改变当前状态到 kActionStateIdle,并且把速度置零。
13.编译运行,可以看到英雄处于空闲状态。如下图所示:

14.出拳动作。打开 Hero.cpp文件,在 init函数idle animation后面,添加如下代码:
1
2
3
4
5
6
7
8
9
//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方法,代码如下:
1
2
3
4
5
6
7
8
9
void ActionSprite::attack()
{
     if (_actionState == kActionStateIdle || _actionState == kActionStateAttack || _actionState == kActionStateWalk)
    {
         this->stopAllActions();
         this->runAction(_attackAction);
        _actionState = kActionStateAttack;
    }
}
英雄只有在空闲、攻击、行走状态才能进行出拳。确保英雄正在受伤时、或者死亡时不能进行攻击。为了触发 attack方法,打开 GameLayer.cpp文件,在 init函数添加如下代码:
1
this->setTouchEnabled( true);
重载 ccTouchesBegan方法,代码如下:
1
2
3
4
void GameLayer::ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent)
{
    _hero->attack();
}
15.编译运行,点击屏幕进行出拳,如下图所示:

16.创建8个方向的方向键。我们需要创建虚拟的8个方向的方向键来让英雄在地图上进行移动。添加 SimpleDPad类,派生自 CCSprite类, SimpleDPad.h文件代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#pragma once
#include  "cocos2d.h"

class SimpleDPad;

class SimpleDPadDelegate
{
public:
     virtual  void didChangeDirectionTo(SimpleDPad *simpleDPad, cocos2d::CCPoint direction) =  0;
     virtual  void isHoldingDirection(SimpleDPad *simpleDPad, cocos2d::CCPoint direction) =  0;
     virtual  void simpleDPadTouchEnded(SimpleDPad *simpleDPad) =  0

给我老师的人工智能教程打call!http://blog.csdn.net/jiangjunshow

这里写图片描述

你可能感兴趣的:(如何制作一个横版格斗过关游戏 Cocos2d x 2 0 4)