仿《雷霆戰機》飛行射擊手遊開發--子彈、跟蹤導彈和激光

轉載請註明:http://www.javashuo.com/article/p-yijeausq-cb.html
試玩版下載:https://pan.baidu.com/s/1mhLBNVa (Windows版)
源代碼下載:https://git.oschina.net/thorqq/RaidenFreegit

飛機的武器類型衆多,大體可分爲子彈、跟蹤導彈和激光。子彈是直線飛行的;導彈會自動跟蹤目標,可曲線飛行;而激光是一道光束,可持續地對照射到的敵機產生傷害。子彈根據一次性發射的數量,可分爲單發和多發,根據子彈發射的方向可分爲:自動瞄準、平行、散射。本文將結合代碼講述有關飛機武器的程序設計。ide

程序設計

先看一下本遊戲中有關武器的類的設計 類結構 其中,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,用於」複製「出一顆一顆的子彈)、發射動做、與飛機以及敵機間的關係。詳細代碼以下:this

//子彈的基類。內部維護了一個子彈池
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來通知飛機,這樣,飛機就會對子彈進行降檔,例如從暴走狀態恢復到普通狀態。.net

下面咱們看看散彈、跟蹤導彈、激光等各個子類的具體實現。設計

散彈

最簡單的散彈就是每次只發射一顆子彈,而且全部子彈都朝着一個不變的方向飛行,看起來就像一條直線。當每次發射兩顆或多顆子彈時,看起來就是兩條或多條直線,這多條直線或平行,或相鄰之間存在一個固定的夾角。在代碼中是經過angle_interval參數來控制的。code

還有一種偏轉角度隨時間變化的散彈,即每發射一顆子彈,其飛行角度都會向某個方向偏轉一個固定的角度,當達到一個最大角度值時,又會逐漸減少偏轉角度,看起來就像個「之」字形。若是最大偏轉角度不存在時,子彈軌跡看起來就是個螺旋形狀了。在代碼中是經過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.javashuo.com/article/p-yijeausq-cb.html

相關文章
相關標籤/搜索