Cocos2d-x 3.x《飞机大战》教程4:游戏场景
在解决了开发环境、项目搭建、菜单场景设置、素材准备,并确定了物理引擎的使用之后,接下来的重点便是构建游戏场景,这主要包括背景与我方飞机的创建、敌机的创建以及物理世界的构建。
一、背景与我方飞机的创建
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.h
和 GameScene.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. 添加我方飞机精灵
上一节已经介绍过如何设置滚动背景和创建飞机,这里不再赘述。不过,这次我们要为飞机添加物理世界的实体。在 GameScene
的 Init
方法中添加以下代码:
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); // 手指移动
然后,在 GameScene
的 init
方法中注册监听器:
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);
然后,在 GameScene
的 init
方法中设置两个定时器:
// 我机发射子弹
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);
然后,在 GameScene
的 init
方法中创建一个定时器:
// 敌机创建
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--;
}
}
至此,本节的内容就完成了。运行项目,你将看到敌机和我方飞机都能正常移动,并且飞机可以发射子弹。
下一节我们将处理敌我碰撞、分数计算以及音乐播放等内容。