首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

Cocos2d-x 整合 Box2D 打造彈弓遊戲完整教程(下)

     編輯:關於C語言

接續上篇,本節將完善遊戲場景:佈局敵人與建築結構、實現碰撞衝量檢測銷毀敵人、添加粒子爆炸特效、實現子彈自動裝填與關卡重置邏輯,最終構建可無限循環遊玩的完整遊戲閉環。

敵人與建築佈局:靈活的創建輔助方法

在類中聲明 vector* targets、vector* enemies 記錄所有目標與敵人剛體。封裝通用創建方法 createTarget,支援圓形/矩形夾具、靜態/動態剛體、敵人標記:

void HelloWorld::createTarget(const char* imageName, CCPoint position,
    float rotation, bool isCircle, bool isStatic, bool isEnemy) {
    CCSprite* sprite = CCSprite::spriteWithFile(imageName);
    this->addChild(sprite, 1);

    b2BodyDef bodyDef;
    bodyDef.type = isStatic ? b2_staticBody : b2_dynamicBody;
    bodyDef.position.Set((position.x + sprite->getContentSize().width/2)/PTM_RATIO,
                         (position.y + sprite->getContentSize().height/2)/PTM_RATIO);
    bodyDef.angle = CC_DEGREES_TO_RADIANS(rotation);
    bodyDef.userData = sprite;
    b2Body* body = m_world->CreateBody(&bodyDef);

    b2FixtureDef fixtureDef;
    if (isCircle) {
        b2CircleShape circle;
        circle.m_radius = sprite->getContentSize().width/2/PTM_RATIO;
        fixtureDef.shape = &circle;
        body->CreateFixture(&fixtureDef);
    } else {
        b2PolygonShape box;
        box.SetAsBox(sprite->getContentSize().width/2/PTM_RATIO,
                     sprite->getContentSize().height/2/PTM_RATIO);
        fixtureDef.shape = &box;
        body->CreateFixture(&fixtureDef);
    }
    targets->push_back(body);
    if (isEnemy) {
        fixtureDef.userData = (void*)1; // 標記敵人夾具
        enemies->push_back(body);
    }
}

調用該方法按座標佈局磚塊、平台、狗頭、貓頭等敵人:

void HelloWorld::createTarget() {
    createTarget("brick_2.png", ccp(675, FLOOR_HEIGHT), 0, false, false, false);
    createTarget("brick_1.png", ccp(741, FLOOR_HEIGHT), 0, false, false, false);
    // ... 更多磚塊堆疊 ...
    createTarget("head_dog.png", ccp(702, FLOOR_HEIGHT), 0, true, false, true); // 敵人
    createTarget("head_cat.png", ccp(680, FLOOR_HEIGHT+58), 0, true, false, true);
    // 第二區塊平台、磚塊、敵人佈局 ...
}

移除右側邊界:讓倒塌更自然

初始化時創建的右側世界邊界會阻擋建築向右倒塌,刪除右側邊界創建代碼:

// 刪除這兩行
groundBox.Set(b2Vec2(screenSize.width*2.0f/PTM_RATIO, screenSize.height/PTM_RATIO),
              b2Vec2(screenSize.width*2.0f/PTM_RATIO, 0));
m_groundBody->CreateFixture(&groundBox, 0);

重新運行,建築倒塌效果更符合物理直覺。

碰撞監聽器:基於衝量判斷敵人銷毀

簡單碰撞檢測會導致敵人接觸磚塊即消失,需引入 b2ContactListener 並在 PostSolve 中獲取碰撞衝量:

// MyContactListener.h
class MyContactListener : public b2ContactListener {
public:
    std::set contacts;
    virtual void PostSolve(b2Contact* contact, const b2ContactImpulse* impulse) {
        bool isAEnemy = (contact->GetFixtureA()->GetUserData() != NULL);
        bool isBEnemy = (contact->GetFixtureB()->GetUserData() != NULL);
        if (isAEnemy || isBEnemy) {
            int32 count = contact->GetManifold()->pointCount;
            float32 maxImpulse = 0;
            for (int i=0; inormalImpulses[i]);
            if (maxImpulse > 1.0f) { // 衝量閾值需實測調整
                if (isAEnemy) contacts.insert(contact->GetFixtureA()->GetBody());
                if (isBEnemy) contacts.insert(contact->GetFixtureB()->GetBody());
            }
        }
    }
};

