知易游戏开发教程cocos2d-x移植版006

在上一节中,我们使用经典FC游戏《坦克大战》的元素设计了一张地图,来演示Tiled Map Editor工具的基本用法,并在cocos2d-x程序中完成了tmx地图加载、查看以及动态修改地图元素的功能。

这一节,我们将对示例5进一步扩充、完善,使其成为能“玩”一下的游戏。

为了这个目标,我们需要做以下调整:

1)增加双方坦克,我方一辆,敌方八辆。
2)坦克在地图上行走时,需要完成基本的碰撞检测,不可以穿墙而过。
3)坦克可以开炮,炮弹可以摧毁砖墙和敌对方坦克。
4)视角锁定在我方坦克上,显示区域跟随我方坦克的移动而改变。
5)敌方(电脑)坦克拥有基本的AI来增加点儿乐趣。

增加坦克

我们的目标只是一个简单的示例游戏,所以我准备用同一个类来表示双方坦克,通过一个布尔型变量来区分敌我。

1 class TankSprite : public CCSprite
2 {
3 public:
4     TankSprite(void);
5     virtual ~TankSprite(void);
6 
7     bool bIsEnemy;
8 }

在地图中漫游

作为一个“完整”的游戏,我们需要实现在游戏地图中漫游的功能。这包含两方面的内容,下面我们来分别描述一下。

1)坦克精灵在地图上的移动

游戏讲究一个代入感。是谁在玩游戏?不是上帝,我只是一辆小小的坦克。当操作指令下达时,应该是一辆坦克去完成它,而不是通过上帝的眼睛来观察。所以,现在需要完成的功能就是让坦克在tmx地图上移动。

要实现这样的功能并不困难,因为CCSprite精灵类为我们准备好的大部分内容。我们要做的只是为坦克增加当前的状态、移动速度以及四个方向的移动函数。

1     // TankAction 是一个枚举类型,用来表示坦克当期的状态
2     TankAction kAct;
3     float moveStep;
4     void moveLeft(GameLayer *layer);
5     void moveRight(GameLayer *layer);
6     void moveUp(GameLayer *layer);
7     void moveDown(GameLayer *layer);

在实现四方向移动函数时,因为坦克是tmx地图的子节点,所以坐标不需要太多计算。

 1 void TankSprite::moveLeft(GameLayer *layer)
 2 {
 3     kAct = kLeft;
 4     setRotation(-90.0f);
 5     CCPoint tankPos = getPosition();
 6     tankPos.x -= moveStep;
 7     CCSize tankSize = getTextureRect().size;
 8 
 9     // 越界检测
10     if (tankPos.x - layer->mapX < tankSize.width / 2)
11         setPosition(ccp(tankSize.width / 2, tankPos.y));
12     else
13         setPosition(tankPos);
14 }

2)视口跟随

完成上面的功能,坦克就可以在地图上行走了。但是我们只能看见屏幕大小的地图,坦克很容易就走到屏幕之外去了。我们不愿意做一只“井底之蛙”,眼睛要跟上坦克。

我们前面介绍过视口这个概念,它就是整个游戏世界向我们打开的一扇窗子。而且,在示例5中我们也尝试了移动视口来观察整个游戏地图。我们现在要做的就是加一个视口跟随的功能,让视口跟随主角(我方)坦克车移动,将合适的地图区域展示给我们。

纵观大多数人的设计,对于视口跟随,一个普遍的做法是这样的。

大部分时间都将主角精灵固定在视口中心,主角的移动是通过反方向移动背景地图模拟的。只有当视口已经到达地图边缘,再没有更多地图供我们移动时,才移动主角本身。

句子有点儿拗口,没理解的朋友请看下面的演示。

将上面的过程写成代码:

 1 void GameLayer::setWorldPosition(void)
 2 {
 3     CCSize tankSize = tank->getTextureRect().size;
 4     CCPoint tankPos = tank->getPosition();
 5 
 6     if (tankPos.x < (screenWidth - tankSize.width) / 2)
 7         mapX = 0;
 8     else if (tankPos.x > gameWorldWidth() - screenWidth / 2)
 9         mapX = -gameWorldWidth();
10     else
11         mapX = -(tankPos.x - screenWidth / 2 + tankSize.width / 2);
12 
13     if (tankPos.y < (screenHeight - tankSize.height) / 2)
14         mapY = 0;
15     else if (tankPos.y > gameWorldHeight() - screenHeight / 2)
16         mapY = -gameWorldHeight();
17     else
18         mapY = -(tankPos.y - screenHeight / 2 + tankSize.height / 2);
19 
20     // 越界复位
21     if (mapX > 0)
22         mapX = 0;
23     if (mapY > 0)
24         mapY = 0;
25     if (mapX < -(gameWorldWidth() - screenWidth))
26         mapX = -(gameWorldWidth() - screenWidth);
27     if (mapY < -(gameWorldHeight() - screenHeight))
28         mapY = -(gameWorldHeight() - screenHeight);
29     gameWorld->setPosition(mapX, mapY);
30 }

碰撞检测

坦克也不是说是无坚不摧的,所以遇到河啊什么的,还是从桥上走方便一些。所以我们就得判断是不是没路了,是不是撞墙了,于是“碰撞检测”的概念就引入进来了。

