2D物理引擎 Box2D for javascript Games 第七章 子弹和感应器

你知道 Box2D 可以在每一个时间步中管理刚体间的碰撞并决算它们。

总之,在愤怒的小鸟中制作攻城机器期间,发生了一些错误

你可能需要注意一下,有时抛射物会穿过城堡,忽略了碰撞。

这里发生了什么?

通常,Javascript 游戏运行在 30 与 60 帧每秒之间,如果我们使世界的时间步与帧率同步,每一个时间步将代表 1/30 到 1/60 秒。

依赖时间步的模拟叫作离散模拟,这不同于真实的世界,在真实世界中事件的发生的是连续的,我们称之为连续模拟

离散型模拟中,我们无法知道在时间中的第n步与第(n+1)步之间发生了什么

如果一个刚体的移动真的很快,那么他可以在小于一个时间步的时间之内穿过另一个刚体,你会发现它在穿过的刚体的 另一边了,而没有发生碰撞。

这个现象叫作隧道效应 (tunneling),并且很自然的,我们想要阻止它的发生

在本章,你将学习两种不同的方式去管理刚体间的接触:

  • 设置刚体为子弹
  • 设置刚体为感应器

通过本章的学习,你将不会再对管理高速移动的刚体有任何问题

感受隧道效应

Box2d 默认的情况下对阻止隧道效应做的很好,使用一个连续的碰撞检测来计算离散型模拟。

不幸的是,处于性能相关的考虑,这种类型的碰撞检测只应用在 dynamic 类型刚体与 static 类型刚体之间的碰撞上。

这意味着,我们可以在两个 dynamic 类型的刚体之间产生隧道效应。

  1. 让我们来看一下,下面的脚本:

     const stage = document.querySelector('#canvas');
    function main() {
    world = new b2World(gravity, sleep);
    debugDraw(); var bodyDef = new b2BodyDef();
    bodyDef.position.Set(320 / worldScale, 470 / worldScale);
    var polygonShape = new b2PolygonShape();
    polygonShape.SetAsBox(320 / worldScale, 10 / worldScale);
    var fixtureDef = new b2FixtureDef();
    fixtureDef.shape = polygonShape;
    fixtureDef.density = 1;
    fixtureDef.restitution = 0.5;
    fixtureDef.friction = 0.5;
    var body = world.CreateBody(bodyDef);
    body.CreateFixture(fixtureDef); bodyDef.position.Set(600 / worldScale, 240 / worldScale);
    bodyDef.type = b2Body.b2_dynamicBody;
    polygonShape.SetAsBox(10 / worldScale, 220 / worldScale);
    var body2 = world.CreateBody(bodyDef);
    body2.CreateFixture(fixtureDef); bodyDef.position.Set(320 / worldScale, 455 / worldScale);
    polygonShape.SetAsBox(5 / worldScale, 5 / worldScale);
    var body3 = world.CreateBody(bodyDef);
    body3.CreateFixture(fixtureDef);
    body3.SetLinearVelocity(new b2Vec2(100, -10)); stage.addEventListener('click', updateWorld);
    }

    这个脚本中没有什么新的知识。

    它放置了三个刚体,分别叫作 body,body2,body3,它们分别代表了一个 static 类型的地面,一个 dynamic 类型的障碍物,以及一个 dynamic 类型的小子弹。

    如你所见,子弹以一个非常快的速度(100,-10)发射,然后 updateWorld() 方法将执行世界的时间步,它不是在每一帧被调用,而是在每一次的鼠标点击时被调用。

    这样可以使我们一步一步的来运行模拟,如你想要的一样慢,并且我们可以看到发生了什么。

  2. 测试网页,然后多次点击鼠标。

    源码: article/ch07/ch07-1.html

    发生了什么?抛物块在没有接触到障碍物的情况下穿过了它。

    我们刚刚体验了隧道效应。

    现在,让我们做些什么来阻止它的发生吧!

阻止隧道效应——设置刚体为子弹

因为连续的碰撞检测使得隧道效应无法在 static 类型的刚体上发生,

在某些情况下,我们也可以将这种方法应用到 dynamic 类型的刚体上,通过设置它们为子弹(bullets)。

一个子弹(bullets)执行连续的碰撞检测,来检测它与 static 类型的和 dynamic 类型的刚体之间的碰撞。

记住,如果你将所有的刚体都设置为子弹(bullets),你将会发现很高的性能消耗,所以这将由你在性能和精确度之间找到一个平衡点。

