仿《雷霆战机》飞行射击手游开发--子弹、跟踪导弹和激光
轉載請注明:https://my.oschina.net/u/1986600/blog/863445
試玩版下載:https://pan.baidu.com/s/1mhLBNVa (Windows版)
源代碼下載:https://git.oschina.net/thorqq/RaidenFree
飛機的武器類型眾多,大致可分為子彈、跟蹤導彈和激光。子彈是直線飛行的;導彈會自動跟蹤目標,可曲線飛行;而激光是一道光束,可持續地對照射到的敵機產生傷害。子彈根據一次性發射的數量,可分為單發和多發,根據子彈發射的方向可分為:自動瞄準、平行、散射。本文將結合代碼講述有關飛機武器的程序設計。
程序設計
先看一下本游戲中有關武器的類的設計 其中,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;//小周期里子彈數量,這里是3float 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);}}//bodyVec2 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來通知飛機,這樣,飛機就會對子彈進行降檔,例如從暴走狀態恢復到普通狀態。
下面我們看看散彈、跟蹤導彈、激光等各個子類的具體實現。
散彈
最簡單的散彈就是每次只發射一顆子彈,并且所有子彈都朝著一個不變的方向飛行,看起來就像一條直線。當每次發射兩顆或多顆子彈時,看起來就是兩條或多條直線,這多條直線或平行,或相鄰之間存在一個固定的夾角。在代碼中是通過angle_interval參數來控制的。
還有一種偏轉角度隨時間變化的散彈,即每發射一顆子彈,其飛行角度都會向某個方向偏轉一個固定的角度,當達到一個最大角度值時,又會逐漸減小偏轉角度,看起來就像個“之”字形。如果最大偏轉角度不存在時,子彈軌跡看起來就是個螺旋形狀了。在代碼中是通過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);}//bodyVec2& 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; }激光
激光看似與普通子彈或者導彈完全不同,但是仔細分析下來,也可以看成是一顆特殊的子彈:
通過以上分析,可以得到激光最與眾不同的地方在于其外觀的可變性,下面是具體的代碼
其中,init用于創建激光外觀,這里通過{ GL_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT }來創建一個重復的紋理,然后在update中讓紋理不停的向上滾動。在rejustHeight中,我們根據目標的Y軸坐標來計算激光的高度,并在激光接觸目標的位置增加一個擊中效果的貼圖。
轉載請注明:https://my.oschina.net/u/1986600/blog/863445
轉載于:https://my.oschina.net/u/1986600/blog/863445
總結
以上是生活随笔為你收集整理的仿《雷霆战机》飞行射击手游开发--子弹、跟踪导弹和激光的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Java基础】之AWT,一篇文章搞懂a
- 下一篇: 利用朴素贝叶斯进行新闻文本分类