接續上篇,本節將完善遊戲場景:佈局敵人與建築結構、實現碰撞衝量檢測銷毀敵人、添加粒子爆炸特效、實現子彈自動裝填與關卡重置邏輯,最終構建可無限循環遊玩的完整遊戲閉環。
在類中聲明 vector、vector 記錄所有目標與敵人剛體。封裝通用創建方法 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 教程第二部分