2D物理引擎 Box2D for javascript Games 第三章 刚体的交互

简介: 2D物理引擎 Box2D for javascript Games 第三章 刚体的交互

2D物理引擎 Box2D for javascript Games 第三章 刚体的交互

刚体的交互

基于Box2D的游戏都有各自的一套刚体交互方法。

图腾破坏者(Totem Destroyer)和红 砖移除(Red Remover)允许玩家通过鼠标点击来销毁刚体,然而愤怒的小鸟(Angry Birds)是通过拖拽使刚体(小鸟)飞行。

你已经知道怎样创建原始和复杂的刚体;

是时候来看看Box2D是怎样允许我们在世界中与刚体交互的。

在本章,你L将学习获u取Box2D的刚体信息进行交互的多种方式,包括:

  • 通过鼠标选择刚体
  • 销毁刚体
  • 对 刚体设置自定义属性
  • 循环遍历世界中的所有刚体
  • 获得刚体信息

通过本章的学习,你将有一个完全可玩的图腾破坏者关卡。我们与Box2D刚体最简单 并且最直观的交互方式是通过鼠标点击来销毁它们。

通过鼠标点击选择并销毁刚体

我们需要完成之前的图腾破坏者的关卡,所以这里将使用你在第二章,向世界添加

刚体中创建的脚本。你应该准备销毁顶部有神像的图腾。

  1. Main() 方法中添加事件监听:
document.querySelector('#canvas').addEventListener('click', destroyBrick)
  1. 我不会去解释前面的这几行代码,因为它与Box2D无关并且你应该已经知 道怎样创建一个鼠标监听。
  2. 事情变得越来越有趣了,在 destroyBrick() 方法中,它将被每一次鼠标点击调用:
function destroyBrick(e){
  var px = e.pageX/worldScale;
  var py = e.pageY/worldScale;
  mousePVec = new b2Vec2(px/worldScale, py/worldScale);
  world.QueryPoint(queryCallback, new b2Vec2(px, py));
}
  1. 让我们来一行行的解释。首先前两行获取mouseX和mouseY的鼠标x和y位置的值,并依据 worldScale 变量,分别将它们的像素(pixels)转换为米 (meters)。这样变量pX和pY将存储我们刚刚点击的世界坐标(米)。
    现在,是时候来看看如果一个刚体在我们点击的点上。世界的QueryPoint()方 法查询世界中所有的夹具,找出在点上的夹具,然后如果有夹具在鼠标点击 的点上,于是我们可以说我们点击了一个夹具。
    让我们来看看QueryPoint()方法的参数;首先,是一个名字为 queryCallback 的回调函数,然后是一个作为b2Vec2对象的点坐标,它代表了转换的鼠标坐标。
  2. queryCallback 方法是这个脚本的核心,它有一个参数,如果有的话,这个 参数是在鼠标点击的点上的夹具。因为一个点上可以有不止一个的夹具(试 想一下重叠的staitc类型的刚体),如果你希望检查下一个夹具,那么你需要 让函数返回true,或者返回false将停止检查。
    目前,我们假设只可以有一个夹具在鼠标点击的点上,因此,我将如下这 样编写queryCallback()方法:
function queryCallback(fixture){
  console.log(fixture)
}
  1. 此刻,我们只想要在输出窗口中输出一些一般的调试文本,所以,一旦
    你测试影片然后点击刚体,你将会看到下面的文本:
b2Fixture{...}
  1. 这意味着我们执行queryCallback()方法成功,然后我们可以确定用户点击 的夹具。
  2. 不幸的是,只是知道夹具是不够的,因为我们需要知道我将要移除的刚体。你 可以使用GetBody()方法从夹具获得刚体,因此你可以将queryCallback()方法改 变成下面这样:
function queryCallback(fixture){
  var touchedBody = fixture.GetBody();
  console.log(touchedBody)
}
  1. 如果你测试影片然后点击一个刚体,你将在输出窗口中看到下面的文本:
b2Body{...}
  1. 现在,我们知道了怎样获取玩家点击的刚体,是时候真正的开始销毁它 了。将一个刚体从世界中移除,使用世界的 DstroyBody() 方法然后你就能 打碎图腾了。
function queryCallback(fixture){
  var touchedBody = fixture.GetBody();
  world.DestroyBody(touchedBody);
}
  1. 测试网页。点击砖块然后你就会看到砖块消失了,图腾被销毁了。

    源码在 article/ch03/ch03-1.html

总之,我们仍然有一个问题。在图腾破坏者中,不是所有的砖块都可以被销毁

的,但是目前如果你点击任何一个图腾砖块,你将销毁这个图腾砖块。

而且,你甚至可以销毁神像和地面。

看看之前的截图。左边的,我销毁了地面,这是错误的。右边,我销毁了最上面的