试想,有8辆敌方坦克正在地图上游荡,他们撞墙要检测,互相之间也要检测,他们偶尔还会发射炮弹,每个炮弹的飞行和爆炸也都需要检测。而且这些都是并发进行的。哇!多么混乱的一个场面啊!

呵呵,其实“碰撞检测”并没有大家想的那么复杂,不是说要做碰撞检测就要弄个物理引擎进来,只要不是在实现逼真的物理效果,我们完全可以用自己的方法来检测碰撞。

大家知道,什么同时啊,并行啊,这些统统都是理论上的,或者说是在一定精度范围上的东西。即便是多核CPU,在共享同一资源时,它们也要分优先级的。所以,在小游戏这类简单的应用上,我们完全可以认为,一次碰撞发生时,世界是静止的。

这样一来,我们只需要按照一定的优先级,顺次为每一个需要检测碰撞的对象进行检测即可。

对于坦克的行走来说,我们需要做的检测只有地形一个因素,又因为是在同一坐标系下,事情灰常简单。

考虑到坦克是有体积的,这里取3个采样点检测碰撞,以避免其边缘进入墙里。

 1     // 这是坦克左移时的碰撞检测代码
 2     CCPoint nextPos = ccp(tankPos.x - tankSize.width / 2, tankPos.y);
 3     unsigned int tid = layer->tileIDFromPosition(nextPos);
 4     if (tid != 4)
 5         return;
 6     nextPos = ccp(tankPos.x - tankSize.width / 2, tankPos.y + tankSize.height / 4);
 7     tid = layer->tileIDFromPosition(nextPos);
 8     if (tid != 4)
 9         return;
10     nextPos = ccp(tankPos.x - tankSize.width / 2, tankPos.y - tankSize.height / 4);
11     tid = layer->tileIDFromPosition(nextPos);
12     if (tid != 4)
13         return;

开火射击

瞧,那辆破坦克摇头摆尾地开过来了,让我们干掉他。Fire! BOOM...

好吧,既然你想开火,那就给每辆坦克都配上炮弹吧。

 1 BulletSprite* BulletSprite::bulletWithLayer(GameLayer *layer)
 2 {
 3     BulletSprite *sprite = new BulletSprite();
 4     if (sprite && sprite->initWithFile("bullet.png", CCRectMake(0, 0, 16, 16)))
 5     {
 6         sprite->autorelease();
 7         layer->gameWorld->addChild(sprite, 100);
 8         sprite->setIsVisible(false);
 9         sprite->setGameLayer(layer);
10         return sprite;
11     }
12     CC_SAFE_DELETE(sprite);
13     return NULL;
14 }

虽然炮弹是坦克的配备,但是为了方便坐标计算,我们将炮弹归为地图的子节点。换个思路想,炮弹发射后就跟坦克分离了,所以我们这么设计也是可以接受的。

理所当然的,我们还需要为每一辆坦克增加一个开火按钮。

 1 void TankSprite::onFire(GameLayer *layer)
 2 {
 3     CCPoint tankPos = getPosition();
 4     CCSize tankSize = getTextureRect().size;
 5     CCPoint bulletPos, bulletTarget;
 6     switch (kAct)
 7     {
 8     case kUp:
 9         bulletPos = ccp(tankPos.x, tankPos.y + tankSize.height / 2);
10         bulletTarget = ccp(bulletPos.x, bulletPos.y + 1024);
11         break;
12     case kDown:
13         bulletPos = ccp(tankPos.x, tankPos.y - tankSize.height / 2);
14         bulletTarget = ccp(bulletPos.x, bulletPos.y - 1024);
15         break;
16     case kLeft:
17         bulletPos = ccp(tankPos.x - tankSize.width / 2, tankPos.y);
18         bulletTarget = ccp(bulletPos.x - 1024, bulletPos.y);
19         break;
20     case kRight:
21         bulletPos = ccp(tankPos.x + tankSize.width / 2, tankPos.y);
22         bulletTarget = ccp(bulletPos.x + 1024, bulletPos.y);
23         break;
24     default:
25         break;
26     }
27     bullet->setPosition(bulletPos);
28     bullet->setIsVisible(true);
29     CCShow *ac1 = CCShow::action();
30     CCMoveTo *ac2 = CCMoveTo::actionWithDuration(5.0f, bulletTarget);
31     bullet->runAction(CCSequence::actions(ac1, ac2, NULL));
32     // 启动炮弹的碰撞检测
33     this->schedule(schedule_selector(TankSprite::checkExplosion), 1.0f / 30.0f);
34 }

大家可以看到,炮弹发射是用系统内置的MoveTo动作模拟的,最后一行开启每秒30次的炮弹碰撞检测。检测的方法与行走时的检测类似,这里不再重复。

小结

至此,一个简单的坦克大战演示就初步完成了。为了给大家一个更形象的认识,上一张层结构示意图。

本章示例代码基于cocos2d-1.0.1-x-0.13.0-beta编写。虽然cocos2d-2.0-rc0a-x-2.0已经发布了,但是变动比较大,主要是得换模板向导,我比较懒,所以本系列结束之前,我先不换版本了。

下载地址

http://files.cnblogs.com/cocos2d-x/ZYG006.rar

posted @ 2012-06-03 18:25 Bugs Bunny 阅读(...) 评论(...) 编辑 收藏