走进MatterJs的物理世界
我们开始之前, 首先思考几个问题:
- 为什么被称为物理引擎?
- 为什么要使用物理引擎?
- 一个物理世界需要什么元素?
- 如何用代码实现一个物理引擎?
一、思考一个物理世界的实现
为什么被称为物理引擎?
大家都知道:
一辆汽车的引擎就是他的发动机, 是控制汽车能够运动的核心.
搜索引擎就是一系列的算法的集成, 是控制互联网搜索功能的核心.
物理引擎就是一个虚拟物理世界之所以存在和被使用的控制器. 它可以管理和更新我们的物理世界.
而我们要说的物理引擎就是一个可以模拟真实的物理世界的计算机程序, 一段核心的算法库.
而这个「物理世界」就是运用计算机程序, 模拟出一个近乎真实的物理系统, 这个系统具备真实世界的一些例如重力、摩擦力和空气阻力等物理力学, 这个是世界中的物体会受到各种力的效果, 从而具有重力、旋转和碰撞等效果.
在web中我们使用的是实时的物理系统, 减少了算法开支和降低了精确度, 但同样的我们收获了计算机更少的处理时间和更快的计算速度.
而高精度的物理系统一般用于科学研究领域. (造火星探测器啥的)
为什么要使用用物理引擎?
作为一个客户端开发人员, 我们因何要使用物理引擎呢?
1、开发比较复杂同时需要计算很多物理碰撞的需求
2、需要实现一些比较真实的场景,比如跳高, 投篮, 投掷标枪等
这些场景如果给到我们的开发人员, 如果需要写算法一步一步的实现, 那是不是就要耗费很多的时间呢?
再说, 有能力写算法出来的(此处省略.....)
所以我们有理由去使用一些优秀的算法库.
一个物理世界需要什么元素?
一个比较真实的物理世界需要什么呢?
- 负责管理和控制物理世界正常运行和更新的控制器
- 负责人机交互的事件管理器
- 一系列具有物理属性的物体(刚体)
- 一些能够随意组合的复合材料集合
- 一些物理世界的坐标系、旋转轴等
- 可能还需要一个实时显示的渲染器来显示我们的世界
这些模块就可以组合出一个生动的物理世界了.
如何用代码实现一个物理引擎?
首先, 需要具备优秀的代码开发能力.
其次, 需要有一定的物理学基础.
最后, 需要具备一定的CV能力, 好的东西需要借鉴和学习嘛?.
(笔者也在探索中~~~).
二、MatterJs的控制核心
Matter
Matter
是整个MatterJs最顶层的命名空间.
定义了函数链执行顺序的方法和插件的使用.
Matter.Engine
引擎模块
负责管理物理世界的运行和更新.
options 一些属性的介绍
属性名 | 默认值 | 介绍 | positionIterations | 6 | 每次更新位置迭代次数, 值越大, 效果越好 | velocityIterations | 4 | 每次更新速度迭代次数, 值越大, 效果越好 | constraintIterations | 2 | 每次更新约束迭代次数, 值越大, 效果越好 | enableSleeping | false | 约定是否可以允许物体休眠 | events | [] | 事件集合 | plugin | {} | 插件集合 | grid | nulll | 网格实例 | gravity | { x: 0, y: 1, scale: 0.001 } | x: 重力在x方向上的分量; y: 重力在y方向上的分量; scale: 重力比例系数 | timing | timing: { timestamp: 0, timeScale: 1, lastDelta: 0, lastElapsed: 0 } | 计时系统属性 |
---|
计时系统timing
的属性:
属性名 | 默认值 | 介绍 | timestamp | 0 | 当前时间戳 | timeScale | 1 | 作用于物体上的时间的比例系数, 大于1快速, 小于1慢速, 0会冻结 | lastDelta | 0 | 上一次更新时的时间 | lastElapsed | 0 | 每次更新耗时, 包括更新和事件处理时间 |
---|
var defaults = {
positionIterations: 6,
velocityIterations: 4,
constraintIterations: 2,
enableSleeping: false,
events: [],
plugin: {},
grid: null,
gravity: {
x: 0,
y: 1,
scale: 0.001
},
timing: {
timestamp: 0,
timeScale: 1,
lastDelta: 0,
lastElapsed: 0
}
};
method 一些方法的探索
- Engine.create = (options) => {}
创建一个物理引擎的控制器.
//参数
options = {
positionIterations: 6,
velocityIterations: 4,
constraintIterations: 2,
enableSleeping: false,
events: [],
plugin: {},
grid: null,
gravity: {
x: 0,
y: 1,
scale: 0.001
},
timing: {
timestamp: 0,
timeScale: 1,
lastDelta: 0,
lastElapsed: 0
}
};
- Engine.update = (engine, delta, correction) => {}
每一帧执行的方法.
这里每一帧更新的engine
, 即上面create
创建的控制器.每一帧默认16.666ms
执行.
correction
为时间的修正比例, 默认为1, 即不修正每一帧16.6ms
update
就是真正运行物理世界的方法.我们可以在我们的游戏引擎的游戏循环中调用该方法.
MatterJs
中循环方法也是请求序列帧requestAnimationFrame
.
- Engine.merge = (engineA, engineB) => {}
可以合并两个物理世界.
使用engineB
的世界代替engineA
, 同时保留engineA
.
engineA.world = engineB.world;
Engine.clear(engineA);
var bodies = Composite.allBodies(engineA.world);
for (var i = 0; i < bodies.length; i++) {
var body = bodies[i];
Sleeping.set(body, false);
body.id = Common.nextId();
}
- Engine.clear = (engine) => {}
清除控制器engine
. 同时会清除世界、碰撞对(Pairs)以及网格实例.
var world = engine.world,
bodies = Composite.allBodies(world);
Pairs.clear(engine.pairs);
Grid.clear(engine.grid);
Grid.update(engine.grid, bodies, engine, true);
Matter.Events
事件模块.
包含事件的监听、移除和触发的方法.
- Matter.Events.on = (object, eventNames, callback) => {}
监听某一个对象上派发的事件.
可以监听多个事件名, 用空格
隔开.
- Events.off = (object, eventNames, callback) => {}
移除指定一个或者全部的事件.
指定移除eventNames
的事件回调, 同时移除事件缓存.
可以移除多个事件名, eventNames
用空格
隔开.
如果不指定事件名, 则会移除对象上的全部事件.
- Events.trigger = (object, eventNames, event) => {}
触发一个对象上的名为eventNames
的事件.
指定触发一个对象上名为eventNames
的事件, 执行其回调函数.
Matter.Plugin
插件模块.
包含了自定义插件在Matter
中注册和安装插件的功能.
- Plugin.register = (plugin) => {}
注册一个插件.
- Plugin.resolve = (dependency) => {}
解析一个插件.
- Plugin.use = (module, plugins) => {}
可以使用顶层空间Matter
的use
方法来使用此方法.
在module
上使用插件plugins
.
Matter.Plugin
模块中还有不少不重要的方法就不细讲了, 以后会再讲.
我们可以使用一些动画插件来扩展Matter
.
Matter.Mouse
人机交互模块, 鼠标事件和触摸事件.
- Mouse.create = (element) => {}
创建一个鼠标事件.
会返回一个鼠标实例.
element
是鼠标事件挂载的节点.
options
属性名 | 默认值 | 介绍 | element | document.body | 鼠标事件挂载节点 | absolute | { x: 0, y: 0 } | 绝对位置 | position | { x: 0, y: 0 } | 鼠标位置 | mousedownPosition | { x: 0, y: 0 } | 鼠标按下位置 | mouseupPosition | { x: 0, y: 0 } | 鼠标抬起位置 | offset | { x: 0, y: 0 } | 鼠标偏移量 | scale | { x: 0, y: 0 } | 鼠标位置比例系数 | wheelDelta | 0 | 鼠标转动变量 | button | -1 | 是否按钮, 触摸为0 | pixelRatio | 1 | 分辨率 |
---|
methods
- mousemove 鼠标移动事件
- mousedown 鼠标按下事件
- mouseup 鼠标抬起事件
- mousewheel 鼠标换方向事件
一共四个事件可以被我们监听处理.
- Matter.Mouse.clearSourceEvents = (mouse) => {}
清除所有的鼠标事件.
mouse.sourceEvents.mousemove = null;
mouse.sourceEvents.mousedown = null;
mouse.sourceEvents.mouseup = null;
mouse.sourceEvents.mousewheel = null;
mouse.wheelDelta = 0;
- Mouse.setElement = (mouse, element) => {}
设置鼠标事件挂载到哪一个节点.
同时设置触摸事件与鼠标操作同类型.
element.addEventListener('touchmove', mouse.mousemove);
element.addEventListener('touchstart', mouse.mousedown);
element.addEventListener('touchend', mouse.mouseup);
- Mouse.setOffset = (mouse, offset) => {}
设置鼠标事件的偏移量.
mouse.offset.x = offset.x;
mouse.offset.y = offset.y;
mouse.position.x = mouse.absolute.x * mouse.scale.x + mouse.offset.x;
mouse.position.y = mouse.absolute.y * mouse.scale.y + mouse.offset.y;
需要重置position
.
- Mouse.setScale = (mouse, scale) => {}
设置鼠标的比例系数.
mouse.scale.x = scale.x;
mouse.scale.y = scale.y;
mouse.position.x = mouse.absolute.x * mouse.scale.x + mouse.offset.x;
mouse.position.y = mouse.absolute.y * mouse.scale.y + mouse.offset.y;
需要重置position
.
一个游戏的玩法很大程度上取决于我们做的人机交互有没有恰到好处.
Matter.Runner
物理世界循环模块.
主要用于开发和调试, 一般我们在使用MatterJs
时, 都会配合我们自己的游戏引擎.
这个时候我们就可以使用我们的游戏循环来达到Matter.Runner
的效果.
这里也例举一下,触类旁通?.
options
属性名 | 默认值 | 介绍 | fps | 60 | 帧率 | correction | 1 | 是否修正, 修正系数 | deltaSampleSize | 60 | 默认间隔帧 | counterTimestamp | 0 | 计时 | frameCounter | 0 | 帧数自增 | deltaHistory | [] | 间隔数组 | timePrev | null | 上一个时间 | timeScalePrev | 1 | 上一个时间修正 | frameRequestId | null | 请求序列帧的id | isFixed | false | 是否使用固定间隔时间, 即每一帧间隔时间是否固定 | enabled | true | 是否在进行更新 |
---|
method
- Runner.create = (options) => {}
这个函数会返回一个Runner
实例.
- Runner.run = (runner, engine) => {}
这个函数会返回一个Runner
实例.
该方法运行我们创建的engine
实例.
其实这个方法只是在循环致执行Runner.tick(runner, engine, time)
;
(function render(time){
runner.frameRequestId = _requestAnimationFrame(render);
if (time && runner.enabled) {
Runner.tick(runner, engine, time);
}
})();
- Runner.tick = (runner, engine, time) => {}
这属于Matterjs
的循环机制.
在进入方法是派发事件beforeTick
.
在计算完之后派发事件tick
.
在引擎更新前派发事件beforeUpdate
.
在引擎更新之后派发事件afterUpdate
和afterTick
.
其实该方法只是在每一帧执行引擎的循环方法
Engine.update(engine, delta, correction);
我们如果不用这个方法刷新, 可以在我们自己的循环机制里调用引擎的update
方法.
- Runner.stop = (runner) => {}
循环方法的停止.
- Runner.start = (runner, engine) => {}
循环方法的开始.
Runner.run(runner, engine);
Matter.Sleeping
管理刚体睡眠状态的模块.
在物理世界中, 设置刚体的状态能有效的提升计算力.
- Sleeping.update = (bodies, timeScale) => {}
让刚体改变状态的方法.
主要还是使用Sleeping.set()
方法, 改变状态.
不需要我们主动使用, 我们会在Engine.update
时调用.
前提是我们引擎的enableSleeping
(休眠调度器)属性为true
.
- Sleeping.afterCollisions = (pairs, timeScale) => {}
在碰撞之后唤醒刚体.
唤醒条件: 非静态刚体
if (bodyA.isSleeping || bodyB.isSleeping) {
var sleepingBody = (bodyA.isSleeping && !bodyA.isStatic) ? bodyA : bodyB,
movingBody = sleepingBody === bodyA ? bodyB : bodyA;
if (!sleepingBody.isStatic && movingBody.motion > Sleeping._motionWakeThreshold * timeFactor) {
Sleeping.set(sleepingBody, false);
}
}
- Sleeping.set = (body, isSleeping) => {}
设置一个刚体是唤醒状态还是睡眠状态.
刚体入睡前会触发事件sleepStart
, 睡眠之后会触发事件sleepEnd
.
Matter.Common
通用公共模块.
这个模块封装了一系列全局使用的方法, 包括继承、克隆、获取键值等方法.
我们可以去学习它, 但并不是必须的.
其中的很多方法可以复用和学习:
/** 颜色转化 css hex colour into an integer **/
Common.colorToNumber = function(colorString) {
colorString = colorString.replace('#','');
if (colorString.length == 3) {
colorString = colorString.charAt(0) + colorString.charAt(0)
+ colorString.charAt(1) + colorString.charAt(1)
+ colorString.charAt(2) + colorString.charAt(2);
}
return parseInt(colorString, 16);
};
/** 随机数组 **/
Common.shuffle = function(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Common.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
};
等等, 就不列举了, 有兴趣可以自己查阅.
三、总结
本章节介绍了MatterJs
的核心控制模块.
一共包含7大模块:
- Matter.Engine 物理世界驱动力
- Matter.Mouse 鼠标交互事件
- Matter.Events 事件管理模块
- Matter.Plugin 可扩展插件
- Matter.Runner 循环模块
- Matter.Sleeping 刚体状态管理
- Matter.Common 通用方法模块
这些模块就是整个物理引擎的核心模块了.
这时候我们的物理引擎也出来一个模糊的世界了.
上面我们多次提到了「刚体」,下一章去我们将开始讲解物理世界中物体存在的模式.
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!