继续打飞机之旅,这一篇主要加入敌机类,和碰撞检测,以及爆炸效果管理器。写完他,基本的打飞机游戏就出来了,只要在这个架构上继续完善,加入其他功能,一个完整的游戏就可以出来。废话不多说,现在开始。
这个其实主要从子弹类那里copy过来,修改一下就可以了。因为他们的行为是类似的,所以不必重新构建。最终我们还要实现多种类型的敌机,现在只先实现一种类型的敌机,称之为EnemyNormal吧,代码如下:
#ifndef _ENEMY_NORMAL_H_
#define _ENEMY_NORMAL_H_
#include "EnemyBase.h"
class EnemyNormal : public EnemyBase
{
public:
EnemyNormal();
~EnemyNormal();
//CREATE_FUNC(EnemyNormal);
//virtual bool init();
static EnemyNormal* create(Sprite* sprite);
bool init(Sprite* sprite);
};
#endif
#include "EnemyNormal.h"
EnemyNormal::EnemyNormal()
{
m_speed = SPEED_ENEMY_NORMAL_DEFAULT;
}
EnemyNormal::~EnemyNormal()
{}
bool EnemyNormal::init(Sprite* sprite)
{
bool ret = false;
bindSprite(sprite);
ret = true;
return ret;
}
EnemyNormal* EnemyNormal::create(Sprite* sprite)
{
EnemyNormal* enemy = new EnemyNormal();
if(enemy && enemy->init(sprite))
{
enemy->autorelease();
}
else
{
CC_SAFE_DELETE(enemy);
}
return enemy;
}
这个没什么好说的,到时候需要什么其他属性,直接在这个类添加即可。
忘了EnemyBase这个基类了,把敌机的共同属性都放到这里,以后不同敌机直接继承他便可。
#ifndef _ENEMY_BASE_H_
#define _ENEMY_BASE_H_
#include "Entity.h"
#define SPEED_DEFAULT 10
#define SPEED_NORMAL 5
#define SPEED_ENEMY_NORMAL_DEFAULT 10
class EnemyBase:public Entity
{
public:
EnemyBase();
~EnemyBase();
bool getEnemyOutScreen();
void setEnemyOutScreen(bool out);
bool getEnemyUsed();
void setEnemyUsed(bool used);
int getEnemySpeed();
void setEnemySpeed(int speed);
//virtual void bombDead();
private:
bool m_isOutScreen;
bool m_isUsed;
protected:
int m_speed;
};
#endif
#include "EnemyBase.h"
EnemyBase::EnemyBase()
{}
EnemyBase::~EnemyBase()
{}
bool EnemyBase::getEnemyOutScreen()
{
return m_isOutScreen;
}
void EnemyBase::setEnemyOutScreen(bool out)
{
m_isOutScreen = out;
}
bool EnemyBase::getEnemyUsed()
{
return m_isUsed;
}
void EnemyBase::setEnemyUsed(bool use)
{
m_isUsed = use;
}
int EnemyBase::getEnemySpeed()
{
return m_speed;
}
void EnemyBase::setEnemySpeed(int speed)
{
m_speed = speed;
}
接着,我们敌机造出来了,那么怎么源源不断的生成飞机呢,我们先把敌机生成,然后在PlaneGameScene不断生成。所以,现在先生成敌机吧,简称敌机管理器:
#ifndef _ENEMY_MANAGER_H_
#define _ENEMY_MANAGER_H_
#include "cocos2d.h"
USING_NS_CC;
#define ENEMY_NORMAL_NUM 20
class EnemyBase;
class PlaneHero;
class EnemyManager : public Node
{
public:
EnemyManager();
~EnemyManager();
//获取未用的子弹
EnemyBase* getUnsedEnemy();
virtual bool init();
static EnemyManager* create();
//获取敌机列表
Vector getEnemyList();
//注入英雄飞机
void setHeroPlane(PlaneHero* hero);
PlaneHero* getHeroPlane();
private:
Vector m_enemyList;//敌机列表
void createEnemy();
void EnemyLogicCheck(float dt);
void receiveMessge(Ref* pSender);
private:
PlaneHero* hero_plane;//持有英雄飞机
bool isGameOver;
};
#endif
#include "EnemyManager.h"
#include "EnemyNormal.h"
#include "PlaneHero.h"
#include "BombManager.h"
EnemyManager::EnemyManager()
{
hero_plane = NULL;
isGameOver = false;
}
EnemyManager::~EnemyManager()
{}
EnemyBase* EnemyManager::getUnsedEnemy()
{
for(auto enemy : m_enemyList)
{
if(enemy->getEnemyUsed() == false)
{
enemy->setEnemyUsed(true);
enemy->setEnemyOutScreen(false);
return enemy;
}
}
log("no bullet unused");
return NULL;
}
bool EnemyManager::init()
{
//创建敌机列表
createEnemy();
//循环检测子弹列表
this->schedule(schedule_selector(EnemyManager::EnemyLogicCheck), 0.01f);
NotificationCenter::getInstance()->addObserver(this, callfuncO_selector(EnemyManager::receiveMessge),"gameover",NULL);
return true;
}
EnemyManager* EnemyManager::create()
{
EnemyManager* eMgr = new EnemyManager();
if(eMgr && eMgr->init())
{
eMgr->autorelease();
}
else
{
CC_SAFE_DELETE(eMgr);
}
return eMgr;
}
void EnemyManager::createEnemy()
{
EnemyBase* enemy = NULL;
for(int i = 0; i < ENEMY_NORMAL_NUM; i++)
{
auto enemy_sprite = Sprite::create("enemy1.png");
enemy = EnemyNormal::create(enemy_sprite);
enemy->setEnemyUsed(false);
enemy->setEnemyOutScreen(false);
enemy->setAnchorPoint(Point(0.2,0));
m_enemyList.pushBack(enemy);
this->addChild(enemy);
}
log("m_enemyList size() = %d", m_enemyList.size());
}
void EnemyManager::EnemyLogicCheck(float dt)
{
if(isGameOver == true)
{
return;
}
//auto visibleSize = Director::getInstance()->getVisibleSize();
for(auto enemy : m_enemyList)
{
if(enemy->getEnemyUsed() == true)
{
//敌机运行
Point pos = enemy->getPosition();
pos.y -= SPEED_DEFAULT;
enemy->setPositionY(pos.y);
//out of screen
if(pos.y <= 0)
{
enemy->setEnemyOutScreen(true);
enemy->setEnemyUsed(false);
}
//敌机运行
Point pos_enemy = enemy->getPosition();
//英雄位置
Point pos_hero = hero_plane->getPosition();
Rect enemyRect = enemy->getBoundingBox();
Rect heroRect = hero_plane->getBoundingBox();
enemyRect.size.width *= 0.8;
enemyRect.size.height *= 0.8;
heroRect.size.width *= 0.5;
heroRect.size.height *= 0.5;
//if(enemyRect.containsPoint(pos_hero) || heroRect.containsPoint(pos_enemy))
if(enemyRect.intersectsRect(heroRect))
{
auto enemy_size = enemy->getContentSize();
auto hero_size = hero_plane->getContentSize();
//用于测试位置
log("enemyRect x = %f, y = %f, width = %f, height = %f", enemyRect.origin.x, enemyRect.origin.y, enemyRect.size.width, enemyRect.size.height);
log("heroRect x = %f, y = %f, width = %f, height = %f", heroRect.origin.x, heroRect.origin.y, heroRect.size.width, heroRect.size.height);
log("enemy_size width = %f, height = %f", enemy_size.width, enemy_size.height);
log("hero_size width = %f, height = %f", hero_size.width, hero_size.height);
log("pos_enemy x = %f, y = %f", pos_enemy.x, pos_enemy.y);
log("pos_hero x = %f, y = %f", pos_hero.x, pos_hero.y);
BombManager::getInstance()->bombHeroPlane(pos_hero);
NotificationCenter::getInstance()->postNotification("gameover", NULL);
//hero_plane->removeFromParentAndCleanup(true);
//enemy->setEnemyUsed(false);
//enemy->setPositionY(-105);
break;
}
}
}
}
Vector EnemyManager::getEnemyList()
{
return m_enemyList;
}
void EnemyManager::setHeroPlane(PlaneHero* hero)
{
hero_plane = hero;
}
PlaneHero* EnemyManager::getHeroPlane()
{
return hero_plane;
}
void EnemyManager::receiveMessge(Ref* pSender)
{
isGameOver = true;
log("EnemyManager::receiveMessge()");
}
最后,源源不断的生成敌机和生成子弹是类似的,在PlaneGameScene这里生成:
void PlayGameScene::makeBulletAndEnemy(float dt)
{
if(isGameOver == true)
{
return;
}
//Size visibleSize = Director::getInstance()->getVisibleSize();
makeBullet();
makeEnemy();
}
//生成子弹
void PlayGameScene::makeBullet()
{
auto hero_plane = this->getChildByTag(HERO_PLANE);
Point pos = hero_plane->getPosition();
BulletBase* bullet = bMgr->getUnusedBullet();
//BulletBase* bullet = NULL;
if(bullet == NULL)
{
return;
}
bullet->setPosition(pos);
}
//生成敌机
void PlayGameScene::makeEnemy()
{
Size visibleSize = Director::getInstance()->getVisibleSize();
int posX = rand() % (int)(visibleSize.width);
EnemyBase* enemy = eMgr->getUnsedEnemy();
if(enemy == NULL)
{
return;
}
enemy->setPosition(Point(posX, (int)(visibleSize.height)));
}
碰撞检测还是不那么精确,只能通过一步一步慢慢调,上面代码加了log大部分就是为了看那个位置。至今仍然未找到比较好的方法来检测碰撞。
首先是子弹与敌机的碰撞检测,在子弹管理器进行处理:
//检测子弹
void BulletManager::bulletLogicCheck(float dt)
{
if(isGameOver == true)
{
return;
}
auto visibleSize = Director::getInstance()->getVisibleSize();
for(auto bullet : m_bulletList)
{
if(bullet->isUsed() == true)
{
//子弹运行
Point pos_bullet = bullet->getPosition();
pos_bullet.y += SPEED_DEFAULT;
bullet->setPositionY(pos_bullet.y);
//碰撞处理
for(auto enemy : eMgr->getEnemyList())
{
if(enemy->getEnemyUsed() == true)
{
//敌机运行
Point pos_enemy = enemy->getPosition();
Point pos_bullet = bullet->getPosition();
Rect enemyRect = enemy->getBoundingBox();
Rect bulletRect = bullet->getBoundingBox();
auto enemy_size = enemy->getContentSize();
auto bullet_size = bullet->getContentSize();
if(enemyRect.intersectsRect(bulletRect))
//if(bulletRect.containsPoint(pos_enemy))
{
log("enemyRect x = %f, y = %f, width = %f, height = %f", enemyRect.origin.x, enemyRect.origin.y, enemyRect.size.width, enemyRect.size.height);
log("bulletRect x = %f, y = %f, width = %f, height = %f", bulletRect.origin.x, bulletRect.origin.y, bulletRect.size.width, bulletRect.size.height);
log("enemy_size width = %f, height = %f", enemy_size.width, enemy_size.height);
log("bullet_size width = %f, height = %f", bullet_size.width, bullet_size.height);
log("pos_enemy x = %f, y = %f", pos_enemy.x, pos_enemy.y);
log("pos_bullet x = %f, y = %f", pos_bullet.x, pos_bullet.y);
bullet->setUsed(false);
bullet->setPositionY(visibleSize.height * 10);
BombManager::getInstance()->bombNormalEnemy(pos_enemy);
enemy->setEnemyUsed(false);
enemy->setPositionY(-105);
continue;
}
}
}
//out of screen
if(pos_bullet.y >= visibleSize.height)
{
bullet->setIsMoveOutScreen(true);
bullet->setUsed(false);
}
}
}
}
其次,是敌机与英雄飞机的碰撞检测,在敌机管理器里面处理,其实和子弹管理器差不多,敌机管理器需要持有英雄飞机的引用,才能实现碰撞检测。通过注入英雄飞机即可实现。
//碰撞检测
void EnemyManager::EnemyLogicCheck(float dt)
{
if(isGameOver == true)
{
return;
}
//auto visibleSize = Director::getInstance()->getVisibleSize();
for(auto enemy : m_enemyList)
{
if(enemy->getEnemyUsed() == true)
{
//敌机运行
Point pos = enemy->getPosition();
pos.y -= SPEED_DEFAULT;
enemy->setPositionY(pos.y);
//out of screen
if(pos.y <= 0)
{
enemy->setEnemyOutScreen(true);
enemy->setEnemyUsed(false);
}
//敌机运行
Point pos_enemy = enemy->getPosition();
//英雄位置
Point pos_hero = hero_plane->getPosition();
Rect enemyRect = enemy->getBoundingBox();
Rect heroRect = hero_plane->getBoundingBox();
enemyRect.size.width *= 0.8;
enemyRect.size.height *= 0.8;
heroRect.size.width *= 0.5;
heroRect.size.height *= 0.5;
//是否碰撞
if(enemyRect.intersectsRect(heroRect))
{
auto enemy_size = enemy->getContentSize();
auto hero_size = hero_plane->getContentSize();
//用于测试位置
log("enemyRect x = %f, y = %f, width = %f, height = %f", enemyRect.origin.x, enemyRect.origin.y, enemyRect.size.width, enemyRect.size.height);
log("heroRect x = %f, y = %f, width = %f, height = %f", heroRect.origin.x, heroRect.origin.y, heroRect.size.width, heroRect.size.height);
log("enemy_size width = %f, height = %f", enemy_size.width, enemy_size.height);
log("hero_size width = %f, height = %f", hero_size.width, hero_size.height);
log("pos_enemy x = %f, y = %f", pos_enemy.x, pos_enemy.y);
log("pos_hero x = %f, y = %f", pos_hero.x, pos_hero.y);
//爆炸效果
BombManager::getInstance()->bombHeroPlane(pos_hero);
//发出游戏结束的通知
NotificationCenter::getInstance()->postNotification("gameover", NULL);
break;
}
}
}
}
发现爆炸效果的代码还是挺多的,以后不同类型的敌机效果还要处理,所以就单独提取出来,放在一个类,称之为爆炸效果管理器。我们需要那种类型的爆炸效果,直接通过这个单例类获取就可以了。很是方便,易于管理。
#ifndef _BOMB_MANAGER_H_
#define _BOMB_MANAGER_H_
#include "cocos2d.h"
class BombManager : public cocos2d::Node
{
public:
BombManager();
~BombManager();
//单例模式,获取实例
static BombManager* getInstance();
//普通飞机的爆炸效果
void bombNormalEnemy(cocos2d::Point pos);
//主飞机的爆炸效果
void bombHeroPlane(cocos2d::Point pos);
//初始化各个爆炸的animation
void inite();
//爆炸后的回调,清除爆炸的痕迹,通用
void bombDone(Node* sender);
private:
static BombManager* _instance;//单例
};
#endif
#include "BombManager.h"
USING_NS_CC;
BombManager* BombManager::_instance = NULL;
BombManager::BombManager()
{}
BombManager::~BombManager()
{}
void BombManager::inite()
{
//添加普通敌机爆炸效果
auto spriteFrameCache = SpriteFrameCache::getInstance();
Vector framelist;
for(int i = 1; i <=4; i++)
{
SpriteFrame* sf = SpriteFrame::create(String::createWithFormat("enemy1_down%d.png",i)->_string, Rect(0,0,57,51));
framelist.pushBack(sf);
spriteFrameCache->addSpriteFrame(sf, String::createWithFormat("enemy1_down%d.png",i)->_string);
}
Animation* normal_enemy_animation = Animation::createWithSpriteFrames(framelist, 0.1f);
AnimationCache::getInstance()->addAnimation(normal_enemy_animation, "normal_enemy_bomb");
//添加英雄飞机爆炸效果
framelist.clear();
for(int i = 1; i <=4; i++)
{
SpriteFrame* sf = SpriteFrame::create(String::createWithFormat("hero_blowup_n%d.png",i)->_string, Rect(0,0,102,126));
framelist.pushBack(sf);
spriteFrameCache->addSpriteFrame(sf, String::createWithFormat("hero_blowup_n%d.png",i)->_string);
}
Animation* hero_plane_animation = Animation::createWithSpriteFrames(framelist, 0.3f);
AnimationCache::getInstance()->addAnimation(hero_plane_animation, "hero_plane_bomb");
//add other bomb
}
//普通敌机爆炸效果
void BombManager::bombNormalEnemy(Point pos)
{
auto animate = Animate::create(AnimationCache::getInstance()->getAnimation("normal_enemy_bomb"));
auto repeat = Repeat::create(animate, 1);
auto sprite = Sprite::create();
sprite->setPosition(pos);
this->addChild(sprite);
//回调函数
auto callback = CallFunc::create(CC_CALLBACK_0(BombManager::bombDone,this,sprite));
auto sequence = Sequence::create(repeat,callback,NULL);
sprite->runAction(sequence);
}
//英雄飞机爆炸效果
void BombManager::bombHeroPlane(cocos2d::Point pos)
{
auto animate = Animate::create(AnimationCache::getInstance()->getAnimation("hero_plane_bomb"));
auto repeat = Repeat::create(animate, 1);
auto sprite = Sprite::create();
sprite->setPosition(pos);
this->addChild(sprite);
//回调函数
auto callback = CallFunc::create(CC_CALLBACK_0(BombManager::bombDone,this,sprite));
auto sequence = Sequence::create(repeat,callback,NULL);
sprite->runAction(sequence);
}
BombManager* BombManager::getInstance()
{
if(_instance == NULL)
{
_instance = new BombManager();
if(_instance)
{
//初始化
_instance->inite();
}
}
return _instance;
}
//爆炸后清除自己的痕迹
void BombManager::bombDone(Node* sender)
{
sender->removeFromParentAndCleanup(true);
}
4.修改游戏界面
以上实现后,最后还是要把上面的东西加入游戏界面,那么需要把上面的节点加入到PlaneGameScene中。在这个类的init函数里,加入以下代码:
bMgr = BulletManager::create();
//由于忘记添加进层里,导致bMgr->getUnusedBullet()一直出错
this->addChild(bMgr, 1);
//生成敌机管理器
eMgr = EnemyManager::create();
this->addChild(eMgr, 1);
//注入敌机管理器
bMgr->setEnemyManager(eMgr);
//注入爆炸管理器
this->addChild(BombManager::getInstance(), 1);
//add plane hero
Sprite* hero = Sprite::create("hero1.png");
PlaneHero* hero_plane = PlaneHero::create(hero);
hero_plane->setPosition(Point(visibleSize.width / 2, visibleSize.height / 2));
hero_plane->setTag(HERO_PLANE);
hero_plane->setAnchorPoint(Point(0,0));
this->addChild(hero_plane, 1);
//注入英雄飞机给敌机管理器,做检测碰撞使用
eMgr->setHeroPlane(hero_plane);
拖动飞机的时候,点击感觉也不是很精确,换种方式实现touchevent,感觉精确度提高了不少。
// 添加监听器,监听主飞机
_eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, hero_plane);
//_eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, enemy_plane);
//Touch event handler
bool PlayGameScene::onTouchBegan(Touch *touch, Event* event)
{
if(isGameOver == true)
{
return true;
}
// 获取事件所绑定的 target
auto target = static_cast(event->getCurrentTarget());
if(target == NULL)
{
log("target = NULL");
return true;
}
bTouchPlane = false;
// 获取当前点击点所在相对按钮的位置坐标
// getLocation得到的是openGL坐标系,也就是世界坐标系
Point touchPoint = touch->getLocation();
Point locationInNode = target->convertToNodeSpace(touchPoint);
Size target_size = target->getContentSize();
Rect target_rect = Rect(0, 0, target_size.width, target_size.height);
//auto hero_plane = this->getChildByTag(HERO_PLANE);
//Point hero_pos = hero_plane->getPosition();
//Size hero_size = hero_plane->getContentSize();
//log("hero_pos x = %f, y = %f", hero_pos.x, hero_pos.y);
log("touchPoint x = %f, y = %f", touchPoint.x, touchPoint.y);
log("locationInNode x = %f, y = %f", locationInNode.x, locationInNode.y);
log("target_size width = %f, height = %f", target_size.width, target_size.height);
log("target_rect x = %f, y = %f, width = %f, height = %f", target_rect.origin.x,target_rect.origin.y,target_rect.size.width,target_rect.size.height);
//log("hero_size width = %f, height = %f", hero_size.width, hero_size.height);
//if(touchPoint.x >= hero_pos.x && touchPoint.x <= (hero_pos.x + hero_size.width) && touchPoint.y >= hero_pos.y && touchPoint.y <= (touchPoint.y + hero_size.height))
if(target_rect.containsPoint(locationInNode))
{
//touch the plane, mark this flag for use
bTouchPlane = true;
}
return true;
}
不明觉厉,这个方法还有待研究一下。
好了,这篇到此结束,第三篇后续会更新上来,纯属想到哪写到哪。
效果图如下: