Box2D物理引擎模拟炸弹爆炸效果
2015-01-25 23:02
591 查看
今天咱们来模拟炸弹效果。于是问题一来了:“为什么要模仿这么暴力的效果呢?莫非几日不见,Yorhom人品煞变?”
其实玩过愤怒的小鸟的同学都应该对这种效果似曾相识,因为据非官方报道,第二厉害的小鸟——黑色鸟的特技就是自爆。问题二出现了:“那第一厉害的小鸟是哪一种呢?”据Yorhom我本人测试,那只红色大鸟应该是最厉害的,不过貌似没有特技?愤怒的小鸟这种肤浅的游戏,Y某我最擅长了,以后有时间会专门写写这个游戏的攻略。这两种鸟的靓照如下:
![](https://img-blog.csdn.net/20150125001937765)
![](https://img-blog.csdn.net/20150125001949265)
敷衍了问问题二的同学,问题三就来了:“到底如何实现这个效果呢?” 这个问题不好敷衍,于是接下来的文章,就由我向大家介绍如何使用Box2D实现这种效果。
在学习本章前,你需要先了解一下Box2D这款很实用,很出名的物理引擎。凭借我个人的学习经验,我向大家推荐拉登大叔的博客,其博客Box2D栏目地址如下:
http://www.ladeng6666.com/blog/category/box2d/
方案一:在炸弹刚体爆炸时,由炸弹刚体为起点,向四周喷散小刚体,这些小刚体会被喷射到附近的刚体上,对附近的刚体施加力,然后力是物体运动状态改变的原因(摘自高中物理必修一),然后爆炸效果就可以完成了。这个方法比较简单,但是我觉得有点dirty way。像我这种耳机标有R的必须带右边,标有L的必须带左边的人,怎么可能就此满足了呢?当然,感兴趣的朋友可以自己尝试一下这种方法~
方案二:首先想办法把炸弹刚体周围的其他刚体找到,然后对它们施加一个力,我们只用控制好力的方向就可以实现爆炸效果了。嗯~不错。似乎这种方法更漂亮~
这两种方案显而易见的区别就是方案二比方案一字数要少一点。这意味着什么呢?我想,大家都应该做过至少6年的数学题了吧,据我大约9.5年来总结的经验,题目字数越少的数学题就越难,因为提供的信息量少。所以说,虽然方案二效果相对更好,但是实现起来可能要复杂很多。
方案二之所以比较难以实现,是在于我们不知道如何查找周围的刚体。为此,我将我所了解到的Box2D中的功能都涉猎了一遍,发现我从来没有用过只是听说过的一个功能——射线投射可能会派上用场。不过辛好网上相关的资料挺多的,所以我就此学习了一下b2RayCastInput,b2RayCastOutput这两个类。
上述的两个类很重要,所以大家必须先了解这两个类,具体的教程为:http://ohcoder.com/blog/2012/12/10/ray-casting/,教程中用的是Box2D C++,不过无论是C++版还是Web版,用法都是一样的。
b2RayCastInput,b2RayCastOutput这两个类到底有什么用的?从上面提供的教程可以看出,这两个类的实例一般作为参数传入b2Fixture的RayCast函数中,这个函数用来检查某个射线是否于刚体相交。所以我们只用以炸弹刚体位置为始点,然后向周围喷射射线。然后循环世界里的刚体,判断循环得到的刚体是否与射线相交,如果相交则对当前刚体施加一个力。这样一来,炸弹效果的原理基本就被弄清除了。
<!DOCTYPE html>
<html>
<head>
<title>Box2d Bomb</title>
<script type="text/javascript" src="./Box2dWeb-2.1.a.3.min.js"></script>
<script type="text/javascript" src="./Main.js"></script>
</head>
<body>
<canvas id="mycanvas" width="600" height="400"></canvas>
</body>
</html>html代码不是重点,重点是Main.js:
window.addEventListener("load", main, false);
var b2Vec2 = Box2D.Common.Math.b2Vec2
, b2AABB = Box2D.Collision.b2AABB
, b2BodyDef = Box2D.Dynamics.b2BodyDef
, b2Body = Box2D.Dynamics.b2Body
, b2FixtureDef = Box2D.Dynamics.b2FixtureDef
, b2Fixture = Box2D.Dynamics.b2Fixture
, b2World = Box2D.Dynamics.b2World
, b2MassData = Box2D.Collision.Shapes.b2MassData
, b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape
, b2CircleShape = Box2D.Collision.Shapes.b2CircleShape
, b2DebugDraw = Box2D.Dynamics.b2DebugDraw
, b2MouseJointDef = Box2D.Dynamics.Joints.b2MouseJointDef
, b2RayCastInput = Box2D.Collision.b2RayCastInput
, b2RayCastOutput = Box2D.Collision.b2RayCastOutput;
var world, fixDef, bodyDef;
var bomb = null;
var timer = 0;
function main () {
world = new b2World(new b2Vec2(0, 9.8), true);
fixDef = new b2FixtureDef();
fixDef.density = 1.0;
fixDef.friction = 0.5;
fixDef.restitution = 0.2;
bodyDef = new b2BodyDef();
createGround();
createBlocks();
createBomb();
createDebugDraw();
setInterval(update, 1000 / 60);
}
function createGround () {
fixDef.shape = new b2PolygonShape();
fixDef.shape.SetAsBox(20, 1);
bodyDef.type = b2Body.b2_staticBody;
bodyDef.position.Set(10, 13);
world.CreateBody(bodyDef).CreateFixture(fixDef);
}
function createBlocks () {
var list = [
{width : 120, height : 30, x : 140, y : 330},
{width : 20, height : 100, x : 50, y : 200},
{width : 20, height : 100, x : 230, y : 200},
{width : 120, height : 20, x : 140, y : 80},
{width : 120, height : 30, x : 440, y : 330},
{width : 20, height : 100, x : 350, y : 200},
{width : 20, height : 100, x : 530, y : 200},
{width : 120, height : 20, x : 440, y : 80}
];
for (var i = 0; i < list.length; i++) {
var data = list[i];
fixDef.shape = new b2PolygonShape();
fixDef.shape.SetAsBox(data.width / 30, data.height / 30);
bodyDef.type = b2Body.b2_dynamicBody;
bodyDef.position.Set(data.x / 30, data.y / 30);
world.CreateBody(bodyDef).CreateFixture(fixDef);
}
}
function createBomb () {
fixDef.shape = new b2CircleShape(0.5);
bodyDef.type = b2Body.b2_dynamicBody;
bodyDef.position.Set(9.7, 1);
bomb = world.CreateBody(bodyDef);
bomb.userData = "iambomb";
bomb.CreateFixture(fixDef);
}
function createDebugDraw () {
var debugDraw = new b2DebugDraw();
debugDraw.SetSprite(document.getElementById("mycanvas").getContext("2d"));
debugDraw.SetDrawScale(30.0);
debugDraw.SetFillAlpha(0.5);
debugDraw.SetLineThickness(1.0);
debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit | b2DebugDraw.e_controllerBit | b2DebugDraw.e_pairBit);
world.SetDebugDraw(debugDraw);
}
function update () {
world.Step(1 / 60, 10, 10);
world.DrawDebugData();
world.ClearForces();
if (timer++ == 100) {
detonated();
}
}
值得注意的是timer这个变量,这个变量是用来为爆炸计时的。在update函数里我们调用了dentonated这个函数,我暂时不把这个函数的代码公布,这个函数是引爆炸弹用的。
运行代码,我们得到如图的物理世界:
![](https://img-blog.csdn.net/20150125124647593)
图中的小球实际上就是一颗炸弹。
function detonated () {
var position = bomb.GetWorldCenter();
var range = 3;
for (var i = 0; i <= 100; i++) {
var angle = 360 / 100 * i;
var input = new b2RayCastInput();
input.p1 = position;
input.p2.Set(position.x + range * Math.cos(angle), position.y + range * Math.sin(angle));
input.maxFraction = 1;
var output = new b2RayCastOutput();
for (var currentBody = world.GetBodyList(); currentBody; currentBody = currentBody.GetNext()) {
if (currentBody.userData == bomb.userData) {
continue;
}
var fix = currentBody.GetFixtureList();
if (!fix) {
continue;
}
var isHit = fix.RayCast(output, input);
if (isHit) {
var p1 = input.p1.Copy();
var p2 = input.p2.Copy();
p2.Subtract(p1);
p2.Multiply(output.fraction);
p1.Add(p2);
var hitPoint = p1.Copy();
hitPoint.Subtract(position);
currentBody.ApplyForce(new b2Vec2(hitPoint.x * (1 - output.fraction) * 300, hitPoint.y * (1 - output.fraction) * 300), hitPoint);
}
}
}
world.DestroyBody(bomb);
}在这个函数里,我首先用GetWorldCenter获取到炸弹刚体的位置,然后设定一个爆炸范围变量range。然后通过循环开始向四周创建射线。这里我设定一共要喷出100条射线,所以,每条射线间的夹角为3.6度。知道角度后,我们就可以计算每条射线向量的终点了,并将终点和始点传入b2RayCastInput中。
一系列射线方面的准备完毕后,就要开始循环世界里的刚体了。然后将循环得到的刚体获取刚形(一个b2Fixture对象),并调用刚型的RayCast来判断刚才创建的射线是否与刚体相交。
if (isHit) {
var p1 = input.p1.Copy();
var p2 = input.p2.Copy();
p2.Subtract(p1);
p2.Multiply(output.fraction);
p1.Add(p2);
var hitPoint = p1.Copy();
hitPoint.Subtract(position);
currentBody.ApplyForce(new b2Vec2(hitPoint.x * (1 - output.fraction) * 300, hitPoint.y * (1 - output.fraction) * 300), hitPoint);
}以上几行代码负责计算射线与刚体相交的位置所对应的向量,并在该位置上施加一个力。那么如何计算射线与刚体相交的位置所对应的向量呢?我们不妨来看张图:
![](https://img-blog.csdn.net/20150125131931457)
现在,让我们调动起高中数学必修四里平面向量的知识。于是数学问题来了:已知向量Op1,Op2坐标,且p1,p2,p3三点共线,p1p3 = n p1p2,求Op3。
要解这道题,可以用定比分点等知识,用向量加减,数乘运算也能解决问题。以下是我的解答:
解 设p1(x1, y1),p2(x2, y2)
Op3 = Op1 + p1p3 = np1p2 + Op1 = n(x2 - x1, y2 - y1) + (x1, y1) = (nx2 - nx1 + x1, ny2 - ny1 + y1)
这是数学写法,换到程序代码里就是:
var p1 = input.p1.Copy();
var p2 = input.p2.Copy();
p2.Subtract(p1);
p2.Multiply(output.fraction);
p1.Add(p2);
var hitPoint = p1.Copy();这里的output.fraction就相当于上题所给的n。Copy,Substact,Add,Multiply分别是用来复制当前向量,向量减法,向量加法,向量数乘运算。
计算完了碰撞位置,我们给刚体用ApplyForce施加一个力,然后刚体就类似于被一股爆炸产生的气流冲击了似的,飞了出去~
炸弹爆炸了炸弹本身肯定不负存在了,所以在给周围的刚体施加完力后,用DestroyBody销毁掉这个炸弹刚体~
运行代码,得到下图:
![](https://img-blog.csdn.net/20150125134114484)
在线测试地址:http://yuehaowang.github.io/demo/box2d_bomb/
源代码下载地址:http://yuehaowang.github.io/downloads/box2d_bomb.zip
本章就到此结束了,欢迎大家留言~
----------------------------------------------------------------
欢迎大家转载我的文章。
转载请注明:转自Yorhom's Game Box
http://blog.csdn.net/yorhomwang
欢迎继续关注我的博客
其实玩过愤怒的小鸟的同学都应该对这种效果似曾相识,因为据非官方报道,第二厉害的小鸟——黑色鸟的特技就是自爆。问题二出现了:“那第一厉害的小鸟是哪一种呢?”据Yorhom我本人测试,那只红色大鸟应该是最厉害的,不过貌似没有特技?愤怒的小鸟这种肤浅的游戏,Y某我最擅长了,以后有时间会专门写写这个游戏的攻略。这两种鸟的靓照如下:
敷衍了问问题二的同学,问题三就来了:“到底如何实现这个效果呢?” 这个问题不好敷衍,于是接下来的文章,就由我向大家介绍如何使用Box2D实现这种效果。
在学习本章前,你需要先了解一下Box2D这款很实用,很出名的物理引擎。凭借我个人的学习经验,我向大家推荐拉登大叔的博客,其博客Box2D栏目地址如下:
http://www.ladeng6666.com/blog/category/box2d/
一,炸弹效果原理
在实现这个效果的时候,我想到了两种方案。方案一:在炸弹刚体爆炸时,由炸弹刚体为起点,向四周喷散小刚体,这些小刚体会被喷射到附近的刚体上,对附近的刚体施加力,然后力是物体运动状态改变的原因(摘自高中物理必修一),然后爆炸效果就可以完成了。这个方法比较简单,但是我觉得有点dirty way。像我这种耳机标有R的必须带右边,标有L的必须带左边的人,怎么可能就此满足了呢?当然,感兴趣的朋友可以自己尝试一下这种方法~
方案二:首先想办法把炸弹刚体周围的其他刚体找到,然后对它们施加一个力,我们只用控制好力的方向就可以实现爆炸效果了。嗯~不错。似乎这种方法更漂亮~
这两种方案显而易见的区别就是方案二比方案一字数要少一点。这意味着什么呢?我想,大家都应该做过至少6年的数学题了吧,据我大约9.5年来总结的经验,题目字数越少的数学题就越难,因为提供的信息量少。所以说,虽然方案二效果相对更好,但是实现起来可能要复杂很多。
方案二之所以比较难以实现,是在于我们不知道如何查找周围的刚体。为此,我将我所了解到的Box2D中的功能都涉猎了一遍,发现我从来没有用过只是听说过的一个功能——射线投射可能会派上用场。不过辛好网上相关的资料挺多的,所以我就此学习了一下b2RayCastInput,b2RayCastOutput这两个类。
上述的两个类很重要,所以大家必须先了解这两个类,具体的教程为:http://ohcoder.com/blog/2012/12/10/ray-casting/,教程中用的是Box2D C++,不过无论是C++版还是Web版,用法都是一样的。
b2RayCastInput,b2RayCastOutput这两个类到底有什么用的?从上面提供的教程可以看出,这两个类的实例一般作为参数传入b2Fixture的RayCast函数中,这个函数用来检查某个射线是否于刚体相交。所以我们只用以炸弹刚体位置为始点,然后向周围喷射射线。然后循环世界里的刚体,判断循环得到的刚体是否与射线相交,如果相交则对当前刚体施加一个力。这样一来,炸弹效果的原理基本就被弄清除了。
二,构建一个物理世界
先把最基础的世界创建出来把。于是,我们在index.html里加入以下代码:<!DOCTYPE html>
<html>
<head>
<title>Box2d Bomb</title>
<script type="text/javascript" src="./Box2dWeb-2.1.a.3.min.js"></script>
<script type="text/javascript" src="./Main.js"></script>
</head>
<body>
<canvas id="mycanvas" width="600" height="400"></canvas>
</body>
</html>html代码不是重点,重点是Main.js:
window.addEventListener("load", main, false);
var b2Vec2 = Box2D.Common.Math.b2Vec2
, b2AABB = Box2D.Collision.b2AABB
, b2BodyDef = Box2D.Dynamics.b2BodyDef
, b2Body = Box2D.Dynamics.b2Body
, b2FixtureDef = Box2D.Dynamics.b2FixtureDef
, b2Fixture = Box2D.Dynamics.b2Fixture
, b2World = Box2D.Dynamics.b2World
, b2MassData = Box2D.Collision.Shapes.b2MassData
, b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape
, b2CircleShape = Box2D.Collision.Shapes.b2CircleShape
, b2DebugDraw = Box2D.Dynamics.b2DebugDraw
, b2MouseJointDef = Box2D.Dynamics.Joints.b2MouseJointDef
, b2RayCastInput = Box2D.Collision.b2RayCastInput
, b2RayCastOutput = Box2D.Collision.b2RayCastOutput;
var world, fixDef, bodyDef;
var bomb = null;
var timer = 0;
function main () {
world = new b2World(new b2Vec2(0, 9.8), true);
fixDef = new b2FixtureDef();
fixDef.density = 1.0;
fixDef.friction = 0.5;
fixDef.restitution = 0.2;
bodyDef = new b2BodyDef();
createGround();
createBlocks();
createBomb();
createDebugDraw();
setInterval(update, 1000 / 60);
}
function createGround () {
fixDef.shape = new b2PolygonShape();
fixDef.shape.SetAsBox(20, 1);
bodyDef.type = b2Body.b2_staticBody;
bodyDef.position.Set(10, 13);
world.CreateBody(bodyDef).CreateFixture(fixDef);
}
function createBlocks () {
var list = [
{width : 120, height : 30, x : 140, y : 330},
{width : 20, height : 100, x : 50, y : 200},
{width : 20, height : 100, x : 230, y : 200},
{width : 120, height : 20, x : 140, y : 80},
{width : 120, height : 30, x : 440, y : 330},
{width : 20, height : 100, x : 350, y : 200},
{width : 20, height : 100, x : 530, y : 200},
{width : 120, height : 20, x : 440, y : 80}
];
for (var i = 0; i < list.length; i++) {
var data = list[i];
fixDef.shape = new b2PolygonShape();
fixDef.shape.SetAsBox(data.width / 30, data.height / 30);
bodyDef.type = b2Body.b2_dynamicBody;
bodyDef.position.Set(data.x / 30, data.y / 30);
world.CreateBody(bodyDef).CreateFixture(fixDef);
}
}
function createBomb () {
fixDef.shape = new b2CircleShape(0.5);
bodyDef.type = b2Body.b2_dynamicBody;
bodyDef.position.Set(9.7, 1);
bomb = world.CreateBody(bodyDef);
bomb.userData = "iambomb";
bomb.CreateFixture(fixDef);
}
function createDebugDraw () {
var debugDraw = new b2DebugDraw();
debugDraw.SetSprite(document.getElementById("mycanvas").getContext("2d"));
debugDraw.SetDrawScale(30.0);
debugDraw.SetFillAlpha(0.5);
debugDraw.SetLineThickness(1.0);
debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit | b2DebugDraw.e_controllerBit | b2DebugDraw.e_pairBit);
world.SetDebugDraw(debugDraw);
}
function update () {
world.Step(1 / 60, 10, 10);
world.DrawDebugData();
world.ClearForces();
if (timer++ == 100) {
detonated();
}
}
值得注意的是timer这个变量,这个变量是用来为爆炸计时的。在update函数里我们调用了dentonated这个函数,我暂时不把这个函数的代码公布,这个函数是引爆炸弹用的。
运行代码,我们得到如图的物理世界:
图中的小球实际上就是一颗炸弹。
三,引爆炸弹
上面提到过dentonated这个函数,这里,我们就来学习一下里面的代码,这也是本次开发的核心部分。function detonated () {
var position = bomb.GetWorldCenter();
var range = 3;
for (var i = 0; i <= 100; i++) {
var angle = 360 / 100 * i;
var input = new b2RayCastInput();
input.p1 = position;
input.p2.Set(position.x + range * Math.cos(angle), position.y + range * Math.sin(angle));
input.maxFraction = 1;
var output = new b2RayCastOutput();
for (var currentBody = world.GetBodyList(); currentBody; currentBody = currentBody.GetNext()) {
if (currentBody.userData == bomb.userData) {
continue;
}
var fix = currentBody.GetFixtureList();
if (!fix) {
continue;
}
var isHit = fix.RayCast(output, input);
if (isHit) {
var p1 = input.p1.Copy();
var p2 = input.p2.Copy();
p2.Subtract(p1);
p2.Multiply(output.fraction);
p1.Add(p2);
var hitPoint = p1.Copy();
hitPoint.Subtract(position);
currentBody.ApplyForce(new b2Vec2(hitPoint.x * (1 - output.fraction) * 300, hitPoint.y * (1 - output.fraction) * 300), hitPoint);
}
}
}
world.DestroyBody(bomb);
}在这个函数里,我首先用GetWorldCenter获取到炸弹刚体的位置,然后设定一个爆炸范围变量range。然后通过循环开始向四周创建射线。这里我设定一共要喷出100条射线,所以,每条射线间的夹角为3.6度。知道角度后,我们就可以计算每条射线向量的终点了,并将终点和始点传入b2RayCastInput中。
一系列射线方面的准备完毕后,就要开始循环世界里的刚体了。然后将循环得到的刚体获取刚形(一个b2Fixture对象),并调用刚型的RayCast来判断刚才创建的射线是否与刚体相交。
if (isHit) {
var p1 = input.p1.Copy();
var p2 = input.p2.Copy();
p2.Subtract(p1);
p2.Multiply(output.fraction);
p1.Add(p2);
var hitPoint = p1.Copy();
hitPoint.Subtract(position);
currentBody.ApplyForce(new b2Vec2(hitPoint.x * (1 - output.fraction) * 300, hitPoint.y * (1 - output.fraction) * 300), hitPoint);
}以上几行代码负责计算射线与刚体相交的位置所对应的向量,并在该位置上施加一个力。那么如何计算射线与刚体相交的位置所对应的向量呢?我们不妨来看张图:
现在,让我们调动起高中数学必修四里平面向量的知识。于是数学问题来了:已知向量Op1,Op2坐标,且p1,p2,p3三点共线,p1p3 = n p1p2,求Op3。
要解这道题,可以用定比分点等知识,用向量加减,数乘运算也能解决问题。以下是我的解答:
解 设p1(x1, y1),p2(x2, y2)
Op3 = Op1 + p1p3 = np1p2 + Op1 = n(x2 - x1, y2 - y1) + (x1, y1) = (nx2 - nx1 + x1, ny2 - ny1 + y1)
这是数学写法,换到程序代码里就是:
var p1 = input.p1.Copy();
var p2 = input.p2.Copy();
p2.Subtract(p1);
p2.Multiply(output.fraction);
p1.Add(p2);
var hitPoint = p1.Copy();这里的output.fraction就相当于上题所给的n。Copy,Substact,Add,Multiply分别是用来复制当前向量,向量减法,向量加法,向量数乘运算。
计算完了碰撞位置,我们给刚体用ApplyForce施加一个力,然后刚体就类似于被一股爆炸产生的气流冲击了似的,飞了出去~
炸弹爆炸了炸弹本身肯定不负存在了,所以在给周围的刚体施加完力后,用DestroyBody销毁掉这个炸弹刚体~
运行代码,得到下图:
在线测试地址:http://yuehaowang.github.io/demo/box2d_bomb/
源代码下载地址:http://yuehaowang.github.io/downloads/box2d_bomb.zip
本章就到此结束了,欢迎大家留言~
----------------------------------------------------------------
欢迎大家转载我的文章。
转载请注明:转自Yorhom's Game Box
http://blog.csdn.net/yorhomwang
欢迎继续关注我的博客
相关文章推荐
- Box2D物理引擎模拟炸弹爆炸效果
- Box2d进阶 使用光线投射实现Box2d爆炸效果模拟
- Box2d进阶 使用光线投射实现Box2d爆炸效果模拟
- UE4实现一个简单的炸弹爆炸与镜头振动效果
- Unity 2D炸弹爆炸的效果制作
- 经典!HTML5 Canvas 模拟可撕裂布料效果
- ajax效果模拟——隐藏的iframe无刷新效果
- 在IE8等不支持placeholder属性的浏览器中模拟placeholder效果
- 结合 CSS3 & Canvas 模拟人行走的效果
- 神奇画板效果模拟
- 使用jQuery插件实现 模拟dialog的遮罩效果
- jQuery插件模拟支付宝密码输入框效果
- [C#]模拟实现Visual Stduio工具栏动态效果--扩展控件DocKPanel
- 多图展示点击切换效果模拟的flash效果,点小图放大显示,再点恢复默认。
- HelloChart折线图动态刷新--模拟心电图效果
- LUA中测试鱼死亡动作、炸弹轨迹、炸弹爆炸动作
- 模拟阴影效果纯CSS圆角矩形
- ajax--iframe模拟ajax文件上传效果
- js模拟滤镜的图片渐显效果
- Cocos2d-x 3.2 lua飞机大战开发实例(三)道具的掉落,碰撞检测,声音,分数,爆炸效果,完善游戏的功能细节