加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

【cocos2d-x-3.2】【高仿微信打飞机系列二】【敌机 碰撞检测 爆

发布时间:2020-12-14 20:32:11 所属栏目:百科 来源:网络整理
导读:继续打飞机之旅,这一篇主要加入敌机类,和碰撞检测,以及爆炸效果管理器。写完他,基本的打飞机游戏就出来了,只要在这个架构上继续完善,加入其他功能,一个完整的游戏就可以出来。废话不多说,现在开始。 1.敌机类 这个其实主要从子弹类那里copy过来,修

继续打飞机之旅,这一篇主要加入敌机类,和碰撞检测,以及爆炸效果管理器。写完他,基本的打飞机游戏就出来了,只要在这个架构上继续完善,加入其他功能,一个完整的游戏就可以出来。废话不多说,现在开始。

1.敌机类

这个其实主要从子弹类那里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<EnemyBase*> getEnemyList();

	//注入英雄飞机
	void setHeroPlane(PlaneHero* hero);
	PlaneHero* getHeroPlane();

private:
	Vector<EnemyBase*> 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,heroRect.origin.x,heroRect.origin.y,heroRect.size.width,heroRect.size.height);
				log("enemy_size width = %f,enemy_size.width,enemy_size.height);
				log("hero_size width = %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,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<EnemyBase*> 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)));
}

2.碰撞检测

碰撞检测还是不那么精确,只能通过一步一步慢慢调,上面代码加了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,enemyRect.size.height);
						log("bulletRect x = %f,bulletRect.origin.x,bulletRect.origin.y,bulletRect.size.width,bulletRect.size.height);
						log("enemy_size width = %f,enemy_size.height);
						log("bullet_size width = %f,bullet_size.width,bullet_size.height);
						log("pos_enemy x = %f,pos_enemy.y);
						log("pos_bullet x = %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,pos_hero.y);
				//爆炸效果
				BombManager::getInstance()->bombHeroPlane(pos_hero);
				//发出游戏结束的通知
				NotificationCenter::getInstance()->postNotification("gameover",NULL);
				break;
			}
		}
	}
}

3.爆炸效果管理器

发现爆炸效果的代码还是挺多的,以后不同类型的敌机效果还要处理,所以就单独提取出来,放在一个类,称之为爆炸效果管理器。我们需要那种类型的爆炸效果,直接通过这个单例类获取就可以了。很是方便,易于管理。

#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<SpriteFrame*> framelist;
	for(int i = 1; i <=4; i++)
	{
		SpriteFrame* sf = SpriteFrame::create(String::createWithFormat("enemy1_down%d.png",i)->_string,Rect(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",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,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<Layer*>(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,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,hero_pos.x,hero_pos.y);
	log("touchPoint x = %f,touchPoint.x,touchPoint.y);
	log("locationInNode x = %f,locationInNode.x,locationInNode.y);
	log("target_size width = %f,target_size.height);
	log("target_rect x = %f,target_rect.origin.x,target_rect.origin.y,target_rect.size.width,target_rect.size.height);
	//log("hero_size width = %f,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;
}

不明觉厉,这个方法还有待研究一下。

好了,这篇到此结束,第三篇后续会更新上来,纯属想到哪写到哪。

效果图如下:

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读