走进MatterJs的核心模块-MatterJs(二)

走进MatterJs的物理世界

文章基于MatterJs ^0.17.1

我们开始之前, 首先思考几个问题:

  1. 为什么被称为物理引擎?
  2. 为什么要使用物理引擎?
  3. 一个物理世界需要什么元素?
  4. 如何用代码实现一个物理引擎?

一、思考一个物理世界的实现

为什么被称为物理引擎?

大家都知道:
一辆汽车的引擎就是他的发动机, 是控制汽车能够运动的核心.
搜索引擎就是一系列的算法的集成, 是控制互联网搜索功能的核心.

物理引擎就是一个虚拟物理世界之所以存在和被使用的控制器. 它可以管理和更新我们的物理世界.
而我们要说的物理引擎就是一个可以模拟真实的物理世界的计算机程序, 一段核心的算法库.

而这个「物理世界」就是运用计算机程序, 模拟出一个近乎真实的物理系统, 这个系统具备真实世界的一些例如重力、摩擦力和空气阻力等物理力学, 这个是世界中的物体会受到各种力的效果, 从而具有重力、旋转和碰撞等效果.

在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 每次更新耗时, 包括更新和事件处理时间

前三个属性设置时注意, 值不是越大越好, 越大也会越消耗性能.
enableSleeping, 物体休眠可以提高稳定性和表现形式, 但是物体的位置速度准确性就会下降.

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) => {}

创建一个物理引擎的控制器.

@param {object} [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) => {}

每一帧执行的方法.

@method update
@param {engine} engine
@param {number} [delta=16.666]
@param {number} [correction=1]

这里每一帧更新的engine, 即上面create创建的控制器.每一帧默认16.666ms执行.
correction为时间的修正比例, 默认为1, 即不修正每一帧16.6ms

update就是真正运行物理世界的方法.我们可以在我们的游戏引擎的游戏循环中调用该方法.
MatterJs中循环方法也是请求序列帧requestAnimationFrame.


  • Engine.merge = (engineA, engineB) => {}

可以合并两个物理世界.

@param {engine} engineA
@param {engine} 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)以及网格实例.

@param {engine} engine

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) => {}

监听某一个对象上派发的事件.

@param {} object 对象
@param {string} eventNames 事件名字
@param {function} callback 回调函数

可以监听多个事件名, 用空格 隔开.


  • Events.off = (object, eventNames, callback) => {}

移除指定一个或者全部的事件.

@param {} object
@param {string} eventNames
@param {function} callback

指定移除eventNames的事件回调, 同时移除事件缓存.
可以移除多个事件名, eventNames用空格 隔开.
如果不指定事件名, 则会移除对象上的全部事件.


  • Events.trigger = (object, eventNames, event) => {}

触发一个对象上的名为eventNames的事件.

@param {} object
@param {string} eventNames
@param {} event

指定触发一个对象上名为eventNames的事件, 执行其回调函数.

Matter.Plugin

插件模块.
包含了自定义插件在Matter中注册和安装插件的功能.


  • Plugin.register = (plugin) => {}

注册一个插件.

@param plugin {} The plugin to register. — 注册的插件
@return {object} The plugin. 可以返回该插件模块


  • Plugin.resolve = (dependency) => {}

解析一个插件.

@param dependency {string} The dependency. — 依赖


  • Plugin.use = (module, plugins) => {}

可以使用顶层空间Matteruse方法来使用此方法.

@param module {} The module install plugins on.
@param [plugins=module.uses] {} The plugins to install on module (optional, defaults to module.uses).

module上使用插件plugins.
Matter.Plugin模块中还有不少不重要的方法就不细讲了, 以后会再讲.
我们可以使用一些动画插件来扩展Matter.

Matter.Mouse

人机交互模块, 鼠标事件和触摸事件.


  • Mouse.create = (element) => {}

创建一个鼠标事件.
会返回一个鼠标实例.

@param {HTMLElement} 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) => {}

清除所有的鼠标事件.

@param {mouse} mouse

mouse.sourceEvents.mousemove = null;
mouse.sourceEvents.mousedown = null;
mouse.sourceEvents.mouseup = null;
mouse.sourceEvents.mousewheel = null;
mouse.wheelDelta = 0;
复制代码
  • Mouse.setElement = (mouse, element) => {}

设置鼠标事件挂载到哪一个节点.

@param {mouse} mouse
@param {HTMLElement} element

同时设置触摸事件与鼠标操作同类型.

element.addEventListener('touchmove', mouse.mousemove);
element.addEventListener('touchstart', mouse.mousedown);
element.addEventListener('touchend', mouse.mouseup);
复制代码

  • Mouse.setOffset = (mouse, offset) => {}

设置鼠标事件的偏移量.

@param {mouse} mouse
@param {vector} 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) => {}

设置鼠标的比例系数.

@param {mouse} mouse
@param {vector} 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实例.

@param {} options — 可以配置上述的配置

  • Runner.run = (runner, engine) => {}

这个函数会返回一个Runner实例.
该方法运行我们创建的engine实例.

@param {engine} engine
@param {runner} runner

其实这个方法只是在循环致执行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.
在引擎更新之后派发事件afterUpdateafterTick.

其实该方法只是在每一帧执行引擎的循环方法

Engine.update(engine, delta, correction);
复制代码

我们如果不用这个方法刷新, 可以在我们自己的循环机制里调用引擎的update方法.


  • Runner.stop = (runner) => {}

循环方法的停止.


  • Runner.start = (runner, engine) => {}

循环方法的开始.

Runner.run(runner, engine);
复制代码

Matter.Sleeping

管理刚体睡眠状态的模块.
在物理世界中, 设置刚体的状态能有效的提升计算力.

  • Sleeping.update = (bodies, timeScale) => {}

让刚体改变状态的方法.

@param {body[]} bodies
@param {number} 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 通用方法模块

这些模块就是整个物理引擎的核心模块了.
这时候我们的物理引擎也出来一个模糊的世界了.
上面我们多次提到了「刚体」,下一章去我们将开始讲解物理世界中物体存在的模式.

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享