曾经,微信里面可以玩一个打飞机的小游戏,很有趣,后来又没有了,这里基于原版素材写了一个高仿微信打飞机的小游戏
预览
工程结构
环境
- Mac os Mojave
- xcode 7.0
- cocos2dx 3.17
代码目录
游戏架构
主要包括以下场景
步骤
菜单场景
游戏主菜单界面,进入游戏的入口界面
bool MainMenuScene::init()
{
if ( !Scene::init() )
{
return false;
}
auto visible_size = Director::getInstance()->getVisibleSize();
auto visible_origin = Director::getInstance()->getVisibleOrigin();
Sprite* background = Sprite::create("img/background.png");
background->setContentSize(visible_size);
background->setPosition(visible_origin.x + visible_size.width / 2,
visible_origin.y + visible_size.height / 2);
addChild(background);
Sprite* title = Sprite::create("img/title.png");
title->setScale(1.5);
title->setPosition(visible_origin.x + visible_size.width / 2,
visible_origin.y + visible_size.height / 2 + 40);
addChild(title);
Button* start_btn = Button::create("img/game_resume_nor.png", "img/game_resume_pressed.png", "img/game_resume_nor.png");
start_btn->setScale(1.5);
start_btn->setPosition(Vec2(visible_origin.x + visible_size.width / 2,
visible_origin.y + visible_size.height / 2));
start_btn->addTouchEventListener([&](Ref* sender, Widget::TouchEventType type){
switch (type)
{
case ui::Widget::TouchEventType::BEGAN:
SimpleAudioEngine::getInstance()->playEffect("sound/button.wav");
break;
case ui::Widget::TouchEventType::ENDED:
{
auto game_scene = GameScene::createScene();
TransitionScene* transition_scene = TransitionFade::create(0.5, game_scene);
Director::getInstance()->replaceScene(transition_scene);
}
break;
default:
break;
}
});
addChild(start_btn);
return true;
}
游戏场景
游戏场景就是主要的游戏逻辑,控制玩家飞机发射子弹攻击敌机,拾取道具等
数据结构
在场景中,玩家、敌机、子弹、道具、天空背景都是不同的对象,分别具有不同的行为
玩家
class Player : public cocos2d::Sprite
{
public:
virtual bool init();
CREATE_FUNC(Player);
public:
Bullet* shootSingle();
cocos2d::Vector<Bullet*> shootDouble();
void fetchWeapon(WeaponType weapon_type);
void destroy();
BulletType m_bullet_type;
};
子弹
enum BulletType
{
BASE,
POWER
};
class Bullet : public cocos2d::Sprite
{
public:
virtual bool init();
void initWithType(BulletType bullet_type);
CREATE_FUNC(Bullet);
void pauseMove();
void resumeMove();
public:
int m_kill_hp;
bool m_hit_flag;
private:
void move(float tm);
float m_speed;
};
敌机
enum EnemyType
{
SMALL,
MEDIUM,
BIG
};
class Enemy : public cocos2d::Sprite
{
public:
virtual bool init();
CREATE_FUNC(Enemy);
void pauseMove();
void resumeMove();
void initWithType(EnemyType enemy_type);
void move(float tm);
void hit(int reduce_hp);
void die();
int m_hp;
EnemyType m_type;
private:
float m_speed;
};
- 不同类型
- 敌机飞行
- 不同的速度
- 被子弹击中
- 不同的生命值
- 减生命值
- 被摧毁
道具
enum WeaponType
{
AMMO,
BOMB
};
class Weapon : public cocos2d::Sprite
{
public:
virtual bool init();
CREATE_FUNC(Weapon);
void initWithType(WeaponType weapon_type);
void pauseMove();
void resumeMove();
WeaponType m_type;
private:
void move(float tm);
};
背景滚动
天空背景实际上只有一张图,构造两张图循环移动,产生天空无限移动的效果
bool SkyBackground::init()
{
if (!Node::init())
return false;
Size visible_size = Director::getInstance()->getVisibleSize();
Point visible_origin = Director::getInstance()->getVisibleOrigin();
m_background1 = Sprite::create("img/background.png");
m_background1->setContentSize(visible_size);
m_background1->setAnchorPoint(Point::ZERO);
m_background1->setPosition(visible_origin);
addChild(m_background1);
m_background2 = Sprite::create("img/background.png");
m_background2->setContentSize(visible_size);
m_background2->setAnchorPoint(Point::ZERO);
m_background2->setPosition(visible_origin.x, visible_origin.y + visible_size.height);
addChild(m_background2);
schedule(schedule_selector(SkyBackground::backgroundRotate), kFrameUpdateInterval);
return true;
}
void SkyBackground::backgroundRotate(float tm)
{
Size visible_size = Director::getInstance()->getVisibleSize();
Point visible_origin = Director::getInstance()->getVisibleOrigin();
m_background1->setPositionY(m_background1->getPositionY() - 0.3);
m_background2->setPositionY(m_background1->getPositionY() + visible_size.height);
if (m_background2->getPositionY() <= visible_origin.y)
m_background1->setPositionY(visible_origin.y);
}
玩家控制
玩家飞机根据触摸移动进行相应的移动
bool GameScene::onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *event)
{
if (m_is_over)
return true;
CCLOG("onTouchBegan");
m_pretouch_pos = touch->getLocation();
m_preplayer_pos = m_player->getPosition();
return true;
}
void GameScene::onTouchMoved(cocos2d::Touch *touch, cocos2d::Event *event)
{
if (m_is_over)
return;
CCLOG("onTouchMoved");
Point current_touch_pos = touch->getLocation();
Vec2 move_delta = current_touch_pos - m_pretouch_pos;
m_player->setPosition(m_preplayer_pos + move_delta);
}
void GameScene::onTouchEnded(cocos2d::Touch *touch, cocos2d::Event *event)
{
if (m_is_over)
return;
CCLOG("onTouchEnded");
m_pretouch_pos = kInitPoint;
}
Bullet* Player::shootSingle()
{
SimpleAudioEngine::getInstance()->playEffect("sound/bullet.wav");
Bullet* bullet = Bullet::create();
bullet->initWithType(BulletType::BASE);
bullet->setPosition(getPositionX(), getPositionY() + getContentSize().height / 2);
return bullet;
}
Vector<Bullet*> Player::shootDouble()
{
SimpleAudioEngine::getInstance()->playEffect("sound/bullet.wav");
Vector<Bullet*> double_bullets;
Bullet* bullet_left = Bullet::create();
bullet_left->initWithType(BulletType::POWER);
bullet_left->setPosition(getPositionX() - getContentSize().width / 4, getPositionY() + getContentSize().height / 2);
double_bullets.pushBack(bullet_left);
Bullet* bullet_right = Bullet::create();
bullet_right->initWithType(BulletType::POWER);
bullet_right->setPosition(getPositionX() + getContentSize().width / 4, getPositionY() + getContentSize().height / 2);
double_bullets.pushBack(bullet_right);
return double_bullets;
}
void Player::fetchWeapon(WeaponType weapon_type)
{
if (m_bullet_type == BulletType::BASE && weapon_type == WeaponType::AMMO)
{
m_bullet_type = BulletType::POWER;
SimpleAudioEngine::getInstance()->playEffect("sound/get_double_laser.wav");
scheduleOnce([&](float delay){
m_bullet_type = BulletType::BASE;
SimpleAudioEngine::getInstance()->playEffect("sound/out_porp.wav");
}, kPowerTime, "power_status");
}
else if (weapon_type == WeaponType::BOMB)
{
SimpleAudioEngine::getInstance()->playEffect("sound/get_bomb");
}
}
子弹生成
子弹是由场景通过定时器调度的,每次固定时间间隔根据当前玩家的子弹类型生成新的子弹
- 子弹射出后就自动飞行
- 子弹产生的位置都在玩家飞机头部
void GameScene::generateBullet(float interval)
{
if (m_is_over)
return;
if (m_player->m_bullet_type == BulletType::BASE)
{
Bullet* bullet = m_player->shootSingle();
addChild(bullet, kBattleZorder);
m_bullets.pushBack(bullet);
}
else if (m_player->m_bullet_type == BulletType::POWER)
{
Vector<Bullet*> double_bullets = m_player->shootDouble();
for (Bullet* bullet : double_bullets)
{
addChild(bullet, kBattleZorder);
m_bullets.pushBack(bullet);
}
}
}
敌机生成
敌机会随机从顶部生成出来,也是由定时器调度
- 位置随机
- 类型根据概率来,越厉害的飞机越少出现
- 飞机出现后会飞行
- 飞船有伴随动画
void GameScene::generateEnemy(float interval)
{
if (m_is_over)
return;
Size visible_size = Director::getInstance()->getVisibleSize();
Point visible_origin = Director::getInstance()->getVisibleOrigin();
EnemyType enemy_type;
float random_factor = CCRANDOM_0_1();
if (random_factor >= 0.9)
enemy_type = EnemyType::BIG;
else if (random_factor >= 0.6 && random_factor < 0.9)
enemy_type = EnemyType::MEDIUM;
else
enemy_type = EnemyType::SMALL;
Enemy* enemy = Enemy::create();
enemy->initWithType(enemy_type);
float random_x = enemy->getContentSize().width / 2 +
(visible_size.width - enemy->getContentSize().width) * CCRANDOM_0_1();
enemy->setPosition(visible_origin.x + random_x,
visible_origin.y + visible_size.height + enemy->getContentSize().height / 2);
addChild(enemy, kBattleZorder);
m_enemies.pushBack(enemy);
}
道具生成
道具有子弹和炸弹两种,由定时器调度
- 道具出现位置随机
- 道具类型根据概率来,炸弹概率较小
- 道具出现后会飞行
碰撞检测
判断敌机是否死亡、道具拾取、玩家是否被摧毁都要做碰撞检测
- 敌机碰撞到子弹后,先减生命值,生命值为则死亡
- 道具拾取后,如果是子弹,则玩家射击的子弹专版为加强版,维持一段时间,如果是炸弹,则立即炸毁场景里所有存活的飞机
- 玩家碰撞到任何敌机就被摧毁
- 碰撞会有多种动画伴随
for (Enemy* enemy : m_enemies)
{
if (enemy->m_hp > 0 && enemy->getBoundingBox().intersectsRect(m_player->getBoundingBox()))
{
m_is_over = true;
m_player->destroy();
scheduleOnce([&](float delay){
gameOver();
}, 2.0, "game over");
return;
}
}
Vector<Bullet*> hit_bullets;
for (Bullet* bullet : m_bullets)
{
for (Enemy* enemy : m_enemies)
{
if (!bullet->m_hit_flag
&& enemy->m_hp > 0
&& bullet->getBoundingBox().intersectsRect(enemy->getBoundingBox()))
{
enemy->hit(bullet->m_kill_hp);
if (enemy->m_hp <= 0)
{
getScore(enemy->m_type);
enemy->die();
m_enemies.eraseObject(enemy);
}
bullet->m_hit_flag = true;
hit_bullets.pushBack(bullet);
}
}
}
for (Bullet* bullet : hit_bullets)
{
m_bullets.eraseObject(bullet);
bullet->removeFromParent();
}
hit_bullets.clear();
for (Weapon* weapon : m_weapons)
{
if (weapon->getBoundingBox().intersectsRect(m_player->getBoundingBox()))
{
if (weapon->m_type == WeaponType::AMMO)
m_player->fetchWeapon(WeaponType::AMMO);
else if (weapon->m_type == WeaponType::BOMB)
{
SimpleAudioEngine::getInstance()->playEffect("sound/use_bomb.wav");
CCLOG("before bomb enemy number = %d", m_enemies.size());
for (Enemy* enemy : m_enemies)
{
getScore(enemy->m_type);
enemy->m_hp = 0;
enemy->die();
}
m_enemies.clear();
CCLOG("after bomb enemy number = %d", m_enemies.size());
}
m_weapons.eraseObject(weapon);
weapon->removeFromParent();
}
}
元素管理
场景里看不见的元素需要销毁做内存回收
- 监测子弹、敌机、道具如果出了平面就销毁回收内存
- 这种销毁不需要播放动画
for (Bullet* bullet : m_bullets)
{
if (bullet->getPositionY() > visible_origin.y +
visible_size.height + bullet->getContentSize().height / 2)
{
m_bullets.eraseObject(bullet);
bullet->removeFromParent();
}
}
CCLOG("current bullets count: %d", m_bullets.size());
for (Enemy* enemy : m_enemies)
{
if (enemy->getPositionY() < visible_origin.y - enemy->getContentSize().height / 2)
{
m_enemies.eraseObject(enemy);
enemy->removeFromParent();
}
}
CCLOG("alive enemy count: %d", m_enemies.size());
for (Weapon* weapon : m_weapons)
{
if (weapon->getPositionY() < visible_origin.y - weapon->getContentSize().height / 2)
{
m_weapons.eraseObject(weapon);
weapon->removeFromParent();
}
}
CCLOG("unfetched weapon count: %d", m_weapons.size());
计分
在摧毁敌机或者炸掉敌机时都要更新游戏分数
void GameScene::getScore(EnemyType enemy_type)
{
static int phase = 0;
switch (enemy_type)
{
case EnemyType::SMALL:
m_score += kSmallPlaneScore;
break;
case EnemyType::MEDIUM:
m_score += kMediumPlaneScore;
break;
case EnemyType::BIG:
m_score += kBigPlaneScore;
break;
default:
break;
}
int new_phase = m_score / kAchievementScoreUnit;
if (new_phase > phase)
{
SimpleAudioEngine::getInstance()->playEffect("sound/achievement.wav");
phase = new_phase;
}
m_score_label->setString(__String::createWithFormat("score: %d", m_score)->_string);
}
游戏状态管理
游戏结束、暂停、恢复
- 游戏结束出现新画面
- 游戏暂停和恢复要保证场景里所有元素同时动作
void GameScene::gameOver()
{
CCLOG("player hit, game over");
Size visible_size = Director::getInstance()->getVisibleSize();
Point visible_origin = Director::getInstance()->getVisibleOrigin();
if (SimpleAudioEngine::getInstance()->isBackgroundMusicPlaying())
SimpleAudioEngine::getInstance()->pauseBackgroundMusic();
unschedule(schedule_selector(GameScene::generateEnemy));
unschedule(schedule_selector(GameScene::generateWeapon));
unschedule(schedule_selector(GameScene::generateBullet));
Sprite* game_over_background = Sprite::create("img/gameover.png");
game_over_background->setContentSize(visible_size);
game_over_background->setAnchorPoint(Point::ZERO);
game_over_background->setPosition(visible_origin);
addChild(game_over_background, kGameoverZorder);
Label* final_score_label = Label::createWithTTF(std::to_string(m_score), "fonts/Marker Felt.ttf", 14);
final_score_label->setColor(Color3B::BLACK);
final_score_label->setPosition(visible_origin.x + visible_size.width / 2,
visible_origin.y + visible_size.height / 2);
addChild(final_score_label, kGameoverZorder);
Button* restart_btn = Button::create("img/restart.png");
restart_btn->setScale(1.5);
restart_btn->setPosition(Vec2(visible_origin.x + visible_size.width / 2,
visible_origin.y + visible_size.height / 2 - 30));
restart_btn->addTouchEventListener([&](Ref* sender, Widget::TouchEventType type){
switch (type)
{
case ui::Widget::TouchEventType::BEGAN:
SimpleAudioEngine::getInstance()->playEffect("sound/button.wav");
break;
case ui::Widget::TouchEventType::ENDED:
{
auto game_scene = GameScene::createScene();
TransitionScene* transition_scene = TransitionFade::create(0.5, game_scene);
Director::getInstance()->replaceScene(transition_scene);
}
break;
default:
break;
}
});
addChild(restart_btn, kGameoverZorder);
Button* quit_btn = Button::create("img/quit.png");
quit_btn->setScale(1.5);
quit_btn->setPosition(Vec2(visible_origin.x + visible_size.width / 2,
visible_origin.y + visible_size.height / 2 - 60));
quit_btn->addTouchEventListener([&](Ref* sender, Widget::TouchEventType type){
switch (type)
{
case ui::Widget::TouchEventType::BEGAN:
SimpleAudioEngine::getInstance()->playEffect("sound/button.wav");
break;
case ui::Widget::TouchEventType::ENDED:
{
auto main_menu_scene = MainMenuScene::createScene();
TransitionScene* transition_scene = TransitionFade::create(0.5, main_menu_scene);
Director::getInstance()->replaceScene(main_menu_scene);
}
break;
default:
break;
}
});
addChild(quit_btn, kGameoverZorder);
}
void GameScene::gamePause()
{
SimpleAudioEngine::getInstance()->pauseBackgroundMusic();
m_is_pause = true;
unschedule(schedule_selector(GameScene::generateEnemy));
unschedule(schedule_selector(GameScene::generateWeapon));
unschedule(schedule_selector(GameScene::generateBullet));
m_sky_background->pauseRotate();
for (Enemy* enemy : m_enemies)
enemy->pauseMove();
for (Bullet* bullet : m_bullets)
bullet->pauseMove();
for (Weapon* weapon : m_weapons)
weapon->pauseMove();
}
void GameScene::gameResume()
{
SimpleAudioEngine::getInstance()->resumeBackgroundMusic();
m_is_pause = false;
schedule(schedule_selector(GameScene::generateEnemy), kEnemyGenerateInterval);
schedule(schedule_selector(GameScene::generateWeapon), kWeaponGenerateInterval);
schedule(schedule_selector(GameScene::generateBullet), kBulletGenerateInterval);
m_sky_background->resumeRotate();
for (Enemy* enemy : m_enemies)
enemy->resumeMove();
for (Bullet* bullet : m_bullets)
bullet->resumeMove();
for (Weapon* weapon : m_weapons)
weapon->resumeMove();
}
效果图
代码
csdn:飞机大战
github:飞机大战
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)