从我的经验来看,只是一些被玩家或敌人发射的粒子和投射物被设置为子弹(bullets),通常游戏的角色不会移动的快到需要将它们设置为子弹(bullets)

  1. 在 main 函数中仅需要加入一句代码:

    function main() {
    world = new b2World(gravity, sleep);
    debugDraw(); var bodyDef = new b2BodyDef();
    bodyDef.position.Set(320 / worldScale, 470 / worldScale);
    var polygonShape = new b2PolygonShape();
    polygonShape.SetAsBox(320 / worldScale, 10 / worldScale);
    var fixtureDef = new b2FixtureDef();
    fixtureDef.shape = polygonShape;
    fixtureDef.density = 1;
    fixtureDef.restitution = 0.5;
    fixtureDef.friction = 0.5;
    var body = world.CreateBody(bodyDef);
    body.CreateFixture(fixtureDef); bodyDef.position.Set(600 / worldScale, 240 / worldScale);
    bodyDef.type = b2Body.b2_dynamicBody;
    polygonShape.SetAsBox(10 / worldScale, 220 / worldScale);
    var body2 = world.CreateBody(bodyDef);
    body2.CreateFixture(fixtureDef); bodyDef.position.Set(320 / worldScale, 455 / worldScale); // 加上这一句
    bodyDef.bullet = true; polygonShape.SetAsBox(5 / worldScale, 5 / worldScale);
    var body3 = world.CreateBody(bodyDef);
    body3.CreateFixture(fixtureDef);
    body3.SetLinearVelocity(new b2Vec2(100, -10));
    stage.addEventListener('click', updateWorld);
    // setInterval(updateWorld, 1000 / 60);
    }

    在刚体定义中将bullet属性设置为true将对子弹进行连续碰撞检测。

    现在应该会从障碍物上弹回。

  2. 测试网页,来看看发生了什么。

    源码: article/ch07/ch07-2.html

    看到了吗?如你所见,接触被决算,现在子弹被障碍物弹回。

  3. 将你的愤怒的小鸟模型中,通过抛掷器发射的投射物,应用bullet属性,然后将产生一个精确的模拟运行

现在,让我们来看看最后一个你在 Box2D 可以创建的特殊类型的刚体。

通过感应器检测接触,可以允许刚体重叠

在你的游戏中,有时你可能需要两个刚体就像没有发生任何碰撞一样重叠在一起,同时还能检测到碰撞。

使用感应器(sensor)可以实现这个功能;

感应器:一个夹具可以在检测到碰撞的情况下而不作出任何反应。

你可以使用感应器(sensor)创建刚体,所以你将可以在刚体间没有任何物理触点的情况下,知道它们之间发生的碰撞。

只要想象一下玩家控制的角色和一个开关:你想要知道当玩家控制的角色撞击开关触发一些事件,但是同时你不想开关响应玩家的碰撞。

