Cocos2d游戏物理引擎集成:Box2D核心概念与实战优化指南

发布时间:2026/7/12 6:01:05

Cocos2d游戏物理引擎集成:Box2D核心概念与实战优化指南 1. 项目概述为什么要在Cocos2d游戏里用Box2D如果你正在用Cocos2d-x或者Cocos Creator做2D游戏想让角色跳起来有真实的滞空和下坠感让箱子被推倒后能沿着斜坡滚下去或者让一堆积木搭成的塔被撞击后轰然倒塌那你大概率绕不开物理引擎。而Box2D就是那个在2D游戏领域里像瑞士军刀一样经典且强大的存在。它不是Cocos2d自带的简单碰撞检测而是一个完整的、基于真实物理定律的模拟系统。简单来说Cocos2d负责“画”出游戏世界而Box2D负责定义这个世界里的“物理规则”——重力、摩擦力、弹力、关节约束等等。我见过很多新手开发者包括几年前的我自己一开始会觉得“不就是碰撞检测吗我自己写个矩形、圆形判断不就行了” 确实对于超级简单的场景自己写可能更快。但当你需要处理一个球从不同角度撞击一堆不同形状的砖块每个砖块都要有真实的旋转、飞溅效果时自己手写物理模拟的复杂度会呈指数级上升。Box2D的价值就在于它用一套经过千锤百炼的算法帮你处理了所有这些底层复杂的计算你只需要关注游戏逻辑本身。它能让你用相对较少的代码创造出极具真实感和交互性的游戏体验比如《愤怒的小鸟》、《割绳子》这类游戏的核心物理交互其背后原理与Box2D高度契合。然而把Box2D“接入”Cocos2d游戏并让它流畅、高效地工作远不是引入一个头文件、创建个世界那么简单。你需要理解两个坐标系像素 vs 米的转换懂得如何将Cocos2d的精灵Sprite与Box2D的刚体Body绑定并同步掌握碰撞过滤、关节使用、性能优化等一系列技巧。否则你可能会遇到精灵“鬼畜”抖动、物体穿透、性能骤降或者碰撞回调死活不触发等各种诡异问题。这篇内容就是把我这些年踩过的坑、总结的经验系统地梳理给你目标是让你不仅能“用上”Box2D更能“掌握”它让它成为你游戏开发中的得力工具而不是一个难以驾驭的“黑盒”。2. Box2D核心概念与Cocos2d的映射关系在动手写代码之前我们必须先统一“语言”。Box2D有一套自己的术语体系理解它们与Cocos2d中概念的对应关系是后续一切工作的基础。2.1 物理世界b2World与游戏场景Scene/LayerBox2D视角b2World是物理模拟的宇宙。所有物理实体刚体都在这个世界里诞生、相互作用、消亡。它最重要的属性是重力b2Vec2 gravity你可以把它想象成这个宇宙的基本物理法则之一。创建世界时你就定下了这里的重力方向和大小。Cocos2d视角通常对应你的一个游戏场景Scene或层Layer。一个合理的架构是每个需要独立物理模拟的游戏场景持有自己唯一的b2World实例。例如你的主关卡场景有一个世界暂停菜单场景就不需要或者有另一个静止的世界。关键映射与操作创建在Cocos2d场景的init()或onEnter()方法中创建b2World。驱动物理世界不会自动演化。你必须在Cocos2d的每帧更新update(float dt)中调用world-Step(dt, velocityIterations, positionIterations)。这个Step函数是引擎的心脏它根据时间步长推进物理模拟计算所有物体的新位置和速度。这里的dt通常传入Cocos2d的帧间隔时间但为了模拟稳定性我强烈建议使用一个固定的时间步长如1/60.0f这能避免因帧率波动导致的物理模拟“抽风”。销毁在场景退出onExit()或析构函数时务必手动删除b2World对象并确保所有关联的刚体都已被世界销毁否则会导致内存泄漏。2.2 刚体b2Body与精灵SpriteBox2D视角b2Body是物理世界中的核心实体代表一个“不可变形”的物体。它分为三种类型静态刚体b2_staticBody如山体、地面、固定的墙壁。它永不移动质量无限大其他物体可以撞它但它不受力影响。性能开销极低。动态刚体b2_dynamicBody如玩家角色、飞行的子弹、可被推动的箱子。它会受到重力、力、碰撞的影响而运动。性能开销最大。运动学刚体b2_kinematicBody如移动的平台、升降梯。它的运动由你通过代码直接设置速度SetLinearVelocity来控制不受力的影响但可以影响推动动态刚体。性能开销介于静态和动态之间。Cocos2d视角通常对应一个Sprite或其子类。精灵负责视觉呈现刚体负责物理模拟。关键映射与同步 这是最容易出问题的地方。你不能直接设置精灵的位置等于刚体的位置因为单位不同。单位转换Box2D使用米m作为长度单位而Cocos2d使用像素px。为了让物理模拟稳定且真实Box2D官方建议刚体的尺寸在0.1m到10m之间。因此我们需要一个比例因子PTM_Ratio Pixels To Meters。通常设为32.0f意味着游戏中的32像素对应物理世界的1米。同步策略在update函数中在world-Step()调用之后遍历所有需要同步的刚体获取其物理位置和角度乘以PTM_Ratio转换后再设置给对应的精灵。// 假设 PTM_RATIO 32.0f b2Vec2 pos body-GetPosition(); float angle body-GetAngle(); sprite-setPosition(pos.x * PTM_RATIO, pos.y * PTM_RATIO); sprite-setRotation(-CC_RADIANS_TO_DEGREES(angle)); // Box2D弧度转Cocos2d角度注意方向谁主导谁对于动态刚体永远是Box2D计算位置 - 同步给Sprite。你绝不应该在游戏逻辑中直接setPosition一个绑定动态刚体的精灵这会导致物理模拟失效。你应该通过给刚体施加力ApplyForce或冲量ApplyLinearImpulse来让它运动。2.3 夹具b2Fixture与碰撞形状ShapeBox2D视角b2Fixture可以理解为“物理属性套件”。它附着在刚体上定义了形状Shape物体的几何外形如圆形b2CircleShape、矩形b2PolygonShape、多边形b2PolygonShape复杂顶点或边缘链b2ChainShape用于不规则地面。物理材质属性density密度决定刚体的质量质量 密度 * 面积。密度越大物体越“重”越难被推动。friction摩擦系数0.0表示绝对光滑1.0或更大表示非常粗糙。影响物体在接触面上滑动的难易程度。restitution弹性系数0.0表示完全非弹性碰撞如泥巴1.0表示完全弹性碰撞理想弹球。大于1.0则会“增益”能量不真实但可用于特殊效果。Cocos2d视角没有直接对应概念。它更多是定义精灵的“物理实体”属性。一个刚体可以有多个夹具这意味着一个游戏角色一个精灵可以由多个物理形状组合而成例如一个人物角色可以由一个矩形作为身体和两个圆形作为脚来组合。实操心得形状尽量简单。用多个简单形状矩形、圆形组合比用一个复杂多边形性能好得多。对于静态的复杂地形如起伏的山坡使用b2ChainShape是最高效的选择它只用一串顶点定义一条封闭或不封闭的边。density的设置需要反复调试。一个常见的错误是把所有物体的密度都设为1.0结果一个乒乓球能把一堵墙撞飞。要根据游戏感觉来调整。2.4 关节b2Joint与复杂约束Box2D视角关节用于连接两个刚体限制它们的相对运动。它是实现复杂机械结构的关键。旋转关节b2RevoluteJoint像铰链或门轴允许刚体绕一个公共点旋转。可用于制作风车、摆锤、角色的手臂摆动。平移关节b2PrismaticJoint像活塞或抽屉允许刚体沿一条轴直线滑动。可用于制作升降平台、伸缩杆。距离关节b2DistanceJoint用一根“杆”或“绳子”连接两个刚体保持固定距离或可伸缩像弹簧。可用于制作钟摆、弹簧、吊桥。焊接关节b2WeldJoint将两个刚体牢固地“焊”在一起相对位置和角度完全固定。可用于组合复杂物体。滑轮关节b2PulleyJoint、齿轮关节b2GearJoint、鼠标关节b2MouseJoint用于拖拽等。Cocos2d视角实现高级游戏交互的“粘合剂”。比如用旋转关节和距离关节可以构建一个可伸缩的抓钩用鼠标关节可以实现用手指拖动游戏中的物体。注意事项关节必须关联到已经存在于世界中的两个刚体上。关节定义中的锚点anchor坐标通常是世界坐标米。关节同样需要被创建到物理世界中world-CreateJoint(jointDef)并且在其关联的任一刚体被销毁时关节会自动销毁但最好也主动管理。3. 从零搭建在Cocos2d-x中集成Box2D的完整流程让我们抛开理论动手搭建一个最简单的、包含重力、碰撞和交互的物理demo。这里以Cocos2d-x C版本为例但核心思想完全适用于Cocos CreatorTypeScript/JavaScript。3.1 环境配置与物理世界初始化首先确保你的Cocos2d-x项目已包含Box2D库。现代Cocos2d-x版本通常已内置。在你的游戏场景层如HelloWorldScene.h中声明必要的变量class HelloWorld : public cocos2d::Layer, public b2ContactListener { public: virtual bool init(); void update(float dt) override; CREATE_FUNC(HelloWorld); private: b2World* _world; // 物理世界指针 float _ptmRatio; // 像素与米的比例设为32 // ... 其他成员变量如调试绘制 };在init()方法中创建物理世界并设置基础参数bool HelloWorld::init() { if (!Layer::init()) { return false; } _ptmRatio 32.0f; // 1. 定义重力向量 (0, -10) 模拟向下的重力 b2Vec2 gravity(0.0f, -10.0f); // 2. 创建物理世界 _world new b2World(gravity); // 3. 设置世界属性强烈建议 _world-SetAllowSleeping(true); // 允许刚体休眠大幅提升性能 _world-SetContinuousPhysics(true); // 开启连续碰撞检测防止高速物体穿透 // 4. 设置碰撞监听器本类继承自b2ContactListener _world-SetContactListener(this); // 5. 开启调试绘制开发阶段极其有用 this-setupDebugDraw(); // 6. 开启Cocos2d的每帧更新调度 this-scheduleUpdate(); // 7. 创建地面和测试物体 this-createGround(); this-createTestBody(); return true; }setupDebugDraw函数用于可视化物理形状这在调试阶段不可或缺void HelloWorld::setupDebugDraw() { // GLESDebugDraw 是Cocos2d-x提供的辅助类 auto debugDraw new GLESDebugDraw(_ptmRatio); _world-SetDebugDraw(debugDraw); uint32 flags 0; flags b2Draw::e_shapeBit; // 绘制形状轮廓 // flags b2Draw::e_aabbBit; // 绘制AABB包围盒调试用 // flags b2Draw::e_centerOfMassBit; // 绘制质心 // flags b2Draw::e_jointBit; // 绘制关节 debugDraw-SetFlags(flags); // 将debugDraw添加到渲染中注意内存管理这里简化了 this-addChild(debugDraw, 9999); // 最高层显示 }3.2 创建静态地面与动态物体创建地面静态刚体void HelloWorld::createGround() { // 1. 定义刚体 b2BodyDef groundBodyDef; groundBodyDef.position.Set(0, 0); // 位置在世界坐标原点 // 2. 在世界中创建刚体 b2Body* groundBody _world-CreateBody(groundBodyDef); // 3. 定义形状一个足够宽的盒子作为地面 b2PolygonShape groundBox; // 屏幕宽度假设为 visibleSize.width除以PTM_Ratio转为米 float groundWidth Director::getInstance()-getVisibleSize().width / _ptmRatio; groundBox.SetAsBox(groundWidth, 1.0f); // 半宽半高。高1米表示地面厚度 // 4. 创建夹具并关联到刚体 groundBody-CreateFixture(groundBox, 0.0f); // 密度为0表示静态物体 // 可选为地面创建一个精灵视觉 auto groundSprite Sprite::create(ground.png); groundSprite-setPosition(Vec2(visibleSize.width/2, 50)); // 假设地面在y50像素处 groundSprite-setScaleX(visibleSize.width / groundSprite-getContentSize().width); this-addChild(groundSprite); }创建一个会下落的动态盒子void HelloWorld::createTestBody() { // 1. 定义动态刚体 b2BodyDef bodyDef; bodyDef.type b2_dynamicBody; // 关键设置为动态类型 // 初始位置在屏幕上方中间像素转米 float startX Director::getInstance()-getVisibleSize().width / 2 / _ptmRatio; float startY (Director::getInstance()-getVisibleSize().height - 100) / _ptmRatio; bodyDef.position.Set(startX, startY); // 2. 创建刚体 b2Body* body _world-CreateBody(bodyDef); // 3. 定义形状和物理属性 b2PolygonShape dynamicBox; dynamicBox.SetAsBox(0.5f, 0.5f); // 1米 x 1米的盒子半宽半高各0.5 b2FixtureDef fixtureDef; fixtureDef.shape dynamicBox; fixtureDef.density 1.0f; // 密度 fixtureDef.friction 0.3f; // 摩擦 fixtureDef.restitution 0.5f; // 弹性 // 4. 将夹具添加到刚体 body-CreateFixture(fixtureDef); // 5. 创建对应的精灵并关联重要 auto sprite Sprite::create(box.png); sprite-setTag(100); // 给精灵一个tag方便后续通过刚体的UserData找到它 this-addChild(sprite); // 关键步骤在刚体中存储指向精灵的指针UserData body-SetUserData(sprite); }3.3 驱动世界与同步精灵在update函数中驱动物理模拟并同步视觉void HelloWorld::update(float dt) { // 1. 固定时间步长保证物理模拟稳定性推荐 const float fixedDeltaTime 1.0f / 60.0f; // 每秒60帧物理更新 const int velocityIterations 8; // 速度迭代次数影响求解精度和性能 const int positionIterations 3; // 位置迭代次数 // 2. 推进物理世界 _world-Step(fixedDeltaTime, velocityIterations, positionIterations); // 3. 遍历世界中所有刚体同步精灵位置 for (b2Body* b _world-GetBodyList(); b; b b-GetNext()) { if (b-GetUserData() ! nullptr) { // 获取关联的精灵 auto sprite (Sprite*)b-GetUserData(); // 获取刚体的物理位置和角度单位米 b2Vec2 pos b-GetPosition(); float angle b-GetAngle(); // 单位转换并设置给精灵 sprite-setPosition(pos.x * _ptmRatio, pos.y * _ptmRatio); sprite-setRotation(-1 * CC_RADIANS_TO_DEGREES(angle)); // 弧度转角度注意负号 } } // 4. 可选调试绘制 // _world-DrawDebugData(); // 如果使用了自定义的渲染命令可能需要调用 }现在运行程序你应该能看到一个盒子从空中落下砸在地面上并弹跳几下。一个最基本的物理世界就搭建成功了。3.4 实现碰撞检测与响应物理模拟有了但游戏需要对碰撞事件做出反应比如玩家碰到敌人掉血吃到金币加分。这就需要实现碰撞监听器。我们已经让HelloWorld类继承了b2ContactListener。现在实现其关键的BeginContact方法void HelloWorld::BeginContact(b2Contact* contact) { // 1. 获取发生碰撞的两个夹具 b2Fixture* fixtureA contact-GetFixtureA(); b2Fixture* fixtureB contact-GetFixtureB(); // 2. 通过夹具获取其所属的刚体 b2Body* bodyA fixtureA-GetBody(); b2Body* bodyB fixtureB-GetBody(); // 3. 通过刚体的UserData获取关联的游戏对象精灵 auto spriteA (Sprite*)bodyA-GetUserData(); auto spriteB (Sprite*)bodyB-GetUserData(); // 4. 安全检查 if (spriteA nullptr || spriteB nullptr) { return; // 可能有一个是地面没有关联精灵UserData } // 5. 根据精灵的Tag或其他标识符进行逻辑处理 if (spriteA-getTag() 100 spriteB-getTag() 200) { CCLOG(盒子Tag 100撞到了金币Tag 200); // 处理吃到金币的逻辑加分、播放音效、移除金币精灵和刚体等 this-handleCoinCollected(spriteB, bodyB); } else if (spriteB-getTag() 100 spriteA-getTag() 200) { // 顺序可能相反同样处理 CCLOG(盒子Tag 100撞到了金币Tag 200); this-handleCoinCollected(spriteA, bodyA); } // 可以添加更多碰撞类型判断... } void HelloWorld::handleCoinCollected(Sprite* coinSprite, b2Body* coinBody) { // 1. 从父节点移除精灵 coinSprite-removeFromParent(); // 2. 从物理世界销毁刚体重要 _world-DestroyBody(coinBody); // 3. 游戏逻辑如加分 // _score 100; // updateScoreLabel(); }重要提示在BeginContact回调中绝对不能直接创建或销毁物理世界中的物体如CreateBody,DestroyBody也不能修改接触点信息。因为这可能会破坏Box2D正在进行的碰撞求解过程。正确的做法是记录下需要处理的事件比如将需要被销毁的刚体指针存入一个列表然后在update循环的末尾物理Step完成之后再安全地执行这些销毁或创建操作。4. 高级应用与性能优化实战掌握了基础我们就可以玩些更花的并确保游戏跑得流畅。4.1 使用关节构建复杂机械假设我们要做一个“弹弓”发射小鸟的机制。弹弓可以用一个旋转关节b2RevoluteJoint将橡皮筋一个动态刚体固定在支架静态刚体上橡皮筋和小鸟之间用一个距离关节b2DistanceJoint模拟橡皮筋的拉伸。**创建弹弓支架静态刚体和橡皮筋动态刚体**略过。重点看关节创建void HelloWorld::createSlingshot() { // ... 创建 leftPostBody (静态), rightPostBody (静态), rubberBandBody (动态) // 1. 创建旋转关节将橡皮筋左端固定在左边支架 b2RevoluteJointDef revJointDef; revJointDef.Initialize(leftPostBody, rubberBandBody, leftPostBody-GetWorldCenter()); revJointDef.enableLimit true; revJointDef.lowerAngle -0.25f * b2_pi; // 限制旋转角度范围 revJointDef.upperAngle 0.25f * b2_pi; _world-CreateJoint(revJointDef); // 2. 创建距离关节弹簧效果连接橡皮筋右端和小鸟 b2DistanceJointDef distJointDef; distJointDef.Initialize(rubberBandBody, birdBody, rubberBandBody-GetWorldCenter(), birdBody-GetWorldCenter()); distJointDef.collideConnected false; // 连接的两个刚体之间不产生碰撞 distJointDef.frequencyHz 5.0f; // 弹簧频率值越大“弹力”越强 distJointDef.dampingRatio 0.7f; // 阻尼比值越大回弹震荡越小 _world-CreateJoint(distJointDef); }当玩家向后拖动小鸟时距离关节被拉伸存储弹性势能。松开手时关节的弹力将小鸟发射出去。通过调整frequencyHz和dampingRatio你可以获得从“橡皮筋”到“弹簧”的不同手感。4.2 碰撞过滤让特定物体互不干扰游戏里经常需要某些物体之间不发生碰撞比如同一队的子弹互相穿过或者角色和自己的影子。Box2D通过碰撞过滤Collision Filtering来实现这是b2FixtureDef中的filter属性。// 定义碰撞类别用16位掩码表示 const uint16 CATEGORY_PLAYER 0x0001; const uint16 CATEGORY_ENEMY 0x0002; const uint16 CATEGORY_PLAYER_BULLET 0x0004; const uint16 CATEGORY_ENEMY_BULLET 0x0008; const uint16 CATEGORY_GROUND 0x0010; // 在创建玩家子弹的夹具时设置filter b2FixtureDef bulletFixtureDef; // ... 设置shape, density等 bulletFixtureDef.filter.categoryBits CATEGORY_PLAYER_BULLET; // 我是什么 bulletFixtureDef.filter.maskBits CATEGORY_ENEMY | CATEGORY_GROUND; // 我能和谁碰撞 // 解释玩家子弹0004只会与敌人0010和地面0001发生碰撞 // 按位与操作0004 0010 0? 不碰撞0004 0001 0? 不碰撞0004 0010 !0? 碰撞 // 在创建敌人子弹的夹具时 enemyBulletFixtureDef.filter.categoryBits CATEGORY_ENEMY_BULLET; enemyBulletFixtureDef.filter.maskBits CATEGORY_PLAYER | CATEGORY_GROUND; // 敌人子弹只打玩家和地面 // 创建地面 groundFixtureDef.filter.categoryBits CATEGORY_GROUND; groundFixtureDef.filter.maskBits 0xFFFF; // 地面和所有类别碰撞掩码全1 // 或者 groundFixtureDef.filter.maskBits CATEGORY_PLAYER | CATEGORY_ENEMY | ... ; 显式列出通过精心设计类别和掩码你可以构建非常精细的碰撞关系网这是大型游戏物理系统高效、正确运行的基础。4.3 性能优化黄金法则Box2D很强大但也很吃CPU。物体一多帧率就可能暴跌。以下是我总结的几条铁律静态刚体是免费的永远用b2_staticBody表示不会移动的环境物体地面、墙壁。它们的计算开销远低于动态刚体。善用休眠Sleeping确保world-SetAllowSleeping(true)。当一个动态刚体速度降到几乎为零且一段时间内没有受力它会进入休眠。休眠的刚体几乎不参与物理计算直到被碰撞或施加力唤醒。这是最有效的优化手段之一。控制动态刚体数量屏幕上同时活跃非休眠的动态刚体最好控制在100个以内。对于像“一堆沙子”这样的效果需要考虑粒子系统替代或者使用更简化的物理模拟。简化碰撞形状优先使用圆形b2CircleShape和轴对称矩形b2PolygonShape的SetAsBox它们计算最快。避免使用顶点过多的复杂多边形。如果需要复杂外形用多个简单形状组合Compound Shapes。对于静态的复杂地形使用b2ChainShape而不是一堆小多边形。调整世界迭代次数world-Step()中的velocityIterations和positionIterations参数直接影响精度和性能。通常(8, 3)是个不错的起点。在移动设备上可以尝试降低到(6, 2)以提升性能代价是模拟可能稍微“软”一点或有些许穿透。及时销毁离开屏幕或不再需要的刚体立即用world-DestroyBody(body)销毁。别忘了同时清理关联的精灵。使用“子弹”标志对于移动速度非常快的物体如子弹、发射物调用body-SetBullet(true)。这会启用连续碰撞检测CCD防止它从薄墙或其他物体中“穿”过去但会增加计算量所以只对高速物体使用。5. 常见问题排查与调试技巧即使按照指南操作你还是会遇到各种奇怪的问题。这里记录一些最常见的“坑”和解决方法。5.1 精灵与刚体位置不同步或抖动症状精灵没有出现在刚体应该在的位置或者轻微抖动。排查检查PTM_Ratio确保创建刚体形状SetAsBox的半宽高和设置位置时使用的是米制单位。而在同步给精灵setPosition时乘以了正确的PTM_Ratio。检查更新顺序确保在update函数中先调用world-Step()再遍历刚体同步精灵位置。关闭调试绘制看原始精灵有时是调试绘制的图形和精灵没对齐关掉调试绘制看精灵自己的运动是否正常。检查时间步长在update中传给Step的dt是否稳定使用固定时间步长是解决抖动的最佳实践。float fixedDt 1.0f / 60.0f; _accumulator dt; // dt是Cocos2d传来的真实帧间隔 while (_accumulator fixedDt) { _world-Step(fixedDt, _velocityIterations, _positionIterations); _accumulator - fixedDt; } // 然后进行精灵同步5.2 碰撞回调函数BeginContact不触发症状物体明明撞在一起了但BeginContact里打的日志没输出。排查检查监听器设置world-SetContactListener(this)调用了吗this指针是否正确检查刚体类型两个都是静态刚体b2_staticBody吗静态刚体之间默认不会产生碰撞回调但它们会阻碍其他物体。如果需要回调确保至少有一个是动态或运动学刚体。检查碰撞过滤是不是用filter把这两个物体的碰撞给屏蔽了检查categoryBits和maskBits的按位与结果是否不为0。检查夹具创建刚体创建后成功添加夹具CreateFixture了吗没有夹具的刚体不会参与碰撞。检查物体是否已销毁可能在碰撞发生的同一帧某个刚体已经被销毁了。使用调试绘制打开e_shapeBit标志确认两个物体的物理形状确实在视觉上发生了重叠。5.3 物体表现“太飘”或“太沉”症状物体下落太慢像在月球或者下坠太快像铁块碰撞后弹跳不起来或者弹得太高。调整这是物理参数调校问题没有标准答案需要根据游戏感觉来。重力Gravity调整创建b2World时的重力向量。(0, -10)是接近地球重力(0, -5)会更轻飘(0, -20)则更沉重。密度Density这是决定物体“重量”质量的关键。质量 密度 * 面积。增加密度会让物体更难被推动下落惯性更大。弹性Restitution两个碰撞物体的弹性系数会结合通常取最大值。想要弹跳球就把球的restitution设为0.8以上想要泥巴球就设为0.1以下。阻尼Damping刚体有线性阻尼SetLinearDamping和角度阻尼SetAngularDamping可以模拟空气阻力等让运动慢慢停下来。值越大停得越快。5.4 内存泄漏与对象生命周期管理症状游戏玩久了越来越卡或者切换场景时崩溃。黄金法则谁创建谁销毁。对于b2World中用CreateBody和CreateJoint创建的对象必须用DestroyBody和DestroyJoint来销毁。安全销毁流程在场景的onExit或析构函数中首先遍历所有刚体并销毁。注意顺序先销毁关联了关节的刚体或者先销毁关节。Box2D在销毁一个刚体时会自动销毁附着其上所有夹具和以它为一端的关节。但为了清晰最好主动管理。最后delete _world。同时将Cocos2d精灵从父节点removeFromParent。如果精灵是new出来的还需要delete。UserData的使用在刚体销毁前将其UserData置为nullptr防止在碰撞回调等地方访问到野指针。5.5 调试利器GLESDebugDraw前面提到的调试绘制是开发阶段最重要的工具没有之一。它能让你直观地看到刚体的精确形状和大小而不是精灵的纹理边界。刚体的当前位置和旋转。关节的连接点。静态/动态/运动学刚体可以用不同颜色区分。当物理表现和你的预期不符时第一件事就是打开调试绘制看看物理引擎“眼里”的世界到底是什么样的。很多时候问题比如形状不对、位置偏移会一目了然。掌握Box2D就像是为你Cocos2d游戏世界注入了真实的灵魂。从简单的下落碰撞到复杂的关节机关再到精细的碰撞过滤和性能调优每一步都需要理解和实践。开始时可能会觉得繁琐但一旦你熟悉了这套流程和思维方式开发具有丰富物理交互的游戏将变得事半功倍。记住多动手实验多使用调试工具观察参数调校没有对错只有是否适合你的游戏感觉。

相关新闻