在場景 init 末尾註冊監聽器:

contactListener = new MyContactListener();
m_world->SetContactListener(contactListener);

在 tick 中處理待銷毀敵人:

for (auto pos = contactListener->contacts.begin(); pos != contactListener->contacts.end(); ++pos) {
    b2Body* body = *pos;
    // 從 targets、enemies 向量中移除
    // 移除精靈節點
    CCNode* node = (CCNode*)body->GetUserData();
    CCPoint position = node->getPosition();
    this->removeChild(node, true);
    m_world->DestroyBody(body);

    // 添加爆炸粒子特效
    CCParticleSun* explosion = CCParticleSun::node();
    explosion->retain();
    explosion->setTexture(CCTextureCache::sharedTextureCache()->addImage("fire.png"));
    explosion->setPosition(position);
    explosion->setDuration(1.0f);
    this->addChild(explosion, 11);
    explosion->release();
}
contactListener->contacts.clear();

子彈自動裝填與關卡重置

在 tick 銷毀焊接關節後延遲 5 秒調用 resetBullet:

CCDelayTime* delay = CCDelayTime::actionWithDuration(5.0f);
CCCallFunc* selector = CCCallFunc::actionWithTarget(this, callfunc_selector(HelloWorld::resetBullet));
this->runAction(CCSequence::actions(delay, selector, NULL));

resetBullet 邏輯:若敵人全滅延遲 2 秒重置關卡;否則嘗試裝填下一顆子彈並移動視角回發射位;子彈耗盡則重置關卡:

void HelloWorld::resetBullet() {
    if (enemies->size() == 0) {
        CCDelayTime* delay = CCDelayTime::actionWithDuration(2.0f);
        CCCallFunc* selector = CCCallFunc::actionWithTarget(this, callfunc_selector(HelloWorld::resetGame));
        this->runAction(CCSequence::actions(delay, selector, NULL));
    } else if (attachBullet()) {
        CCAction* action = CCMoveTo::actionWithDuration(0.2f, CCPointZero);
        this->runAction(action);
    } else {
        CCDelayTime* delay = CCDelayTime::actionWithDuration(2.0f);
        CCCallFunc* selector = CCCallFunc::actionWithTarget(this, callfunc_selector(HelloWorld::resetGame));
        this->runAction(CCSequence::actions(delay, selector, NULL));
    }
}

resetGame 開頭添加清理舊子彈、舊目標代碼,確保乾淨重置:

if (!m_bullets.empty()) {
    for (auto bullet : m_bullets) {
        CCNode* node = (CCNode*)bullet->GetUserData();
        this->removeChild(node, true);
        m_world->DestroyBody(bullet);
    }
    m_bullets.clear();
}
if (!targets->empty()) {
    for (auto target : *targets) {
        CCNode* node = (CCNode*)target->GetUserData();
        this->removeChild(node, true);
        m_world->DestroyBody(target);
    }
    targets->clear();
    enemies->clear();
}

遊戲啟動序列:展示目標再回發射位

修改 resetGame 末尾添加動作序列:創建子彈與目標 → 視角平移至右側展示目標 1.5 秒 → 延遲 1 秒 → 視角平移回左側發射位:

this->createBullets(3);
this->createTarget();
this->attachBullet(); // 先焊接但不顯示

CCFiniteTimeAction* action1 = CCMoveTo::actionWithDuration(1.5f, ccp(-480, 0));
CCDelayTime* action3 = CCDelayTime::actionWithDuration(1.0f);
CCFiniteTimeAction* action4 = CCMoveTo::actionWithDuration(1.5f, CCPointZero);
this->runAction(CCSequence::actions(action1, action3, action4, NULL));

至此,完整彈弓遊戲閉環構建完成。資源文件見上篇教程,原文鏈接:Ray Wenderlich 教程第二部分

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved