zoukankan      html  css  js  c++  java
  • 仿《雷霆战机》飞行射击手游开发--子弹、跟踪导弹和激光

    转载请注明:http://www.cnblogs.com/thorqq/p/6563904.html
    试玩版下载:https://pan.baidu.com/s/1mhLBNVa (Windows版)

    飞机的武器类型众多,大致可分为子弹、跟踪导弹和激光。子弹是直线飞行的;导弹会自动跟踪目标,可曲线飞行;而激光是一道光束,可持续地对照射到的敌机产生伤害。子弹根据一次性发射的数量,可分为单发和多发,根据子弹发射的方向可分为:自动瞄准、平行、散射。本文将结合代码讲述有关飞机武器的程序设计。

    程序设计

    先看一下本游戏中有关武器的类的设计
    类结构
    其中,BulletGroup可理解为弹匣,Bullet就是其中一颗一颗的子弹,每颗子弹都有相同的属性,包括外观、伤害值以及飞行属性。而继承自Bullet的五个子类分别为:

    • ScatterBullet 散弹。含单发和多发,平行和散射。
    • AimScatterBullet 自动瞄准子弹。继承自ScatterBullet,但是初始发射角度指向距离最近的敌机(飞行轨迹是直线,发射后不会改变方向)。
    • CustomBullet 可自定义每颗子弹初始的方向、速度。
    • Missile 跟踪导弹。顾名思义,在飞行过程中会自动改变方向,始终对准敌机,但受限于飞行速度、角速度等参数,也可能无法射中目标。
    • Laser 激光。一道光束,伤害值依赖于接触的时间。

    参数

    下面是Bullet的所有参数,包含在结构体TBulletData中:

    std::string name; //名称
    std::string type; //类型:散弹、导弹、激光
    
    std::vector<std::string> styleArray; //帧动画图片资源
    float aniDura;//帧动画时间间隔
    
    std::string armatureName; //骨骼动画名称
    
    int topOfAircraft;//出现在飞机的上层还是下层
    int musicId;//音效
    
    int attack; //攻击值
    int speed;  //飞行速度。单位:像素/秒
    
    int count;//每次发射的子弹的颗数
    
    int max_count;//最多发射多少颗子弹。当达到最大值时,停止发射或自动降档
    float timeLimit; //时间限制。当达到这个时间时,停止发射或自动降档
    
    float angle_of_center;//中心法线的角度。
    float angle_interval;//多颗子弹同时发射时,这个角度就是相邻子弹间的夹角
    
    float x_interval;//多颗子弹同时发射时,两两间的横向间距
    
    float delay;//发射第一颗子弹时的延迟时间
    
    //假设发射的顺序为:1.1.1...1.1.1...1.1.1... 其中“1”表示发射子弹,“.”表示间隔时间
    //我们称“1.1.1...”是一个大周期,“1.1.1”是三个小周期
    float interval;//大周期之间的时间间隔,这里是“...”代表的时间
    float interval_2;//小周期之间的时间间隔,这里是“.”代表的时间
    int interval_2_cnt;//小周期里子弹数量,这里是3
    
    float rotate_angle; //连续发射时,每次发射偏转的角度
    float rotate_max_angle;//最大累计偏转角度
    int rotate_flag;//累计偏转角度达到最大值时的处理方法,0:逐渐减小偏转角度;1:立刻还原到零
    
    float bodyCenterX;//碰撞体相对子弹中心点的坐标偏移
    float bodyCenterY;
    float bodySizeW;//碰撞体的大小
    float bodySizeH;
    
    //发射原点相对飞机中心点的坐标偏移
    float origin_offset_x;
    float origin_offset_y;
    
    std::vector<std::string> fireFameNameArray; //尾部火焰动画,图片列表
    float fireAniDura; //动画帧时长
    int   fireOnTop; //尾部火焰显示在子弹的上层还是下层
    float fireOffsetX;//火焰中心点相对于飞机底部中心点的偏移。如果等于0,则只有一个火焰;否则是两个火焰
    float fireOffsetY;
    	
    std::vector<std::string> blastStyleArray;//子弹打到目标后产生的爆炸的帧动画资源列表
    

    基类设计

    Bullet

    Bullet类很简单,仅仅用于维护子弹的外观,所以重要的只有bool Bullet::init(BulletGroup* pBulletGroup)这一个方法。详见如下代码:

    class Bullet : public GameObject
    {
    public:
    	friend class BulletGroup;
    
    	static Bullet* create(BulletGroup* pBulletGroup);
    
    	//根据pBulletGroup中的子弹相关属性创建子弹
    	virtual bool init(BulletGroup* pBulletGroup);
    	virtual void reset();
    
    protected:
    	Bullet();
    	virtual ~Bullet();
    
    	//添加尾部的左右两个火焰动画
    	bool addFire(float offsetX, float offsetY, bool isFlipped);
    
    protected:
    	cocostudio::Armature* m_pArmature;
    	BulletGroup* m_pBulletGroup;
    	bool m_bBlast;
    };
    
    Bullet::Bullet()
    {
    	reset();
    }
    
    Bullet::~Bullet()
    {
    }
    
    void Bullet::reset()
    {
    	GameObject::reset();
    
    	m_pArmature = nullptr;
    	m_pBulletGroup = NULL;
    	m_bBlast = false;
    }
    
    Bullet* Bullet::create(BulletGroup* pBulletGroup)
    {
    	Bullet *pRet = new(std::nothrow) Bullet();
    	if (pRet && pRet->init(pBulletGroup))
    	{
    		pRet->autorelease();
    		return pRet;
    	}
    	else
    	{
    		delete pRet;
    		pRet = NULL;
    		return NULL;
    	}
    }
    
    //根据pBulletGroup中的子弹相关属性创建子弹
    bool Bullet::init(BulletGroup* pBulletGroup)
    {
    	m_pBulletGroup = pBulletGroup;
    	bool bRet = false;
    	do
    	{
    		CC_BREAK_IF(!GameObject::init());
    
    		if (m_pBulletGroup->m_data.topOfAircraft)
    		{
    			m_pBulletGroup->getParent()->addChild(this, CONSTANT::ZORDER_BULLET_ONTOP);
    		}
    		else
    		{
    			if (m_pBulletGroup->getPlane()->getAircraftType() == EAircraftType::Type_Enemy)
    			{
    				m_pBulletGroup->getParent()->addChild(this, CONSTANT::ZORDER_BULLET_ENEMY);
    			}
    			else
    			{
    				m_pBulletGroup->getParent()->addChild(this, CONSTANT::ZORDER_BULLET_PLAYER);
    			}
    		}
    
    
    		setBodyCenter(m_pBulletGroup->getBodyCenter());
    		setBodySize(m_pBulletGroup->getBodySize());
    
    		GameObject::initSpriteWithFileList(m_pBulletGroup->m_data.styleArray, m_pBulletGroup->m_data.aniDura);
    
    		if (m_pBulletGroup->m_data.armatureName.length() > 0)
    		{
    			m_pArmature = cocostudio::Armature::create(m_pBulletGroup->m_data.armatureName);
    			m_pArmature->setPosition(getContentSize() / 2);
    			m_pArmature->getAnimation()->play(GlobalData::getInstance()->getArmatureData(m_pBulletGroup->m_data.armatureName)->defaultAction);
    
    			addChild(m_pArmature);
    		}
    		else
    		{
    			if (m_pBulletGroup->m_data.fireFameNameArray.size() > 0)
    			{
    				m_pBulletGroup->m_data.fireOffsetX = abs(m_pBulletGroup->m_data.fireOffsetX);
    
    				if (m_pBulletGroup->m_data.fireOffsetX > 0.01)
    				{
    					addFire(+m_pBulletGroup->m_data.fireOffsetX, m_pBulletGroup->m_data.fireOffsetY, false);
    					addFire(-m_pBulletGroup->m_data.fireOffsetX, m_pBulletGroup->m_data.fireOffsetY, true);
    				}
    				else
    				{
    					addFire(0, m_pBulletGroup->m_data.fireOffsetY, false);
    				}
    			}
    		}
    
    		bRet = true;
    	} while (0);
    
    	return bRet;
    }
    
    bool Bullet::addFire(float offsetX, float offsetY, bool isFlipped)
    {
    	Sprite* fire = GameObject::createSpriteWithFileList(
    		m_pBulletGroup->m_data.fireFameNameArray, m_pBulletGroup->m_data.fireAniDura);
    
    	//子弹的飞行速度比较快,所以火焰不需要动画
    	//if (m_pBulletGroup->m_data.fireFameNameArray.size() == 1)
    	//{
    	//	ScaleTo* pScale1 = ScaleTo::create(m_pBulletGroup->m_data.fireAniDura, 1.0f, 0.9f);
    	//	ScaleTo* pScale2 = ScaleTo::create(m_pBulletGroup->m_data.fireAniDura, 1.0f, 1.0f);
    	//	Sequence* sequence = Sequence::create(pScale1, pScale2, NULL);
    	//	Repeat* repeat = Repeat::create(sequence, CC_REPEAT_FOREVER);
    	//	fire->runAction(repeat);
    	//}
    
    	//添加
    	if (m_pBulletGroup->m_data.fireOnTop)
    	{
    		addChild(fire);
    	}
    	else
    	{
    		addChild(fire, CONSTANT::ZORDER_PLAYERPLANE_FIRE);
    	}
    
    	//镜像翻转
    	if (isFlipped)
    	{
    		fire->setFlippedX(true);
    	}
    	//位置
    	fire->setPosition(Vec2(getContentSize().width / 2 + offsetX, offsetY));
    
    	return true;
    }
    

    BulletGroup

    BulletGroup是比较重要的类,包含了子弹的基本参数(结构体TBulletData,用于”复制“出一颗一颗的子弹)、发射动作、与飞机以及敌机间的关系。详细代码如下:

    //子弹的基类。内部维护了一个子弹池
    class BulletGroup : public GameObjectContainer
    {
    public:
    	friend class Bullet;
    	friend class Missile;
    	friend class Laser;
    
    	BulletGroup();
    	virtual ~BulletGroup();
    
    	virtual bool init(Node* pParent, Aircraft* pPlane, const TBulletData* pData);
    	virtual void reset();
    	virtual void destory();
    
    	//开始射击
    	virtual void startShoot();
    	//发射指定数量的子弹
    	virtual void startShoot(int cnt);
    	//停止射击
    	virtual void stopShoot();
    	//是否正在射击
    	virtual bool isShooting();
    
    	//从子弹池中删除一颗子弹。超出屏幕范围,或者击中目标
    	virtual void RemoveBullet(Bullet* pBullet);
    	//爆炸
    	virtual void blast(Bullet* pBullet);
    
    	//当对方飞机增加或者减少时,通知导弹
    	virtual void nodifyTargetRemove(Aircraft* pAircraft) {}
    
    	//注册子弹用完监听器
    	void regBulletUseUpListener(IBulletUseUpListener* l);
    	void notifyBulletUseUp();
    	inline bool isUseUp() { return m_bIsUseUp; }
    
    public:
    	//子弹飞出屏幕后的完成动作(内部函数)
    	virtual void bulletMoveFinished(Node* pSender);
    
    	inline int getAttack()
    	{
    		return m_data.attack;
    	}
    
    	inline void setAttack(int a)
    	{
    		m_data.attack = a;
    	}
    
    	inline void setPlane(Aircraft* plane)
    	{
    		m_plane = plane;
    	}
    
    	inline Aircraft* getPlane()
    	{
    		return m_plane;
    	}
    
    	inline void setOtherSidePlane(Vector<Aircraft*>* const planes)
    	{
    		m_otherSideArray = planes;
    	}
    
    	virtual void update(float dt) override;
    
    protected:
    
    	//发射一次子弹(可能会包含多颗子弹)
    	virtual void AddBullet(float dt) {};
    
    	//从子弹池中获取一颗子弹
    	virtual Bullet* getOneBullet();
    
    	float getPara(const char* const param);
    
    	bool isSameGroup() { return m_bSameGroup; }
    protected:
    
    	TBulletData m_data;
    
    	int m_iCount;
    	int m_iAvailableBullet;
    	bool m_bIsShooting;
    	Aircraft* m_plane;
    	Vector<Aircraft*> * m_otherSideArray;
    
    	int m_iIntervalCnt; //小间隔子弹发射次数
    	float m_iTimeCum;   //时间累计
    	float m_iTimeCumThisGrade;   //本等级下的时间累计
    	bool m_bFirstShoot; //
    
    	bool m_bSameGroup;  //是否在同一个大间隔内
    
    	IBulletUseUpListener* m_pBulletUseUpListener;
    	bool m_bIsUseUp;
    	float m_timeLimitAdd;
    };
    
    
    BulletGroup::BulletGroup()
    {
    	this->reset();
    }
    
    BulletGroup::~BulletGroup()
    {
    }
    
    void BulletGroup::reset()
    {
    	m_data.reset();
    
    	m_iCount = 0;
    	m_bIsShooting = false;
    	m_plane = NULL;
    	m_otherSideArray = NULL;
    	m_iAvailableBullet = 0;
    
    	m_iIntervalCnt = 0;
    	m_iTimeCum = 0;
    	m_iTimeCumThisGrade = 0;
    	m_bFirstShoot = false;
    
    	m_pBulletUseUpListener = NULL;
    	m_bIsUseUp = false;
    	m_timeLimitAdd = 0;
    }
    
    bool BulletGroup::init(Node* pParent, Aircraft* pPlane, const TBulletData* pData)
    {
    	bool bRet = false;
    	do
    	{
    		CC_BREAK_IF(!GameObjectContainer::init());
    
    		setPlane(pPlane);
    		m_data.clone(*pData);
    
    		Node::setName(m_data.type);
    
    		if (m_data.topOfAircraft)
    		{
    			pParent->addChild(this, CONSTANT::ZORDER_BULLET_ONTOP);
    		}
    		else
    		{
    			if (pPlane->getAircraftType() == EAircraftType::Type_Enemy)
    			{
    				pParent->addChild(this, CONSTANT::ZORDER_BULLET_ENEMY);
    			}
    			else
    			{
    				pParent->addChild(this, CONSTANT::ZORDER_BULLET_PLAYER);
    			}
    		}
    
    		//body
    		Vec2 center(m_data.bodyCenterX, m_data.bodyCenterY);
    		setBodyCenter(center);
    
    		//如果没有设置刚体大小,则默认是图片尺寸的60%
    		Size size(m_data.bodySizeW, m_data.bodySizeH);
    		if (fabs(size.width) < 0.01 || fabs(size.height) < 0.01)
    		{
    			size.width = this->getContentSize().width * 0.6f;
    			size.height = this->getContentSize().height * 0.6f;
    		}
    		setBodySize(size);
    
    		//获取对方飞机列表
    		m_otherSideArray = pPlane->getOtherSidePlane();
    
    		if (pPlane->getAircraftType() == EAircraftType::Type_Enemy)
    		{
    			m_timeLimitAdd = GameData::getInstance()->getValueToFloat(GAMEDATA::REINFORCE_VALUE_RAMPAGE_DURA);
    		}
    
    		bRet = true;
    	} while (0);
    
    	return bRet;
    }
    
    void BulletGroup::destory()
    {
    	if (this->m_plane == NULL)
    	{
    		int i = 0;
    
    		for (i = 0; i < getAllObject()->count(); i++)
    		{
    			Bullet* p = dynamic_cast<Bullet*>(getAllObject()->getObjectAtIndex(i));
    
    			if (p != NULL && p->isVisible())
    			{
    				//无需释放
    				return;
    			}
    		}
    
    		if (getAllObject()->count() == i)
    		{
    			for (i = 0; i < getAllObject()->count(); i++)
    			{
    				Bullet* p = dynamic_cast<Bullet*>(getAllObject()->getObjectAtIndex(i));
    
    				if (p != NULL)
    				{
    					p->destory();
    				}
    			}
    
    			getAllObject()->removeAllObjects();
    			
    			removeFromParent();
    		}
    	}
    }
    
    void BulletGroup::startShoot()
    {
    	m_bIsShooting = true;
    
    	this->scheduleUpdate();
    }
    
    void BulletGroup::startShoot(int cnt)
    {
    	m_bIsShooting = false;
    	m_iCount = 0;
    	m_iIntervalCnt = 0;
    	m_iTimeCum = 0;
    	m_iTimeCumThisGrade = 0;
    	m_bFirstShoot = 0;
    	m_bSameGroup = false;
    	m_bIsUseUp = false;
    
    	m_data.max_count = cnt;
    	m_bIsShooting = true;
    
    	this->scheduleUpdate();
    }
    
    void BulletGroup::stopShoot()
    {
    	unscheduleUpdate();
    	unscheduleAllCallbacks();
    
    	m_bIsShooting = false;
    }
    
    bool BulletGroup::isShooting()
    {
    	return m_bIsShooting;
    }
    
    void BulletGroup::update(float dt)
    {
    	m_iTimeCum += dt;
    	m_iTimeCumThisGrade += dt;
    
    	if (m_data.timeLimit > 0 && m_iTimeCumThisGrade > m_data.timeLimit + m_timeLimitAdd)
    	{
    		m_bIsUseUp = true;
    		m_iTimeCumThisGrade = 0;
    		this->notifyBulletUseUp();
    		this->stopShoot();
    		return;
    	}
    
    	if (!m_bFirstShoot && m_iTimeCum < m_data.delay && m_data.delay >= 0.00001) //还没进行第一次发射,等待延迟
    	{
    		return;
    	}
    	else if (!m_bFirstShoot && m_iTimeCum >= m_data.delay && m_data.delay >= 0.00001) //第一次发射,超时了
    	{
    		m_iTimeCum -= m_data.delay;
    		m_bFirstShoot = true;
    
    		m_iIntervalCnt++;
    		if (Sound::isBulletSound())
    		{
    			Sound::playSound(m_data.musicId);
    		}
    		m_bSameGroup = true;
    		AddBullet(dt);
    		return;
    	}
    
    	//小间隔内
    	if (m_iIntervalCnt < m_data.interval_2_cnt)
    	{
    		if (m_iTimeCum >= m_iIntervalCnt * m_data.interval_2)
    		{
    			m_iIntervalCnt++;
    			if (Sound::isBulletSound())
    			{
    				Sound::playSound(m_data.musicId);
    			}
    
    			m_bSameGroup = true;
    			AddBullet(dt);
    			return;
    		}
    
    	}
    	else //大间隔
    	{
    		if (m_iTimeCum >= m_data.interval_2_cnt * m_data.interval_2 + m_data.interval)
    		{
    			m_iIntervalCnt = 1;
    			m_iTimeCum = 0;
    			if (Sound::isBulletSound())
    			{
    				Sound::playSound(m_data.musicId);
    			}
    
    			m_bSameGroup = false;
    			AddBullet(dt);
    			return;
    		}
    	}
    }
    
    Bullet* BulletGroup::getOneBullet()
    {
    	if (!m_bIsShooting)
    	{
    		return NULL;
    	}
    
    	if (m_data.max_count > 0 && m_iCount >= m_data.max_count)
    	{
    		m_bIsUseUp = true;
    		this->notifyBulletUseUp();
    
    		return NULL;
    	}
    
    	int i = 0;
    	int size = getAllObject()->count();
    	for (; i < size; i++)
    	{
    		Bullet* p = dynamic_cast<Bullet*>(m_pAllObject->getObjectAtIndex(i));
    
    		if (p != NULL && p->isVisible() == false)
    		{
    			p->setVisible(true);
    			p->startAnimate();
    			m_iAvailableBullet++;
    			m_iCount++;
    			return p;
    		}
    	}
    
    	if (m_pAllObject->count() == i)
    	{
    		Bullet* pBullet = Bullet::create(this);
    		m_pAllObject->addObject(pBullet);
    		m_iAvailableBullet++;
    		m_iCount++;
    		//CCLOG("Add bullet[%s], size = %d", Node::getName().c_str(), m_pAllObject->count());
    		return pBullet;
    	}
    
    	return NULL;
    }
    
    //击中目标后爆炸并删掉
    void BulletGroup::blast(Bullet* pBullet)
    {
    	//在父层中生成爆炸动画
    	PlaneLayer* pLayer = dynamic_cast<PlaneLayer*>(getParent());
    	if (NULL != pLayer)
    	{
    		Rect rect = pBullet->getBodyBox();
    		Vec2 pos(rect.getMidX(), rect.getMidY());
    
    		//击中的爆炸动画
    		pLayer->addBlast(this->getLocalZOrder(), pos, m_data.blastStyleArray, m_data.blastAniDura);
    
    		//粒子效果。TODO 后续要使用particle表里读取粒子配置
    		ParticleSystemQuad *emitter1 = ParticleSystemQuad::create("img/blast/hitEnemy.plist");
    		emitter1->setPosition(pos);    // 设置发射粒子的位置  
    		emitter1->setAutoRemoveOnFinish(true);                          // 完成后制动移除  
    		emitter1->setDuration(0.3f);                                      // 设置粒子系统的持续时间秒  
    
    		pLayer->addChild(emitter1, getLocalZOrder() + 1);
    	}
    	
    	RemoveBullet(pBullet);
    }
    
    //子弹飞出屏幕后删掉
    void BulletGroup::bulletMoveFinished(Node* pSender)
    {
    	RemoveBullet((Bullet*)pSender);
    }
    
    void BulletGroup::RemoveBullet(Bullet* pBullet)
    {
    	if (pBullet != NULL)
    	{
    		pBullet->stopAllActions();
    		pBullet->setVisible(false);
    		Node* parent = getParent();
    
    		m_iAvailableBullet--;
    
    		int size = getAllObject()->count();
    		int i = 0;
    		for (; i < size; i++)
    		{
    			Bullet* p = dynamic_cast<Bullet*>(m_pAllObject->getObjectAtIndex(i));
    			
    			if (p != NULL && p->isVisible() == true && m_iAvailableBullet <= 0)
    			{
    				return;
    			}
    		}
    
    	}
    
    	//清除本对象
    	if (m_plane == NULL && m_iAvailableBullet <= 0 && getParent() != NULL && !this->isShooting())
    	{
    		int size = getAllObject()->count();
    		int i = 0;
    		for (; i < size; i++)
    		{
    			Bullet* p = dynamic_cast<Bullet*>(m_pAllObject->getObjectAtIndex(i));
    			p->m_pBulletGroup = NULL;
    			p->getParent()->removeChild(p);
    		}
    
    		PlaneLayer* layer = dynamic_cast<PlaneLayer*>(getParent());
    		getParent()->removeChild(this);
    		if (layer)
    		{
    			layer->removeBullet(this);
    		}
    	}
    
    }
    
    void BulletGroup::regBulletUseUpListener(IBulletUseUpListener* l)
    {
    	m_pBulletUseUpListener = l;
    }
    
    void BulletGroup::notifyBulletUseUp()
    {
    	if (m_pBulletUseUpListener != NULL)
    	{
    		m_pBulletUseUpListener->bulletUseUp();
    	}
    }
    
    float BulletGroup::getPara(const char* const param)
    {
    	auto it = m_data.paramMap.find(param);
    	if (it != m_data.paramMap.end())
    	{
    		return it->second;
    	}
    	else
    	{
    		return 0;
    	}
    }
    
    

    子弹的每次发射的时间点控制在update方法中,其中使用到了延时delay、大小周期intervalinterval_2interval_2_cnt等相关参数。当确定好每次发射的时间点后,会通过AddBullet来添加一颗子弹,放在某个位置上,并让其发射出去,具体的放置位置和发射的角度速度等参数留给子类来实现,这里仅仅定义一个空方法:virtual void AddBullet(float dt) {};。从方法RemoveBullet可以看到,当子弹飞出屏幕或者击中目标时,我们销毁子弹的方法仅仅是停止其动画效果,并设置为不可见,这样可以减少频繁创建/销毁子弹带来的性能损失。当子弹用完时,会通过方法notifyBulletUseUp来通知飞机,这样,飞机就会对子弹进行降档,例如从暴走状态恢复到普通状态。

    下面我们看看散弹、跟踪导弹、激光等各个子类的具体实现。

    散弹

    最简单的散弹就是每次只发射一颗子弹,并且所有子弹都朝着一个不变的方向飞行,看起来就像一条直线。当每次发射两颗或多颗子弹时,看起来就是两条或多条直线,这多条直线或平行,或相邻之间存在一个固定的夹角。在代码中是通过angle_interval参数来控制的。

    还有一种偏转角度随时间变化的散弹,即每发射一颗子弹,其飞行角度都会向某个方向偏转一个固定的角度,当达到一个最大角度值时,又会逐渐减小偏转角度,看起来就像个“之”字形。如果最大偏转角度不存在时,子弹轨迹看起来就是个螺旋形状了。在代码中是通过rotate_anglerotate_max_angle来控制的。 下面我们看下具体代码。

    //可自定义子弹的个数、夹角、偏移
    class ScatterBullet : public BulletGroup
    {
    public:
    	ScatterBullet();
    
    	virtual bool init(Node* pParent, Aircraft* pPlane, const TBulletData* pData);
    	virtual void reset();
    
    protected:
    	//发射一次子弹
    	virtual void AddBullet(float dt) override;
    	//计算发射中心线的角度
    	virtual float calculateCenterLineDegree(Bullet* pBullet, Vec2& src);
    	//发射一颗子弹
    	virtual bool AddBulletOne(Vec2& srcOffset, float degree);
    
    protected:
    	float m_flyDistance;
    	long m_shootCnt;
    	int m_shootAddFlag;
    };
    
    
    ScatterBullet::ScatterBullet()
    	: m_flyDistance(0)
    	, m_shootCnt(0)
    	, m_shootAddFlag(0)
    {
    
    }
    
    void ScatterBullet::reset()
    {
    	BulletGroup::reset();
    
    	m_flyDistance = 0;
    	m_shootCnt = 0;
    	m_shootAddFlag = 0;
    }
    
    bool ScatterBullet::init(Node* pParent, Aircraft* pPlane, const TBulletData* pData)
    {
    	bool bRet = false;
    	do
    	{
    		CC_BREAK_IF(!BulletGroup::init(pParent, pPlane, pData));
    
    		m_flyDistance = CONSTANT::DESIGN_RES_DIAGONAL;
    		m_shootAddFlag = 1;
    
    		if (m_data.count < 0)
    		{
    			m_data.count = 1;
    		}
    
    		bRet = true;
    	} while (0);
    
    	return bRet;
    }
    
    void ScatterBullet::AddBullet(float dt)
    {
    	m_shootCnt += m_shootAddFlag;
    
    	float angleLeft = 0;
    	float offsetXLeft = 0;
    	if (m_data.count % 2 == 0)//偶数
    	{
    		//计算最左边的子弹的夹角、偏移
    		angleLeft = m_data.angle_interval / 2 + (m_data.count / 2 - 1) * m_data.angle_interval;
    		offsetXLeft = -1 * (m_data.x_interval / 2 + (m_data.count / 2 - 1)*m_data.x_interval);
    	}
    	else //奇数
    	{
    		angleLeft = (m_data.count - 1) / 2 * m_data.angle_interval;
    		offsetXLeft = -1 * (m_data.count - 1) / 2 * m_data.x_interval;
    	}
    
    	float offsetAngle = 0;
    	if (m_data.angle_of_center > 0 && m_data.angle_of_center < 180)
    	{
    		offsetAngle = m_data.angle_of_center - 90;
    	}
    	else
    	{
    		offsetAngle = m_data.angle_of_center - 270;
    	}
    
    	for (int i = 0; i < m_data.count; i++)
    	{
    		float a = angleLeft - m_data.angle_interval * i;
    
    		float l = offsetXLeft + m_data.x_interval * i;
    		float x = l * cos(CC_DEGREES_TO_RADIANS(offsetAngle));
    		float y = -l * sin(CC_DEGREES_TO_RADIANS(offsetAngle));
    		Vec2 v(m_data.origin_offset_x + x, m_data.origin_offset_y + y);		
    		if (!AddBulletOne(v, a))
    		{
    			break;
    		}
    	}
    }
    
    float ScatterBullet::calculateCenterLineDegree(Bullet* pBullet, Vec2& src)
    {
    	float angle = (m_shootCnt - 1) * m_data.rotate_angle;
    	if (fabs(angle) > m_data.rotate_max_angle && m_data.rotate_max_angle >= 1)
    	{
    		if (m_data.rotate_flag == 0)
    		{
    			//m_shootAddFlag = -m_shootAddFlag;
    			if (angle * m_data.rotate_angle > 0)
    			{
    				m_shootAddFlag = -1;
    			}
    			else
    			{
    				m_shootAddFlag = 1;
    			}
    		}
    		else
    		{
    			m_shootCnt = 0;
    			m_shootAddFlag = 1;
    		}
    	}
    
    	return m_data.angle_of_center + angle;
    }
    
    //degree 角度偏移。正数表示逆时针,负数表示顺时针
    bool ScatterBullet::AddBulletOne(Vec2& srcOffset, float degree)
    {
    	//添加一个子弹精灵
    	Bullet* pBullet = BulletGroup::getOneBullet();
    	if (!pBullet || !getPlane())
    	{
    		return false;
    	}
    
    	//子弹的位置
    	Vec2 src;
    	if (getPlane()->getAircraftType() == EAircraftType::Type_Wingman) //僚机
    	{
    		Aircraft* pMainPlane = dynamic_cast<Aircraft*>(getPlane()->getParent());
    
    		src = pMainPlane->getPosition() + getPlane()->getPosition() - pMainPlane->getContentSize() / 2;
    	}
    	else //非僚机
    	{
    		src = getPlane()->getPosition();
    	}
    
    	src.y += srcOffset.y;
    
    	if (m_data.count == 1)
    	{
    		if (m_data.angle_of_center > 0 && m_data.angle_of_center < 180)
    		{
    			src.x += srcOffset.x;
    		}
    		else
    		{
    			src.x += -srcOffset.x;
    		}
    	}
    
    	//这一句必须要放在src.x += srcOffset.x之前
    	float centerLineDegree = calculateCenterLineDegree(pBullet, src)/* + offsetDegree*/;
    
    	if (m_data.count > 1)
    	{
    		if (m_data.angle_of_center > 0 && m_data.angle_of_center < 180)
    		{
    			src.x += srcOffset.x;
    			src.y += pBullet->getContentSize().height / 2;
    		}
    		else
    		{
    			src.x += -srcOffset.x;
    			src.y -= pBullet->getContentSize().height / 2;
    		}
    	}
    	pBullet->setPosition(src);
    
    
    	if (sinf(CC_DEGREES_TO_RADIANS(m_data.angle_of_center)) > 0)
    	{
    		pBullet->setRotation(90 - (centerLineDegree + degree));
    	}
    	else
    	{
    		pBullet->setRotation(90 - (centerLineDegree + degree) + 180);
    	}
    
    	if (degree > 0)
    	{
    		pBullet->setFlippedX(true);
    	}
    
    	//body
    	Vec2& pos = m_bodyCenter;
    	Vec2 pos2;
    	pos2.x = sqrt(pow(pos.x, 2) + pow(pos.y, 2)) * cos(CC_DEGREES_TO_RADIANS(centerLineDegree + degree));
    	pos2.y = sqrt(pow(pos.x, 2) + pow(pos.y, 2)) * sin(CC_DEGREES_TO_RADIANS(centerLineDegree + degree));
    	pBullet->setBodyCenter(pos2);
    	pBullet->setBodySize(getBodySize());
    
    	//子弹飞出屏幕所需的距离、飞行速度、飞行时间
    	float realMoveDuration = m_flyDistance / m_data.speed;
    
    	//在realMoveDuration时间内,飞到指定位置
    	float deltaX = m_flyDistance * cos(CC_DEGREES_TO_RADIANS(centerLineDegree + degree));
    	float deltaY = m_flyDistance * sin(CC_DEGREES_TO_RADIANS(centerLineDegree + degree));
    
    	Vec2 dest = Vec2(src.x + deltaX, src.y + deltaY);
    	FiniteTimeAction* actionMove = CCMoveTo::create(realMoveDuration, dest);
    	FiniteTimeAction* actionDone = CallFuncN::create(CC_CALLBACK_0(BulletGroup::bulletMoveFinished, this, pBullet));
    
    	//开始执行动作
    	Sequence* sequenceL = Sequence::create(actionMove, actionDone, NULL);
    	pBullet->runAction(sequenceL);
    
    	return true;
    }
    
    

    跟踪导弹

    跟踪导弹是一种特殊的子弹,当发射出导弹时,它首先会自动寻找并锁定距离最近敌机,然后从一个初速度开始,加速飞向目标(普通子弹是匀速飞行)。由于目标是运动的,所以导弹还具备自动调整飞行角度的能力。但是,导弹还存在转弯半径这个限制,即导弹并不能任意转弯,它存在角速度参数,每个单位时间内最大只能偏转一个固定的角度。上述所有的逻辑都在Missile类的update方法中,详见下面的代码:

    void Missile::update(float dt)
    {
    	if (!this->isVisible() || m_pBulletGroup == NULL)
    	{
    		return;
    	}
    
    	//飞出屏幕
    	const Rect& rect = this->getBoundingBox();
    	const Size& windowSize = Director::getInstance()->getWinSize();
    	if (rect.getMinX() > windowSize.width || rect.getMaxX() < 0
    		|| rect.getMinY() > windowSize.height || rect.getMaxY() < 0)
    	{
    		m_pBulletGroup->bulletMoveFinished(this);
    		return;
    	}
    
    	//寻找距离最近,且夹角小于70度的敌机
    	if ((!m_pEnemy || !m_pEnemy->isAlive()) && m_pBulletGroup && m_pBulletGroup->m_otherSideArray)
    	{
    		((MissileGroup*)m_pBulletGroup)->searchEnemy(this);
    	}
    
    	float f = 0;
    	if (m_pEnemy && m_pEnemy->isAlive())
    	{
    		//转向目标
    		float curRot = getRotation();
    		float angle = -CC_RADIANS_TO_DEGREES((getPosition() - m_pEnemy->getPosition()).getAngle());
    		float tmpAngle = angle;
    		if (angle - 90 - curRot < -90 && angle - 90 - curRot + 360 < 90)
    		{
    			angle += 360;
    		}
    		else if (angle - 90 - curRot > 90 && angle - 90 - curRot - 360 > -90)
    		{
    			angle -= 360;
    		}
    		else
    		{
    			if (fabsf(m_fLastAngle - angle) > 180)
    			{
    				if (m_fLastAngle > 0)
    				{
    					angle += 360;
    				}
    				else if (m_fLastAngle < 0)
    				{
    					angle -= 360;
    				}
    			}
    		}
    
    		m_fLastAngle = angle;
    
    		//最大偏转角度
    		float angleDif = std::min(std::max((angle - 90) - curRot, -m_fTurnRate*dt), m_fTurnRate*dt);
    		f = curRot + angleDif;
    		//DEBUG_LOG("Missile[%p,%.0f,%.0f] aimed emeny[%p,%.0f,%.0f], "
    		//	"angle[%.2f, %.2f],max[%.2f], curRot[%.2f],angleDif[%.2f],f[%.2f]",
    		//	this, getPosition().x, getPosition().y,
    		//	m_pEnemy, m_pEnemy->getPosition().x, m_pEnemy->getPosition().y,
    		//	tmpAngle, angle, m_fTurnRate*dt, curRot, angleDif, f);
    
    	}
    	
    	if (!m_pEnemy || !m_pEnemy->isAlive())
    	{
    		f = getRotation();
    		//DEBUG_LOG("Missile[%p,%.0f,%.0f] aimed emeny[NULL], angle[%f]", 
    		//	this, getPosition().x, getPosition().y, f);
    	}
    
    	setRotation(f);
    	setPosition(getPosition() + 
    		Vec2(sinf(CC_DEGREES_TO_RADIANS(f))*m_fVelocity, cosf(CC_DEGREES_TO_RADIANS(f))*m_fVelocity)
    		* Director::getInstance()->getScheduler()->getTimeScale());
    
    	Vec2 pos2;
    	float dd = sqrt(pow(m_bodyCenter.x, 2) + pow(m_bodyCenter.y, 2));
    	pos2.x = dd * cos(CC_DEGREES_TO_RADIANS(90 - f));
    	pos2.y = dd * sin(CC_DEGREES_TO_RADIANS(90 - f));
    	this->setBodyCenter(pos2);
    
    	//当子弹旋转超过45°时,宽高值交换
    	if (fabsf(f) > 45 && fabsf(f) < 135)
    	{
    		Size size = getOrignBodySize();
    		float tmp = size.width;
    		size.width = size.height;
    		size.height = tmp;
    		this->setBodySize(size);
    	}
    
    	m_fVelocity += m_fAccel*dt;
    }
    
    

    激光

    激光看似与普通子弹或者导弹完全不同,但是仔细分析下来,也可以看成是一颗特殊的子弹:

    1. 激光始终都只是一颗子弹(并没有多颗子弹)
    2. 激光外形是一个长度可变的矩形。最大长度是屏幕的高度,当遇到敌机时,激光的长度是从飞机到敌机之间的距离
    3. 激光对目标产生的伤害依赖于其接触目标的时间,并具有线性关系。
      通过以上分析,可以得到激光最与众不同的地方在于其外观的可变性,下面是具体的代码:
    bool Laser::init(BulletGroup* pBulletGroup)
    {
    	if (!GameObject::init())
    	{
    		return false;
    	}
    
    	m_pBulletGroup = pBulletGroup;
    	//int planeZOrder = m_pBulletGroup->m_plane->getLocalZOrder();
    	if (m_pBulletGroup->getPlane()->getAircraftType() == EAircraftType::Type_Killer)
    	{
    		m_pBulletGroup->getParent()->addChild(this, CONSTANT::ZORDER_BULLET_KILLER);
    	}
    	else if (m_pBulletGroup->getPlane()->getAircraftType() == EAircraftType::Type_Player)
    	{
    		m_pBulletGroup->getParent()->addChild(this, CONSTANT::ZORDER_BULLET_PLAYER);
    	}
    	else
    	{
    		int planeZOrder = CONSTANT::ZORDER_BULLET_PLAYER;
    		m_pBulletGroup->getParent()->addChild(this, planeZOrder - 1);
    	}
    
    	//这里不能用createWithSpriteFrameName,否则激光会变成一段一段的(中间有黑色间隙)
    	Sprite* pLaserNode = Sprite::create(m_pBulletGroup->m_data.styleArray.at(0));
    
    	m_fWidth = pLaserNode->getContentSize().width;//宽度
    	m_fHeight = CONSTANT::DESIGN_RES_HEIGHT + 50;//初始长度,理论上是无限高,这里只需要大于屏幕的高度即可
    
    	//设置刚体的大小
    	setBodySize(Size(pBulletGroup->getBodySize().width, m_fHeight)); 
    	setBodyCenter(pBulletGroup->getBodyCenter());
    
    	setContentSize(Size(pBulletGroup->getBodySize().width, m_fHeight));
    
    	//计算纹理的个数
    	int cnt = (int)(m_fHeight / pLaserNode->getContentSize().height + 0.9999);
    	//实际高度
    	float h = cnt * pLaserNode->getContentSize().height;
    
    	//高度调整为2的n次方
    	h = toPOT(h);
    	cnt = (int)(h / pLaserNode->getContentSize().height + 0.9999);
    
    	// 1: Create new CCRenderTexture
    	int potW = toPOT(m_fWidth);
    	RenderTexture *rt = RenderTexture::create(potW, h);
    
    	// 2: Call CCRenderTexture:begin
    	rt->begin();
    
    	//开始贴图
    	pLaserNode->setFlippedY(true);
    	pLaserNode->setPosition(Vec2(m_fWidth / 2, pLaserNode->getContentSize().height / 2));
    	pLaserNode->visit();
    
    	for (int i = 1; i < cnt; i++)
    	{
    		//这里不能用createWithSpriteFrameName,否则激光会变成一段一段的(中间有黑色间隙)
    		Sprite* pLaserNode2 = Sprite::create(m_pBulletGroup->m_data.styleArray.at(0));
    		pLaserNode2->setFlippedY(true);
    		pLaserNode2->setPosition(Vec2(m_fWidth / 2, 
    			pLaserNode->getContentSize().height / 2 + pLaserNode->getContentSize().height * i));
    		pLaserNode2->visit();
    	}
    
    	// 4: Call CCRenderTexture:end
    	rt->end();
    
    	// 5: Create a new Sprite from the texture
    	m_pLaser = Sprite::createWithTexture(rt->getSprite()->getTexture());
    
    	m_pLaser->setTextureRect(Rect(0, 0, m_fWidth, m_fHeight));
    
    	Texture2D::TexParams tp = { GL_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT };
    	m_pLaser->getTexture()->setTexParameters(tp);
    
    	m_pLaser->setAnchorPoint(Vec2(0.5, 0));
    	m_pLaser->setPosition(Vec2(getContentSize().width / 2, 0));
    	addChild(m_pLaser);
    
    	//添加爆炸点
    	m_pBlast = GameObject::createSpriteWithFileList(m_pBulletGroup->m_data.blastStyleArray, m_pBulletGroup->m_data.blastAniDura);
    	if (m_pBlast)
    	{
    		m_pBlast->setPosition(Vec2(getBodySize().width / 2, getBodySize().height));
    		addChild(m_pBlast);
    	}
    
    	scheduleUpdate();
    
    	return true;
    }
    
    void Laser::rejustHeight(float fMinY)
    {
    	if (m_pBulletGroup && m_pBulletGroup->m_plane)
    	{
    		//根据敌机的位置计算激光的高度
    		m_fHeight = fMinY - m_pBulletGroup->m_plane->getPosition().y;
    		if (m_fHeight < 0)
    		{
    			m_fHeight = CONSTANT::DESIGN_RES_HEIGHT + 200;
    		}
    		setBodySize(Size(m_pBulletGroup->getBodySize().width, m_fHeight));
    		if (m_pBlast)
    		{
    			m_pBlast->setPosition(Vec2(getBodySize().width / 2, getBodySize().height));
    		}
    	}
    }
    
    void Laser::update(float dt)
    {
    	if (!this->isVisible() || m_pBulletGroup == NULL)
    	{
    		return;
    	}
    
    	//计算当前位置
    	setBodySize(Size(getBodySize().width, CONSTANT::DESIGN_RES_HEIGHT));
    
    	m_fTmpOffset += m_pBulletGroup->m_data.speed * dt;
    
    	const Size& textureSize = m_pLaser->getTextureRect().size;
    	m_pLaser->setTextureRect(Rect(0, m_fTmpOffset, textureSize.width, m_fHeight));
    	
    }
    
    

    其中,init用于创建激光外观,这里通过{ GL_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT }来创建一个重复的纹理,然后在update中让纹理不停的向上滚动。在rejustHeight中,我们根据目标的Y轴坐标来计算激光的高度,并在激光接触目标的位置增加一个击中效果的贴图。

    转载请注明:http://www.cnblogs.com/thorqq/p/6563904.html
    本文相关源码:https://files.cnblogs.com/files/thorqq/Bullet.rar

  • 相关阅读:
    Oracle:Using the DBMS_STATSpackage
    Oracle partitioning is not always a good idea.
    Oracle: Benefits and consequences of the NOLOGGING option
    Oracle :Insert ways.
    Oracle:临时表的统计信息
    C#中使用DTS来导入数据及相关问题
    [收藏]CSS网页制作时实现自动换行的小技巧
    新加了牛人的Blog链接
    在.Net下使用Access 的日期类型 及与js的日历控件交互
    在程序中生成PDF
  • 原文地址:https://www.cnblogs.com/thorqq/p/6563904.html
Copyright © 2011-2022 走看看