在最后的脚本中,我们将测试一个感应器

  1. 和往常一样,我们给刚体 userData 属性中添加刚体的名字

    将 barrier 刚体设置为 static 类型并通过 isSensor 属性定义的它的夹具为感应器。

    function main() {
    world = new b2World(gravity, sleep);
    debugDraw(); var bodyDef = new b2BodyDef();
    bodyDef.position.Set(320 / worldScale, 470 / worldScale);
    bodyDef.userData = "floor";
    var polygonShape = new b2PolygonShape();
    polygonShape.SetAsBox(320 / worldScale, 10 / worldScale);
    var fixtureDef = new b2FixtureDef();
    fixtureDef.shape = polygonShape;
    fixtureDef.density = 1;
    fixtureDef.restitution = 0.5;
    fixtureDef.friction = 0.5;
    fixtureDef.isSensor = true;
    var body = world.CreateBody(bodyDef);
    body.CreateFixture(fixtureDef); bodyDef.position.Set(600 / worldScale, 240 / worldScale);
    bodyDef.userData = "barrier";
    polygonShape.SetAsBox(10 / worldScale, 220 / worldScale);
    var body2 = world.CreateBody(bodyDef);
    body2.CreateFixture(fixtureDef); bodyDef.position.Set(320 / worldScale, 455 / worldScale);
    bodyDef.bullet = true;
    bodyDef.type = b2Body.b2_dynamicBody;
    bodyDef.userData = "bullet";
    polygonShape.SetAsBox(5 / worldScale, 5 / worldScale);
    fixtureDef.isSensor = false;
    var body3 = world.CreateBody(bodyDef);
    body3.CreateFixture(fixtureDef);
    body3.SetLinearVelocity(new b2Vec2(100, -10));
    // 鼠标多次点击后查看效果
    stage.addEventListener('click', updateWorld);
    }

    为什么我们要将 barrier 设置为 static 类型呢?

    因为它是一个感应器,它的碰撞将不会被决算,所以如果我们设置它为 dynamic 类型,它将不会和地面发生碰撞,所以没有支撑物,将会一直下落。

    将它设置为 static 类型可以确保它固定在一个位置。

  2. 最后,我们修改 updateWorld() 方法的方式与你在处理检测碰撞时学到的方法是一样:

    function updateWorld() {
    world.Step(1 / 30, 10, 10); world.ClearForces(); // 清除作用力
    for (var b = world.GetBodyList(); b; b = b.GetNext()) {
    for (var c = b.GetContactList(); c; c = c.next) {
    var contact = c.contact;
    var fixtureA = contact.GetFixtureA();
    var fixtureB = contact.GetFixtureB();
    var bodyA = fixtureA.GetBody();
    var bodyB = fixtureB.GetBody();
    var userDataA = bodyA.GetUserData();
    var userDataB = bodyB.GetUserData(); if (userDataA == "barrier" || userDataB == "barrier") {
    console.log(userDataA + "->" + userDataB);
    }
    }
    } world.DrawDebugData(); // 显示刚体debug轮廓
    }
  3. 我们遍历所有的刚体,然后遍历所有的接触的刚体,当我们发现与障碍物发生碰撞的刚体时,我们将在输出窗口打印发生的相关信息

  4. 测试网页,然后通过点击使子弹运动:

    源码: article/ch07/ch07-3.html

    如你所见,子弹穿过障碍物,碰撞被检测到,并在开发者工具控制台输出下面的文本:

    bullet->barrier

    上面的信息被输出了两次的情况,是因为当抛射物与障碍物发生碰撞时输出一次,并且障碍物与抛射物发射碰撞时又输出了一次。

注意:在 Javascript 版中,bullet->barrier 输出了 14 次,猜测是循环中

小结

本章探讨了两个属性的使用,你学习了在一个离散型模拟中怎样管理连续碰撞检测,以及怎样创建被动刚体(感应器),为了在检测到碰撞时不要进行决算。

太好了,你们在本书中的学习之旅也在此结束了,但是还有很多Box2D的知识。你也许可以通过本书的知识来制作 Box2D 游戏,但是程序的世界更新非常之快,你要一直更新自己的知识。

我建议你经常去访问 www.box2d.org 和 http://box2d.org/manual.pdf 官方网站和文档,同样我的博客 www.emanueleferonato.com 也会及时的更新最新的技巧和教程。

一旦你完成了你的第一个 Box2d 游戏,不要忘记感谢 Erin Catto(类库的设计者)


本文相关代码请在

https://github.com/willian12345/Box2D-for-Javascript-Games

注:转载请注明出处博客园:王二狗Sheldon池中物 (willian12345@126.com)

