本教程系列將引導你使用 Cocos2d-x 1.0.1-x-0.11.0 版本與 Box2D 物理引擎,從零構建一款酷炫的彈弓射擊遊戲。教程改編自 Ray Wenderlich 經典 iOS 教程,將原 Objective-C 代碼移植為 C++ 版本,並使用 Vicki Wenderlich 繪製的彈弓、橡子、狗、貓與憤怒松鼠等美術素材。
先備知識: 需熟悉 Box2D 入門教程 或具備同等物理引擎基礎。
創建 HelloWorld 專案並清空預設內容,勾選 Box2D 支援。聲明 Catapult 類繼承自 CCLayer。在 init 方法中加載背景、彈弓基座、地面、松鼠等靜態精靈,將錨點統一設為左下角 CCPointZero 以便精確定位:
CCSprite* sprite = CCSprite::spriteWithFile("bg.png");
sprite->setAnchorPoint(CCPointZero);
this->addChild(sprite, -1); // 背景層
#define FLOOR_HEIGHT 62.0f // 地面高度宏,便於統一調整
sprite = CCSprite::spriteWithFile("catapult_base_2.png");
sprite->setAnchorPoint(CCPointZero);
sprite->setPosition(ccp(181.0, FLOOR_HEIGHT));
this->addChild(sprite, 0); // 彈弓底座後層
// 依序添加左松鼠、彈弓前座、右松鼠、冰面地面...

在類中聲明 b2World* m_world、b2Body* m_groundBody、b2Body* m_armBody、b2Fixture* m_armFixture。初始化物理世界,重力設為 (0, -10),啟用睡眠機制與連續碰撞檢測。創建地面剛體並添加四邊邊界:
b2EdgeShape groundBox;
// 底部邊界
groundBox.Set(b2Vec2(0, FLOOR_HEIGHT/PTM_RATIO),
b2Vec2(screenSize.width*2.0f/PTM_RATIO, FLOOR_HEIGHT/PTM_RATIO));
m_groundBody->CreateFixture(&groundBox, 0);
// 頂部、左側、右側邊界同理...
創建彈弓臂動態剛體,位置設在基座上方,夾具尺寸略小於精靈圖片實際顯示區域:
CCSprite* arm = CCSprite::spriteWithFile("catapult_arm.png");
this->addChild(arm, 1);
b2BodyDef armBodyDef;
armBodyDef.type = b2_dynamicBody;
armBodyDef.linearDamping = 1;
armBodyDef.angularDamping = 1;
armBodyDef.position.Set(230.0f/PTM_RATIO, (FLOOR_HEIGHT+91.0f)/PTM_RATIO);
armBodyDef.userData = arm;
m_armBody = m_world->CreateBody(&armBodyDef);
b2PolygonShape armBox;
b2FixtureDef armBoxDef;
armBoxDef.shape = &armBox;
armBoxDef.density = 0.3f;
armBox.SetAsBox(11.0f/PTM_RATIO, 91.0f/PTM_RATIO);
m_armFixture = m_armBody->CreateFixture(&armBoxDef);

使用 b2RevoluteJoint 將彈弓臂釘在地面剛體上,限制旋轉角度 9°~75°,並啟用馬達模擬彈性回彈:
b2RevoluteJointDef armJointDef;
armJointDef.Initialize(m_groundBody, m_armBody,
b2Vec2(233.0f/PTM_RATIO, FLOOR_HEIGHT/PTM_RATIO));
armJointDef.enableMotor = true;
armJointDef.enableLimit = true;
armJointDef.motorSpeed = -10; // 負值順時針旋轉
armJointDef.lowerAngle = CC_DEGREES_TO_RADIANS(9);
armJointDef.upperAngle = CC_DEGREES_TO_RADIANS(75);
armJointDef.maxMotorTorque = 700;
m_armJoint = (b2RevoluteJoint*)m_world->CreateJoint(&armJointDef);

