轉載請註明: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
、大小週期interval
、interval_2
、interval_2_cnt
等相關參數。當肯定好每次發射的時間點後,會經過AddBullet
來添加一顆子彈,放在某個位置上,並讓其發射出去,具體的放置位置和發射的角度速度等參數留給子類來實現,這裏僅僅定義一個空方法:virtual void AddBullet(float dt) {};
。從方法RemoveBullet
能夠看到,當子彈飛出屏幕或者擊中目標時,咱們銷燬子彈的方法僅僅是中止其動畫效果,並設置爲不可見,這樣能夠減小頻繁建立/銷燬子彈帶來的性能損失。當子彈用完時,會經過方法notifyBulletUseUp
來通知飛機,這樣,飛機就會對子彈進行降檔,例如從暴走狀態恢復到普通狀態。.net
下面咱們看看散彈、跟蹤導彈、激光等各個子類的具體實現。設計
最簡單的散彈就是每次只發射一顆子彈,而且全部子彈都朝着一個不變的方向飛行,看起來就像一條直線。當每次發射兩顆或多顆子彈時,看起來就是兩條或多條直線,這多條直線或平行,或相鄰之間存在一個固定的夾角。在代碼中是經過angle_interval
參數來控制的。code
還有一種偏轉角度隨時間變化的散彈,即每發射一顆子彈,其飛行角度都會向某個方向偏轉一個固定的角度,當達到一個最大角度值時,又會逐漸減少偏轉角度,看起來就像個「之」字形。若是最大偏轉角度不存在時,子彈軌跡看起來就是個螺旋形狀了。在代碼中是經過rotate_angle
和rotate_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; }
激光看似與普通子彈或者導彈徹底不一樣,可是仔細分析下來,也能夠當作是一顆特殊的子彈:
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軸座標來計算激光的高度,並在激光接觸目標的位置增長一個擊中效果的貼圖。