2D物理引擎 Box2D for javascript Games 第七章 子弹和感应器的更多相关文章

  1. HTML5之2D物理引擎 Box2D for javascript Games 系列 翻外篇--如何结合createJS应用box2d.js

    太久没有更新了,新年回来工作,突然有收到网友的邮件提问,居然还有人在关注,惭愧,找了下电脑上还有一点儿存着,顺便先发这一个番外篇吧,好歹可以看到真实的效果,等我考完英语,一定会更新下一章," ...

  2. HTML5之2D物理引擎 Box2D for javascript Games 系列 第一部分

    我要的是能在H5页面上跑的javascript版的Box2D啊!!! 最近想学习Javascript版本的Box2D JS物理引擎,无奈搜了半天也没找到相对比较系统的资料 官方网站也只是简单的介绍,A ...

  3. HTML5之2D物理引擎 Box2D for javascript Games 系列 第二部分

    这是系列第二部分,之前部分在本博客中找 源码demo存放在https://github.com/willian12345/Box2D-for-Javascript-Games 向世界添加刚体 刚体(B ...

  4. HTML5之2D物理引擎 Box2D for javascript Games 系列 第三部分之创建图腾破坏者的关卡

    创建图腾破坏者的关卡 现在你有能力创建你的第一个游戏原型,我们将从创建图腾破坏者的级别开始. 为了展示我们所做事情的真实性,我们将流行的Flash游戏图腾破坏者的一关作为 我们模仿的对象.请看下面的截 ...

  5. Matter.js – 你不能错过的 2D 物理引擎

    Matter.js 是一个 JavaScript 2D 刚体物理引擎的网页.Matter.Engine 模块包含用于创建和操作引擎的方法.这个引擎是一个管理更新和渲染世界的模拟控制器. Matter. ...

  6. cocos2dx-3.x物理引擎Box2D介绍

    理引擎 Cocos2d-x引擎内置了两种物理引擎,它们分别是Box2D和Chipmunk,都是非常优秀的2D物理引擎,而且x引擎将它们都内置在SDK中.Box2D使用较为广泛,在这里选择Box2D来进 ...

  7. 制作简单的2D物理引擎(零)

    最近发现了Github上的开源物理引擎项目Matter.js,对它很感兴趣,发现源码并不算长,算上注释大约1万行左右,值得剖析一番.Matter.js实现一个最小化的2D物理引擎,性能不错,故打算用C ...

  8. 制作简单的2D物理引擎(一)——动力学基础

    一切的基础 点 在二维平面中,点$P$就是坐标$(x,y)$,点集就是一系列坐标的集合$\{P_1,P_2,...,P_n\}$,不过这个集合是有序的(顺时针). 向量 加减运算 $$\vec{P}\ ...

  9. VUE,使用物理引擎Box2D设计类愤怒小鸟的击球游戏--基本架构设置

  10. 读《编写可维护的JavaScript》第七章总结

      第七章 事件处理 7.1 典型用法 作者首先给了个我们一个处理事件的方法.看起来也没啥俩样,不过后来给出的优化方法很值得学习: // 不好的写法 function handleClick(even ...

随机推荐

  1. linux内核编译体验篇(一)

    文章目录 一. 准备环境 二. 获取内核源码 三. 交叉编译工具链的配置 1. 博友们常用安装方法链接 2. 公司常用的交叉工具链使用方法 四. 内核解压以及如何打补丁 五. 内核基本配置 1. 编译 ...

  2. 新版Google浏览器跨域Cookie解决方案

    一.前言 针对Chrome版本67及以上 不能将其他域的Cookie传递过来 注意,这个里面的SameSite不能设为null,设空的话,还是会走默认值Lax 其中,SameSite的值可以填3个:S ...

  3. Python操纵数据库:peewee

    模块名:peewee 安装方法: pip install peewee 官方教程:http://docs.peewee-orm.com/en/latest/

  4. Blazor前后端框架Known-V1.2.10

    V1.2.10 Known是基于C#和Blazor开发的前后端分离快速开发框架,开箱即用,跨平台,一处代码,多处运行. Gitee: https://gitee.com/known/Known Git ...

  5. 介绍vue3的钩子函数activated和deactivated使用场景

    activated和deactivated是Vue3中的两个生命周期钩子函数. activated钩子函数在组件被激活时调用,通常用于恢复组件的状态或执行一些初始化操作.例如,如果一个组件被从路由中激 ...

  6. 原来你是这样的JAVA[06]-反射

    1.JVM为每个加载的class及interface创建了对应的Class实例来保存class及interface的所有信息: 获取一个class对应的Class实例后,就可以获取该class的所有信 ...

  7. react移动端上拉加载更多组件

    在开发移动端react项目中,遇到了上拉加载更多数据的分页功能,自己封装了一个组件,供大家参考,写的不好还请多多指教! import React, {Component} from 'react'; ...

  8. 文盘Rust——子命令提示,提高用户体验

    上次我们聊到 CLI 的领域交互模式.在领域交互模式中,可能存在多层次的子命令.在使用过程中如果全评记忆的话,命令少还好,多了真心记不住.频繁 --help 也是个很麻烦的事情.如果每次按 'tab' ...

  9. ubuntu22.04 安装nginx 卸载

    1.使用apt-get 安装nginx(得机器能联网才行) # 切换到root用户 # 切换到root用户 sudo -i # 更新apt源 apt-get update# 安装nginx apt-g ...

  10. 编译nw-node版本的插件

    编译nw-node版本的插件 下载nwjs对应版本的nodejs 原始源码目录 yh@yh:~/addon$ tree . ├── addon.cc ├── binding.gyp ├── CppLi ...