在類中聲明 b2MouseJoint* m_mouseJoint 並實現觸控回調:
void Catapult::ccTouchesBegan(CCSet* touches, CCEvent* event) {
if (m_mouseJoint != NULL) return;
CCTouch* touch = (CCTouch*)touches->anyObject();
CCPoint location = touch->locationInView(touch->view());
location = CCDirector::sharedDirector()->convertToGL(location);
b2Vec2 locationWorld = b2Vec2(location.x/PTM_RATIO, location.y/PTM_RATIO);
// 僅在觸點位於彈弓臂左側區域時創建關節
if (locationWorld.x < m_armBody->GetWorldCenter().x + 150.0/PTM_RATIO) {
b2MouseJointDef md;
md.bodyA = m_groundBody;
md.bodyB = m_armBody;
md.target = locationWorld;
md.maxForce = 2000; // 需足夠大以對抗旋轉關節馬達扭矩
m_mouseJoint = (b2MouseJoint*)m_world->CreateJoint(&md);
}
}
void Catapult::ccTouchesMoved(CCSet* touches, CCEvent* event) {
if (m_mouseJoint == NULL) return;
// 同步更新鼠標關節目標點
CCTouch* touch = (CCTouch*)touches->anyObject();
CCPoint location = touch->locationInView(touch->view());
location = CCDirector::sharedDirector()->convertToGL(location);
b2Vec2 locationWorld = b2Vec2(location.x/PTM_RATIO, location.y/PTM_RATIO);
m_mouseJoint->SetTarget(locationWorld);
}
void Catapult::ccTouchesEnded(CCSet* touches, CCEvent* event) {
if (m_mouseJoint != NULL) {
m_world->DestroyJoint(m_mouseJoint);
m_mouseJoint = NULL;
}
}
運行測試,滑鼠左鍵拖拽彈弓臂後釋放,臂會在馬達驅動下快速回彈。

為避免運行時頻繁創建銷毀,啟動時預生成多顆橡子子彈並停用:
std::vector m_bullets;
int m_currentBullet;
void Catapult::createBullets(int count) {
m_currentBullet = 0;
float pos = 62.0f;
for (int i=0; iaddChild(sprite, 1);
b2BodyDef bulletBodyDef;
bulletBodyDef.type = b2_dynamicBody;
bulletBodyDef.bullet = true; // 關鍵:標記為高速子彈,啟用 CCD 防止穿透
bulletBodyDef.position.Set(pos/PTM_RATIO, (FLOOR_HEIGHT+15)/PTM_RATIO);
bulletBodyDef.userData = sprite;
b2Body* bullet = m_world->CreateBody(&bulletBodyDef);
bullet->SetActive(false); // 初始停用
b2CircleShape circle;
circle.m_radius = 15.0/PTM_RATIO;
b2FixtureDef ballShapeDef;
ballShapeDef.shape = &circle;
ballShapeDef.density = 0.8f;
ballShapeDef.restitution = 0.2f;
ballShapeDef.friction = 0.99f;
bullet->CreateFixture(&ballShapeDef);
m_bullets.push_back(bullet);
}
}
發射前調用 attachBullet() 將當前子彈移至彈弓尖端,用 b2WeldJoint 焊接固定:
bool Catapult::attachBullet() {
if (m_currentBullet < m_bullets.size()) {
m_bulletBody = m_bullets.at(m_currentBullet++);
m_bulletBody->SetTransform(b2Vec2(230/PTM_RATIO, (155+FLOOR_HEIGHT)/PTM_RATIO), 0);
m_bulletBody->SetActive(true);
b2WeldJointDef weldJointDef;
weldJointDef.Initialize(m_bulletBody, m_armBody,
b2Vec2(230/PTM_RATIO, (155+FLOOR_HEIGHT)/PTM_RATIO));
weldJointDef.collideConnected = false; // 避免子彈與彈弓臂碰撞
m_bulletJoint = (b2WeldJoint*)m_world->CreateJoint(&weldJointDef);
return true;
}
return false;
}
在 tick 更新循環中檢測彈弓臂回彈角度,當接近初始位置(≤10°)時銷毀焊接關節,子彈隨即飛出:
if (m_releasingArm && m_bulletJoint != NULL) {
if (m_armJoint->GetJointAngle() <= CC_DEGREES_TO_RADIANS(10)) {
m_releasingArm = false;
m_world->DestroyJoint(m_bulletJoint);
m_bulletJoint = NULL;
}
}
調整旋轉關節 maxMotorTorque 為 700 可獲得適中的發射速度。
在 tick 中添加視角跟隨邏輯:
if (m_bulletBody && m_bulletJoint == NULL) { // 子彈已發射且未焊接
b2Vec2 pos = m_bulletBody->GetPosition();
CCPoint myPos = this->getPosition();
CCSize screenSize = CCDirector::sharedDirector()->getWinSize();
if (pos.x > screenSize.width/2/PTM_RATIO) {
myPos.x = -MIN(screenSize.width*2 - screenSize.width,
pos.x*PTM_RATIO - screenSize.width/2);
this->setPosition(myPos);
}
}

第一部分完成彈弓核心機制,第二部分將實現敵人佈局、碰撞銷毀、粒子特效與完整遊戲循環。資源打包見附件,原文地址:Ray Wenderlich 教程原文