一块图腾砖块,原游戏关卡中,这块图腾是不可被销毁的。

这是个问题,我们需要找到一个办法,去告诉Box2D那一个刚体可以被销毁, 而且那一个刚体不可以被销毁。

幸运的是,Box2D的刚体定义很出色,我们甚至可以向刚体添加我们自定义的属性特征。

将自定义属性指定到刚体上

自定义属性可以是任何类型,但是目前我们只要添加字符串 breakable 为

可销毁的图腾砖块,以及 unbreakable 为不可销毁砖块。

  1. 首先,我们向 brick() 方法传入一个字符串,因此,复制第一关的图
    腾破坏者,我们将要向下面这样修改 main() 方法:
function main(){
  world = new b2World(gravity, sleep);
  debugDraw();
  brick(275,435,30,30,"breakable"); 
  brick(365,435,30,30,"breakable"); 
  brick(320,405,120,30,"breakable");
  brick(320,375,60,30,"unbreakable");
  brick(305,345,90,30,"breakable");
  brick(320,300,120,60,"unbreakable");
  idol(320, 242);
  floor();
  setInterval(updateWorld, 1000 / 60);
  document.querySelector('#canvas').addEventListener('click', destroyBrick)
}
  1. 然后,让我们来看看怎样改变 brick() 方法
function brick(px, py, w, h, s){
  var bodyDef = new b2BodyDef();
  bodyDef.position.Set(px/worldScale, py/worldScale);
  bodyDef.type = b2Body.b2_dynamicBody;
  bodyDef.userData = s;
  var polygonShape = new b2PolygonShape();
  polygonShape.SetAsBox(w/2/worldScale, h/2/worldScale);
  var fixtureDef = new b2FixtureDef();
  fixtureDef.shape = polygonShape;
  fixtureDef.density = 2;
  fixtureDef.restitution = .4;
  fixtureDef.friction = .5;
  var theBrick = world.CreateBody(bodyDef);
  theBrick.CreateFixture(fixtureDef);
}
  1. 这个字符串参数名为 s,正如你所看到的,当我创建刚体定义时,userData 属性来存储字符串。记住这个属性,因为在你的大多数的项目中,每当你需要
    存储指定的刚体数据时,你将需要它。
    为刚添加数据之后,当我们执行queryCallback()方法时,我们需要查看我们
    的刚体的userData属性的可销毁字符串(string)。
    只有在我们发现breakable时,我们将销毁刚体,然后当我们发现unbreakable
    时,我们可以选择输出一条消息,例如“you can't destroy this brick”。但是我
    并没有这样做,因为我只是想要销毁图腾砖块。
  2. 向下面这样改变queryCallback()方法:
function queryCallback(fixture){
  var touchedBody = fixture.GetBody();
  var userData = touchedBody.GetUserData();
  if (userData === "breakable") {
      world.DestroyBody(touchedBody);
  }
}
  1. 你可以通过GetUserData()方法获取自定义属性,因此,我们只要检查 breakable 值即可。
  2. 测试,然后你将看到你只能销毁可销毁的图腾砖块。

    源码在 article/ch03/ch03-2.html

在前面的截图中,虽然左脚边的图腾砖块可以被销毁,但是如果你点击最大的那块图腾砖块它将不可被销毁。这都是关于正确的数据的存储和检索。

总之,设置和获取刚体的用户数据不只是有一种方法,我们必须检查关于我们刚体的信息。

在任何时候,我们可以扫描世界中所有的刚体然后获取它们的位置和旋转角度,以及它的速度和别的有用的信息。

遍历刚体并获取它们的属性

下一个示例将创建监视神像的一些种类的信息显示。在每一帧上,我们读取神像的位置,旋转角度以及熟读。这样,你可以根据神像的属性分派事件,例如:神像没有落地,要给与奖励,或者它从未达到一定的y轴速度,或者它移动到太左边等等。知道刚体的属性是非常有用的,当你想要为你的游戏添加皮肤时,以为你可以同步自定义绘制的资源与Box2D世界中发生的事情。

一步步来,让我们先从神像的监视器开始。我们将神像的数据显示在一个动态的文本框,因此我们需要对我们的类做一些基础的改动。

  1. 添加一个 id 为 textMon 的 div 用于显示信息,文本框样式你自己随便设置,我把文本样式设为了白色绝对定位在了左上角
<div class="info" id="textMon"></div>
  1. 现在,我们需要一种方法来识别神像。可以使用我们刚刚使用的那个方法,使用 userData 属性。
  2. 所以,我们按照和之前向图腾砖块添加属性时一样的方式,为神像添加属性,我们使用 idol 作为属性值,而不是 breakable 或 unbreakable。
    在 function idol(px, py){} 方法内添加:
