├── 1-basic ├── 1-1-physical-engine.md ├── 1-2-graphic-engine.md ├── 1-3-hello-box2d.md └── README.md ├── 2-world └── README.md ├── 3-body ├── 3-1-shape.md ├── 3-2-box-shape.md ├── 3-3-cicle-shape.md ├── 3-4-poly-shape.md ├── 3-5-shape-to-body.md └── README.md ├── 4-joint ├── 4-1-distance-joint.md ├── 4-2-revolute-joint.md ├── 4-3-prismatic-joint.md ├── 4-4-pulley-joint.md ├── 4-5-gear-joint.md └── README.md ├── 5-operation ├── 5-1-mouse-get-body.md ├── 5-2-get-contact-list.md ├── 5-3-get-body-attributes.md ├── 5-4-set-body-attributes.md ├── 5-5-body-with-image.md └── README.md ├── 6-practice ├── 6-1-create-and-init-world │ ├── 6-1-create-and-init-world.md │ └── 6-1-create-and-init-world │ │ ├── index.html │ │ └── js │ │ ├── box2d.js │ │ ├── prototype.js │ │ └── test.js ├── 6-2-add-body │ ├── 6-2-add-body.md │ └── 6-2-add-body │ │ ├── box.png │ │ ├── index.html │ │ └── js │ │ ├── box2d.js │ │ ├── prototype.js │ │ └── test.js ├── 6-3-add-bound │ ├── 6-3-add-bound.md │ └── 6-3-add-bound │ │ ├── box.png │ │ ├── index.html │ │ └── js │ │ ├── box2d.js │ │ ├── prototype.js │ │ └── test.js ├── 6-4-mouse-operate-body │ ├── 6-4-mouse-operate-body.md │ └── 6-4-mouse-operate-body │ │ ├── box.png │ │ ├── index.html │ │ └── js │ │ ├── box2d.js │ │ ├── prototype.js │ │ └── test.js ├── 6-5-handle-contact │ ├── 6-5-handle-contact.md │ └── 6-5-handle-contact │ │ ├── box.png │ │ ├── index.html │ │ └── js │ │ ├── box2d.js │ │ ├── prototype.js │ │ └── test.js └── README.md ├── 7-api ├── 7-1-common-apis.md ├── 7-2-collisions-apis.md ├── 7-3-dynamics-apis.md └── README.md └── README.md /1-basic/1-1-physical-engine.md: -------------------------------------------------------------------------------- 1 | ## 物理引擎 2 | ### 物理效果 3 | 所谓物理效果,就是在游戏中模仿现实中真实物理世界的运动方式。 4 | 5 | 在游戏中,玩家甚至能感受到箱子、石头、布料的触感是怎样的。游戏之所以能实现如此多的动态效果,都要归功于物理运算。 6 | 7 | 但是,在游戏中实现物理运算效果绝非易事。物理运算效果是一种对计算性能要求极高的环境,以一整套独特的物理学算法为基础,需要大量同步运算的能力。 8 | 9 | 游戏开发者通常使用功能强大的物理引擎来实现想要达到的物理效果。 10 | 11 | ### 物理引擎 12 | 物理引擎是一个由计算机模拟牛顿力学的模型。它通过定义刚体(刚性物体)的参数,如质量、速度、摩擦力、动量、扭矩和阻力等,来赋予刚体真实的物理属性,以此计算它们的运动、旋转、碰撞等反应,测试不同情况下的刚体运动效果。 13 | 14 | 物理引擎主要用在科学模拟和电子游戏中。 15 | 16 | 相比之前游戏中的“物理效果”,在游戏中加入了物理引擎之后,游戏不再单纯只是按照预定脚本执行,而是按照预先设定的物理参数遵照宏观世界里的物理规律来运行,换句话说,只要显卡和处理器足够强大,加入了物理引擎的游戏可以模拟真实世界中各种物体的运动,使得游戏效果不再刻板单一,一成不变。 17 | 18 | 通过利用物理引擎,模拟并实现物体之间的相互影响效果是非常简单的,但是,所谓物理引擎并不是指实实在在的物理效果,它只是提供一个平台,游戏开发者可以通过几行简单的代码实现某个特定的效果,非常方便。 19 | 20 | 21 | ### [百度百科-物理引擎](http://baike.baidu.com/view/721450.htm) 22 | 物理引擎通过为刚性物体赋予真实的物理属性的方式来计算运动、旋转和碰撞反映。为每个游戏使用物理引擎并不是完全必要的—简单的“牛顿”物理(比如加速和减速)也可以在一定程度上通过编程或编写脚本来实现。然而,当游戏需要比较复杂的物体碰撞、滚动、滑动或者弹跳的时候(比如赛车类游戏或者保龄球游戏),通过编程的方法就比较困难了。 23 | 24 | 物理引擎使用对象属性(动量、扭矩或者弹性)来模拟刚体行为,这不仅可以得到更加真实的结果,对于开发人员来说也比编写行为脚本要更加容易掌握。好的物理引擎允许有复杂的机械装置,像球形关节、轮子、气缸或者铰链。有些也支持非刚性体的物理属性,比如流体。物理引擎可以从另外的厂商购买,而一些游戏开发系统具备完整的物理引擎。但是要注意,虽然有的系统在其特性列表中说他们有物理引擎,但其实是一些简单的加速和碰撞检测属性而已。 25 | 26 | -------------------------------------------------------------------------------- /1-basic/1-2-graphic-engine.md: -------------------------------------------------------------------------------- 1 | ## 有关图像引擎 2 | ### 二维图像引擎 3 | 二维图像引擎是主要使用在二维游戏中,绘制图像,并向外部表达图像的系统。在三维游戏中,亦有使用二维引擎来绘制游戏界面以及一些二维元素。 4 | 5 | 由于二维游戏的图像都是平面结构,所以图像引擎在显示图像之前也往往只对图像本身进行处理,而不像三维游戏那样还要处理图像周围的环境。这种处理一般是将图像伸缩,变形,色彩处理,图像合成等。 6 | 7 | 二维图像引擎由角色图像、场景地图、动态、光影和特效组成,功能强大。 8 | 9 | ### [百度百科-二维图像引擎](http://baike.baidu.com/view/4432674.htm) 10 | 一般来说,一套引擎可以使用在多个不同的游戏中,即“可重用性”,由于引擎研发的投资较大,一个商业公司往往开发一款引擎,反复用于多款游戏,从而获得较高利润。如大宇公司的《轩辕剑系列》其正传和外传就是采用同一款引擎开发。也有本身不制作游戏,专门开发引擎并出售的公司。这样的引擎也称作“商业引擎”。出于游戏开发周期和开发成本的考虑,一些商业游戏公司更愿意购买“商业引擎”。 11 | 12 | 游戏引擎的程序基础是建立在使用图形API函数基础之上的,而不是直接控制显卡,主要适用于2D图像的API是DirectX,而纯2D图形API往往不能提供更丰富的图像操作支持,一些引擎也使用用于3D图像的API如OpenGL,Direct3D等来模拟2D。 13 | 14 | ### 三维图像引擎 15 | 目前,计算机图形学已进入三维时代,三维图形在人们周围无所不在。编写三维图形应用一般使用OpenGL或DirectX,但是它们在系统开发中仍存在一些缺点: 16 | 1. 它们都是非面向对象的,设计场景和操作场景中的对象比较困难。 17 | 2. 它们主要使用基层图元,在显示比较复杂的场景时,编写程序相对困难。 18 | 3. 没有与建模工具很好的结合。 19 | 4. 缺乏对一些十分重要的关键技术如LOD(Level of Detail)、动态裁剪等的支持。 20 | 21 | 基于以上情况,游戏开发者需要一个封装了硬件操作和图形算法、简单易用、功能丰富的三维图形开发环境,这个环境即三维图像引擎。 22 | 23 | 相比二维图像引擎,三维引擎需要解决场景构造、对象处理、场景渲染、事件处理、碰撞检测等问题。 -------------------------------------------------------------------------------- /1-basic/1-3-hello-box2d.md: -------------------------------------------------------------------------------- 1 | ## 有关Box2D 2 | ### Box2D介绍 3 | Box2D是一个用于游戏的2D刚体仿真库,它是一个非常出名的物理引擎。 4 | 5 | 它通过用户设定的参数如重力,密度,摩擦,弹性等参数计算碰撞,角度,力和动力等,使物体运动完全遵循牛顿定律,使游戏中物体的运动更加真实,增强游戏世界中物体动作的真实感,从而提高游戏质量,让游戏场景看起来更具交互性。这些计算需要大量的物理和数学知识。 6 | 7 | ### Box2D版本 8 | Box2D提供C++、Java、flash等版本。 9 | 10 | ### Box2D基本概念 11 | Box2D 中有一些基本的对象,这里我们先做一个简要的定义,在随后的文档里会有更详细的描述。 12 | 13 | - 刚体(rigid body) 14 | 15 | 一块十分坚硬的物质,它上面的任何两点之间的距离都是完全不变的。它们就像钻石那样坚硬。在后 16 | 面的讨论中,我们用物体(body)来代替刚体。 17 | 18 | - 形状(shape) 19 | 20 | 一块严格依附于物体(body)的2D碰撞几何结构(collision geometry)。形状具有摩擦(friction)和恢 21 | 复(restitution)的材料性质。 22 | 23 | - 约束(constraint) 24 | 25 | 一个约束(constraint)就是消除物体自由度的物理连接。在2D中,一个物体有3个自由度。如果我 26 | 们把一个物体钉在墙上(像摆锤那样),那我们就把它约束到了墙上。这样,此物体就只能绕着这个钉子旋 27 | 转,所以这个约束消除了它2个自由度。 28 | 29 | - 接触约束(contact constraint) 30 | 31 | 一个防止刚体穿透,以及用于模拟摩擦(friction)和恢复(restitution)的特殊约束。你永远都不必创建 32 | 一个接触约束,它们会自动被Box2D创建。 33 | 34 | - 关节(joint) 35 | 36 | 它是一种用于把两个或多个物体固定到一起的约束。Box2D支持的关节类型有:旋转,棱柱,距离等 37 | 等。关节可以支持限制(limits)和马达(motors)。 38 | 39 | - 关节限制(joint limit) 40 | 41 | 一个关节限制(joint limit)限定了一个关节的运动范围。例如人类的胳膊肘只能做某一范围角度的运 42 | 动。 43 | 44 | - 关节马达(joint motor) 45 | 46 | 一个关节马达能依照关节的自由度来驱动所连接的物体。例如,你可以使用一个马达来驱动一个肘的 47 | 旋转。 48 | 49 | - 世界(world) 50 | 51 | 一个物理世界就是物体,形状和约束相互作用的集合。Box2D支持创建多个世界,但这通常是不必要 52 | 的。 53 | 54 | 55 | ## HelloWorld 56 | ### 步骤 57 | 1. 建立一个世界,这个世界基于一个b2AABB框,并设立了一个g值和一个是否允许休眠的bool型变量。 58 | 2. 建立一个静态刚体地表,这里讲述了定义Box2D物理引擎中最为重要的一个东西——刚体的详细过程: 59 | - 首先是定义一个形状(可以是复合形状,这个在第二部分讲述) 60 | - 然后把形状通过AddShape添加进刚体定义,创建这个刚体。 61 | 3. 重复创建刚体这个过程,直至你没有需求了。 62 | 4. 在你的循环中加入世界的更新函数。 63 | 64 | ### 概念定义 65 | - 世界(b2World): 66 | 世界就是一个环境,所有物理运算都在这个里面进行。 67 | 68 | - 形状定义(b2ShapeDef): 69 | 形状定义是什么?说简单点形状定义就是定义你这个对象的样子,它用来做什么?就是用来确定你的碰撞。 70 | 71 | - 刚体定义(b2BodyDef): 72 | 刚体定义就是设定刚体的初始具体,在目前来说,最大的功能就是把你定义好的形状加到你想到的刚体上。 73 | 74 | - 刚体(b2Body): 75 | 刚体就是物理引擎里面的东西(对象),它可以受力的作用进行当前位置的变化旋转等。你要在世界中使用的所有物体目前来说都是刚体。 76 | -------------------------------------------------------------------------------- /1-basic/README.md: -------------------------------------------------------------------------------- 1 | ## 基本概念 2 | ### 总览 3 | 1. 基本概念 4 | [1.1 有关物理引擎](https://github.com/godbasin/box2djs-tutorial/tree/master/1-basic/1-1-physical-engine.md) 5 | [1.2 有关图像引擎](https://github.com/godbasin/box2djs-tutorial/tree/master/1-basic/1-2-graphic-engine.md) 6 | [1.3 有关Box2D](https://github.com/godbasin/box2djs-tutorial/tree/master/1-basic/1-3-hello-box2d.md) 7 | -------------------------------------------------------------------------------- /2-world/README.md: -------------------------------------------------------------------------------- 1 | ## 物理世界 2 | ### 世界初始化 3 | 每个Box2D程序都是从一个世界对象(world object)的创建开始的,它是一个管理内存,对象和模拟的中心,在这个世界中,刚体将遵循物理规律运动。 4 | 5 | 所以,我们首先需要创建一个世界,世界初始化类即用于创建一个世界对象,并设定有关世界的初始参数。 6 | 7 | - 创建物理世界 8 | 9 | 利用Box2D开发程序时,首先要创建一个世界对象。它负责管理内部一切对象的内存和模拟过程。 10 | 11 | - 添加物理边界 12 | 13 | 要创建一个世界中的对象,首先要为世界定义边界区域,Box2D针对区域内的所有对象进行模拟碰撞,区域的大小并不重要,但更适合的区域将提高程序性能,一般来讲这个区域设置的要比演示区域更大一些,因为一旦对象在运动时到达了边界,它就会被“冻结”并停止一切模拟活动。 14 | 15 | - 添加基本力--重力 16 | 17 | 然后要为这个世界设置重力,用向量`b2Vec2(x,y)`来表示的,x代表水平运动,正数向右,负数向左,y代表垂直运动,正数向下,负数向上; 18 | 19 | 同时需要定义一个布尔型参数用来表示是否允许睡眠,在这个世界中生成的一切对象的模拟效果都是实时计算出来的,当doSleep=false的时候,即使物体停止了运动,计算机还是在不停的进行着运算,其实这是完全不必要的,所以一般都设为true,这样当物体停止之后就不会进行无谓的cpu消耗了。 20 | 21 | - 添加物理对象 22 | 23 | 参数都准备好之后,传入b2World对象中并将其实例化,这样一个物理引擎的模拟区域就做好了,可以开始加入模拟的对象了。 24 | 基本步骤为: 25 | 1. 创建并定义刚体位置; 26 | 2. 给刚体定义皮肤; 27 | 3. 用世界对象添加刚体实例; 28 | 4. 根据皮肤形状创建模拟图形类:摩擦力、密度、弹力等等; 29 | 5. 在刚体上添加模拟图形实例; 30 | 6. 根据刚体的密度和面积计算出质量,密度*面积=质量。 31 | 32 | - 更新状态 33 | 34 | 然后,就可以通过监听帧频率而不断刷新实现让所有对象模拟运动,把上面那两个参数传入世界对象的Step方法中即可,同时我们需要遍历世界中的一切对象,并对每个对象的坐标和角度进行更新。 35 | 36 | ## b2World 37 | ### 介绍 38 | b2World类包含着物体和关节。它管理着模拟的方方面面,并允许异步查询(就像AABB查询)。你与Box2D的大部分交互都将通过b2World对象来完成。 39 | 40 | 创建一个世界十分的简单。你只需提供一个包围盒和一个重力向量。 41 | 42 | 轴对齐包围盒(AABB)应该包围着世界。稍微比世界大一些的包围盒可以提升性能,比方2倍大小才安全。如果你的许多物体都掉进了深渊,你应该侦测并移除它们。这能提升性能并预防浮点溢出。 43 | 44 | ### 创建一个世界 45 | 创建一个世界,并设置其有效区域的大小,重力大小及方向,以及是否允许休眠等。 46 | 47 | 1. 定义有效区域大小:创建一个`b2AABB`类,然后设定其左上角及右下角坐标。有效区域即box2d可以发挥作用,反映各种物理规律的区域。 48 | 49 | ``` javascript 50 | worldAABB = new b2AABB(); 51 | worldAABB.minVertex.Set(-1000, -1000); 52 | worldAABB.maxVertex.Set(1200, 1000); 53 | ``` 54 | 55 | 注意 : worldAABB应该永远比物体所在的区域要大,让worldAABB更大总比太小要好。如果一个物体到达了worldAABB的边界,它就会被冻结并停止模拟。 56 | 57 | 2. 设置重力大小及方向:通过定义一个二维矢量来实现,创建一个`b2Vec2`类,并在括号中给出其x,y方向的大小。 58 | 59 | ``` javascript 60 | gravity = new b2Vec2(0, 1000); 61 | ``` 62 | 63 | 备注:gravity是重力加速度。 64 | 65 | 3. 设置世界是否会休眠:true即允许休眠,false不允许休眠。当世界被设置为允许休眠时,物体静止一段时间后会被判定为休眠,直到有碰撞发生或者人为激活。一个休眠中的物体不需要任何模拟。 66 | 67 | ``` javascript 68 | var doSleep = true; 69 | ``` 70 | 71 | 4. 创建世界:利用`b2World`创建拥有上述性质的世界。 72 | 73 | ``` javascript 74 | world = new b2World(worldAABB, gravity, doSleep); 75 | ``` 76 | 77 | ### 创建一个世界的完整代码 78 | ``` javascript 79 | var worldAABB = new b2AABB(); 80 | worldAABB.minVertex.Set(-1000, -1000); 81 | worldAABB.maxVertex.Set(1000, 1000); 82 | var gravity = new b2Vec2(0, 300); 83 | var doSleep = true; 84 | var world = new b2World(worldAABB, gravity, doSleep); 85 | ``` 86 | -------------------------------------------------------------------------------- /3-body/3-1-shape.md: -------------------------------------------------------------------------------- 1 | ## 形状 2 | ### 关于 3 | 形状就是物体上的碰撞几何结构。另外形状也用于定义物体的质量。也就是说,你来指定密度,Box2D可以帮你计算出质量。 4 | 5 | 形状具有摩擦和恢复的性质。形状还可以携带筛选信息,使你可以防止某些游戏对象之间的碰撞。 6 | 7 | 形状永远属于某物体,单个物体可以拥有多个形状。形状是抽象类,所以在Box2D中可以实现许多类型的形状。如果你有勇气,那便可以实现出自己的形状类型(和碰撞算法)。 8 | 9 | ### 形状性质 10 | - 摩擦和恢复 11 | 12 | 摩擦可以使对象逼真地沿其它对象滑动。Box2D支持静摩擦和动摩擦,但使用相同的参数。 13 | 14 | 摩擦在Box2D中会被正确地模拟,并且摩擦力的强度与正交力(称之为库仑摩擦)成正比。摩擦参数经常会设置在0到1之间,0意味着没有摩擦,1会产生强摩擦。 15 | 当计算两个形状之间的摩擦时,Box2D必须联合两个形状的摩擦参数。 16 | 17 | 恢复可以使对象弹起,想象一下,在桌面上方丢下一个小球。恢复的值通常设置在0到1之间,0的意思是小球不会弹起,这称为非弹性碰撞;1的意思是小球的速度会得到精确的反射,这称为完全弹性碰撞。 18 | 19 | 当一个形状发生多碰撞时,恢复会被近似地模拟。这是因为Box2D使用了迭代求解器。当冲撞速度很小时,Box2D也会使用非弹性碰撞,这是为了防止抖动。 20 | 21 | - 密度 22 | 23 | Box2D可以根据附加形状的质量分配来计算物体的质量以及转动惯量。 24 | 25 | 直接指定物体质量常常会导致不协调的模拟。因此,推荐的方法是使用b2Body来根据形状设置质量。 26 | 27 | ### 形状定义 28 | 形状定义用于创建形状。通用的形状数据会保存在b2ShapeDef中,特殊的形状数据会保存在其派生类中。 29 | 30 | - `b2ShapeDef` 31 | - type来表示形状类型 32 | - userdata表示用户数据 33 | - localPosition来表示当前位置 34 | - localRotation来表示当前角度 35 | - friction、density、restitution来表示摩擦力、密度、弹性系数 36 | - categoryBits和maskBits来表示碰撞位及位标识(可以用来过滤一些碰撞) 37 | - groupIndex来表示组号,这个组号可以用来过滤还比位标识优先 38 | 39 | - `b2CircleDef` 40 | - 继承于b2ShapeDef 41 | - type为e_circleShape 42 | - radius来表示半径值 43 | 44 | - `b2BoxDef` 45 | - 继承于b2ShapeDef 46 | - type为e_ boxShape 47 | - extents来表示区域值 48 | 49 | - `b2PolyDef` 50 | - 继承于b2ShapeDef 51 | - type为e_ polyShape 52 | - vertices来表示顶点 53 | - vertexCount来表示顶点数,目前顶点数最多支持8个 54 | 55 | -------------------------------------------------------------------------------- /3-body/3-2-box-shape.md: -------------------------------------------------------------------------------- 1 | ## 矩形 2 | ### b2BoxDef 3 | 使用基类b2BoxDef创建一个矩形形状,并且设置其大小、密度、弹性、摩擦力等属性。 4 | 5 | - `b2BoxDef` 6 | - 继承于b2ShapeDef 7 | - type为e_ boxShape 8 | - extents来表示区域值 9 | 10 | ``` javascript 11 | var Shape = new b2BoxDef(); //创建一个形状Shape,然后设置有关Shape的属性 12 | Shape.extents.Set(1200, 5); //设置矩形高、宽, 13 | Shape.density = 0; //设置矩形的密度 14 | Shape.restitution = .3; //设置矩形的弹性 15 | Shape.friction = 1; //设置矩形的摩擦因子,可以设置为0-1之间任意一个数,0表示光滑,1表示强摩擦 16 | ``` 17 | 18 | 备注:如果将密度设置为0或者null,那么该形状是静止,即不可被移动的地面或者墙体等。 19 | 20 | 21 | -------------------------------------------------------------------------------- /3-body/3-3-cicle-shape.md: -------------------------------------------------------------------------------- 1 | ## 圆形 2 | ### b2CircleDef 3 | 使用基类b2CircleDef创建一个圆形形状,并且设置其属性。 4 | 5 | - `b2CircleDef` 6 | - 继承于b2ShapeDef 7 | - type为e_circleShape 8 | - radius来表示半径值 9 | 10 | ``` javascript 11 | var Shape = new b2CircleDef(); //创建一个圆形Shape,然后设置有关Shape的属性 12 | Shape.radius = 20; //设置圆形的半径 13 | Shape.localPosition.Set(0, 0); //设置圆形的偏移量 14 | Shape.density = 1.0; //设置圆形的密度 15 | Shape.restitution = .3; //设置圆形的弹性 16 | Shape.friction = 1; //设置圆形的摩擦因子 17 | ``` 18 | 19 | -------------------------------------------------------------------------------- /3-body/3-4-poly-shape.md: -------------------------------------------------------------------------------- 1 | ## 凸多边形 2 | ### b2PolyDef 3 | 使用基类b2PolyDef创建一个多边形形状,并且设置其属性。 4 | 5 | - `b2PolyDef` 6 | - 继承于b2ShapeDef 7 | - type为e_ polyShape 8 | - vertices来表示顶点 9 | - vertexCount来表示顶点数,目前顶点数最多支持8个 10 | 11 | ``` javascript 12 | var Shape = new b2PolyDef(); //创建一个多边形Shape,然后设置有关Shape的属性 13 | Shape.vertexCount = 5; //设置多边形的顶点数,这里设置为5,意味着Shape是个五边形 14 | Shape.vertices[0] = new b2Vec2(0,-20); //分别定义五个顶点的坐标 15 | Shape.vertices[1] = new b2Vec2(25,0); 16 | Shape.vertices[2] = new b2Vec2(15,30); 17 | Shape.vertices[3] = new b2Vec2(-15,30); 18 | Shape.vertices[4] = new b2Vec2(-25,0); 19 | Shape.localPosition.Set(0, 30); //设置多边形的偏移量 20 | Shape.density = 1.0; //设置多边形的密度 21 | Shape.restitution = .3; //设置多边形的弹性 22 | Shape.friction = 1; //设置多边形的摩擦因子 23 | ``` 24 | 25 | 注意:box2d只能创建顶点数不超过8的凸多边形。 26 | 27 | 多形状刚体中形状的偏移,在多个形状所组成的刚体中,所有形状的中心点都是刚体的初始位置,我们可以设置偏移量使形状偏移中心点。 -------------------------------------------------------------------------------- /3-body/3-5-shape-to-body.md: -------------------------------------------------------------------------------- 1 | ## 刚体 2 | ### 刚体定义(b2BodyDef) 3 | 在创建刚体之前你需要创建一个刚体定义(b2BodyDef)。你可以把刚体定义创建在栈上,也可以在你的游戏数据结构中保存它们。这取决于你的选择。 4 | Box2D会从刚体定义中拷贝出数据,这意味着你可以循环使用一个刚体定义去创建多个刚体。 5 | 6 | - 质量性质 7 | 8 | 有多种建立刚体质量性质的方法: 9 | 1. 在刚体定义中显式地设置 10 | 2. 显式地在刚体上设置(在其创建之后) 11 | 3. 基于刚体上形状之密度设置 12 | 在很多游戏环境中,根据形状密度计算质量是有意义的。这能帮助确保刚体有合理和一致的质量。然而,其它游戏环境可能需要指定质量值。例如,可能你有一个机械装置,需要一个精确的质量。 13 | 14 | - 位置和角度 15 | 16 | 物体定义为你提供了一个在创建时初始化位置的机会,这要比在世界原点创建物体而后移动它到某个位置更具性能。 17 | 18 | 一个物体上主要有两个令人感兴趣的点。其中一个是物体的原点,形状和关节都相对于物体的原点而被附加。另一个点是物体的质心。质心取决于物体上形状的质量分配,或显式地由b2MassData设置。 19 | Box2D内部的许多计算都要使用物体的质心,例如b2Body会存储质心的线速度。 20 | 21 | ``` javascript 22 | bodyDef.position.Set(x, y); // 设置刚体的初始位置 23 | ``` 24 | 25 | 当你构造物体定义的时候,可能你并不知道质心在哪里,因此你会指定物体的原点。你可能也会以弧度指定物体的角度,角度并不受质心位置的影响。 26 | 如果随后你改变了物体的质量性质,那么质心也会随之移动,但是原点以及物体上的形状和关节都不会改变。 27 | 28 | - 阻尼 29 | 30 | 阻尼用于减小物体在世界中的速率。阻尼与摩擦是不同的,因为摩擦仅在物体有接触的时候才会发生,而阻尼的模拟要比摩擦便宜多了。然而,阻尼并不能取代摩擦,往往这两个效果需要同时使用。 31 | 32 | 阻尼参数的范围可以在0到无穷之间,0的就是没有阻尼,无穷就是满阻尼。通常来说,阻尼的值应该在0到0.1之间,我通常不使用线性阻尼,因为它会使物体看起来发飘。 33 | 34 | ``` javascript 35 | bodyDef.linearDamping = 0.0; 36 | bodyDef.angularDamping = 0.01; 37 | ``` 38 | 39 | 阻尼相似于稳定性与性能,阻尼值较小的时候阻尼效应几乎不依赖于时间步,而阻尼值较大的时候阻 40 | 尼效应将随着时间步而变化。 41 | 42 | - 休眠参数 43 | 44 | 休眠是什么意思?好的。模拟物体的成本是高昂的,所以如果物体更少,那模拟的效果就能更好。当一个物体停止了运动时,我们喜欢停止去模拟它。 45 | 46 | 当Box2D确定一个物体(或一组物体)已经停止移动时,物体就会进入休眠状态,消耗很小的CPU开销。如果一个醒着的物体接触到了一个休眠中的物体,那么休眠中的物体就会醒来。当物体上的关节或触点被摧毁的时候,它们同样会醒来。你也可以手动地唤醒物体。 47 | 通过物体定义,你可以指定一个物体是否可以休眠,或者创建一个休眠的物体。 48 | 49 | ``` javascript 50 | bodyDef.allowSleep = true; 51 | bodyDef.isSleeping = false; 52 | ``` 53 | 54 | ## 由形状到刚体 55 | ### b2BodyDef 56 | 之前虽然创建了形状,但是这些形状并不会直接在世界里面显示出来。 57 | 58 | 下面利用这些形状来组成一些刚体,然后把它们放入到世界里面去。 59 | 60 | 首先,利用b2BodyDef创建一个刚体,然后设置刚体的初始位置并加入之间已创建的形状,然后使用CreateBody将刚体放入到世界里面,这样就可以看到一些模拟刚体了。 61 | 62 | ``` javascript 63 | var BodyDef= new b2BodyDef(); // 利用b2BodyDef类创建一个刚体 64 | BodyDef.position.Set(0, 600); // 设置刚体的初始位置,这里把BodyDef放在(0,600) 65 | BodyDef.AddShape(Shape); // 在刚体BodyDef中加入形状Shape 66 | Body= world.CreateBody(BodyDef); // 把刚体BodyDef放到world中 67 | ``` 68 | 69 | -------------------------------------------------------------------------------- /3-body/README.md: -------------------------------------------------------------------------------- 1 | ## 形状-刚体 2 | ### 形状-刚体-关节 3 | 创建了box2d世界后,我们需要创建一些刚体和关节添加进去,比如游戏中的边界、元素等,使得世界更丰富一些。 4 | 5 | 接下来就创造一些基本形状,然后用这些形状组成刚体,刚体又可以进一步组合成关节,换言之,世界即是由刚体和关节组成的世界。 6 | 7 | box2d中创建刚体、关节的过程类似于堆积木,由形状构成刚体,由刚体构成关节,稍微不同的地方是,这里的形状可以多次使用,其过程: 8 | `形状 -> 刚体 -> 关节` 9 | 10 | 此处需要注意的是,box2d只能创建顶点数不超过8的凸多边形。 11 | 12 | ### 总览 13 | 3. 形状(shape)-刚体(body) 14 | [3.1 形状](https://github.com/godbasin/box2djs-tutorial/tree/master/3-body/3-1-shape.md) 15 | [3.2 矩形](https://github.com/godbasin/box2djs-tutorial/tree/master/3-body/3-2-box-shape.md) 16 | [3.3 圆形](https://github.com/godbasin/box2djs-tutorial/tree/master/3-body/3-3-cicle-shape.md) 17 | [3.4 凸多边形](https://github.com/godbasin/box2djs-tutorial/tree/master/3-body/3-4-poly-shape.md) 18 | [3.5 由形状到刚体](https://github.com/godbasin/box2djs-tutorial/tree/master/3-body/3-5-shape-to-body.md) 19 | -------------------------------------------------------------------------------- /4-joint/4-1-distance-joint.md: -------------------------------------------------------------------------------- 1 | ## 距离关节(distance-joint) 2 | ### 说明 3 | 使用距离关节连接的两个物体上的选定的两个点之间的距离是常量,它限制了两个物体之间的距离,使它始终保持一个常量,就像自行车的两个轮子。 4 | 5 | ### b2DistanceJointDef 6 | 距离关节是最简单的关节之一,它描述了两个物体上的两个点之间的距离应该是常量。当你指定一个距离关节时,两个物体必须已在应有的位置上。 7 | 随后,你指定两个世界坐标中的锚点 。第一个锚点连接到物体1,第二个锚点连接到物体2。这些点隐含了距离约束的长度。 8 | 9 | 首先创建两个物体,然后放到世界里,然后使用基类b2DistanceJointDef创建一个距离关节,连接它们。 10 | 11 | 每个距离关节都有两个锚点,分别赋予两个物体上某一点的坐标,这样子就使用一个距离关节将两个物体的选定点连起来。 12 | 13 | ``` javascript 14 | var Shape1 = new b2BoxDef(); //创建一个形状Shape1 15 | Shape1.extents.Set(10, 10); 16 | Shape1.density = 1; 17 | Shape1.restitution = .3; 18 | Shape1.friction = 1; 19 | 20 | var BodyDef1 = new b2BodyDef(); //用Shape1创建一个物体BodyDef1 21 | BodyDef1.position.Set(300, 490); 22 | BodyDef1.AddShape(Shape1); 23 | Body1 = world.CreateBody(BodyDef1); //将BodyDef1放到世界里 24 | 25 | var Shape2 = new b2BoxDef(); 26 | Shape2.extents.Set(20, 20); 27 | Shape2.density = 1; 28 | Shape2.restitution = .3; 29 | Shape2.friction = 1; 30 | 31 | var BodyDef2 = new b2BodyDef(); 32 | BodyDef2.position.Set(400, 480); 33 | BodyDef2.AddShape(Shape2); 34 | Body2 = world.CreateBody(BodyDef2); 35 | 36 | var jointDefDistance = new b2DistanceJointDef(); //创建一个距离关节jointDefDistance 37 | jointDefDistance.body1 = Body1; //该关节一端连在Body1上 38 | jointDefDistance.body2 = Body2; //该关节另一端连在Body2上 39 | jointDefDistance.anchorPoint1=Body1.GetCenterPosition(); //关节和Body1的连接锚点是Body1的中心位置 40 | jointDefDistance.anchorPoint2=Body2.GetCenterPosition(); //关节和Body2的连接锚点是Body2的中心位置 41 | var jointDistance=world.CreateJoint(jointDefDistance); //将距离关节jointDefDistance放入到世界中 42 | ``` -------------------------------------------------------------------------------- /4-joint/4-2-revolute-joint.md: -------------------------------------------------------------------------------- 1 | ## 旋转关节(revolute-joint) 2 | ### 说明 3 | 一个旋转关节会强制两个物体共享一个锚点,即铰接点。旋转关节只有一个自由度:两个物体的相对旋转。 4 | 5 | 旋转关节即相当于将两个物体用钉子钉在一起,两个物体都可以绕这颗钉子旋转。当然,如果其中一个物体为地面的话,就相当于将物体钉在地面上,物体都可以绕这颗钉子旋转。 6 | 7 | ### b2RevoluteJointDef 8 | 首先创建两个物体,然后将它们放在世界里,使用基类b2RevoluteJointDef创建一个旋转关节,设定铰接点,及旋转关节连接的是哪两个物体之后即可将其放入到世界里。 9 | 10 | ``` javascript 11 | var jointDefRevolute = new b2RevoluteJointDef(); //创建一个旋转关节jointDefRevolute 12 | jointDefRevolute.anchorPoint.Set(450, 450); //设定铰接点坐标 13 | jointDefRevolute.body1 = Body1; //设定旋转关节一端连接Body1 14 | jointDefRevolute.body2 = Body2; //设定旋转关节另一端连接Body2 15 | var jointRevolute= world.CreateJoint(jointDefRevolute); //将旋转关节放入世界中 16 | jointRevolute.SetMotorSpeed(100); //设置旋转关节马达的速度 17 | ``` -------------------------------------------------------------------------------- /4-joint/4-3-prismatic-joint.md: -------------------------------------------------------------------------------- 1 | ## 移动关节(prismatic-joint) 2 | ### 说明 3 | 移动关节允许两个物体沿指定轴相对移动,它会阻止相对旋转,简而言之,就是关节对物体的影响只体现在轴向方向。因此,移动关节只有一个自由度。 4 | 5 | 移动关节类似旋转关节,只是将旋转角度换成了平移。关节中的两个刚体的相对运动只能发生在其轴向上。 6 | 7 | ### b2PrismaticJointDef 8 | 首先创建两个物体,将其放入世界里,然后使用基类b2PrismaticJointDef创建一个移动关节,设置其各个参数,再将其放入到世界里即可。 9 | 10 | ``` javascript 11 | var jointDefPrismatic = new b2PrismaticJointDef(); //创建一个移动关节jointDefPrismatic 12 | jointDefPrismatic.anchorPoint.Set(700, 565); //一般选择两个锚点的中心点 13 | jointDefPrismatic.axis.Set(1, 0); //两物体只沿轴向方向有相对运动 14 | jointDefPrismatic.body1 = Body1; //移动关节的一端连接到Body1上 15 | jointDefPrismatic.body2 = Body2; //移动关节的另一端连接到Body2上 16 | var jointPrismatic= world.CreateJoint(jointDefPrismatic); //将设置好参数的移动关节放到世界里 17 | ``` -------------------------------------------------------------------------------- /4-joint/4-4-pulley-joint.md: -------------------------------------------------------------------------------- 1 | ## 滑轮关节(pulley-joint) 2 | ### 说明 3 | 滑轮关节用于模拟定滑轮或动滑轮,即由滑轮关节连接的两个物体就像由绳子和滑轮连在一起一样。 4 | 5 | 在此,简单介绍动滑轮的特点,使用动滑轮能省一半力,但是费距离。 6 | 这是因为使用动滑轮时,物体由两段绳子吊着,每段绳子只承担钩码重的一半。使用动滑轮虽然省了力,但是动力移动的距离是钩码升高的距离的2倍,即费了距离。 7 | 8 | 一般可以简单地套用公式来计算F和G的关系: 9 | `F=(G+G动滑轮)/n` 10 | 对于滑轮组,n代表接在动滑轮上的绳子的段数,而比例系数ratio即是n。 11 | 12 | ### b2PulleyJointDef 13 | 创建好物体之后,用基类b2PulleyJointDef创建一个滑轮关节,然后分别设置其连接的是哪两个物体及其锚点,连接绳子顶点,两物体绳子最大长度及比例系数等参数即可。 14 | 15 | ``` javascript 16 | var jointDefPulley = new b2PulleyJointDef(); //创建一个滑轮关节jointDefPulley 17 | jointDefPulley.body1 = Body1; //滑轮关节的一端连接在Body1上 18 | jointDefPulley.body2 = Body2; //滑轮关节的另一端连接在Body2上 19 | jointDefPulley.anchorPoint1=Body1.GetCenterPosition(); //滑轮关节与Body1的连接点是Body1的中心位置 20 | jointDefPulley.anchorPoint2=Body2.GetCenterPosition(); //滑轮关节与Body2的连接点是Body2的中心位置 21 | var groundPoint1=new b2Vec2(850, 345); 22 | var groundPoint2=new b2Vec2(950, 365); 23 | jointDefPulley.groundPoint1=groundPoint1; 24 | jointDefPulley.groundPoint2=groundPoint2; //用b2Vec2创建两个矢量,然后赋值给groundPoint,设定两段绳子的顶点 25 | jointDefPulley.maxLength1=300; 26 | jointDefPulley.maxLength2=100; //设定滑轮两侧的绳子的最大长度 27 | //这两个maxLength至少有一个要大于|groundPoint-anchorPoint| jointDefPulley.ratio=1; 28 | //设定滑轮关节的比例系数,body1上下移动S,body2上下移动S/ratio,当ratio不等于1时,即模拟有动滑轮的情况 29 | 30 | var jointPulley= world.CreateJoint(jointDefPulley); //将滑轮关节放到世界内 31 | ``` -------------------------------------------------------------------------------- /4-joint/4-5-gear-joint.md: -------------------------------------------------------------------------------- 1 | ## 齿轮关节(gear-joint) 2 | ### 说明 3 | 齿轮关节需要两个被旋转关节或移动关节接地(ground)的物体,可以任意组合这些关节类型。 4 | 5 | ### b2PulleyJointDef 6 | 另外,创建旋转或移动关节时,Box2D 需要地(ground)作为 body1。 7 | 8 | ``` javascript 9 | var Shape1 = new b2BoxDef(); //也可以是圆形或者多边形刚体 10 | Shape1.extents.Set(10, 10); 11 | Shape1.density = 1; 12 | Shape1.restitution = .3; 13 | Shape1.friction = 1; 14 | 15 | var BodyDef1 = new b2BodyDef(); 16 | BodyDef1.position.Set(950, 450); 17 | BodyDef1.AddShape(Shape1); 18 | Body1 = world.CreateBody(BodyDef1); 19 | 20 | var jointDefRevolute = new b2RevoluteJointDef(); //创建一个旋转关节 21 | jointDefRevolute.anchorPoint.Set(950, 450); 22 | jointDefRevolute.body1 =world.GetGroundBody(); //用作齿轮关节的旋转关节body1须为GroundBody 23 | jointDefRevolute.body2 = Body1; //将旋转关节另外一个物体设为Body1 24 | var jointRevolute= world.CreateJoint(jointDefRevolute); //将旋转关节放入世界中 25 | 26 | var Shape2 = new b2BoxDef(); 27 | Shape2.extents.Set(100, 10); 28 | Shape2.density = 1; 29 | Shape2.restitution = .3; 30 | Shape2.friction = 1; 31 | 32 | var BodyDef2 = new b2BodyDef(); 33 | BodyDef2.position.Set(950, 530); 34 | BodyDef2.AddShape(Shape2); 35 | Body2 = world.CreateBody(BodyDef2); 36 | 37 | var jointDefPrismatic = new b2PrismaticJointDef(); //创建一个移动关节 38 | jointDefPrismatic.anchorPoint.Set(950, 565); 39 | jointDefPrismatic.axis.Set(1,0); //在齿轮关节中,这个轴只要设置成平行于屏幕就行,也就是说随着齿轮旋转,移动关节可以上下、左右来回运动,(斜向运动会出现bug) 40 | jointDefPrismatic.body1 =world.GetGroundBody(); //用作移动关节的移动关节body1须为GroundBody 41 | jointDefPrismatic.body2 =Body2; //移动关节的另外一个物体设置为Body2 42 | var jointPrismatic= world.CreateJoint(jointDefPrismatic); //将移动关节放入到世界中 43 | 44 | var jointDefGear = new b2GearJointDef(); //使用基类b2GearJointDef创建一个齿轮关节 45 | jointDefGear.body1=Body1; 46 | jointDefGear.body2=Body2; //设置齿轮关节连接的两个物体分别是Body1和Body2 47 | jointDefGear.joint1=jointRevolute; 48 | jointDefGear.joint2=jointPrismatic; 49 | var jointGear=world.CreateJoint(jointDefGear); //将齿轮关节放入到世界中 50 | jointGear.m_ratio=1; //设定比例系数,ratio=转动圈数/移动距离 51 | ``` -------------------------------------------------------------------------------- /4-joint/README.md: -------------------------------------------------------------------------------- 1 | ## 关节(joint) 2 | ### 关于 3 | 关节的作用是把物体约束到世界,或约束到其它物体上。在游戏中的典型例子是木偶,跷跷板和滑轮。关节可以用许多种不同的方法结合起来,创造出有趣的运动。 4 | 5 | 有些关节提供了限制(limit),以便你控制运动范围。有些关节还提供了马达(motor),它可以以指定的速度驱动关节,直到你指定了更大的力或扭矩。 6 | 7 | 关节马达有许多不同的用途。你可以使用关节来控制位置,只要提供一个与目标之距离成正比例的关节速度即可。你还可以模拟关节摩擦:将关节速度置零,并且提供一个小的、但有效的最大力或扭矩; 那么马达就会努力保持关节不动,直到负载变得过大。 8 | 9 | ### 关节定义 10 | 各种关节类型都派生自b2JointDef。所有关节都连接两个不同的物体,可能其中一个是静态物体。如果你想浪费内存的话,那就创建一个连接两个静态物体的关节。 11 | 12 | 你可以为任何一种关节指定用户数据。你还可以提供一个标记,用于预防相连的物体发生碰撞。实际上,这是默认行为,你可以设置collideConnected布尔值来允许相连的物体碰撞。 13 | 14 | 很多关节定义需要你提供一些几何数据。一个关节常常需要一个锚点(anchor point)来定义,这是固 定于相接物体中的点。在Box2D中这些点需要在局部坐标系中指定,这样,即便当前物体的变化违反了关节约束,关节还是可以被指定——在游戏存取进度时这经常会发生。另外,有些关节定义需要默认的物体之间的相对角度。这样才能通过关节限制或固定的相对角来正确地约束旋转。 15 | 16 | 初始化几何数据可能有些乏味。所以很多关节提供了初始化函数,消除了大部分工作。然而,这些初始化函数通常只应用于原型,在产品代码中应该直接地定义几何数据。这能使关节行为更加稳固。 17 | 18 | 其余的关节定义数据依赖于关节的类型。 19 | 20 | ### 总览 21 | 4. 关节(joint) 22 | [4.1 距离关节(distance-joint)](https://github.com/godbasin/box2djs-tutorial/tree/master/4-joint/4-1-distance-joint.md) 23 | [4.2 旋转关节(revolute-joint)](https://github.com/godbasin/box2djs-tutorial/tree/master/4-joint/4-2-revolute-joint.md) 24 | [4.3 移动关节(prismatic-joint)](https://github.com/godbasin/box2djs-tutorial/tree/master/4-joint/4-3-prismatic-joint.md) 25 | [4.4 滑轮关节(pulley-joint)](https://github.com/godbasin/box2djs-tutorial/tree/master/4-joint/4-4-pulley-joint.md) 26 | [4.5 齿轮关节(gear-joint)](https://github.com/godbasin/box2djs-tutorial/tree/master/4-joint/4-5-gear-joint.md) 27 | 28 | -------------------------------------------------------------------------------- /5-operation/5-1-mouse-get-body.md: -------------------------------------------------------------------------------- 1 | ## 鼠标获取刚体 2 | ### 鼠标获取刚体--对刚体进行操作的前提 3 | Box2D中,只要提供一个AABB的坐标,b2World就可以返回一个数组,用于保存所有相交于此AABB的形状。 4 | 5 | 所以,为了利用鼠标获取刚体,首先要画出一个小区域,这个区域小到近似于一个点,然后遍历整个世界的形状,判断鼠标点击位置所画出的小区域是否与某个形状重合,如果有重合,再由这个形状得到这个形状所属的刚体。 6 | 7 | ``` javascript 8 | function GetBodyAtMouse() { 9 | //首先创建一个近似于点的小区域 10 | var mousePVec = new b2Vec2(mousedwX, mousedwY); 11 | //利用b2Vec2定义一个矢量,用来保存鼠标点击的点 12 | var aabb = new b2AABB(); 13 | //利用b2AABB创建一个环境 14 | aabb.minVertex.Set(mousePVec.x - 0.001, mousePVec.y - 0.001); 15 | aabb.maxVertex.Set(mousePVec.x + 0.001, mousePVec.y + 0.001); 16 | //设置aabb的左上角及右下角坐标,这里是以鼠标点击位置为中心创建了一个长、宽均为0.002的矩形区域 17 | 18 | //然后查询与指定区域有重叠的刚体 19 | var k_maxCount = 10; //设定所要查找形状的数量,注意合理设置其大小,过大会影响运行速度 20 | var shapes = new Array(); //保存查找到的与已知边界盒相交的形状 21 | var count = world.Query(aabb, shapes, k_maxCount); //在世界中查找与边界盒相交的maxCount个形状,并返回边界盒区域内实际包含的形状的个数 22 | 23 | var findBody = null; //首先设定没有找到物体 24 | for (var i = 0; i < count; ++i) { 25 | if (shapes[i].GetBody().IsStatic() == false) 26 | //条件假定查找到的形状不是静态刚体,比如墙 27 | { 28 | var tShape = shapes[i]; //将查找到的形状赋给tShape变量 29 | var inside = tShape.GetBody(); //将tShape对应的刚体赋给inside 30 | if (inside) //如果inside这个刚体存在 31 | { 32 | //那么返回这个刚体,并跳出遍历 33 | findBody = tShape.GetBody(); 34 | break; 35 | } 36 | } 37 | } 38 | return findBody; 39 | } 40 | ``` -------------------------------------------------------------------------------- /5-operation/5-2-get-contact-list.md: -------------------------------------------------------------------------------- 1 | ## 获取参与碰撞的刚体 2 | ### m_contactList 3 | m_contactList的类型是b2ContactNode,取得附加在该物体上的碰撞表,所有碰撞都保存在这个列表里。 4 | 同ShapeList相比,ShapeList保存的是世界中所有的形状,而contactlist保存的是参与碰撞的形状。 5 | 6 | ``` javascript 7 | function getContactInf() { 8 | for (var b = world.m_contactList; b; b = b.m_next) 9 | // 遍历contactlist所有世界,直到b不存在,跳出循环 10 | { 11 | // 将b里的两个刚体分别定义为b1和b2 12 | var b1 = b.m_shape1.m_body; 13 | var b2 = b.m_shape2.m_body; 14 | 15 | // 向下执行的条件是b1和b2不同,且不是三面挡板(BodyTop/BodyLeft/BodyRight) 16 | if ((b1 != Body2) && (b1 != BodyTop) && (b1 != BodyLeft) && (b1 != BodyRight) && (b2 != Body2) && (b2 != BodyTop) && (b2 != BodyLeft) && (b2 != BodyRight)){} 17 | // 备注:得到了参与碰撞的刚体,则可以对其中的所有刚体的操作,这个发挥的空间留给游戏开发过程 18 | } 19 | } 20 | ``` 21 | 22 | ### GetContactList 23 | GetContactList其实与上面的作用效果一样,而且其判断接触的逻辑思路也是一致的。 24 | 25 | ``` javascript 26 | for (var cn = world.GetContactList(); cn != null; cn = cn.GetNext()) { 27 | var body1 = cn.GetShape1().GetBody(); 28 | var body2 = cn.GetShape2().GetBody(); 29 | 30 | // 处理判断发生碰撞的两个刚体是不是分别为箱子和墙壁 31 | if ((body1 == box && body2 == wall) || (body2 == box && body1 == wall)){ 32 | // 进行逻辑处理 33 | } 34 | } 35 | ``` -------------------------------------------------------------------------------- /5-operation/5-3-get-body-attributes.md: -------------------------------------------------------------------------------- 1 | ## 获取刚体的各属性 2 | ### `Body.Get属性名()` 3 | 在实际编程中,往往需要得到一些关于刚体的信息、数据等,以便下一步对刚体进行一系列操作。 4 | 下面代码中给出了获取某些数据的方法,还有一些设置刚体属性的语句。语法格式一般为Body.Get属性名()。 5 | 6 | ``` javascript 7 | function showAllFuc() { 8 | //获得刚体位置 9 | var OriginPosition = new b2Vec2(); //利用b2Vec2定义一个起始位置的二维矢量 10 | OriginPosition = slectBody.GetOriginPosition(); //获取刚体坐标原点,对称图形的原点即中心点 11 | alert(OriginPosition.x + "," + OriginPosition.y); //显示该坐标 12 | 13 | var mass; 14 | mass = slectBody.GetMass(); //获取刚体质量 15 | 16 | var inertia; 17 | inertia = slectBody.GetInertia(); //获取惯性,类似力的大小 18 | alert(inertia); 19 | 20 | var worldPoint; 21 | var localPoint = new b2Vec2(100, 100); 22 | worldPoint = slectBody.GetWorldPoint(localPoint); //由局部坐标得到世界坐标 23 | alert(slectBody.GetCenterPosition().x + "," + slectBody.GetCenterPosition().y); //世界坐标=局部坐标+刚体中心点坐标 24 | alert(localPoint.x + "," + localPoint.y); 25 | alert(worldPoint.x + "," + worldPoint.y); 26 | 27 | var localVector; 28 | var worldVector = new b2Vec2(slectBody.GetCenterPosition().x, 29 | slectBody.GetCenterPosition().y); 30 | localVector = slectBody.GetLocalVector(worldVector); 31 | alert(worldVector.x + "," + worldVector.y); 32 | alert(localVector.x + "," + localVector.y); 33 | 34 | var staticFlage; //定义一个静态标志 35 | staticFlage = slectBody.IsStatic(); //判断选中物体是否为静止不可移动的刚体,并将结果反馈给静态标志 36 | alert(staticFlage); 37 | 38 | var frozenFlage; 39 | frozenFlage = slectBody.IsFrozen(); 40 | alert(frozenFlage); 41 | 42 | var sleepingFlage; //定义一个睡眠标志 43 | sleepingFlage = slectBody.IsSleeping(); //判断选中物体是否已进入睡眠状态,并将结果反 馈给静态标志 44 | alert(sleepingFlage); 45 | 46 | var allowSleepingFlage = 1; //定义一个允许睡眠的标志,并设定初值为1,即允许睡眠 47 | slectBody.AllowSleeping(allowSleepingFlage); //根据标志判断是否允许睡眠,是则可进入睡眠状态,否则唤醒 48 | slectBody.WakeUp(); //唤醒睡眠刚体,对于非静止刚体,只有碰撞才能自动唤醒,对其赋予速度、力等值时,需要先行唤醒才有效果 49 | 50 | var shapeList = new Array(); 51 | shapeList = slectBody.GetShapeList(); //数组内保存选中刚体的shapelist;直接用m_shapeList效果一样,列表里是一个个的shape,shape是一个类,其表面形状类型的属性为shape.m_type 52 | slectBody.Destroy(); //在世界里销毁该刚体 53 | slectBody.GetNext(); //获取下一刚体,在world里刚体储存在m_BodyList里 54 | 55 | var otherBody = Body4; 56 | var conectJuge; 57 | conectJuge = slectBody.IsConnected(otherBody); //判定选定的刚体是否与另一刚体相连,相连则不碰撞 58 | alert(conectJuge); 59 | } 60 | ``` -------------------------------------------------------------------------------- /5-operation/5-4-set-body-attributes.md: -------------------------------------------------------------------------------- 1 | ## 为刚体设置属性 2 | ### 设置刚体的属性 3 | 创建一个刚体之后,还可以对它进行许多操作,比如设置质量,访问其位置和速度,施加力,以及转换点和向量等。 4 | 5 | ``` javascript 6 | // 设置刚体的属性 7 | var position = new b2Vec2(200, 100); //定义一个点坐标(200,100) 8 | var rotation = 0.3 * Math.PI; //定义一个弧度值 9 | spclBodyDef.position.Set(basicX, basicY); //设置物体的初始位置置,括号内是矢量坐标 10 | slectBody.SetCenterPosition(position, rotation); //设置选中的刚体的中心位置及其旋转弧度 11 | 12 | var CenterPosition = new b2Vec2(); 13 | CenterPosition = slectBody.GetCenterPosition(); 14 | alert(CenterPosition.x + "," + CenterPosition.y); 15 | 16 | var RotationMatrix = new b2Mat22(); 17 | RotationMatrix = slectBody.GetRotationMatrix(); //获取一个2X2的刚体旋转矩阵 18 | alert(RotationMatrix); 19 | 20 | var LinearVelocity = new b2Vec2(500, -500); //定义一个线速度 21 | slectBody.WakeUp(); //唤醒选中物体,刷新数据 22 | slectBody.SetLinearVelocity(LinearVelocity); //设定选中物体的线速度 23 | 24 | var LinearVelocity = new b2Vec2(); 25 | LinearVelocity = slectBody.GetLinearVelocity(); //获取选中刚体的线速度向量 26 | alert(LinearVelocity.x + "," + LinearVelocity.y); 27 | 28 | var AngularVelocity = 100; //定义一个角速度 29 | slectBody.WakeUp(); 30 | slectBody.SetAngularVelocity(AngularVelocity); //设定选中物体的角速度 31 | 32 | var AngularVelocity; 33 | AngularVelocity = slectBody.GetAngularVelocity(); //获取选中刚体的角速度向量 34 | alert(AngularVelocity); 35 | 36 | var force = new b2Vec2(10000, 1000000000); //定义一个力的大小及方向 37 | var point = new b2Vec2(slectBody.GetCenterPosition().x, slectBody.GetCenterPosition().y); 38 | slectBody.WakeUp(); 39 | //在指定点给物体施加一个已知大小与方向的力 40 | //施加一个力,需要很大才有效果,不如直接给定线速度,一般给力据F=ma给定 41 | slectBody.ApplyForce(force, point); 42 | alert(slectBody.m_linearVelocity); 43 | 44 | var torque = 100000000000; //定义一个力矩 45 | slectBody.WakeUp(); 46 | slectBody.ApplyTorque(torque); //设定选定物体的力矩,数值较大,可以通过设定角速度大小实现相同功能 47 | 48 | var impulse = new b2Vec2(0, 100000000); //定义一个物体的冲量 49 | var point = new b2Vec2(slectBody.GetCenterPosition().x, slectBody.GetCenterPosition().y); 50 | slectBody.WakeUp(); 51 | //在指定点给物体施加一个已知大小与方向的冲量,一般在初始的时候给定,以便决定初始运动 52 | slectBody.ApplyImpulse(impulse, point); 53 | ``` -------------------------------------------------------------------------------- /5-operation/5-5-body-with-image.md: -------------------------------------------------------------------------------- 1 | ## 绘制刚体 2 | ### 绘制刚体图片 3 | 1. 在页面中加入图片,并捆绑id 4 | 5 | ``` html 6 | 7 | ``` 8 | 9 | 2. 设置形状定义的userData为图片id 10 | 11 | ``` javascript 12 | boxSd.userData = document.getElementById('box'); 13 | ``` 14 | 15 | 3. 在绘制世界时,将图片信息绘制进去 16 | 17 | ``` javascript 18 | // 绘画世界 19 | function drawWorld(world, context) { 20 | for (var b = world.m_bodyList; b != null; b = b.m_next) { 21 | for (var s = b.GetShapeList(); s != null; s = s.GetNext()) { 22 | if (s.GetUserData() != undefined) { 23 | // 使用数据包括图片 24 | var img = s.GetUserData(); 25 | 26 | // 图片的长和宽 27 | var x = s.GetPosition().x; 28 | var y = s.GetPosition().y; 29 | var topleftX = - $(img).width() / 2; 30 | var topleftY = - $(img).height() / 2; 31 | 32 | context.save(); 33 | context.translate(x, y); 34 | context.rotate(s.GetBody().GetRotation()); 35 | context.drawImage(img, topleftX, topleftY); 36 | context.restore(); 37 | } 38 | drawShape(s, context); 39 | } 40 | } 41 | } 42 | ``` 43 | 44 | 注意,以上的方法只试用于圆形和矩形,多边形待验证。 45 | 46 | ### 绘制刚体形状 47 | 有时候我们需要将刚体和关节的形状绘制出来: 48 | 49 | ``` javascript 50 | // 从draw_world.js里面引用的绘画功能 51 | function drawShape(shape, context) { 52 | context.strokeStyle = '#003300'; 53 | context.beginPath(); 54 | switch (shape.m_type) { 55 | // 绘制圆 56 | case b2Shape.e_circleShape: 57 | var circle = shape; 58 | var pos = circle.m_position; 59 | var r = circle.m_radius; 60 | var segments = 16.0; 61 | var theta = 0.0; 62 | var dtheta = 2.0 * Math.PI / segments; 63 | // 画圆圈 64 | context.moveTo(pos.x + r, pos.y); 65 | for (var i = 0; i < segments; i++) { 66 | var d = new b2Vec2(r * Math.cos(theta), r * Math.sin(theta)); 67 | var v = b2Math.AddVV(pos, d); 68 | context.lineTo(v.x, v.y); 69 | theta += dtheta; 70 | } 71 | context.lineTo(pos.x + r, pos.y); 72 | 73 | // 画半径 74 | context.moveTo(pos.x, pos.y); 75 | var ax = circle.m_R.col1; 76 | var pos2 = new b2Vec2(pos.x + r * ax.x, pos.y + r * ax.y); 77 | context.lineTo(pos2.x, pos2.y); 78 | break; 79 | // 绘制多边形 80 | case b2Shape.e_polyShape: 81 | var poly = shape; 82 | var tV = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[0])); 83 | context.moveTo(tV.x, tV.y); 84 | for (var i = 0; i < poly.m_vertexCount; i++) { 85 | var v = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i])); 86 | context.lineTo(v.x, v.y); 87 | } 88 | context.lineTo(tV.x, tV.y); 89 | break; 90 | } 91 | context.stroke(); 92 | } 93 | ``` 94 | 95 | ## 绘制关节 96 | ### 绘制关节 97 | - 在绘制世界时加入关节的判断和绘制调用 98 | 99 | ``` javascript 100 | function drawWorld(world, context) { 101 | for (var j = world.m_jointList; j; j = j.m_next) { 102 | // 绘制关节 103 | drawJoint(j, context); 104 | } 105 | for (var b = world.m_bodyList; b; b = b.m_next) { 106 | for (var s = b.GetShapeList(); s != null; s = s.GetNext()) { 107 | // 绘制刚体形状 108 | drawShape(s, context); 109 | } 110 | } 111 | } 112 | ``` 113 | 114 | - 绘制关节 115 | 116 | ``` javascript 117 | function drawJoint(joint, context) { 118 | var b1 = joint.m_body1; 119 | var b2 = joint.m_body2; 120 | var x1 = b1.m_position; 121 | var x2 = b2.m_position; 122 | var p1 = joint.GetAnchor1(); 123 | var p2 = joint.GetAnchor2(); 124 | context.strokeStyle ='#00eeee'; 125 | context.beginPath(); 126 | switch (joint.m_type) { 127 | // 绘制距离关节 128 | case b2Joint.e_distanceJoint: 129 | context.moveTo(p1.x, p1.y); 130 | context.lineTo(p2.x, p2.y); 131 | break; 132 | 133 | case b2Joint.e_pulleyJoint: 134 | // TODO 135 | break; 136 | 137 | default: 138 | if (b1 == world.m_groundBody) { 139 | context.moveTo(p1.x, p1.y); 140 | context.lineTo(x2.x, x2.y); 141 | } 142 | else if (b2 == world.m_groundBody) { 143 | context.moveTo(p1.x, p1.y); 144 | context.lineTo(x1.x, x1.y); 145 | } 146 | else { 147 | context.moveTo(x1.x, x1.y); 148 | context.lineTo(p1.x, p1.y); 149 | context.lineTo(x2.x, x2.y); 150 | context.lineTo(p2.x, p2.y); 151 | } 152 | break; 153 | } 154 | context.stroke(); 155 | } 156 | ``` -------------------------------------------------------------------------------- /5-operation/README.md: -------------------------------------------------------------------------------- 1 | ## 操作(operation) 2 | ### 总览 3 | 5. 操作(operation) 4 | [5.1 鼠标获取刚体](https://github.com/godbasin/box2djs-tutorial/tree/master/5-operation/5-1-mouse-get-body.md) 5 | [5.2 获取参与碰撞的刚体](https://github.com/godbasin/box2djs-tutorial/tree/master/5-operation/5-2-get-contact-list.md) 6 | [5.3 获取刚体的各属性](https://github.com/godbasin/box2djs-tutorial/tree/master/5-operation/5-3-get-body-attributes.md) 7 | [5.4 为刚体设置属性](https://github.com/godbasin/box2djs-tutorial/tree/master/5-operation/5-4-set-body-attributes.md) 8 | [5.5 绘制功能](https://github.com/godbasin/box2djs-tutorial/tree/master/5-operation/5-5-body-with-image.md) 9 | 10 | -------------------------------------------------------------------------------- /6-practice/6-1-create-and-init-world/6-1-create-and-init-world.md: -------------------------------------------------------------------------------- 1 | ## 创建世界并初始化 2 | ### 创建世界 3 | 前面我们在介绍世界的时候,也有简单说明如何创建一个物理世界: 4 | 5 | ``` javascript 6 | var canvas = document.getElementById('game'); 7 | var ctx = canvas.getContext('2d'); 8 | var canvasWidth = parseInt(canvas.width); 9 | var canvasHeight = parseInt(canvas.height); 10 | 11 | // 我们将创建世界封装至createWorld函数内 12 | function createWorld() { 13 | // 世界的大小 14 | var worldAABB = new b2AABB(); 15 | worldAABB.minVertex.Set(-4000, -4000); 16 | worldAABB.maxVertex.Set(4000, 4000); 17 | 18 | //定义重力 19 | var gravity = new b2Vec2(0, 300); 20 | 21 | // 是否休眠 22 | var doSleep = false; 23 | 24 | // 最终创建世界 25 | var world = new b2World(worldAABB, gravity, doSleep); 26 | 27 | return world; 28 | } 29 | ``` 30 | 31 | ### 绘制世界 32 | 我们可以添加一个绘制世界的功能,讲里面的刚体、关节等都进行绘制: 33 | 34 | ``` javascript 35 | //绘画功能 36 | function drawWorld(world, context) { 37 | for (var j = world.m_jointList; j; j = j.m_next) { 38 | // 绘制关节 39 | // drawJoint(j, context); 40 | } 41 | for (var b = world.m_bodyList; b; b = b.m_next) { 42 | for (var s = b.GetShapeList(); s != null; s = s.GetNext()) { 43 | // 绘制刚体形状 44 | // drawShape(s, context); 45 | } 46 | } 47 | } 48 | ``` 49 | 50 | ### 刷新世界 51 | 现在我们可以开始模拟循环了,在游戏中模拟循环应该并入游戏循环。每次循环你都应该调用`world.Step(timeStep, iterations)`,通常调用一次就够了,这取决于帧频以及物理时间步。 52 | 53 | - 时间步timeStep 54 | 55 | Box2D中有一些数学代码构成的积分器(integrator),积分器在离散的时间点上模拟物理方程,它将与游戏动画循环一同运行。 56 | 所以我们需要为Box2D选取一个时间步,通常来说游戏物理引擎需要至少60Hz的速度,也就是1/60的时间步。你可以使用更大的时间步,但是你必须更加小心地为你的世界调整定义。 57 | 58 | - 迭代次数iterations 59 | 60 | 除了积分器之外,Box2D中还有约束求解器(constraint solver)。约束求解器用于解决模拟中的所有约束,一次一个。 61 | 单个的约束会被完美的求解,然而当我们求解一个约束的时候,我们就会稍微耽误另一个。要得到良好的解,我们需要迭代所有约束多次。 62 | 63 | 建议的Box2D迭代次数是10次。你可以按自己的喜好去调整这个数,但要记得它是速度与质量之间的平衡。更少的迭代会增加性能并降低精度,同样地,更多的迭代会减少性能但提高模拟质量。 64 | 65 | ``` javascript 66 | // 定义step函数,用于世界的迭代运行 67 | function step() { 68 | // 69 | world.Step(1.0 / 60, 1); 70 | // 清除画布 71 | ctx.clearRect(0, 0, canvasWidth, canvasHeight); 72 | // 重新绘制 73 | drawWorld(world, ctx); 74 | 75 | // 再次刷新 76 | setTimeout(step, 10); 77 | } 78 | ``` 79 | 80 | ### 完整代码 81 | ``` javascript 82 | var canvas = document.getElementById('canvas'); 83 | var ctx = canvas.getContext('2d'); 84 | var canvasWidth = parseInt(canvas.width); 85 | var canvasHeight = parseInt(canvas.height); 86 | 87 | var world; 88 | 89 | // 我们将创建世界封装至createWorld函数内 90 | function createWorld() { 91 | // 世界的大小 92 | var worldAABB = new b2AABB(); 93 | worldAABB.minVertex.Set(-4000, -4000); 94 | worldAABB.maxVertex.Set(4000, 4000); 95 | 96 | //定义重力 97 | var gravity = new b2Vec2(0, 300); 98 | 99 | // 是否休眠 100 | var doSleep = false; 101 | 102 | // 最终创建世界 103 | var world = new b2World(worldAABB, gravity, doSleep); 104 | 105 | return world; 106 | } 107 | 108 | //绘画功能 109 | function drawWorld(world, context) { 110 | for (var j = world.m_jointList; j; j = j.m_next) { 111 | // 绘制关节 112 | // drawJoint(j, context); 113 | } 114 | for (var b = world.m_bodyList; b; b = b.m_next) { 115 | for (var s = b.GetShapeList(); s != null; s = s.GetNext()) { 116 | // 绘制刚体形状 117 | // drawShape(s, context); 118 | } 119 | } 120 | } 121 | 122 | // 定义step函数,用于游戏的迭代运行 123 | function step() { 124 | // 模拟世界 125 | world.Step(1.0 / 60, 1); 126 | // 清除画布 127 | ctx.clearRect(0, 0, canvasWidth, canvasHeight); 128 | // 重新绘制 129 | drawWorld(world, ctx); 130 | 131 | // 再次刷新 132 | setTimeout(step, 10); 133 | console.log('step...'); 134 | } 135 | 136 | // 启动游戏 137 | world = createWorld(); 138 | step(); 139 | ``` 140 | 141 | [此处查看项目代码(仅包含src部分)](https://github.com/godbasin/box2djs-tutorial/tree/master/6-practice/6-1-create-and-init-world/6-1-create-and-init-world) 142 | [此处查看页面效果](http://old7pzwup.bkt.clouddn.com/6-1-create-and-init-world/index.html) 143 | 144 | 145 | ### [返回总目录](https://github.com/godbasin/box2djs-tutorial) -------------------------------------------------------------------------------- /6-practice/6-1-create-and-init-world/6-1-create-and-init-world/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | box2d-test 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /6-practice/6-1-create-and-init-world/6-1-create-and-init-world/js/test.js: -------------------------------------------------------------------------------- 1 | var canvas = document.getElementById('canvas'); 2 | var ctx = canvas.getContext('2d'); 3 | var canvasWidth = parseInt(canvas.width); 4 | var canvasHeight = parseInt(canvas.height); 5 | 6 | var world; 7 | 8 | // 我们将创建世界封装至createWorld函数内 9 | function createWorld() { 10 | // 世界的大小 11 | var worldAABB = new b2AABB(); 12 | worldAABB.minVertex.Set(-4000, -4000); 13 | worldAABB.maxVertex.Set(4000, 4000); 14 | 15 | //定义重力 16 | var gravity = new b2Vec2(0, 300); 17 | 18 | // 是否休眠 19 | var doSleep = false; 20 | 21 | // 最终创建世界 22 | var world = new b2World(worldAABB, gravity, doSleep); 23 | 24 | return world; 25 | } 26 | 27 | //绘画功能 28 | function drawWorld(world, context) { 29 | for (var j = world.m_jointList; j; j = j.m_next) { 30 | // 绘制关节 31 | // drawJoint(j, context); 32 | } 33 | for (var b = world.m_bodyList; b; b = b.m_next) { 34 | for (var s = b.GetShapeList(); s != null; s = s.GetNext()) { 35 | // 绘制刚体形状 36 | // drawShape(s, context); 37 | } 38 | } 39 | } 40 | 41 | // 定义step函数,用于游戏的迭代运行 42 | function step() { 43 | // 模拟世界 44 | world.Step(1.0 / 60, 1); 45 | // 清除画布 46 | ctx.clearRect(0, 0, canvasWidth, canvasHeight); 47 | // 重新绘制 48 | drawWorld(world, ctx); 49 | 50 | // 再次刷新 51 | setTimeout(step, 10); 52 | console.log('step...'); 53 | } 54 | 55 | // 启动游戏 56 | world = createWorld(); 57 | step(); -------------------------------------------------------------------------------- /6-practice/6-2-add-body/6-2-add-body.md: -------------------------------------------------------------------------------- 1 | ## 添加刚体 2 | ### 创建刚体 3 | 前面我们说过,box2d中创建刚体、关节的过程类似于堆积木,由形状构成刚体,由刚体构成关节,稍微不同的地方是,这里的形状可以多次使用,其过程: 4 | `形状 -> 刚体 -> 关节` 5 | 6 | 这里我们首先创建各种形状,然后创建对应的刚体。 7 | 8 | - 创建圆形 9 | 10 | ``` javascript 11 | function createBall(world, x, y, r) { 12 | // 创建圆形定义 13 | var ballSd = new b2CircleDef(); 14 | ballSd.density = 1.0; // 设置密度 15 | ballSd.radius = 20; // 设置半径 16 | ballSd.restitution = 1.0; // 设置弹性 17 | ballSd.friction = 0; // 设置摩擦因子 18 | var ballBd = new b2BodyDef(); // 创建刚体定义 19 | ballBd.AddShape(ballSd); // 添加形状 20 | ballBd.position.Set(x || 0,y || 0); // 设置位置 21 | return world.CreateBody(ballBd); // 创建并返回刚体 22 | } 23 | ``` 24 | 25 | - 创建矩形 26 | 27 | ``` javascript 28 | // 创建矩形刚体 29 | function createBox(world, x, y, width, height, userData) { 30 | var boxSd = new b2BoxDef(); // 创建一个形状Shape,然后设置有关Shape的属性 31 | boxSd.extents.Set(width || 1200, height || 5); // 设置矩形高、宽 32 | boxSd.density = 1.0; // 设置矩形的密度 33 | boxSd.userData = userData; // 传入图片数据 34 | boxSd.restitution = .3; //设置矩形的弹性 35 | boxSd.friction = 1; //设置矩形的摩擦因子,可以设置为0-1之间任意一个数,0表示光滑,1表示强摩擦 36 | 37 | var boxBd = new b2BodyDef(); // 创建刚体定义 38 | boxBd.AddShape(boxSd); // 添加形状 39 | boxBd.position.Set(x || 10, y || 10); // 设置位置 40 | return world.CreateBody(boxBd) // 创建并返回刚体 41 | } 42 | ``` 43 | 44 | ### 添加刚体 45 | 利用上面创建刚体的函数,我们可以往世界中添加刚体: 46 | 47 | ``` javascript 48 | var ball1 = createBall(world, 100, 20, 20); 49 | var ball2 = createBall(world, 300, 60, 10); 50 | var box1 = createBox(world, 100, 200, 25, 30, true); 51 | var box2 = createBox(world, 200, 50, 20, 20); 52 | ``` 53 | 54 | ### 绘制刚体 55 | 我们可以通过添加绘制刚体的函数,在绘制世界时调用: 56 | 57 | ``` javascript 58 | // 从draw_world.js里面引用的绘画功能 59 | function drawShape(shape, context) { 60 | context.strokeStyle = '#003300'; 61 | context.beginPath(); 62 | switch (shape.m_type) { 63 | // 绘制圆 64 | case b2Shape.e_circleShape: 65 | var circle = shape; 66 | var pos = circle.m_position; 67 | var r = circle.m_radius; 68 | var segments = 16.0; 69 | var theta = 0.0; 70 | var dtheta = 2.0 * Math.PI / segments; 71 | // 画圆圈 72 | context.moveTo(pos.x + r, pos.y); 73 | for (var i = 0; i < segments; i++) { 74 | var d = new b2Vec2(r * Math.cos(theta), r * Math.sin(theta)); 75 | var v = b2Math.AddVV(pos, d); 76 | context.lineTo(v.x, v.y); 77 | theta += dtheta; 78 | } 79 | context.lineTo(pos.x + r, pos.y); 80 | 81 | // 画半径 82 | context.moveTo(pos.x, pos.y); 83 | var ax = circle.m_R.col1; 84 | var pos2 = new b2Vec2(pos.x + r * ax.x, pos.y + r * ax.y); 85 | context.lineTo(pos2.x, pos2.y); 86 | break; 87 | // 绘制多边形 88 | case b2Shape.e_polyShape: 89 | var poly = shape; 90 | var tV = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[0])); 91 | context.moveTo(tV.x, tV.y); 92 | for (var i = 0; i < poly.m_vertexCount; i++) { 93 | var v = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i])); 94 | context.lineTo(v.x, v.y); 95 | } 96 | context.lineTo(tV.x, tV.y); 97 | break; 98 | } 99 | context.stroke(); 100 | } 101 | ``` 102 | 103 | 我们也可以在其中一个刚体里将图片与刚体结合: 104 | 105 | ``` javascript 106 | //绘画功能 107 | function drawWorld(world, context) { 108 | for (var j = world.m_jointList; j; j = j.m_next) { 109 | // 绘制关节 110 | // drawJoint(j, context); 111 | } 112 | for (var b = world.m_bodyList; b != null; b = b.m_next) { 113 | for (var s = b.GetShapeList(); s != null; s = s.GetNext()) { 114 | if (s.GetUserData() != undefined) { 115 | // 使用数据包括图片 116 | var img = s.GetUserData(); 117 | 118 | // 图片的长和宽 119 | var x = s.GetPosition().x; 120 | var y = s.GetPosition().y; 121 | var topleftX = - img.clientWidth / 2; 122 | var topleftY = - img.clientHeight / 2; 123 | 124 | context.save(); 125 | context.translate(x, y); 126 | context.rotate(s.GetBody().GetRotation()); 127 | context.drawImage(img, topleftX, topleftY); 128 | context.restore(); 129 | } 130 | drawShape(s, context); 131 | } 132 | } 133 | } 134 | ``` 135 | 136 | ### 完整代码 137 | ``` javascript 138 | var canvas; 139 | var ctx; 140 | var canvasWidth; 141 | var canvasHeight; 142 | 143 | var world; 144 | 145 | // 我们将创建世界封装至createWorld函数内 146 | function createWorld() { 147 | // 世界的大小 148 | var worldAABB = new b2AABB(); 149 | worldAABB.minVertex.Set(-4000, -4000); 150 | worldAABB.maxVertex.Set(4000, 4000); 151 | 152 | //定义重力 153 | var gravity = new b2Vec2(0, 300); 154 | 155 | // 是否休眠 156 | var doSleep = false; 157 | 158 | // 最终创建世界 159 | var world = new b2World(worldAABB, gravity, doSleep); 160 | 161 | return world; 162 | } 163 | 164 | //绘画功能 165 | function drawWorld(world, context) { 166 | for (var j = world.m_jointList; j; j = j.m_next) { 167 | // 绘制关节 168 | // drawJoint(j, context); 169 | } 170 | for (var b = world.m_bodyList; b != null; b = b.m_next) { 171 | for (var s = b.GetShapeList(); s != null; s = s.GetNext()) { 172 | if (s.GetUserData() != undefined) { 173 | // 使用数据包括图片 174 | var img = s.GetUserData(); 175 | 176 | // 图片的长和宽 177 | var x = s.GetPosition().x; 178 | var y = s.GetPosition().y; 179 | var topleftX = -img.clientWidth / 2; 180 | var topleftY = -img.clientHeight / 2; 181 | 182 | context.save(); 183 | context.translate(x, y); 184 | context.rotate(s.GetBody().GetRotation()); 185 | context.drawImage(img, topleftX, topleftY); 186 | context.restore(); 187 | } 188 | drawShape(s, context); 189 | } 190 | } 191 | } 192 | 193 | // 创建圆形刚体 194 | function createBall(world, x, y, r) { 195 | // 创建圆形定义 196 | var ballSd = new b2CircleDef(); 197 | ballSd.density = 1.0; // 设置密度 198 | ballSd.radius = 20; // 设置半径 199 | ballSd.restitution = 1.0; // 设置弹性 200 | ballSd.friction = 0; // 设置摩擦因子 201 | var ballBd = new b2BodyDef(); // 创建刚体定义 202 | ballBd.AddShape(ballSd); // 添加形状 203 | ballBd.position.Set(x || 0, y || 0); // 设置位置 204 | return world.CreateBody(ballBd); // 创建并返回刚体 205 | } 206 | 207 | // 创建矩形刚体 208 | function createBox(world, x, y, width, height, userData) { 209 | var boxSd = new b2BoxDef(); //创建一个形状Shape,然后设置有关Shape的属性 210 | boxSd.extents.Set(width || 1200, height || 5); //设置矩形高、宽 211 | boxSd.density = 1.0; 212 | boxSd.userData = userData; 213 | boxSd.restitution = .3; //设置矩形的弹性 214 | boxSd.friction = 1; //设置矩形的摩擦因子,可以设置为0-1之间任意一个数,0表示光滑,1表示强摩擦 215 | 216 | var boxBd = new b2BodyDef(); // 创建刚体定义 217 | boxBd.AddShape(boxSd); // 添加形状 218 | boxBd.position.Set(x || 10, y || 10); // 设置位置 219 | return world.CreateBody(boxBd) // 创建并返回刚体 220 | } 221 | 222 | // 定义step函数,用于游戏的迭代运行 223 | function step() { 224 | // 模拟世界 225 | world.Step(1.0 / 60, 1); 226 | // 清除画布 227 | ctx.clearRect(0, 0, canvasWidth, canvasHeight); 228 | // 重新绘制 229 | drawWorld(world, ctx); 230 | 231 | // 再次刷新 232 | setTimeout(step, 10); 233 | console.log('step...'); 234 | } 235 | 236 | // 从draw_world.js里面引用的绘画功能 237 | function drawShape(shape, context) { 238 | context.strokeStyle = '#003300'; 239 | context.beginPath(); 240 | switch (shape.m_type) { 241 | // 绘制圆 242 | case b2Shape.e_circleShape: 243 | var circle = shape; 244 | var pos = circle.m_position; 245 | var r = circle.m_radius; 246 | var segments = 16.0; 247 | var theta = 0.0; 248 | var dtheta = 2.0 * Math.PI / segments; 249 | // 画圆圈 250 | context.moveTo(pos.x + r, pos.y); 251 | for (var i = 0; i < segments; i++) { 252 | var d = new b2Vec2(r * Math.cos(theta), r * Math.sin(theta)); 253 | var v = b2Math.AddVV(pos, d); 254 | context.lineTo(v.x, v.y); 255 | theta += dtheta; 256 | } 257 | context.lineTo(pos.x + r, pos.y); 258 | 259 | // 画半径 260 | context.moveTo(pos.x, pos.y); 261 | var ax = circle.m_R.col1; 262 | var pos2 = new b2Vec2(pos.x + r * ax.x, pos.y + r * ax.y); 263 | context.lineTo(pos2.x, pos2.y); 264 | break; 265 | // 绘制多边形 266 | case b2Shape.e_polyShape: 267 | var poly = shape; 268 | var tV = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[0])); 269 | context.moveTo(tV.x, tV.y); 270 | for (var i = 0; i < poly.m_vertexCount; i++) { 271 | var v = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i])); 272 | context.lineTo(v.x, v.y); 273 | } 274 | context.lineTo(tV.x, tV.y); 275 | break; 276 | } 277 | context.stroke(); 278 | } 279 | 280 | window.onload = function() { 281 | canvas = document.getElementById('canvas'); 282 | ctx = canvas.getContext('2d'); 283 | canvasWidth = parseInt(canvas.width); 284 | canvasHeight = parseInt(canvas.height); 285 | // 启动游戏 286 | world = createWorld(); 287 | var ball1 = createBall(world, 100, 20, 20); 288 | var ball2 = createBall(world, 300, 60, 10); 289 | var box1 = createBox(world, 200, 50, 20, 20); 290 | var box2 = createBox(world, 400, 80, 20, 20, document.getElementById('box')); 291 | step(); 292 | }; 293 | ``` 294 | 295 | [此处查看项目代码(仅包含src部分)](https://github.com/godbasin/box2djs-tutorial/tree/master/6-practice/6-2-add-body/6-2-add-body) 296 | [此处查看页面效果](http://old7pzwup.bkt.clouddn.com/6-2-add-body/index.html) 297 | 298 | 299 | ### [返回总目录](https://github.com/godbasin/box2djs-tutorial) -------------------------------------------------------------------------------- /6-practice/6-2-add-body/6-2-add-body/box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godbasin/box2djs-tutorial/99e9520dcbac39626193d80f505ff6f882f1483b/6-practice/6-2-add-body/6-2-add-body/box.png -------------------------------------------------------------------------------- /6-practice/6-2-add-body/6-2-add-body/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | box2d-test 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /6-practice/6-2-add-body/6-2-add-body/js/test.js: -------------------------------------------------------------------------------- 1 | var canvas; 2 | var ctx; 3 | var canvasWidth; 4 | var canvasHeight; 5 | 6 | var world; 7 | 8 | // 我们将创建世界封装至createWorld函数内 9 | function createWorld() { 10 | // 世界的大小 11 | var worldAABB = new b2AABB(); 12 | worldAABB.minVertex.Set(-4000, -4000); 13 | worldAABB.maxVertex.Set(4000, 4000); 14 | 15 | //定义重力 16 | var gravity = new b2Vec2(0, 300); 17 | 18 | // 是否休眠 19 | var doSleep = false; 20 | 21 | // 最终创建世界 22 | var world = new b2World(worldAABB, gravity, doSleep); 23 | 24 | return world; 25 | } 26 | 27 | //绘画功能 28 | function drawWorld(world, context) { 29 | for (var j = world.m_jointList; j; j = j.m_next) { 30 | // 绘制关节 31 | // drawJoint(j, context); 32 | } 33 | for (var b = world.m_bodyList; b != null; b = b.m_next) { 34 | for (var s = b.GetShapeList(); s != null; s = s.GetNext()) { 35 | if (s.GetUserData() != undefined) { 36 | // 使用数据包括图片 37 | var img = s.GetUserData(); 38 | 39 | // 图片的长和宽 40 | var x = s.GetPosition().x; 41 | var y = s.GetPosition().y; 42 | var topleftX = - img.clientWidth / 2; 43 | var topleftY = - img.clientHeight / 2; 44 | 45 | context.save(); 46 | context.translate(x, y); 47 | context.rotate(s.GetBody().GetRotation()); 48 | context.drawImage(img, topleftX, topleftY); 49 | context.restore(); 50 | } 51 | drawShape(s, context); 52 | } 53 | } 54 | } 55 | 56 | // 创建圆形刚体 57 | function createBall(world, x, y, r) { 58 | // 创建圆形定义 59 | var ballSd = new b2CircleDef(); 60 | ballSd.density = 1.0; // 设置密度 61 | ballSd.radius = 20; // 设置半径 62 | ballSd.restitution = 1.0; // 设置弹性 63 | ballSd.friction = 0; // 设置摩擦因子 64 | var ballBd = new b2BodyDef(); // 创建刚体定义 65 | ballBd.AddShape(ballSd); // 添加形状 66 | ballBd.position.Set(x || 0, y || 0); // 设置位置 67 | return world.CreateBody(ballBd); // 创建并返回刚体 68 | } 69 | 70 | // 创建矩形刚体 71 | function createBox(world, x, y, width, height, custom) { 72 | var boxSd = new b2BoxDef(); //创建一个形状Shape,然后设置有关Shape的属性 73 | boxSd.extents.Set(width || 1200, height || 5); //设置矩形高、宽 74 | boxSd.density = 1.0; 75 | if (custom === 'fixed') boxSd.density = 0.0; //设置矩形的密度 76 | else boxSd.userData = custom; 77 | boxSd.restitution = .3; //设置矩形的弹性 78 | boxSd.friction = 1; //设置矩形的摩擦因子,可以设置为0-1之间任意一个数,0表示光滑,1表示强摩擦 79 | 80 | var boxBd = new b2BodyDef(); // 创建刚体定义 81 | boxBd.AddShape(boxSd); // 添加形状 82 | boxBd.position.Set(x || 10, y || 10); // 设置位置 83 | return world.CreateBody(boxBd) // 创建并返回刚体 84 | } 85 | 86 | // 定义step函数,用于游戏的迭代运行 87 | function step() { 88 | // 模拟世界 89 | world.Step(1.0 / 60, 1); 90 | // 清除画布 91 | ctx.clearRect(0, 0, canvasWidth, canvasHeight); 92 | // 重新绘制 93 | drawWorld(world, ctx); 94 | 95 | // 再次刷新 96 | setTimeout(step, 10); 97 | console.log('step...'); 98 | } 99 | 100 | // 从draw_world.js里面引用的绘画功能 101 | function drawShape(shape, context) { 102 | context.strokeStyle = '#003300'; 103 | context.beginPath(); 104 | switch (shape.m_type) { 105 | // 绘制圆 106 | case b2Shape.e_circleShape: 107 | var circle = shape; 108 | var pos = circle.m_position; 109 | var r = circle.m_radius; 110 | var segments = 16.0; 111 | var theta = 0.0; 112 | var dtheta = 2.0 * Math.PI / segments; 113 | // 画圆圈 114 | context.moveTo(pos.x + r, pos.y); 115 | for (var i = 0; i < segments; i++) { 116 | var d = new b2Vec2(r * Math.cos(theta), r * Math.sin(theta)); 117 | var v = b2Math.AddVV(pos, d); 118 | context.lineTo(v.x, v.y); 119 | theta += dtheta; 120 | } 121 | context.lineTo(pos.x + r, pos.y); 122 | 123 | // 画半径 124 | context.moveTo(pos.x, pos.y); 125 | var ax = circle.m_R.col1; 126 | var pos2 = new b2Vec2(pos.x + r * ax.x, pos.y + r * ax.y); 127 | context.lineTo(pos2.x, pos2.y); 128 | break; 129 | // 绘制多边形 130 | case b2Shape.e_polyShape: 131 | var poly = shape; 132 | var tV = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[0])); 133 | context.moveTo(tV.x, tV.y); 134 | for (var i = 0; i < poly.m_vertexCount; i++) { 135 | var v = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i])); 136 | context.lineTo(v.x, v.y); 137 | } 138 | context.lineTo(tV.x, tV.y); 139 | break; 140 | } 141 | context.stroke(); 142 | } 143 | 144 | window.onload = function () { 145 | canvas = document.getElementById('canvas'); 146 | ctx = canvas.getContext('2d'); 147 | canvasWidth = parseInt(canvas.width); 148 | canvasHeight = parseInt(canvas.height); 149 | // 启动游戏 150 | world = createWorld(); 151 | var ball1 = createBall(world, 100, 20, 20); 152 | var ball2 = createBall(world, 300, 60, 10); 153 | var box1 = createBox(world, 100, 200, 25, 30, 'fixed'); 154 | var box2 = createBox(world, 200, 50, 20, 20); 155 | var box3 = createBox(world, 400, 80, 20, 20, document.getElementById('box')); 156 | step(); 157 | }; 158 | -------------------------------------------------------------------------------- /6-practice/6-3-add-bound/6-3-add-bound.md: -------------------------------------------------------------------------------- 1 | ## 添加固定刚体 2 | ### 密度为0的刚体 3 | 要创建边界,如地板、墙壁等,即添加固定的刚体,这时候我们只需要把刚体的密度设置为0就可以实现。 4 | 5 | 为此,我们需要调整创建刚体的函数: 6 | 7 | ``` javascript 8 | // 创建圆形刚体 9 | function createBall(world, x, y, r, custom) { 10 | // 创建圆形定义 11 | var ballSd = new b2CircleDef(); 12 | ballSd.density = 1.0; // 设置密度 13 | if (custom === 'fixed') ballSd.density = 0.0; // 若传入'fixed',则需固定,此时设置密度为0 14 | else ballSd.userData = custom; // 若传入其他,则视为图片数据 15 | ballSd.radius = 20; // 设置半径 16 | ballSd.restitution = 1.0; // 设置弹性 17 | ballSd.friction = 0; // 设置摩擦因子 18 | var ballBd = new b2BodyDef(); // 创建刚体定义 19 | ballBd.AddShape(ballSd); // 添加形状 20 | ballBd.position.Set(x || 0, y || 0); // 设置位置 21 | return world.CreateBody(ballBd); // 创建并返回刚体 22 | } 23 | 24 | // 创建矩形刚体 25 | function createBox(world, x, y, width, height, custom) { 26 | var boxSd = new b2BoxDef(); // 创建一个形状Shape,然后设置有关Shape的属性 27 | boxSd.extents.Set(width || 1200, height || 5); // 设置矩形高、宽 28 | boxSd.density = 1.0; // 设置矩形的密度 29 | if (custom === 'fixed') boxSd.density = 0.0; // 若传入'fixed',则需固定,此时设置密度为0 30 | else boxSd.userData = custom; // 若传入其他,则视为图片数据 31 | boxSd.restitution = .3; // 设置矩形的弹性 32 | boxSd.friction = 1; // 设置矩形的摩擦因子,可以设置为0-1之间任意一个数,0表示光滑,1表示强摩擦 33 | 34 | var boxBd = new b2BodyDef(); // 创建刚体定义 35 | boxBd.AddShape(boxSd); // 添加形状 36 | boxBd.position.Set(x || 10, y || 10); // 设置位置 37 | return world.CreateBody(boxBd) // 创建并返回刚体 38 | } 39 | ``` 40 | 41 | ### 添加边界 42 | 利用上面创建刚体的函数,我们可以往世界中添加边界: 43 | 44 | ``` javascript 45 | var wallLeft = createBox(world, 0, 0, 10, 600, 'fixed'); 46 | var wallRight = createBox(world, 1290, 0, 10, 400, 'fixed'); 47 | var ground = createBox(world, 30, 595, 1200, 5, 'fixed'); 48 | ``` 49 | 50 | ### 添加固定物 51 | 同时我们也可以添加固定物体: 52 | 53 | ``` javascript 54 | var box1 = createBox(world, 100, 200, 25, 30, 'fixed'); 55 | ``` 56 | 57 | ### 完整代码 58 | ``` javascript 59 | var canvas; 60 | var ctx; 61 | var canvasWidth; 62 | var canvasHeight; 63 | 64 | var world; 65 | 66 | // 我们将创建世界封装至createWorld函数内 67 | function createWorld() { 68 | // 世界的大小 69 | var worldAABB = new b2AABB(); 70 | worldAABB.minVertex.Set(-4000, -4000); 71 | worldAABB.maxVertex.Set(4000, 4000); 72 | 73 | //定义重力 74 | var gravity = new b2Vec2(0, 300); 75 | 76 | // 是否休眠 77 | var doSleep = false; 78 | 79 | // 最终创建世界 80 | var world = new b2World(worldAABB, gravity, doSleep); 81 | 82 | return world; 83 | } 84 | 85 | //绘画功能 86 | function drawWorld(world, context) { 87 | for (var j = world.m_jointList; j; j = j.m_next) { 88 | // 绘制关节 89 | // drawJoint(j, context); 90 | } 91 | for (var b = world.m_bodyList; b != null; b = b.m_next) { 92 | for (var s = b.GetShapeList(); s != null; s = s.GetNext()) { 93 | if (s.GetUserData() != undefined) { 94 | // 使用数据包括图片 95 | var img = s.GetUserData(); 96 | 97 | // 图片的长和宽 98 | var x = s.GetPosition().x; 99 | var y = s.GetPosition().y; 100 | var topleftX = -img.clientWidth / 2; 101 | var topleftY = -img.clientHeight / 2; 102 | 103 | context.save(); 104 | context.translate(x, y); 105 | context.rotate(s.GetBody().GetRotation()); 106 | context.drawImage(img, topleftX, topleftY); 107 | context.restore(); 108 | } 109 | drawShape(s, context); 110 | } 111 | } 112 | } 113 | 114 | // 创建圆形刚体 115 | function createBall(world, x, y, r, custom) { 116 | // 创建圆形定义 117 | var ballSd = new b2CircleDef(); 118 | ballSd.density = 1.0; // 设置密度 119 | if (custom === 'fixed') ballSd.density = 0.0; // 若传入'fixed',则需固定,此时设置密度为0 120 | else ballSd.userData = custom; // 若传入其他,则视为图片数据 121 | ballSd.radius = 20; // 设置半径 122 | ballSd.restitution = 1.0; // 设置弹性 123 | ballSd.friction = 0; // 设置摩擦因子 124 | var ballBd = new b2BodyDef(); // 创建刚体定义 125 | ballBd.AddShape(ballSd); // 添加形状 126 | ballBd.position.Set(x || 0, y || 0); // 设置位置 127 | return world.CreateBody(ballBd); // 创建并返回刚体 128 | } 129 | 130 | // 创建矩形刚体 131 | function createBox(world, x, y, width, height, custom) { 132 | var boxSd = new b2BoxDef(); // 创建一个形状Shape,然后设置有关Shape的属性 133 | boxSd.extents.Set(width || 1200, height || 5); // 设置矩形高、宽 134 | boxSd.density = 1.0; // 设置矩形的密度 135 | if (custom === 'fixed') boxSd.density = 0.0; // 若传入'fixed',则需固定,此时设置密度为0 136 | else boxSd.userData = custom; // 若传入其他,则视为图片数据 137 | boxSd.restitution = .3; // 设置矩形的弹性 138 | boxSd.friction = 1; // 设置矩形的摩擦因子,可以设置为0-1之间任意一个数,0表示光滑,1表示强摩擦 139 | 140 | var boxBd = new b2BodyDef(); // 创建刚体定义 141 | boxBd.AddShape(boxSd); // 添加形状 142 | boxBd.position.Set(x || 10, y || 10); // 设置位置 143 | return world.CreateBody(boxBd) // 创建并返回刚体 144 | } 145 | 146 | // 定义step函数,用于游戏的迭代运行 147 | function step() { 148 | // 模拟世界 149 | world.Step(1.0 / 60, 1); 150 | // 清除画布 151 | ctx.clearRect(0, 0, canvasWidth, canvasHeight); 152 | // 重新绘制 153 | drawWorld(world, ctx); 154 | 155 | // 再次刷新 156 | setTimeout(step, 10); 157 | console.log('step...'); 158 | } 159 | 160 | // 从draw_world.js里面引用的绘画功能 161 | function drawShape(shape, context) { 162 | context.strokeStyle = '#003300'; 163 | context.beginPath(); 164 | switch (shape.m_type) { 165 | // 绘制圆 166 | case b2Shape.e_circleShape: 167 | var circle = shape; 168 | var pos = circle.m_position; 169 | var r = circle.m_radius; 170 | var segments = 16.0; 171 | var theta = 0.0; 172 | var dtheta = 2.0 * Math.PI / segments; 173 | // 画圆圈 174 | context.moveTo(pos.x + r, pos.y); 175 | for (var i = 0; i < segments; i++) { 176 | var d = new b2Vec2(r * Math.cos(theta), r * Math.sin(theta)); 177 | var v = b2Math.AddVV(pos, d); 178 | context.lineTo(v.x, v.y); 179 | theta += dtheta; 180 | } 181 | context.lineTo(pos.x + r, pos.y); 182 | 183 | // 画半径 184 | context.moveTo(pos.x, pos.y); 185 | var ax = circle.m_R.col1; 186 | var pos2 = new b2Vec2(pos.x + r * ax.x, pos.y + r * ax.y); 187 | context.lineTo(pos2.x, pos2.y); 188 | break; 189 | // 绘制多边形 190 | case b2Shape.e_polyShape: 191 | var poly = shape; 192 | var tV = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[0])); 193 | context.moveTo(tV.x, tV.y); 194 | for (var i = 0; i < poly.m_vertexCount; i++) { 195 | var v = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i])); 196 | context.lineTo(v.x, v.y); 197 | } 198 | context.lineTo(tV.x, tV.y); 199 | break; 200 | } 201 | context.stroke(); 202 | } 203 | 204 | window.onload = function() { 205 | canvas = document.getElementById('canvas'); 206 | ctx = canvas.getContext('2d'); 207 | canvasWidth = parseInt(canvas.width); 208 | canvasHeight = parseInt(canvas.height); 209 | // 启动游戏 210 | world = createWorld(); 211 | var ball1 = createBall(world, 100, 20, 20); 212 | var ball2 = createBall(world, 300, 60, 10); 213 | var box1 = createBox(world, 100, 200, 25, 30, 'fixed'); 214 | var box2 = createBox(world, 200, 50, 20, 20); 215 | var box3 = createBox(world, 400, 80, 20, 20, document.getElementById('box')); 216 | var wallLeft = createBox(world, 0, 0, 10, 600, 'fixed'); 217 | var wallRight = createBox(world, 1290, 0, 10, 400, 'fixed'); 218 | var ground = createBox(world, 30, 595, 1200, 5, 'fixed'); 219 | step(); 220 | }; 221 | ``` 222 | 223 | [此处查看项目代码(仅包含src部分)](https://github.com/godbasin/box2djs-tutorial/tree/master/6-practice/6-3-add-bound/6-3-add-bound) 224 | [此处查看页面效果](http://old7pzwup.bkt.clouddn.com/6-3-add-bound/index.html) 225 | 226 | 227 | ### [返回总目录](https://github.com/godbasin/box2djs-tutorial) -------------------------------------------------------------------------------- /6-practice/6-3-add-bound/6-3-add-bound/box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godbasin/box2djs-tutorial/99e9520dcbac39626193d80f505ff6f882f1483b/6-practice/6-3-add-bound/6-3-add-bound/box.png -------------------------------------------------------------------------------- /6-practice/6-3-add-bound/6-3-add-bound/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | box2d-test 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /6-practice/6-3-add-bound/6-3-add-bound/js/test.js: -------------------------------------------------------------------------------- 1 | var canvas; 2 | var ctx; 3 | var canvasWidth; 4 | var canvasHeight; 5 | 6 | var world; 7 | 8 | // 我们将创建世界封装至createWorld函数内 9 | function createWorld() { 10 | // 世界的大小 11 | var worldAABB = new b2AABB(); 12 | worldAABB.minVertex.Set(-4000, -4000); 13 | worldAABB.maxVertex.Set(4000, 4000); 14 | 15 | //定义重力 16 | var gravity = new b2Vec2(0, 300); 17 | 18 | // 是否休眠 19 | var doSleep = false; 20 | 21 | // 最终创建世界 22 | var world = new b2World(worldAABB, gravity, doSleep); 23 | 24 | return world; 25 | } 26 | 27 | //绘画功能 28 | function drawWorld(world, context) { 29 | for (var j = world.m_jointList; j; j = j.m_next) { 30 | // 绘制关节 31 | // drawJoint(j, context); 32 | } 33 | for (var b = world.m_bodyList; b != null; b = b.m_next) { 34 | for (var s = b.GetShapeList(); s != null; s = s.GetNext()) { 35 | if (s.GetUserData() != undefined) { 36 | // 使用数据包括图片 37 | var img = s.GetUserData(); 38 | 39 | // 图片的长和宽 40 | var x = s.GetPosition().x; 41 | var y = s.GetPosition().y; 42 | var topleftX = -img.clientWidth / 2; 43 | var topleftY = -img.clientHeight / 2; 44 | 45 | context.save(); 46 | context.translate(x, y); 47 | context.rotate(s.GetBody().GetRotation()); 48 | context.drawImage(img, topleftX, topleftY); 49 | context.restore(); 50 | } 51 | drawShape(s, context); 52 | } 53 | } 54 | } 55 | 56 | // 创建圆形刚体 57 | function createBall(world, x, y, r, custom) { 58 | // 创建圆形定义 59 | var ballSd = new b2CircleDef(); 60 | ballSd.density = 1.0; // 设置密度 61 | if (custom === 'fixed') ballSd.density = 0.0; // 若传入'fixed',则需固定,此时设置密度为0 62 | else ballSd.userData = custom; // 若传入其他,则视为图片数据 63 | ballSd.radius = 20; // 设置半径 64 | ballSd.restitution = 1.0; // 设置弹性 65 | ballSd.friction = 0; // 设置摩擦因子 66 | var ballBd = new b2BodyDef(); // 创建刚体定义 67 | ballBd.AddShape(ballSd); // 添加形状 68 | ballBd.position.Set(x || 0, y || 0); // 设置位置 69 | return world.CreateBody(ballBd); // 创建并返回刚体 70 | } 71 | 72 | // 创建矩形刚体 73 | function createBox(world, x, y, width, height, custom) { 74 | var boxSd = new b2BoxDef(); // 创建一个形状Shape,然后设置有关Shape的属性 75 | boxSd.extents.Set(width || 1200, height || 5); // 设置矩形高、宽 76 | boxSd.density = 1.0; // 设置矩形的密度 77 | if (custom === 'fixed') boxSd.density = 0.0; // 若传入'fixed',则需固定,此时设置密度为0 78 | else boxSd.userData = custom; // 若传入其他,则视为图片数据 79 | boxSd.restitution = .3; // 设置矩形的弹性 80 | boxSd.friction = 1; // 设置矩形的摩擦因子,可以设置为0-1之间任意一个数,0表示光滑,1表示强摩擦 81 | 82 | var boxBd = new b2BodyDef(); // 创建刚体定义 83 | boxBd.AddShape(boxSd); // 添加形状 84 | boxBd.position.Set(x || 10, y || 10); // 设置位置 85 | return world.CreateBody(boxBd) // 创建并返回刚体 86 | } 87 | 88 | // 定义step函数,用于游戏的迭代运行 89 | function step() { 90 | // 模拟世界 91 | world.Step(1.0 / 60, 1); 92 | // 清除画布 93 | ctx.clearRect(0, 0, canvasWidth, canvasHeight); 94 | // 重新绘制 95 | drawWorld(world, ctx); 96 | 97 | // 再次刷新 98 | setTimeout(step, 10); 99 | console.log('step...'); 100 | } 101 | 102 | // 从draw_world.js里面引用的绘画功能 103 | function drawShape(shape, context) { 104 | context.strokeStyle = '#003300'; 105 | context.beginPath(); 106 | switch (shape.m_type) { 107 | // 绘制圆 108 | case b2Shape.e_circleShape: 109 | var circle = shape; 110 | var pos = circle.m_position; 111 | var r = circle.m_radius; 112 | var segments = 16.0; 113 | var theta = 0.0; 114 | var dtheta = 2.0 * Math.PI / segments; 115 | // 画圆圈 116 | context.moveTo(pos.x + r, pos.y); 117 | for (var i = 0; i < segments; i++) { 118 | var d = new b2Vec2(r * Math.cos(theta), r * Math.sin(theta)); 119 | var v = b2Math.AddVV(pos, d); 120 | context.lineTo(v.x, v.y); 121 | theta += dtheta; 122 | } 123 | context.lineTo(pos.x + r, pos.y); 124 | 125 | // 画半径 126 | context.moveTo(pos.x, pos.y); 127 | var ax = circle.m_R.col1; 128 | var pos2 = new b2Vec2(pos.x + r * ax.x, pos.y + r * ax.y); 129 | context.lineTo(pos2.x, pos2.y); 130 | break; 131 | // 绘制多边形 132 | case b2Shape.e_polyShape: 133 | var poly = shape; 134 | var tV = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[0])); 135 | context.moveTo(tV.x, tV.y); 136 | for (var i = 0; i < poly.m_vertexCount; i++) { 137 | var v = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i])); 138 | context.lineTo(v.x, v.y); 139 | } 140 | context.lineTo(tV.x, tV.y); 141 | break; 142 | } 143 | context.stroke(); 144 | } 145 | 146 | window.onload = function() { 147 | canvas = document.getElementById('canvas'); 148 | ctx = canvas.getContext('2d'); 149 | canvasWidth = parseInt(canvas.width); 150 | canvasHeight = parseInt(canvas.height); 151 | // 启动游戏 152 | world = createWorld(); 153 | var ball1 = createBall(world, 100, 20, 20); 154 | var ball2 = createBall(world, 300, 60, 10); 155 | var box1 = createBox(world, 100, 200, 25, 30, 'fixed'); 156 | var box2 = createBox(world, 200, 50, 20, 20); 157 | var box3 = createBox(world, 400, 80, 20, 20, document.getElementById('box')); 158 | var wallLeft = createBox(world, 0, 0, 10, 600, 'fixed'); 159 | var wallRight = createBox(world, 1290, 0, 10, 400, 'fixed'); 160 | var ground = createBox(world, 30, 595, 1200, 5, 'fixed'); 161 | step(); 162 | }; -------------------------------------------------------------------------------- /6-practice/6-4-mouse-operate-body/6-4-mouse-operate-body.md: -------------------------------------------------------------------------------- 1 | ## 鼠标操作刚体 2 | ### 获取所在位置刚体 3 | 首先,我们需要通过一个位置坐标(x, y)来获取该坐标下的刚体,若无则返回null。 4 | 5 | 前面我们也提到过,我们可以通过画一个较小的矩形,然后判断是否有相交来获取刚体: 6 | 7 | ``` javascript 8 | function GetBodyAtPosition(x, y) { 9 | // 首先创建一个近似于点的小区域 10 | var mousePVec = new b2Vec2(x, y); 11 | // 利用b2Vec2定义一个矢量,用来保存鼠标点击的点 12 | var aabb = new b2AABB(); 13 | // 利用b2AABB创建一个环境 14 | aabb.minVertex.Set(mousePVec.x - 0.001, mousePVec.y - 0.001); 15 | aabb.maxVertex.Set(mousePVec.x + 0.001, mousePVec.y + 0.001); 16 | // 设置aabb的左上角及右下角坐标,这里是以鼠标点击位置为中心创建了一个长、宽均为0.002的矩形区域 17 | 18 | // 然后查询与指定区域有重叠的刚体 19 | var k_maxCount = 10; // 设定所要查找形状的数量,注意合理设置其大小,过大会影响运行速度 20 | var shapes = new Array(); // 保存查找到的与已知边界盒相交的形状 21 | var count = world.Query(aabb, shapes, k_maxCount); // 在世界中查找与边界盒相交的maxCount个形状,并返回边界盒区域内实际包含的形状的个数 22 | 23 | var findBody = null; // 首先设定没有找到物体 24 | for (var i = 0; i < count; ++i) { 25 | if (shapes[i].GetBody().IsStatic() == false) 26 | // 条件假定查找到的形状不是静态刚体,比如墙 27 | { 28 | var tShape = shapes[i]; // 将查找到的形状赋给tShape变量 29 | var inside = tShape.GetBody(); // 将tShape对应的刚体赋给inside 30 | if (inside) // 如果inside这个刚体存在 31 | { 32 | // 那么返回这个刚体,并跳出遍历 33 | findBody = tShape.GetBody(); 34 | break; 35 | } 36 | } 37 | } 38 | return findBody; 39 | } 40 | ``` 41 | 42 | ### 添加鼠标点击事件 43 | 这里我们使用鼠标点击(mousedown)事件,去触发刚体的状态改变。 44 | 45 | - 添加事件监听 46 | 47 | ``` javascript 48 | document.addEventListener('mousedown', handleMousedown, false); 49 | ``` 50 | 51 | - 获取鼠标位置 52 | 53 | ``` javascript 54 | // 获取鼠标坐标 55 | function getMousePos(event) { 56 | var e = event || window.event; 57 | var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; 58 | var scrollY = document.documentElement.scrollTop || document.body.scrollTop; 59 | var x = e.pageX || e.clientX + scrollX; 60 | var y = e.pageY || e.clientY + scrollY; 61 | return { 'x': x, 'y': y }; 62 | } 63 | ``` 64 | 65 | - 处理鼠标移动 66 | 67 | ``` javascript 68 | // 处理鼠标移动 69 | function handleMousedown(e) { 70 | var e = e || window.event; 71 | // 获取鼠标x坐标 72 | var newMouse = getMousePos(e); 73 | var selectBody = GetBodyAtPosition(newMouse.x - canvas.offsetLeft, newMouse.y - canvas.offsetTop); // 选择刚体 74 | if (selectBody) { 75 | // 处理选中的刚体 76 | } else { 77 | // 未选中时处理 78 | } 79 | } 80 | ``` 81 | 82 | ### 操作刚体 83 | - 给选中刚体添加速度向量 84 | 85 | ``` javascript 86 | // 若有选中刚体,则处理 87 | var LinearVelocity = new b2Vec2(500, -200); // 定义一个向量 88 | selectBody.WakeUp(); // 激活休眠状态 89 | selectBody.SetLinearVelocity(LinearVelocity); //给定一个速度向量 90 | ``` 91 | 92 | - 未选中刚体,则新建添加一个矩形刚体 93 | 94 | ``` javascript 95 | // 若无,则随机生成一个矩形添加 96 | var width = parseInt(Math.random() * 50); 97 | var height = parseInt(Math.random() * 50); 98 | createBox(world, newMouse.x, newMouse.y, width, height) 99 | ``` 100 | 101 | ### 完整代码 102 | ``` javascript 103 | var canvas; 104 | var ctx; 105 | var canvasWidth; 106 | var canvasHeight; 107 | 108 | var world; 109 | 110 | // 我们将创建世界封装至createWorld函数内 111 | function createWorld() { 112 | // 世界的大小 113 | var worldAABB = new b2AABB(); 114 | worldAABB.minVertex.Set(-4000, -4000); 115 | worldAABB.maxVertex.Set(4000, 4000); 116 | 117 | //定义重力 118 | var gravity = new b2Vec2(0, 300); 119 | 120 | // 是否休眠 121 | var doSleep = false; 122 | 123 | // 最终创建世界 124 | var world = new b2World(worldAABB, gravity, doSleep); 125 | 126 | return world; 127 | } 128 | 129 | //绘画功能 130 | function drawWorld(world, context) { 131 | for (var j = world.m_jointList; j; j = j.m_next) { 132 | // 绘制关节 133 | // drawJoint(j, context); 134 | } 135 | for (var b = world.m_bodyList; b != null; b = b.m_next) { 136 | for (var s = b.GetShapeList(); s != null; s = s.GetNext()) { 137 | if (s.GetUserData() != undefined) { 138 | // 使用数据包括图片 139 | var img = s.GetUserData(); 140 | 141 | // 图片的长和宽 142 | var x = s.GetPosition().x; 143 | var y = s.GetPosition().y; 144 | var topleftX = -img.clientWidth / 2; 145 | var topleftY = -img.clientHeight / 2; 146 | 147 | context.save(); 148 | context.translate(x, y); 149 | context.rotate(s.GetBody().GetRotation()); 150 | context.drawImage(img, topleftX, topleftY); 151 | context.restore(); 152 | } 153 | drawShape(s, context); 154 | } 155 | } 156 | } 157 | 158 | // 创建圆形刚体 159 | function createBall(world, x, y, r, custom) { 160 | // 创建圆形定义 161 | var ballSd = new b2CircleDef(); 162 | ballSd.density = 1.0; // 设置密度 163 | if (custom === 'fixed') ballSd.density = 0.0; // 若传入'fixed',则需固定,此时设置密度为0 164 | else ballSd.userData = custom; // 若传入其他,则视为图片数据 165 | ballSd.radius = 20; // 设置半径 166 | ballSd.restitution = 1.0; // 设置弹性 167 | ballSd.friction = 0; // 设置摩擦因子 168 | var ballBd = new b2BodyDef(); // 创建刚体定义 169 | ballBd.AddShape(ballSd); // 添加形状 170 | ballBd.position.Set(x || 0, y || 0); // 设置位置 171 | return world.CreateBody(ballBd); // 创建并返回刚体 172 | } 173 | 174 | // 创建矩形刚体 175 | function createBox(world, x, y, width, height, custom) { 176 | var boxSd = new b2BoxDef(); // 创建一个形状Shape,然后设置有关Shape的属性 177 | boxSd.extents.Set(width || 1200, height || 5); // 设置矩形高、宽 178 | boxSd.density = 1.0; // 设置矩形的密度 179 | if (custom === 'fixed') boxSd.density = 0.0; // 若传入'fixed',则需固定,此时设置密度为0 180 | else boxSd.userData = custom; // 若传入其他,则视为图片数据 181 | boxSd.restitution = .3; // 设置矩形的弹性 182 | boxSd.friction = 1; // 设置矩形的摩擦因子,可以设置为0-1之间任意一个数,0表示光滑,1表示强摩擦 183 | 184 | var boxBd = new b2BodyDef(); // 创建刚体定义 185 | boxBd.AddShape(boxSd); // 添加形状 186 | boxBd.position.Set(x || 10, y || 10); // 设置位置 187 | return world.CreateBody(boxBd) // 创建并返回刚体 188 | } 189 | 190 | // 定义step函数,用于游戏的迭代运行 191 | function step() { 192 | // 模拟世界 193 | world.Step(1.0 / 60, 1); 194 | // 清除画布 195 | ctx.clearRect(0, 0, canvasWidth, canvasHeight); 196 | // 重新绘制 197 | drawWorld(world, ctx); 198 | 199 | // 再次刷新 200 | setTimeout(step, 10); 201 | } 202 | 203 | // 从draw_world.js里面引用的绘画功能 204 | function drawShape(shape, context) { 205 | context.strokeStyle = '#003300'; 206 | context.beginPath(); 207 | switch (shape.m_type) { 208 | // 绘制圆 209 | case b2Shape.e_circleShape: 210 | var circle = shape; 211 | var pos = circle.m_position; 212 | var r = circle.m_radius; 213 | var segments = 16.0; 214 | var theta = 0.0; 215 | var dtheta = 2.0 * Math.PI / segments; 216 | // 画圆圈 217 | context.moveTo(pos.x + r, pos.y); 218 | for (var i = 0; i < segments; i++) { 219 | var d = new b2Vec2(r * Math.cos(theta), r * Math.sin(theta)); 220 | var v = b2Math.AddVV(pos, d); 221 | context.lineTo(v.x, v.y); 222 | theta += dtheta; 223 | } 224 | context.lineTo(pos.x + r, pos.y); 225 | 226 | // 画半径 227 | context.moveTo(pos.x, pos.y); 228 | var ax = circle.m_R.col1; 229 | var pos2 = new b2Vec2(pos.x + r * ax.x, pos.y + r * ax.y); 230 | context.lineTo(pos2.x, pos2.y); 231 | break; 232 | // 绘制多边形 233 | case b2Shape.e_polyShape: 234 | var poly = shape; 235 | var tV = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[0])); 236 | context.moveTo(tV.x, tV.y); 237 | for (var i = 0; i < poly.m_vertexCount; i++) { 238 | var v = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i])); 239 | context.lineTo(v.x, v.y); 240 | } 241 | context.lineTo(tV.x, tV.y); 242 | break; 243 | } 244 | context.stroke(); 245 | } 246 | 247 | function GetBodyAtPosition(x, y) { 248 | // 首先创建一个近似于点的小区域 249 | var mousePVec = new b2Vec2(x, y); 250 | // 利用b2Vec2定义一个矢量,用来保存鼠标点击的点 251 | var aabb = new b2AABB(); 252 | // 利用b2AABB创建一个环境 253 | aabb.minVertex.Set(mousePVec.x - 0.001, mousePVec.y - 0.001); 254 | aabb.maxVertex.Set(mousePVec.x + 0.001, mousePVec.y + 0.001); 255 | // 设置aabb的左上角及右下角坐标,这里是以鼠标点击位置为中心创建了一个长、宽均为0.002的矩形区域 256 | 257 | // 然后查询与指定区域有重叠的刚体 258 | var k_maxCount = 10; // 设定所要查找形状的数量,注意合理设置其大小,过大会影响运行速度 259 | var shapes = new Array(); // 保存查找到的与已知边界盒相交的形状 260 | var count = world.Query(aabb, shapes, k_maxCount); // 在世界中查找与边界盒相交的maxCount个形状,并返回边界盒区域内实际包含的形状的个数 261 | 262 | var findBody = null; // 首先设定没有找到物体 263 | for (var i = 0; i < count; ++i) { 264 | if (shapes[i].GetBody().IsStatic() == false) 265 | // 条件假定查找到的形状不是静态刚体,比如墙 266 | { 267 | var tShape = shapes[i]; // 将查找到的形状赋给tShape变量 268 | var inside = tShape.GetBody(); // 将tShape对应的刚体赋给inside 269 | if (inside) // 如果inside这个刚体存在 270 | { 271 | // 那么返回这个刚体,并跳出遍历 272 | findBody = tShape.GetBody(); 273 | break; 274 | } 275 | } 276 | } 277 | return findBody; 278 | } 279 | 280 | // 获取鼠标坐标 281 | function getMousePos(event) { 282 | var e = event || window.event; 283 | var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; 284 | var scrollY = document.documentElement.scrollTop || document.body.scrollTop; 285 | var x = e.pageX || e.clientX + scrollX; 286 | var y = e.pageY || e.clientY + scrollY; 287 | return { 'x': x, 'y': y }; 288 | } 289 | 290 | // 处理鼠标移动 291 | function handleMousedown(e) { 292 | var e = e || window.event; 293 | // 获取鼠标x坐标 294 | var newMouse = getMousePos(e); 295 | var selectBody = GetBodyAtPosition(newMouse.x - canvas.offsetLeft, newMouse.y - canvas.offsetTop); // 选择刚体 296 | if (selectBody) { 297 | // 若有选中刚体,则处理 298 | var LinearVelocity = new b2Vec2(500, -200); // 定义一个向量 299 | selectBody.WakeUp(); // 激活休眠状态 300 | selectBody.SetLinearVelocity(LinearVelocity); //给定一个速度向量 301 | } else { 302 | // 若无,则随机生成一个矩形添加 303 | var width = parseInt(Math.random() * 50); 304 | var height = parseInt(Math.random() * 50); 305 | createBox(world, newMouse.x, newMouse.y, width, height); 306 | } 307 | } 308 | 309 | document.addEventListener('mousedown', handleMousedown, false); 310 | 311 | window.onload = function () { 312 | canvas = document.getElementById('canvas'); 313 | ctx = canvas.getContext('2d'); 314 | canvasWidth = parseInt(canvas.width); 315 | canvasHeight = parseInt(canvas.height); 316 | // 启动游戏 317 | world = createWorld(); 318 | var ball1 = createBall(world, 100, 20, 20); 319 | var ball2 = createBall(world, 300, 60, 10); 320 | var box1 = createBox(world, 100, 200, 25, 30, 'fixed'); 321 | var box2 = createBox(world, 200, 50, 20, 20); 322 | var box3 = createBox(world, 400, 80, 20, 20, document.getElementById('box')); 323 | var wallLeft = createBox(world, 0, 0, 10, 600, 'fixed'); 324 | var wallRight = createBox(world, 1290, 0, 10, 400, 'fixed'); 325 | var ground = createBox(world, 30, 595, 1200, 5, 'fixed'); 326 | step(); 327 | }; 328 | ``` 329 | 330 | [此处查看项目代码(仅包含src部分)](https://github.com/godbasin/box2djs-tutorial/tree/master/6-practice/6-4-mouse-operate-body/6-4-mouse-operate-body) 331 | [此处查看页面效果](http://old7pzwup.bkt.clouddn.com/6-4-mouse-operate-body/index.html) 332 | 333 | 334 | ### [返回总目录](https://github.com/godbasin/box2djs-tutorial) -------------------------------------------------------------------------------- /6-practice/6-4-mouse-operate-body/6-4-mouse-operate-body/box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godbasin/box2djs-tutorial/99e9520dcbac39626193d80f505ff6f882f1483b/6-practice/6-4-mouse-operate-body/6-4-mouse-operate-body/box.png -------------------------------------------------------------------------------- /6-practice/6-4-mouse-operate-body/6-4-mouse-operate-body/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | box2d-test 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /6-practice/6-4-mouse-operate-body/6-4-mouse-operate-body/js/test.js: -------------------------------------------------------------------------------- 1 | var canvas; 2 | var ctx; 3 | var canvasWidth; 4 | var canvasHeight; 5 | 6 | var world; 7 | 8 | // 我们将创建世界封装至createWorld函数内 9 | function createWorld() { 10 | // 世界的大小 11 | var worldAABB = new b2AABB(); 12 | worldAABB.minVertex.Set(-4000, -4000); 13 | worldAABB.maxVertex.Set(4000, 4000); 14 | 15 | //定义重力 16 | var gravity = new b2Vec2(0, 300); 17 | 18 | // 是否休眠 19 | var doSleep = false; 20 | 21 | // 最终创建世界 22 | var world = new b2World(worldAABB, gravity, doSleep); 23 | 24 | return world; 25 | } 26 | 27 | //绘画功能 28 | function drawWorld(world, context) { 29 | for (var j = world.m_jointList; j; j = j.m_next) { 30 | // 绘制关节 31 | // drawJoint(j, context); 32 | } 33 | for (var b = world.m_bodyList; b != null; b = b.m_next) { 34 | for (var s = b.GetShapeList(); s != null; s = s.GetNext()) { 35 | if (s.GetUserData() != undefined) { 36 | // 使用数据包括图片 37 | var img = s.GetUserData(); 38 | 39 | // 图片的长和宽 40 | var x = s.GetPosition().x; 41 | var y = s.GetPosition().y; 42 | var topleftX = -img.clientWidth / 2; 43 | var topleftY = -img.clientHeight / 2; 44 | 45 | context.save(); 46 | context.translate(x, y); 47 | context.rotate(s.GetBody().GetRotation()); 48 | context.drawImage(img, topleftX, topleftY); 49 | context.restore(); 50 | } 51 | drawShape(s, context); 52 | } 53 | } 54 | } 55 | 56 | // 创建圆形刚体 57 | function createBall(world, x, y, r, custom) { 58 | // 创建圆形定义 59 | var ballSd = new b2CircleDef(); 60 | ballSd.density = 1.0; // 设置密度 61 | if (custom === 'fixed') ballSd.density = 0.0; // 若传入'fixed',则需固定,此时设置密度为0 62 | else ballSd.userData = custom; // 若传入其他,则视为图片数据 63 | ballSd.radius = 20; // 设置半径 64 | ballSd.restitution = 1.0; // 设置弹性 65 | ballSd.friction = 0; // 设置摩擦因子 66 | var ballBd = new b2BodyDef(); // 创建刚体定义 67 | ballBd.AddShape(ballSd); // 添加形状 68 | ballBd.position.Set(x || 0, y || 0); // 设置位置 69 | return world.CreateBody(ballBd); // 创建并返回刚体 70 | } 71 | 72 | // 创建矩形刚体 73 | function createBox(world, x, y, width, height, custom) { 74 | var boxSd = new b2BoxDef(); // 创建一个形状Shape,然后设置有关Shape的属性 75 | boxSd.extents.Set(width || 1200, height || 5); // 设置矩形高、宽 76 | boxSd.density = 1.0; // 设置矩形的密度 77 | if (custom === 'fixed') boxSd.density = 0.0; // 若传入'fixed',则需固定,此时设置密度为0 78 | else boxSd.userData = custom; // 若传入其他,则视为图片数据 79 | boxSd.restitution = .3; // 设置矩形的弹性 80 | boxSd.friction = 1; // 设置矩形的摩擦因子,可以设置为0-1之间任意一个数,0表示光滑,1表示强摩擦 81 | 82 | var boxBd = new b2BodyDef(); // 创建刚体定义 83 | boxBd.AddShape(boxSd); // 添加形状 84 | boxBd.position.Set(x || 10, y || 10); // 设置位置 85 | return world.CreateBody(boxBd) // 创建并返回刚体 86 | } 87 | 88 | // 定义step函数,用于游戏的迭代运行 89 | function step() { 90 | // 模拟世界 91 | world.Step(1.0 / 60, 1); 92 | // 清除画布 93 | ctx.clearRect(0, 0, canvasWidth, canvasHeight); 94 | // 重新绘制 95 | drawWorld(world, ctx); 96 | 97 | // 再次刷新 98 | setTimeout(step, 10); 99 | } 100 | 101 | // 从draw_world.js里面引用的绘画功能 102 | function drawShape(shape, context) { 103 | context.strokeStyle = '#003300'; 104 | context.beginPath(); 105 | switch (shape.m_type) { 106 | // 绘制圆 107 | case b2Shape.e_circleShape: 108 | var circle = shape; 109 | var pos = circle.m_position; 110 | var r = circle.m_radius; 111 | var segments = 16.0; 112 | var theta = 0.0; 113 | var dtheta = 2.0 * Math.PI / segments; 114 | // 画圆圈 115 | context.moveTo(pos.x + r, pos.y); 116 | for (var i = 0; i < segments; i++) { 117 | var d = new b2Vec2(r * Math.cos(theta), r * Math.sin(theta)); 118 | var v = b2Math.AddVV(pos, d); 119 | context.lineTo(v.x, v.y); 120 | theta += dtheta; 121 | } 122 | context.lineTo(pos.x + r, pos.y); 123 | 124 | // 画半径 125 | context.moveTo(pos.x, pos.y); 126 | var ax = circle.m_R.col1; 127 | var pos2 = new b2Vec2(pos.x + r * ax.x, pos.y + r * ax.y); 128 | context.lineTo(pos2.x, pos2.y); 129 | break; 130 | // 绘制多边形 131 | case b2Shape.e_polyShape: 132 | var poly = shape; 133 | var tV = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[0])); 134 | context.moveTo(tV.x, tV.y); 135 | for (var i = 0; i < poly.m_vertexCount; i++) { 136 | var v = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i])); 137 | context.lineTo(v.x, v.y); 138 | } 139 | context.lineTo(tV.x, tV.y); 140 | break; 141 | } 142 | context.stroke(); 143 | } 144 | 145 | function GetBodyAtPosition(x, y) { 146 | // 首先创建一个近似于点的小区域 147 | var mousePVec = new b2Vec2(x, y); 148 | // 利用b2Vec2定义一个矢量,用来保存鼠标点击的点 149 | var aabb = new b2AABB(); 150 | // 利用b2AABB创建一个环境 151 | aabb.minVertex.Set(mousePVec.x - 0.001, mousePVec.y - 0.001); 152 | aabb.maxVertex.Set(mousePVec.x + 0.001, mousePVec.y + 0.001); 153 | // 设置aabb的左上角及右下角坐标,这里是以鼠标点击位置为中心创建了一个长、宽均为0.002的矩形区域 154 | 155 | // 然后查询与指定区域有重叠的刚体 156 | var k_maxCount = 10; // 设定所要查找形状的数量,注意合理设置其大小,过大会影响运行速度 157 | var shapes = new Array(); // 保存查找到的与已知边界盒相交的形状 158 | var count = world.Query(aabb, shapes, k_maxCount); // 在世界中查找与边界盒相交的maxCount个形状,并返回边界盒区域内实际包含的形状的个数 159 | 160 | var findBody = null; // 首先设定没有找到物体 161 | for (var i = 0; i < count; ++i) { 162 | if (shapes[i].GetBody().IsStatic() == false) 163 | // 条件假定查找到的形状不是静态刚体,比如墙 164 | { 165 | var tShape = shapes[i]; // 将查找到的形状赋给tShape变量 166 | var inside = tShape.GetBody(); // 将tShape对应的刚体赋给inside 167 | if (inside) // 如果inside这个刚体存在 168 | { 169 | // 那么返回这个刚体,并跳出遍历 170 | findBody = tShape.GetBody(); 171 | break; 172 | } 173 | } 174 | } 175 | return findBody; 176 | } 177 | 178 | // 获取鼠标坐标 179 | function getMousePos(event) { 180 | var e = event || window.event; 181 | var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; 182 | var scrollY = document.documentElement.scrollTop || document.body.scrollTop; 183 | var x = e.pageX || e.clientX + scrollX; 184 | var y = e.pageY || e.clientY + scrollY; 185 | return { 'x': x, 'y': y }; 186 | } 187 | 188 | // 处理鼠标移动 189 | function handleMousedown(e) { 190 | var e = e || window.event; 191 | // 获取鼠标x坐标 192 | var newMouse = getMousePos(e); 193 | var selectBody = GetBodyAtPosition(newMouse.x - canvas.offsetLeft, newMouse.y - canvas.offsetTop); // 选择刚体 194 | if (selectBody) { 195 | // 若有选中刚体,则处理 196 | var LinearVelocity = new b2Vec2(500, -200); // 定义一个向量 197 | selectBody.WakeUp(); // 激活休眠状态 198 | selectBody.SetLinearVelocity(LinearVelocity); //给定一个速度向量 199 | } else { 200 | // 若无,则随机生成一个矩形添加 201 | var width = parseInt(Math.random() * 50); 202 | var height = parseInt(Math.random() * 50); 203 | createBox(world, newMouse.x, newMouse.y, width, height); 204 | } 205 | } 206 | 207 | document.addEventListener('mousedown', handleMousedown, false); 208 | 209 | window.onload = function () { 210 | canvas = document.getElementById('canvas'); 211 | ctx = canvas.getContext('2d'); 212 | canvasWidth = parseInt(canvas.width); 213 | canvasHeight = parseInt(canvas.height); 214 | // 启动游戏 215 | world = createWorld(); 216 | var ball1 = createBall(world, 100, 20, 20); 217 | var ball2 = createBall(world, 300, 60, 10); 218 | var box1 = createBox(world, 100, 200, 25, 30, 'fixed'); 219 | var box2 = createBox(world, 200, 50, 20, 20); 220 | var box3 = createBox(world, 400, 80, 20, 20, document.getElementById('box')); 221 | var wallLeft = createBox(world, 0, 0, 10, 600, 'fixed'); 222 | var wallRight = createBox(world, 1290, 0, 10, 400, 'fixed'); 223 | var ground = createBox(world, 30, 595, 1200, 5, 'fixed'); 224 | step(); 225 | }; -------------------------------------------------------------------------------- /6-practice/6-5-handle-contact/6-5-handle-contact.md: -------------------------------------------------------------------------------- 1 | ## 处理碰撞刚体 2 | ### 检查刚体的碰撞 3 | 我们可以通过`world.GetContactList()`获取碰撞刚体列表: 4 | 5 | ``` javascript 6 | // 检查是否有碰撞 7 | function checkContact(){ 8 | for (var cn = world.GetContactList(); cn != null; cn = cn.GetNext()) { 9 | var body1 = cn.GetShape1().GetBody(); 10 | var body2 = cn.GetShape2().GetBody(); 11 | 12 | // 处理碰撞刚体 13 | } 14 | } 15 | ``` 16 | 17 | ### 处理碰撞刚体 18 | 这里我们设定与箱子(带图片的刚体)发生碰撞则销毁刚体。 19 | 20 | ``` javascript 21 | // 若与箱子碰撞,切不是静态刚体,则销毁该刚体 22 | if(body1 === box && body2.IsStatic() == false ){ 23 | world.DestroyBody(body2); 24 | } 25 | if(body2 === box && body1.IsStatic() == false ){ 26 | world.DestroyBody(body1); 27 | } 28 | ``` 29 | 30 | ### 在step中添加检测 31 | 我们需要在每次模拟世界中进行检测: 32 | 33 | ``` javascript 34 | // 定义step函数,用于游戏的迭代运行 35 | function step() { 36 | ... 37 | // 添加检测 38 | checkContact(); 39 | ... 40 | } 41 | ``` 42 | 43 | ### 完整代码 44 | ``` javascript 45 | var canvas; 46 | var ctx; 47 | var canvasWidth; 48 | var canvasHeight; 49 | 50 | var world; 51 | var box, wallLeft, wallRight, ground; 52 | 53 | // 我们将创建世界封装至createWorld函数内 54 | function createWorld() { 55 | // 世界的大小 56 | var worldAABB = new b2AABB(); 57 | worldAABB.minVertex.Set(-4000, -4000); 58 | worldAABB.maxVertex.Set(4000, 4000); 59 | 60 | //定义重力 61 | var gravity = new b2Vec2(0, 300); 62 | 63 | // 是否休眠 64 | var doSleep = false; 65 | 66 | // 最终创建世界 67 | var world = new b2World(worldAABB, gravity, doSleep); 68 | 69 | return world; 70 | } 71 | 72 | //绘画功能 73 | function drawWorld(world, context) { 74 | for (var j = world.m_jointList; j; j = j.m_next) { 75 | // 绘制关节 76 | // drawJoint(j, context); 77 | } 78 | for (var b = world.m_bodyList; b != null; b = b.m_next) { 79 | for (var s = b.GetShapeList(); s != null; s = s.GetNext()) { 80 | if (s.GetUserData() != undefined) { 81 | // 使用数据包括图片 82 | var img = s.GetUserData(); 83 | 84 | // 图片的长和宽 85 | var x = s.GetPosition().x; 86 | var y = s.GetPosition().y; 87 | var topleftX = -img.clientWidth / 2; 88 | var topleftY = -img.clientHeight / 2; 89 | 90 | context.save(); 91 | context.translate(x, y); 92 | context.rotate(s.GetBody().GetRotation()); 93 | context.drawImage(img, topleftX, topleftY); 94 | context.restore(); 95 | } 96 | drawShape(s, context); 97 | } 98 | } 99 | } 100 | 101 | // 创建圆形刚体 102 | function createBall(world, x, y, r, custom) { 103 | // 创建圆形定义 104 | var ballSd = new b2CircleDef(); 105 | ballSd.density = 1.0; // 设置密度 106 | if (custom === 'fixed') ballSd.density = 0.0; // 若传入'fixed',则需固定,此时设置密度为0 107 | else ballSd.userData = custom; // 若传入其他,则视为图片数据 108 | ballSd.radius = 20; // 设置半径 109 | ballSd.restitution = 1.0; // 设置弹性 110 | ballSd.friction = 0; // 设置摩擦因子 111 | var ballBd = new b2BodyDef(); // 创建刚体定义 112 | ballBd.AddShape(ballSd); // 添加形状 113 | ballBd.position.Set(x || 0, y || 0); // 设置位置 114 | return world.CreateBody(ballBd); // 创建并返回刚体 115 | } 116 | 117 | // 创建矩形刚体 118 | function createBox(world, x, y, width, height, custom) { 119 | var boxSd = new b2BoxDef(); // 创建一个形状Shape,然后设置有关Shape的属性 120 | boxSd.extents.Set(width || 1200, height || 5); // 设置矩形高、宽 121 | boxSd.density = 1.0; // 设置矩形的密度 122 | if (custom === 'fixed') boxSd.density = 0.0; // 若传入'fixed',则需固定,此时设置密度为0 123 | else boxSd.userData = custom; // 若传入其他,则视为图片数据 124 | boxSd.restitution = .3; // 设置矩形的弹性 125 | boxSd.friction = 1; // 设置矩形的摩擦因子,可以设置为0-1之间任意一个数,0表示光滑,1表示强摩擦 126 | 127 | var boxBd = new b2BodyDef(); // 创建刚体定义 128 | boxBd.AddShape(boxSd); // 添加形状 129 | boxBd.position.Set(x || 10, y || 10); // 设置位置 130 | return world.CreateBody(boxBd) // 创建并返回刚体 131 | } 132 | 133 | // 定义step函数,用于游戏的迭代运行 134 | function step() { 135 | // 模拟世界 136 | world.Step(1.0 / 60, 1); 137 | checkContact(); 138 | // 清除画布 139 | ctx.clearRect(0, 0, canvasWidth, canvasHeight); 140 | // 重新绘制 141 | drawWorld(world, ctx); 142 | 143 | // 再次刷新 144 | setTimeout(step, 10); 145 | } 146 | 147 | // 从draw_world.js里面引用的绘画功能 148 | function drawShape(shape, context) { 149 | context.strokeStyle = '#003300'; 150 | context.beginPath(); 151 | switch (shape.m_type) { 152 | // 绘制圆 153 | case b2Shape.e_circleShape: 154 | var circle = shape; 155 | var pos = circle.m_position; 156 | var r = circle.m_radius; 157 | var segments = 16.0; 158 | var theta = 0.0; 159 | var dtheta = 2.0 * Math.PI / segments; 160 | // 画圆圈 161 | context.moveTo(pos.x + r, pos.y); 162 | for (var i = 0; i < segments; i++) { 163 | var d = new b2Vec2(r * Math.cos(theta), r * Math.sin(theta)); 164 | var v = b2Math.AddVV(pos, d); 165 | context.lineTo(v.x, v.y); 166 | theta += dtheta; 167 | } 168 | context.lineTo(pos.x + r, pos.y); 169 | 170 | // 画半径 171 | context.moveTo(pos.x, pos.y); 172 | var ax = circle.m_R.col1; 173 | var pos2 = new b2Vec2(pos.x + r * ax.x, pos.y + r * ax.y); 174 | context.lineTo(pos2.x, pos2.y); 175 | break; 176 | // 绘制多边形 177 | case b2Shape.e_polyShape: 178 | var poly = shape; 179 | var tV = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[0])); 180 | context.moveTo(tV.x, tV.y); 181 | for (var i = 0; i < poly.m_vertexCount; i++) { 182 | var v = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i])); 183 | context.lineTo(v.x, v.y); 184 | } 185 | context.lineTo(tV.x, tV.y); 186 | break; 187 | } 188 | context.stroke(); 189 | } 190 | 191 | // 检查是否有碰撞 192 | function checkContact(){ 193 | for (var cn = world.GetContactList(); cn != null; cn = cn.GetNext()) { 194 | var body1 = cn.GetShape1().GetBody(); 195 | var body2 = cn.GetShape2().GetBody(); 196 | 197 | // 若与箱子碰撞,则销毁该刚体 198 | 199 | if(body1 === box && body2.IsStatic() == false ){ 200 | world.DestroyBody(body2); 201 | } 202 | 203 | if(body2 === box && body1.IsStatic() == false ){ 204 | world.DestroyBody(body1); 205 | } 206 | } 207 | } 208 | 209 | function GetBodyAtPosition(x, y) { 210 | // 首先创建一个近似于点的小区域 211 | var mousePVec = new b2Vec2(x, y); 212 | // 利用b2Vec2定义一个矢量,用来保存鼠标点击的点 213 | var aabb = new b2AABB(); 214 | // 利用b2AABB创建一个环境 215 | aabb.minVertex.Set(mousePVec.x - 0.001, mousePVec.y - 0.001); 216 | aabb.maxVertex.Set(mousePVec.x + 0.001, mousePVec.y + 0.001); 217 | // 设置aabb的左上角及右下角坐标,这里是以鼠标点击位置为中心创建了一个长、宽均为0.002的矩形区域 218 | 219 | // 然后查询与指定区域有重叠的刚体 220 | var k_maxCount = 10; // 设定所要查找形状的数量,注意合理设置其大小,过大会影响运行速度 221 | var shapes = new Array(); // 保存查找到的与已知边界盒相交的形状 222 | var count = world.Query(aabb, shapes, k_maxCount); // 在世界中查找与边界盒相交的maxCount个形状,并返回边界盒区域内实际包含的形状的个数 223 | 224 | var findBody = null; // 首先设定没有找到物体 225 | for (var i = 0; i < count; ++i) { 226 | if (shapes[i].GetBody().IsStatic() == false) 227 | // 条件假定查找到的形状不是静态刚体,比如墙 228 | { 229 | var tShape = shapes[i]; // 将查找到的形状赋给tShape变量 230 | var inside = tShape.GetBody(); // 将tShape对应的刚体赋给inside 231 | if (inside) // 如果inside这个刚体存在 232 | { 233 | // 那么返回这个刚体,并跳出遍历 234 | findBody = tShape.GetBody(); 235 | break; 236 | } 237 | } 238 | } 239 | return findBody; 240 | } 241 | 242 | // 获取鼠标坐标 243 | function getMousePos(event) { 244 | var e = event || window.event; 245 | var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; 246 | var scrollY = document.documentElement.scrollTop || document.body.scrollTop; 247 | var x = e.pageX || e.clientX + scrollX; 248 | var y = e.pageY || e.clientY + scrollY; 249 | return { 'x': x, 'y': y }; 250 | } 251 | 252 | // 处理鼠标移动 253 | function handleMousedown(e) { 254 | var e = e || window.event; 255 | // 获取鼠标x坐标 256 | var newMouse = getMousePos(e); 257 | var selectBody = GetBodyAtPosition(newMouse.x - canvas.offsetLeft, newMouse.y - canvas.offsetTop); // 选择刚体 258 | if (selectBody) { 259 | // 若有选中刚体,则处理 260 | var LinearVelocity = new b2Vec2(500, -200); // 定义一个向量 261 | selectBody.WakeUp(); // 激活休眠状态 262 | selectBody.SetLinearVelocity(LinearVelocity); //给定一个速度向量 263 | } else { 264 | // 若无,则随机生成一个矩形添加 265 | var width = parseInt(Math.random() * 50); 266 | var height = parseInt(Math.random() * 50); 267 | createBox(world, newMouse.x, newMouse.y, width, height); 268 | } 269 | } 270 | 271 | document.addEventListener('mousedown', handleMousedown, false); 272 | 273 | window.onload = function () { 274 | canvas = document.getElementById('canvas'); 275 | ctx = canvas.getContext('2d'); 276 | canvasWidth = parseInt(canvas.width); 277 | canvasHeight = parseInt(canvas.height); 278 | // 启动游戏 279 | world = createWorld(); 280 | createBall(world, 100, 20, 20); 281 | createBall(world, 300, 60, 10); 282 | createBox(world, 100, 200, 25, 30, 'fixed'); 283 | createBox(world, 200, 50, 20, 20); 284 | box = createBox(world, 400, 80, 20, 20, document.getElementById('box')); 285 | wallLeft = createBox(world, 0, 0, 10, 600, 'fixed'); 286 | wallRight = createBox(world, 1290, 0, 10, 400, 'fixed'); 287 | ground = createBox(world, 30, 595, 1200, 5, 'fixed'); 288 | step(); 289 | }; 290 | ``` 291 | 292 | [此处查看项目代码(仅包含src部分)](https://github.com/godbasin/box2djs-tutorial/tree/master/6-practice/6-5-handle-contact/6-5-handle-contact) 293 | [此处查看页面效果](http://old7pzwup.bkt.clouddn.com/6-5-handle-contact/index.html) 294 | 295 | 296 | ### [返回总目录](https://github.com/godbasin/box2djs-tutorial) -------------------------------------------------------------------------------- /6-practice/6-5-handle-contact/6-5-handle-contact/box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godbasin/box2djs-tutorial/99e9520dcbac39626193d80f505ff6f882f1483b/6-practice/6-5-handle-contact/6-5-handle-contact/box.png -------------------------------------------------------------------------------- /6-practice/6-5-handle-contact/6-5-handle-contact/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | box2d-test 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /6-practice/6-5-handle-contact/6-5-handle-contact/js/test.js: -------------------------------------------------------------------------------- 1 | var canvas; 2 | var ctx; 3 | var canvasWidth; 4 | var canvasHeight; 5 | 6 | var world; 7 | var box, wallLeft, wallRight, ground; 8 | 9 | // 我们将创建世界封装至createWorld函数内 10 | function createWorld() { 11 | // 世界的大小 12 | var worldAABB = new b2AABB(); 13 | worldAABB.minVertex.Set(-4000, -4000); 14 | worldAABB.maxVertex.Set(4000, 4000); 15 | 16 | //定义重力 17 | var gravity = new b2Vec2(0, 300); 18 | 19 | // 是否休眠 20 | var doSleep = false; 21 | 22 | // 最终创建世界 23 | var world = new b2World(worldAABB, gravity, doSleep); 24 | 25 | return world; 26 | } 27 | 28 | //绘画功能 29 | function drawWorld(world, context) { 30 | for (var j = world.m_jointList; j; j = j.m_next) { 31 | // 绘制关节 32 | // drawJoint(j, context); 33 | } 34 | for (var b = world.m_bodyList; b != null; b = b.m_next) { 35 | for (var s = b.GetShapeList(); s != null; s = s.GetNext()) { 36 | if (s.GetUserData() != undefined) { 37 | // 使用数据包括图片 38 | var img = s.GetUserData(); 39 | 40 | // 图片的长和宽 41 | var x = s.GetPosition().x; 42 | var y = s.GetPosition().y; 43 | var topleftX = -img.clientWidth / 2; 44 | var topleftY = -img.clientHeight / 2; 45 | 46 | context.save(); 47 | context.translate(x, y); 48 | context.rotate(s.GetBody().GetRotation()); 49 | context.drawImage(img, topleftX, topleftY); 50 | context.restore(); 51 | } 52 | drawShape(s, context); 53 | } 54 | } 55 | } 56 | 57 | // 创建圆形刚体 58 | function createBall(world, x, y, r, custom) { 59 | // 创建圆形定义 60 | var ballSd = new b2CircleDef(); 61 | ballSd.density = 1.0; // 设置密度 62 | if (custom === 'fixed') ballSd.density = 0.0; // 若传入'fixed',则需固定,此时设置密度为0 63 | else ballSd.userData = custom; // 若传入其他,则视为图片数据 64 | ballSd.radius = 20; // 设置半径 65 | ballSd.restitution = 1.0; // 设置弹性 66 | ballSd.friction = 0; // 设置摩擦因子 67 | var ballBd = new b2BodyDef(); // 创建刚体定义 68 | ballBd.AddShape(ballSd); // 添加形状 69 | ballBd.position.Set(x || 0, y || 0); // 设置位置 70 | return world.CreateBody(ballBd); // 创建并返回刚体 71 | } 72 | 73 | // 创建矩形刚体 74 | function createBox(world, x, y, width, height, custom) { 75 | var boxSd = new b2BoxDef(); // 创建一个形状Shape,然后设置有关Shape的属性 76 | boxSd.extents.Set(width || 1200, height || 5); // 设置矩形高、宽 77 | boxSd.density = 1.0; // 设置矩形的密度 78 | if (custom === 'fixed') boxSd.density = 0.0; // 若传入'fixed',则需固定,此时设置密度为0 79 | else boxSd.userData = custom; // 若传入其他,则视为图片数据 80 | boxSd.restitution = .3; // 设置矩形的弹性 81 | boxSd.friction = 1; // 设置矩形的摩擦因子,可以设置为0-1之间任意一个数,0表示光滑,1表示强摩擦 82 | 83 | var boxBd = new b2BodyDef(); // 创建刚体定义 84 | boxBd.AddShape(boxSd); // 添加形状 85 | boxBd.position.Set(x || 10, y || 10); // 设置位置 86 | return world.CreateBody(boxBd) // 创建并返回刚体 87 | } 88 | 89 | // 定义step函数,用于游戏的迭代运行 90 | function step() { 91 | // 模拟世界 92 | world.Step(1.0 / 60, 1); 93 | checkContact(); 94 | // 清除画布 95 | ctx.clearRect(0, 0, canvasWidth, canvasHeight); 96 | // 重新绘制 97 | drawWorld(world, ctx); 98 | 99 | // 再次刷新 100 | setTimeout(step, 10); 101 | } 102 | 103 | // 从draw_world.js里面引用的绘画功能 104 | function drawShape(shape, context) { 105 | context.strokeStyle = '#003300'; 106 | context.beginPath(); 107 | switch (shape.m_type) { 108 | // 绘制圆 109 | case b2Shape.e_circleShape: 110 | var circle = shape; 111 | var pos = circle.m_position; 112 | var r = circle.m_radius; 113 | var segments = 16.0; 114 | var theta = 0.0; 115 | var dtheta = 2.0 * Math.PI / segments; 116 | // 画圆圈 117 | context.moveTo(pos.x + r, pos.y); 118 | for (var i = 0; i < segments; i++) { 119 | var d = new b2Vec2(r * Math.cos(theta), r * Math.sin(theta)); 120 | var v = b2Math.AddVV(pos, d); 121 | context.lineTo(v.x, v.y); 122 | theta += dtheta; 123 | } 124 | context.lineTo(pos.x + r, pos.y); 125 | 126 | // 画半径 127 | context.moveTo(pos.x, pos.y); 128 | var ax = circle.m_R.col1; 129 | var pos2 = new b2Vec2(pos.x + r * ax.x, pos.y + r * ax.y); 130 | context.lineTo(pos2.x, pos2.y); 131 | break; 132 | // 绘制多边形 133 | case b2Shape.e_polyShape: 134 | var poly = shape; 135 | var tV = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[0])); 136 | context.moveTo(tV.x, tV.y); 137 | for (var i = 0; i < poly.m_vertexCount; i++) { 138 | var v = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i])); 139 | context.lineTo(v.x, v.y); 140 | } 141 | context.lineTo(tV.x, tV.y); 142 | break; 143 | } 144 | context.stroke(); 145 | } 146 | 147 | // 检查是否有碰撞 148 | function checkContact(){ 149 | for (var cn = world.GetContactList(); cn != null; cn = cn.GetNext()) { 150 | var body1 = cn.GetShape1().GetBody(); 151 | var body2 = cn.GetShape2().GetBody(); 152 | 153 | // 若与箱子碰撞,则销毁该刚体 154 | 155 | if(body1 === box && body2.IsStatic() == false ){ 156 | world.DestroyBody(body2); 157 | } 158 | 159 | if(body2 === box && body1.IsStatic() == false ){ 160 | world.DestroyBody(body1); 161 | } 162 | } 163 | } 164 | 165 | function GetBodyAtPosition(x, y) { 166 | // 首先创建一个近似于点的小区域 167 | var mousePVec = new b2Vec2(x, y); 168 | // 利用b2Vec2定义一个矢量,用来保存鼠标点击的点 169 | var aabb = new b2AABB(); 170 | // 利用b2AABB创建一个环境 171 | aabb.minVertex.Set(mousePVec.x - 0.001, mousePVec.y - 0.001); 172 | aabb.maxVertex.Set(mousePVec.x + 0.001, mousePVec.y + 0.001); 173 | // 设置aabb的左上角及右下角坐标,这里是以鼠标点击位置为中心创建了一个长、宽均为0.002的矩形区域 174 | 175 | // 然后查询与指定区域有重叠的刚体 176 | var k_maxCount = 10; // 设定所要查找形状的数量,注意合理设置其大小,过大会影响运行速度 177 | var shapes = new Array(); // 保存查找到的与已知边界盒相交的形状 178 | var count = world.Query(aabb, shapes, k_maxCount); // 在世界中查找与边界盒相交的maxCount个形状,并返回边界盒区域内实际包含的形状的个数 179 | 180 | var findBody = null; // 首先设定没有找到物体 181 | for (var i = 0; i < count; ++i) { 182 | if (shapes[i].GetBody().IsStatic() == false) 183 | // 条件假定查找到的形状不是静态刚体,比如墙 184 | { 185 | var tShape = shapes[i]; // 将查找到的形状赋给tShape变量 186 | var inside = tShape.GetBody(); // 将tShape对应的刚体赋给inside 187 | if (inside) // 如果inside这个刚体存在 188 | { 189 | // 那么返回这个刚体,并跳出遍历 190 | findBody = tShape.GetBody(); 191 | break; 192 | } 193 | } 194 | } 195 | return findBody; 196 | } 197 | 198 | // 获取鼠标坐标 199 | function getMousePos(event) { 200 | var e = event || window.event; 201 | var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; 202 | var scrollY = document.documentElement.scrollTop || document.body.scrollTop; 203 | var x = e.pageX || e.clientX + scrollX; 204 | var y = e.pageY || e.clientY + scrollY; 205 | return { 'x': x, 'y': y }; 206 | } 207 | 208 | // 处理鼠标移动 209 | function handleMousedown(e) { 210 | var e = e || window.event; 211 | // 获取鼠标x坐标 212 | var newMouse = getMousePos(e); 213 | var selectBody = GetBodyAtPosition(newMouse.x - canvas.offsetLeft, newMouse.y - canvas.offsetTop); // 选择刚体 214 | if (selectBody) { 215 | // 若有选中刚体,则处理 216 | var LinearVelocity = new b2Vec2(500, -200); // 定义一个向量 217 | selectBody.WakeUp(); // 激活休眠状态 218 | selectBody.SetLinearVelocity(LinearVelocity); //给定一个速度向量 219 | } else { 220 | // 若无,则随机生成一个矩形添加 221 | var width = parseInt(Math.random() * 50); 222 | var height = parseInt(Math.random() * 50); 223 | createBox(world, newMouse.x, newMouse.y, width, height); 224 | } 225 | } 226 | 227 | document.addEventListener('mousedown', handleMousedown, false); 228 | 229 | window.onload = function () { 230 | canvas = document.getElementById('canvas'); 231 | ctx = canvas.getContext('2d'); 232 | canvasWidth = parseInt(canvas.width); 233 | canvasHeight = parseInt(canvas.height); 234 | // 启动游戏 235 | world = createWorld(); 236 | createBall(world, 100, 20, 20); 237 | createBall(world, 300, 60, 10); 238 | createBox(world, 100, 200, 25, 30, 'fixed'); 239 | createBox(world, 200, 50, 20, 20); 240 | box = createBox(world, 400, 80, 20, 20, document.getElementById('box')); 241 | wallLeft = createBox(world, 0, 0, 10, 600, 'fixed'); 242 | wallRight = createBox(world, 1290, 0, 10, 400, 'fixed'); 243 | ground = createBox(world, 30, 595, 1200, 5, 'fixed'); 244 | step(); 245 | }; -------------------------------------------------------------------------------- /6-practice/README.md: -------------------------------------------------------------------------------- 1 | ## 创建一个物理世界吧 2 | ### 总览 3 | 6. 创建一个物理世界吧 4 | [6.1 创建世界并初始化](https://github.com/godbasin/box2djs-tutorial/tree/master/6-practice/6-1-create-and-init-world/6-1-create-and-init-world.md) 5 | [6.3 添加刚体](https://github.com/godbasin/box2djs-tutorial/tree/master/6-practice/6-2-add-body/6-2-add-body.md) 6 | [6.4 添加边界](https://github.com/godbasin/box2djs-tutorial/tree/master/6-practice/6-3-add-bound/6-3-add-bound.md) 7 | [6.4 鼠标操作刚体](https://github.com/godbasin/box2djs-tutorial/tree/master/6-practice/6-4-mouse-operate-body/6-4-mouse-operate-body.md) 8 | [6.5 处理碰撞刚体](https://github.com/godbasin/box2djs-tutorial/tree/master/6-practice/6-5-handle-contact/6-5-handle-contact.md) -------------------------------------------------------------------------------- /7-api/7-1-common-apis.md: -------------------------------------------------------------------------------- 1 | ## common 2 | ### math 3 | - `b2Vec2`:二维向量 4 | - `b2Mat22`:一个由两个b2Vec2组成的2*2方阵,可直接由两个b2Vec2(col1、col2)构造或者由一个角度值构造 5 | - `b2Math`:基本数学运算,如向量和矩阵的计算、产生随机数等 6 | 7 | ### bsSettings 8 | - `bsSettings`:基础系统配置,如pi的值、每秒的时间、每米的大小、睡眠时间等等 -------------------------------------------------------------------------------- /7-api/7-2-collisions-apis.md: -------------------------------------------------------------------------------- 1 | ## collisions 2 | ### shapes形状定义 3 | - `b2ShapeDef`:形状定义 4 | - b2ShapeDef为形状定义 5 | - type来表示形状类型 6 | - localPosition来表示当前位置 7 | - localRotation来表示当前角度 8 | - friction、density、restitution来表示摩擦力、密度、弹性系数 9 | - categoryBits和maskBits来表示碰撞位及位标识(可以用来过滤一些碰撞) 10 | - groupIndex来表示组号,这个组号可以用来过滤还比位标识优先 11 | 12 | - `b2CircleDef`:圆形 13 | - 继承于b2ShapeDef 14 | - type 为 e_circleShape 15 | - 带有一个类型为float32的量radius来表示半径值 16 | 17 | - `b2BoxDef`:矩形 18 | - 继承于b2ShapeDef 19 | - type 为 e_ boxShape 20 | - 带有一个类型为b2Vec2的量extents来表示区域值 21 | 22 | - `b2PolyDef`:凸多边形 23 | - 继承于b2ShapeDef 24 | - type 为 e_ polyShape 25 | - 带有一个类型为b2Vec2的数组vertices来表示顶点 26 | - 带有一个int32型的量vertexCount来表示顶点数,目前顶点数最多支持8个 27 | 28 | ### 碰撞的功能/查询 29 | - `b2AABB`:AABB坐标 30 | - 盒子,由两个向量组成,一个为minVertex是最小顶点,另一个为maxVertex是最大顶点,通过这两个顶点来表示最为普通的AABB框 31 | 32 | - `b2OBB`:OBB坐标 33 | - `b2ContactID`:接触ID 34 | - `b2ContactPoint`:接触点 35 | 36 | ### broad-phase算法 37 | - `b2BroadPhase`:通过使用动态树降低了管理数据方面的开销,极大的降低了调用narrow-phase算法的次数 -------------------------------------------------------------------------------- /7-api/7-3-dynamics-apis.md: -------------------------------------------------------------------------------- 1 | ## dynamics 2 | ### 刚体 3 | - `b2BodyDef`: 4 | - 刚体定义结构 5 | - userData来表示用户数据 6 | - shapes来表示形状队列,目前形状数最大支持64个 7 | - position来表示当前位置 8 | - rotation来表示当前角度 9 | - linearVelocity表示线速度 10 | - angularVelocity来表示角速度 11 | - linearDamping来表示线性阻尼 12 | - angularDamping来表示角阻抗 13 | - allowSleep来表示是否可以允许休眠 14 | - isSleeping来表示是否正在休眠 15 | - preventRotation来表示是否防止旋转 16 | - 支持方法:AddShape(b2ShapeDef* shape) 17 | 18 | - `b2CollisionFilters`: 19 | - 碰撞过滤是用来防止形状与形状之间进行碰撞的,可以用碰撞种类和组名来区别 20 | - Box2D总共提供16种碰撞种类,每个形状都可以提定属于什么种类,那么它就可以和其他不同种类的形状碰撞 21 | 22 | ### 结点(joints) 23 | - `b2DistanceJoint`:距离连接 24 | - `b2DistanceJointDef`:距离连接定义 25 | - `b2GearJoint`:齿轮连接 26 | - `b2GearJointDef`:齿轮连接定义 27 | - `b2Joint`:连接基类 28 | - `b2JointDef`:连接定义基类 29 | - `b2JointEdge`:用于组合刚体或连接到一起.刚体相当于节点,而连接相当于边 30 | - `b2MouseJoint`:鼠标连接 31 | - `b2MouseJointDef`:鼠标连接定义 32 | - `b2PrismaticJoint`:移动连接 33 | - `b2PrismaticJointDef`:移动连接定义 34 | - `b2PulleyJoint`:滑轮连接 35 | - `b2PulleyJointDef`:滑轮连接定义 36 | - `b2RevoluteJoint`:旋转连接 37 | - `b2RevoluteJointDef`:旋转连接定义 38 | 39 | ### 接触管理(contacts) 40 | - `b2Contact`:管理两个外形接触 41 | - `b2ContactEdge`:接触边用来连接多个物体和接触到一个接触表(物体是一个节点而接触相当于一个接触边) 42 | - `b2ContactResult`:记录接触结果 -------------------------------------------------------------------------------- /7-api/README.md: -------------------------------------------------------------------------------- 1 | ## Box2D api 2 | ### Box2D分类 3 | Box2D所有的api可参照[Box2D_api](http://www.kyucon.com/doc/box2d/)。 4 | 5 | 可发现,Box2D具体分为三大类: 6 | - 碰撞类(collision):碰撞模块定义了形状,broad-phase算法,碰撞的功能/查询 7 | - 基础类(common):包括基础系统配置、数据类型、基础数学运算 8 | - 动力学类(dynamics):包括模拟物理世界,刚体(body),接触管理(contacts),以及关节(joint) 9 | 10 | 具体的说明是个人整理的,有些参照网上资源,可能不完整且有偏差。 11 | 12 | ### 总览 13 | 6. api 14 | [6.1 碰撞类(collision)](https://github.com/godbasin/box2djs-tutorial/tree/master/6-api/6-1-common-apis.md) 15 | [6.2 基础类(common)](https://github.com/godbasin/box2djs-tutorial/tree/master/6-api/6-2-collisions-apis.md) 16 | [6.3 动力学类(dynamics)](https://github.com/godbasin/box2djs-tutorial/tree/master/6-api/6-3-dynamics-apis.md) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 本文为Box2D教程的目录总览。 2 | 3 | ## 前言 4 | ### 触碰Box2D 5 | Box2D其实是我在刚接触前端时的第一个项目,与它的结实主要是在学校参加教授的项目,在师兄师姐们的帮助下认识这个物理引擎。 6 | 7 | ### 教程说明 8 | 在以前端作为职业两年的这个时候,才发现Box2D相关的教程和说明很少,便产生了整理一份相关的说明和教程的想法。 9 | 10 | Box2Djs早已停止了维护,但我依然认为它是一个很棒很棒的库。 11 | 12 | 该教程中有些内容来自于当时的项目研究和调查,其中也有不少当年一起参与项目的师兄师姐们的一些整理,非常感谢大家的努力。 13 | 14 | [博客教程地址: https://godbasin.github.io/2017/02/17/box2d-tutorial-0-catalog](https://godbasin.github.io/2017/02/17/box2d-tutorial-0-catalog) 15 | 16 | ## 教程目录 17 | ### [1. 基本概念](https://github.com/godbasin/box2djs-tutorial/tree/master/1-basic) 18 | [1.1 有关物理引擎](https://github.com/godbasin/box2djs-tutorial/tree/master/1-basic/1-1-physical-engine.md) 19 | [1.2 有关图像引擎](https://github.com/godbasin/box2djs-tutorial/tree/master/1-basic/1-2-graphic-engine.md) 20 | [1.3 有关Box2D](https://github.com/godbasin/box2djs-tutorial/tree/master/1-basic/1-3-hello-box2d.md) 21 | 22 | ### [2. 物理世界(world)](https://github.com/godbasin/box2djs-tutorial/tree/master/2-world) 23 | 24 | ### [3. 形状(shape)-刚体(body)](https://github.com/godbasin/box2djs-tutorial/tree/master/3-body) 25 | [3.1 形状](https://github.com/godbasin/box2djs-tutorial/tree/master/3-body/3-1-shape.md) 26 | [3.2 矩形](https://github.com/godbasin/box2djs-tutorial/tree/master/3-body/3-2-box-shape.md) 27 | [3.3 圆形](https://github.com/godbasin/box2djs-tutorial/tree/master/3-body/3-3-cicle-shape.md) 28 | [3.4 凸多边形](https://github.com/godbasin/box2djs-tutorial/tree/master/3-body/3-4-poly-shape.md) 29 | [3.5 由形状到刚体](https://github.com/godbasin/box2djs-tutorial/tree/master/3-body/3-5-shape-to-body.md) 30 | 31 | ### [4. 关节(joint)](https://github.com/godbasin/box2djs-tutorial/tree/master/4-joint) 32 | [4.1 距离关节(distance-joint)](https://github.com/godbasin/box2djs-tutorial/tree/master/4-joint/4-1-distance-joint.md) 33 | [4.2 旋转关节(revolute-joint)](https://github.com/godbasin/box2djs-tutorial/tree/master/4-joint/4-2-revolute-joint.md) 34 | [4.3 移动关节(prismatic-joint)](https://github.com/godbasin/box2djs-tutorial/tree/master/4-joint/4-3-prismatic-joint.md) 35 | [4.4 滑轮关节(pulley-joint)](https://github.com/godbasin/box2djs-tutorial/tree/master/4-joint/4-4-pulley-joint.md) 36 | [4.5 齿轮关节(gear-joint)](https://github.com/godbasin/box2djs-tutorial/tree/master/4-joint/4-5-gear-joint.md) 37 | 38 | ### [5. 操作(operation)](https://github.com/godbasin/box2djs-tutorial/tree/master/5-operation) 39 | [5.1 鼠标获取刚体](https://github.com/godbasin/box2djs-tutorial/tree/master/5-operation/5-1-mouse-get-body.md) 40 | [5.2 获取参与碰撞的刚体](https://github.com/godbasin/box2djs-tutorial/tree/master/5-operation/5-2-get-contact-list.md) 41 | [5.3 获取刚体的各属性](https://github.com/godbasin/box2djs-tutorial/tree/master/5-operation/5-3-get-body-attributes.md) 42 | [5.4 为刚体设置属性](https://github.com/godbasin/box2djs-tutorial/tree/master/5-operation/5-4-set-body-attributes.md) 43 | [5.5 绘制功能](https://github.com/godbasin/box2djs-tutorial/tree/master/5-operation/5-5-body-with-image.md) 44 | 45 | ### [6. 创建一个物理世界吧](https://github.com/godbasin/box2djs-tutorial/tree/master/6-practice) 46 | [6.1 创建世界并初始化](https://github.com/godbasin/box2djs-tutorial/tree/master/6-practice/6-1-create-and-init-world/6-1-create-and-init-world.md) 47 | [6.3 添加刚体](https://github.com/godbasin/box2djs-tutorial/tree/master/6-practice/6-2-add-body/6-2-add-body.md) 48 | [6.4 添加边界](https://github.com/godbasin/box2djs-tutorial/tree/master/6-practice/6-3-add-bound/6-3-add-bound.md) 49 | [6.4 鼠标操作刚体](https://github.com/godbasin/box2djs-tutorial/tree/master/6-practice/6-4-mouse-operate-body/6-4-mouse-operate-body.md) 50 | [6.5 处理碰撞刚体](https://github.com/godbasin/box2djs-tutorial/tree/master/6-practice/6-5-handle-contact/6-5-handle-contact.md) 51 | 52 | ### [7. api](https://github.com/godbasin/box2djs-tutorial/tree/master/7-api) 53 | [7.1 碰撞类(collision)](https://github.com/godbasin/box2djs-tutorial/tree/master/7-api/7-1-common-apis.md) 54 | [7.2 基础类(common)](https://github.com/godbasin/box2djs-tutorial/tree/master/7-api/7-2-collisions-apis.md) 55 | [7.3 动力学类(dynamics)](https://github.com/godbasin/box2djs-tutorial/tree/master/7-api/7-3-dynamics-apis.md) 56 | 57 | 以上内容可能有缺失或者错误,但还是希望能帮助到大家。 58 | 59 | ### 说明 60 | [box2djs官网](http://box2d-js.sourceforge.net/) 61 | [box2d教程](http://box2d.org/manual.pdf) 62 | [box2d中文教程](http://ss.sysu.edu.cn/~pml/se347/2012fall/info/box2d%E4%B8%AD%E6%96%87%E6%95%99%E7%A8%8B.pdf) 63 | [box2djs_api在线版](http://old7pzwup.bkt.clouddn.com/box2Dapi/index.html?Box2D/Collision/b2AABB.html&Box2D/Collision/class-list.html) 64 | [box2djs_api.zip下载](http://old7pzwup.bkt.clouddn.com/box2Dapi.zip) 65 | [box2d-js_0.1.0.zip下载](http://old7pzwup.bkt.clouddn.com/box2d-js_0.1.0.zip) 66 | 67 | ### 版权许可 68 | 只要保持原作者署名和非商用,您可以自由地阅读、分享、修改本教程。 --------------------------------------------------------------------------------