Cocos2d-x 3.x《飞机大战》教程4:游戏场景

2015年03月18日 10:01 0 点赞 0 评论 更新于 2017-05-05 10:23

在解决了开发环境、项目搭建、菜单场景设置、素材准备,并确定了物理引擎的使用之后,接下来的重点便是构建游戏场景,这主要包括背景与我方飞机的创建、敌机的创建以及物理世界的构建。

一、背景与我方飞机的创建

1. 切换到游戏场景

首先,我们要创建一个新的游戏场景。当玩家选择开始游戏时,程序将从菜单场景跳转到游戏场景。为此,我们需要完善 HelloWorldScene 的代码。找到开始游戏的回调方法,并添加以下代码:

// 开始游戏
void HelloWorld::menuStartCallback(Ref* pSender)
{
auto scene = GameScene::createScene();  // 这个场景类理应先创建好的。为了线性介绍只能这样了。
auto gameScene = TransitionSlideInR::create(1.0f, scene);
Director::getInstance()->replaceScene(gameScene);
}

2. 创建游戏场景类

接着,创建新的场景类 GameScene 相关的 GameScene.hGameScene.cpp 文件,代码格式可参照 HelloWorldScene。需要注意的是,创建的代码文件必须放置在 classes 目录下,否则会出现错误。

GameScene 的创建场景方法中,添加如下代码:

Scene* GameScene::createScene()
{
auto scene = Scene::createWithPhysics();  // 创建物理世界的场景
// PhysicsWorld* phyWorld = scene->getPhysicsWorld();  // 测试专用,如果发布就注释掉就好了。
// phyWorld->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);
auto layer = GameScene::create();
scene->addChild(layer);
return scene;
}

至此,我们的物理世界场景就创建完成了,接下来将添加各种精灵。

3. 添加我方飞机精灵

上一节已经介绍过如何设置滚动背景和创建飞机,这里不再赘述。不过,这次我们要为飞机添加物理世界的实体。在 GameSceneInit 方法中添加以下代码:

auto plane = Sprite::create("hero1.png");
plane->setPosition(visibleSize.width / 2 + origin.x, 200);
plane->setTag(103);  // 设置物理世界实体
// 这里只有我机、敌机、子弹,子弹和敌机可以碰撞,敌机和我机可以碰撞,我机和子弹不可以碰撞
auto planeBody = PhysicsBody::createBox(plane->getContentSize());
planeBody->setContactTestBitmask(0x0003);  // 碰撞测试掩码
planeBody->setCategoryBitmask(0x0001);      // 类别掩码
planeBody->setCollisionBitmask(0x0007);     // 碰撞掩码
planeBody->setGravityEnable(false);         // 设置重力无效,飞机是在天空中的,别让它掉下来。
plane->setPhysicsBody(planeBody);
this->addChild(plane);

// 启动飞机动画
Animation * animation = Animation::create();
SpriteFrame * spriteFrame1 = SpriteFrame::create("hero1.png", Rect(0, 0, 102, 126));  // 优化可以用 SpriteFrameCache 用法查 Api
SpriteFrame * spriteFrame2 = SpriteFrame::create("hero2.png", Rect(0, 0, 102, 126));
animation->addSpriteFrame(spriteFrame1);
animation->addSpriteFrame(spriteFrame2);
animation->setDelayPerUnit(0.15f);
Animate * animate = Animate::create(animation);
plane->runAction(RepeatForever::create(animate));  // 执行动画

4. 实现飞机的触屏控制

目前飞机是静止的,根据游戏逻辑,飞机应跟随玩家的手指上下移动。为此,我们需要添加一个触屏监听事件来控制飞机的移动。

首先,在 GameScene.h 文件的 public 部分添加如下代码:

int status;         // 游戏状态  1为正常、2为暂停、3为结束
float fx, fy;       // 用来记录手指点击的开始位置
// 触屏事件 ,由系统监听
virtual bool onTouchBegan(cocos2d::Touch * touch, cocos2d::Event * event);  // 手指首次点击
virtual void onTouchMoved(cocos2d::Touch * touch, cocos2d::Event * event);  // 手指移动

然后,在 GameSceneinit 方法中注册监听器:

setTouchEnabled(true);  // 设置为单点触碰
setTouchMode(Touch::DispatchMode::ONE_BY_ONE);

接着,实现触屏反应函数:

// 手指点击下时,记录该点的位置,该点为起点
bool GameScene::onTouchBegan(Touch * touch, Event * event)
{
if (status == 1)
{
fx = touch->getLocation().x;
fy = touch->getLocation().y;
}
return true;
}

// 每次移动把移动的位置(终点)记录下来,并与之前记录下的位置相减,得到飞机该位移的相对量(x、y轴移动多少),并刷新起点位置
void GameScene::onTouchMoved(Touch * touch, Event * event)
{
if (status == 1)
{
int mx = (touch->getLocation().x - fx);
int my = (touch->getLocation().y - fy);
auto spPlane = this->getChildByTag(103);
spPlane->runAction(MoveBy::create(0, Point(mx, my)));
fx = touch->getLocation().x;
fy = touch->getLocation().y;
}
}

现在,你可以编译运行项目,查看飞机跟随手指移动的效果。

5. 实现飞机发射子弹

为了实现飞机发射子弹的效果,我们将使用定时器。让定时器每隔一段时间调用一个函数,在飞机的位置创建一个子弹精灵,并使用 Vector 存储所有的子弹。然后,再创建一个定时器,让所有的子弹每隔一段时间向上移动。