bodyDef.userData="idol";
  1. 现在,我们将核心的脚本代码插入 updateWorld() 方法:
var radToDeg = 180 / Math.PI;
var idolBody, $text = document.querySelector('#textMon');
for (var b = world.GetBodyList();
    b; b = b.GetNext()) {
    if (b.GetUserData() == 'idol') {
      idolBody = b;
    }
}
function updateWorld() {
    world.Step(1 / 30, 10, 10);// 更新世界模拟
    world.DrawDebugData(); // 显示刚体debug轮廓
    world.ClearForces(); // 清除作用力
    if (idolBody) {
      var position = idolBody.GetPosition();
      var xPos = Math.round(position.x * worldScale);
      var yPos = Math.round(position.y * worldScale);
      var angle = idolBody.GetAngle() * radToDeg;
      var velocity = idolBody.GetLinearVelocity();
      var xVel = Math.round(velocity.x * worldScale);
      var yVel = Math.round(velocity.y * worldScale);
      $text.innerHTML = (xPos + ',' + yPos + '<br/>' + angle + '<br/>' + xVel + ',' + yVel);
    }
}
  1. 在 updateWorld 方法外添加了获取 idoBody 的方式,注意这代码需要在 idol 添加之后让我先来看看for循环。GetBodyList() 方法获得世界刚体列表并返回列表的第一个刚体。你可以使用 GetNext() 方法获得列表的下一个刚体,但是如果你的刚体已经到达了刚体列表的末端,这时GetNext()方法将返回null。所以在for循环里有三个参数,我们可以在下面看到它们的具体意义:
  • var b:b2Body=world.GetBodyList() 定义一个 b2Body 变量,变量名为b然后指派世界刚体列表的第一个刚体给它。
  • b 是循环继续的条件,所以只要 b 一直存在,循环将一直继续,这也意味着我们处理的刚体还没到达刚体列表的末端。
  • b=b.GetNext() 获得下一个刚体,这类似于i++在for循环中的作用
  1. 现在,在 for 循环中我们有一个 b 变量代表当前的刚体。让我们看看对它做些什么吧。首先,我们需要知道我们处理的是不是神像,所以我们将检查它的 userData(该属性是设置时的属性,而要读取该属性需从 b2Body 的 GetUserData() 方法返回值):
if (b.GetUserData() == "idol") { /* 对神像操作的代码 */ }
  1. 一旦我们知道我们处理的是神像刚体,我们就获取它的位置:
var position:b2Vec2=b.GetPosition();
  1. GePosiion 方法返回一个 b2Vec2 对象,它代表刚体的原始位置。如果我们想要知道以像素为单位的位置,我们必须将米(meters)转换为像素(pixels),这个你已经很习惯使用了:
var xPos:Number=Math.round(position.x*worldScale);
var yPos:Number=Math.round(position.y*worldScale);
  1. 前面的代码将执行转换任务。现在我们来看看神像的旋转。
var angle:Number=Math.round(b.GetAngle()*radToDeg);
  1. GetAngle 方法获得刚体旋转的弧度。但是,我们有时会觉得使用角度会更舒服,那么我通过将 GetAngle() 方法返回的弧度乘以一个变量名为 radToDeg 的数值,将弧度转换为角度,你可以在 updateWorld() 方法前的第一行找到这个变量。
    我们可以从神像获得很多额外的数据,但是目前我们只要获得某些信息,例如:速率:
var velocity:b2Vec2=b.GetLinearVelocity();
  1. GetLinearVelocity() 方法获得物体中心的线性速率,然后返回一个 b2Vec2 对象它代表水平和垂直速度,单位是米每秒。
    因为我们仍然想以像素为单位,那么就需要将它们转换:
var xVel = Math.round(velocity.x * worldScale);
var yVel = Math.round(velocity.y * worldScale);
  1. 用 $text 输入显示到网页上
$text.innerHTML = (xPos + ',' + yPos + '<br/>' + angle + '<br/>' + xVel + ',' + yVel);
  1. 测试网页
    做一些可以移动神像的事情(例如点击一个可销毁的图腾砖块,这样平衡被打破,神像就会移动了),然后你将看到神像监视器在更新神像的实时数据:

    目前,这些可以满足刚体的交互;我不想给你添加过多的负载信息,因为在你弄明白这些交互的真实用处之前,仍然还有很多要解释的东西。
    通过这种方法,现在你学习到了怎样通过鼠标点击销毁图腾砖块,然后跟踪神像的位置和角度信息
    源码在 article/ch03/ch03-3.html

小结

现在,你知道这样选择并销毁一个刚体,以及获得一个刚体信息。有了这些知识,你可以创建一个完整的,但还不是很高级的图腾破坏者原型了。

虽然你还不能确定神像是否接触到地面(游戏失败),但是你可以检测它的角度