首先,在 GameScene.h 中声明相关变量和函数:

// 存储所有的子弹
cocos2d::Vector<Sprite*> bulletList;
// 子弹创建的定时器回调函数
void bulletCreate(float f);
// 让子弹飞和让敌机飞  因为敌机和子弹移动的速度一样,不用创建多个定时器
void objectMove(float f);

然后,在 GameSceneinit 方法中设置两个定时器:

// 我机发射子弹
this->schedule(schedule_selector(GameScene::bulletCreate), 0.3);
// 让子弹飞
this->schedule(schedule_selector(GameScene::objectMove), 0.01);

最后,实现定时器的回调方法:

// 创建子弹
void GameScene::bulletCreate(float f)
{
SimpleAudioEngine::getInstance()->playEffect("sounds/bullet.wav");
auto plane = this->getChildByTag(103);
Sprite * bullet = Sprite::create("bullet.png");
bullet->setPosition(plane->getPosition().x, plane->getPosition().y + 60);
bullet->setTag(106);
auto bulletBody = PhysicsBody::createBox(bullet->getContentSize());
bulletBody->setContactTestBitmask(0x0002);
bulletBody->setCategoryBitmask(0x0005);
bulletBody->setCollisionBitmask(0x0002);
bulletBody->setGravityEnable(false);
bullet->setPhysicsBody(bulletBody);
this->addChild(bullet);
this->bulletList.pushBack(bullet);
}

// 让子弹飞
void GameScene::objectMove(float f)
{
// 遍历 vector 取出所有的子弹,让子弹的位置往上移
for (int i = 0; i < bulletList.size(); i++)
{
auto bullet = bulletList.at(i);
bullet->setPositionY(bullet->getPositionY() + 3);
// 如果该子弹已经超出屏幕范围,则移除它
if (bullet->getPositionY() > Director::getInstance()->getWinSize().height)
{
bullet->removeFromParent();  // 从层中移除
bulletList.eraseObject(bullet);  // 从记录所有子弹的 vector 中移除
// 移除后上一个对象会移到当前这个对象的位置,实际还是当前这个 i,所以要 i-- 才能访问到下一个对象
i--;
}
}

// 取出所有的敌机,让敌机往下移动
for (int i = 0; i < enemyList.size(); i++)
{
auto enemy = enemyList.at(i);
enemy->setPositionY(enemy->getPositionY() - 5);
// 如果该敌机已经超出屏幕范围,则移除它
if (enemy->getPositionY() < -enemy->getContentSize().height)
{
enemy->removeFromParent();  // 从层中移除
enemyList.eraseObject(enemy);  // 从记录所有敌机的 vector 中移除
// 移除后上一个对象会移到当前这个对象的位置,实际还是当前这个 i,所以要 i-- 才能访问到下一个对象
i--;
}
}
}

至此,飞机就可以发射子弹了。

二、敌机的创建

敌机的创建方法与子弹类似,但敌机的位置是在屏幕最上方随机生成的。

首先,在 GameScene.h 中声明相关变量和函数:

// 用来存储所有的敌机
cocos2d::Vector<Sprite*> enemyList;
// 创建敌机
void enemyCreate(float f);

然后,在 GameSceneinit 方法中创建一个定时器:

// 敌机创建
this->schedule(schedule_selector(GameScene::enemyCreate), 0.5);

最后,实现创建敌机的函数:

// 敌机创建
void GameScene::enemyCreate(float f)
{
// 随机出现敌机1或敌机2
int ranDom = rand() % 2 + 1;
auto string = cocos2d::__String::createWithFormat("enemy%d.png", ranDom);
auto enemy = Sprite::create(string->getCString());
if (ranDom == 1)
{
enemy->setTag(104);  // 敌机的类型,由这个来判断,用于分数计算
}
else
{
enemy->setTag(105);
}
enemy->setPosition(Vec2(rand() % (int)(Director::getInstance()->getVisibleSize().width), Director::getInstance()->getVisibleSize().height + enemy->getContentSize().height));  // 随机在屏幕最上方的出现敌机
auto enemyBody = PhysicsBody::createBox(enemy->getContentSize());  // 创建物理实体
enemyBody->setContactTestBitmask(0x0003);
enemyBody->setCategoryBitmask(0x0002);
enemyBody->setCollisionBitmask(0x0001);
enemyBody->setGravityEnable(false);
enemy->setPhysicsBody(enemyBody);
this->addChild(enemy);
this->enemyList.pushBack(enemy);
}

由于敌机的飞行速度与子弹的飞行速度相同,因此只需在 void GameScene::objectMove(float f) 方法中添加敌机移动的代码即可:

// 取出所有的敌机,让敌机往下移动
for (int i = 0; i < enemyList.size(); i++)
{
auto enemy = enemyList.at(i);
enemy->setPositionY(enemy->getPositionY() - 5);
// 如果该敌机已经超出屏幕范围,则移除它
if (enemy->getPositionY() < -enemy->getContentSize().height)
{
enemy->removeFromParent();  // 从层中移除
enemyList.eraseObject(enemy);  // 从记录所有敌机的 vector 中移除
// 移除后上一个对象会移到当前这个对象的位置,实际还是当前这个 i,所以要 i-- 才能访问到下一个对象
i--;
}
}

至此,本节的内容就完成了。运行项目,你将看到敌机和我方飞机都能正常移动,并且飞机可以发射子弹。

下一节我们将处理敌我碰撞、分数计算以及音乐播放等内容。

相关教程

作者信息

boke

boke

共发布了 1025 篇文章