相关文章
|
4月前
|
开发框架 JavaScript 前端开发
揭秘:如何让你的asp.net页面变身交互魔术师——先施展JavaScript咒语,再引发服务器端魔法!
【8月更文挑战第16天】在ASP.NET开发中,处理客户端与服务器交互时,常需先执行客户端验证再提交数据。传统上使用ASP.NET Button控件直接触发服务器事件,但难以插入客户端逻辑。本文对比此法与改进方案:利用HTML按钮及JavaScript手动控制表单提交。后者通过`onclick`事件调用JavaScript函数`SubmitForm()`来检查输入并决定是否提交,增强了灵活性和用户体验,同时确保了服务器端逻辑的执行。
50 5
|
24天前
|
设计模式 前端开发 JavaScript
揭秘!前端大牛们如何巧妙利用JavaScript,打造智能交互体验!
【10月更文挑战第30天】前端开发领域充满了无限可能与创意,JavaScript作为核心语言,凭借强大的功能和灵活性,成为打造智能交互体验的重要工具。本文介绍前端大牛如何利用JavaScript实现平滑滚动、复杂动画、实时数据更新和智能表单验证等效果,展示了JavaScript的多样性和强大能力。
39 4
|
2月前
|
存储 JavaScript 前端开发
【JavaScript】网页交互的灵魂舞者
本文介绍了 JavaScript 的三种引入方式(行内、内部、外部)和基础语法,包括变量、数据类型、运算符、数组、函数和对象等内容。同时,文章还详细讲解了 jQuery 的基本语法和常用方法,如 `text()`、`html()`、`val()`、`attr()` 和 `css()` 等,以及如何插入和删除元素。通过示例代码和图解,帮助读者更好地理解和应用这些知识。
19 1
【JavaScript】网页交互的灵魂舞者
|
3月前
|
JavaScript 前端开发
JavaScript 与 DOM 交互
【9月更文挑战第01天】
35 2
|
4月前
|
JavaScript 前端开发 UED
Vue.js动画魔法:解锁流畅过渡,让每一次交互都成为用户心中的小确幸!
【8月更文挑战第30天】在Vue.js中,动画与过渡效果不仅是视觉点缀,更是提升用户体验的关键。通过流畅的动态效果,应用的互动性和吸引力得以增强,从而提高用户满意度和参与度。`&lt;transition&gt;`和`&lt;transition-group&gt;`组件结合CSS过渡,可轻松实现元素的进入、离开及列表变化动画。合理的性能优化,如使用硬件加速,能避免页面卡顿,确保动画既美观又高效。下面是一个简单的淡入淡出效果示例,展示了如何利用Vue.js实现平滑的动画过渡。总之,恰当的动画设计能显著提升应用的用户体验。
59 0
Vue.js动画魔法:解锁流畅过渡,让每一次交互都成为用户心中的小确幸!
|
4月前
|
前端开发 JavaScript 内存技术
2D物理引擎 Box2D for javascript Games 第二章 向世界添加刚体
2D物理引擎 Box2D for javascript Games 第二章 向世界添加刚体
|
4月前
|
Devops 持续交付 测试技术
JSF遇上DevOps:开发流程将迎巨变?一篇文章带你领略高效协同的魅力!
【8月更文挑战第31天】本文探讨了如何在JavaServer Faces(JSF)开发中融入DevOps文化,通过持续集成与部署、自动化测试、监控与日志记录及反馈机制,提升软件交付速度与质量。文中详细介绍了使用Jenkins进行自动化部署、JUnit与Selenium进行自动化测试、ELK Stack进行日志监控的具体方法,并强调了持续改进的重要性。
39 0
|
4月前
|
JavaScript 前端开发 API
从零开始学表单操作,jQuery 与原生 JavaScript 完全指南,带你轻松掌握网页交互关键!
【8月更文挑战第31天】在网页开发中,表单是实现用户互动的关键元素。无论是收集信息、提交数据还是验证输入,都需要对表单进行有效操作。本文档介绍了如何使用原生 JavaScript 和 jQuery 操作表单,包括获取表单元素、读写表单值、处理表单提交及验证等核心功能。jQuery 提供了更简洁的语法和更好的兼容性,但原生 JavaScript 在性能上有优势。选择合适的方法取决于项目需求和个人偏好。下面通过具体示例展示了两种方式的操作方法。
40 0
|
4月前
|
前端开发 JavaScript
前端 JavaScript 与 HTML 怎么实现交互
前端 JavaScript 与 HTML 怎么实现交互
|
4月前
|
移动开发 前端开发 JavaScript
2D物理引擎 Box2D for javascript Games -- 番外篇-- (为游戏添加皮肤)
2D物理引擎 Box2D for javascript Games -- 番外篇-- (为游戏添加皮肤)