Laya 物理引擎系列二 如何在Laya中使用matter.js
2018-07-18 本文已影响961人
合肥黑
一、在LAYA中使用
1.需要在index.html中添加如下引用
<!-- 物理引擎matter.js -->
<script type="text/javascript" src="libs/matter.js"></script>
<script type="text/javascript" src="libs/LayaRender.js"></script>
LayaRender这个js文件,只有js和ts项目有,以下是源码:
/**
* Matter.js 渲染器在 LayaAir 的实现。
*/
(function()
{
var LayaRender = {};
var Common = Matter.Common;
var Composite = Matter.Composite;
var Bounds = Matter.Bounds;
var Events = Matter.Events;
var Grid = Matter.Grid;
var Vector = Matter.Vector;
/**
* 创建新的渲染器。
* @param {object} options 所有属性都有默认值,options中的属性会覆盖默认属性。
* @return {render} 返回创建的旋绕器
*/
LayaRender.create = function(options)
{
var defaults = {
controller: LayaRender,
engine: null,
element: null,
canvas: null,
mouse: null,
frameRequestId: null,
options:
{
width: 800,
height: 600,
pixelRatio: 1,
background: '#fafafa',
wireframeBackground: '#222222',
hasBounds: !!options.bounds,
enabled: true,
wireframes: true,
showSleeping: true,
showDebug: false,
showBroadphase: false,
showBounds: false,
showVelocity: false,
showCollisions: false,
showSeparations: false,
showAxes: false,
showPositions: false,
showAngleIndicator: false,
showIds: false,
showShadows: false,
showVertexNumbers: false,
showConvexHulls: false,
showInternalEdges: false,
showMousePosition: false
}
};
var render = Common.extend(defaults, options);
render.mouse = options.mouse;
render.engine = options.engine;
// 如果用户没有指定contaienr,默认使用stage
render.container = render.container || Laya.stage;
render.bounds = render.bounds ||
{
min:
{
x: 0,
y: 0
},
max:
{
x: render.width,
y: render.height
}
};
return render;
}
/**
* 运行渲染器。
* @param {render} render 渲染的目标是LayaRender.create()返回的对象
* @return {void}
*/
LayaRender.run = function(render)
{
Laya.timer.frameLoop(1, this, LayaRender.world, [render]);
Events.on(render.engine.world, 'afterRemove', LayaRender.onRemoveSprite);
};
/**
* 停止渲染器。
* @param {render} LayaRender.create()返回的对象
* @return {void}
*/
LayaRender.stop = function(render)
{
Laya.timer.clear(this, LayaRender.world);
Events.off(render.engine.world, 'afterRemove', LayaRender.onRemoveSprite);
}
LayaRender.onRemoveSprite = function(args)
{
var sprite = args.object.layaSprite;
if (sprite && sprite.parent)
sprite.parent.removeChild(sprite);
}
/**
* 渲染给定的 engine 的 Matter.World 对象。
* 这是渲染的入口,每次场景改变时都应该被调用。
* @param {render} render
* @return {void}
*/
LayaRender.world = function(render)
{
var engine = render.engine,
world = engine.world,
renderer = render.renderer,
container = render.container,
options = render.options,
bodies = Composite.allBodies(world),
allConstraints = Composite.allConstraints(world),
constraints = [],
i;
if (options.wireframes)
{
LayaRender.setBackground(render, options.wireframeBackground);
}
else
{
LayaRender.setBackground(render, options.background);
}
// 处理 bounds
var boundsWidth = render.bounds.max.x - render.bounds.min.x,
boundsHeight = render.bounds.max.y - render.bounds.min.y,
boundsScaleX = boundsWidth / render.options.width,
boundsScaleY = boundsHeight / render.options.height;
if (options.hasBounds)
{
// 隐藏不在视口内的bodies
for (i = 0; i < bodies.length; i++)
{
var body = bodies[i];
body.render.sprite.visible = Bounds.overlaps(body.bounds, render.bounds);
}
// 过滤掉不在视口内的 constraints
for (i = 0; i < allConstraints.length; i++)
{
var constraint = allConstraints[i],
bodyA = constraint.bodyA,
bodyB = constraint.bodyB,
pointAWorld = constraint.pointA,
pointBWorld = constraint.pointB;
if (bodyA) pointAWorld = Vector.add(bodyA.position, constraint.pointA);
if (bodyB) pointBWorld = Vector.add(bodyB.position, constraint.pointB);
if (!pointAWorld || !pointBWorld)
continue;
if (Bounds.contains(render.bounds, pointAWorld) || Bounds.contains(render.bounds, pointBWorld))
constraints.push(constraint);
}
// 改变视口
container.scale(1 / boundsScaleX, 1 / boundsScaleY);
container.pos(-render.bounds.min.x * (1 / boundsScaleX), -render.bounds.min.y * (1 / boundsScaleY));
}
else
{
constraints = allConstraints;
}
for (i = 0; i < bodies.length; i++)
LayaRender.body(render, bodies[i]);
for (i = 0; i < constraints.length; i++)
LayaRender.constraint(render, constraints[i]);
};
/**
* 设置背景色或者背景图片。
* @param {render} render
* @param {string} background 16进制颜色字符串或者图片路径
*/
LayaRender.setBackground = function(render, background)
{
if (render.currentBackground !== background)
{
var isColor = background.indexOf && background.indexOf('#') !== -1;
render.container.graphics.clear();
if (isColor)
{
// 使用纯色背景
render.container.bgColor = background;
}
else
{
render.container.loadImage(background);
// 使用背景图片时把背景色设置为白色
render.container.bgColor = "#FFFFFF";
}
render.currentBackground = background;
}
}
/**
* 渲染 body
* @param {render} render
* @param {body} body
* @return {void}
*/
LayaRender.body = function(render, body)
{
var engine = render.engine,
bodyRender = body.render;
if (!bodyRender.visible)
return;
// 有纹理的body
if (bodyRender.sprite && bodyRender.sprite.texture)
{
var spriteId = 'b-' + body.id,
sprite = body.layaSprite,
container = render.container;
// 如果sprite不存在,则初始化一个
if (!sprite)
sprite = body.layaSprite = _createBodySprite(render, body);
// 如果sprite未在显示列表,则添加至显示列表
if (!container.contains(sprite))
container.addChild(sprite);
// 更新sprite位置
sprite.x = body.position.x;
sprite.y = body.position.y;
sprite.rotation = body.angle * 180 / Math.PI;
sprite.scaleX = bodyRender.sprite.xScale || 1;
sprite.scaleY = bodyRender.sprite.yScale || 1;
}
else // 没有纹理的body
{
var primitiveId = 'b-' + body.id,
sprite = body.layaSprite,
container = render.container;
// 如果sprite不存在,则初始化一个
if (!sprite)
{
sprite = body.layaSprite = _createBodyPrimitive(render, body);
sprite.initialAngle = body.angle;
}
// 如果sprite未在显示列表,则添加至显示列表
if (!container.contains(sprite))
container.addChild(sprite);
// 更新sprite位置
sprite.x = body.position.x;
sprite.y = body.position.y;
sprite.rotation = (body.angle - sprite.initialAngle) * 180 / Math.PI;
}
};
/**
* 创建使用纹理的Sprite对象。
* @param {render} render
* @param {body} body
* @return {void}
*/
var _createBodySprite = function(render, body)
{
var bodyRender = body.render,
texturePath = bodyRender.sprite.texture,
sprite = new Laya.Sprite();
sprite.loadImage(texturePath);
sprite.pivotX = body.render.sprite.xOffset;
sprite.pivotY = body.render.sprite.yOffset;
return sprite;
};
/**
* 创建使用矢量绘图的Sprite对象。
* @param {render} render
* @param {body} body
* @return {void}
*/
var _createBodyPrimitive = function(render, body)
{
var bodyRender = body.render,
options = render.options,
sprite = new Laya.Sprite(),
fillStyle, strokeStyle, lineWidth,
part, points = [];
var primitive = sprite.graphics;
primitive.clear();
// 处理 compound parts
for (var k = body.parts.length > 1 ? 1 : 0; k < body.parts.length; k++)
{
part = body.parts[k];
if (!options.wireframes)
{
fillStyle = bodyRender.fillStyle;
strokeStyle = bodyRender.strokeStyle;
lineWidth = bodyRender.lineWidth;
}
else
{
fillStyle = null;
strokeStyle = '#bbbbbb';
lineWidth = 1;
}
points.push(part.vertices[0].x - body.position.x, part.vertices[0].y - body.position.y);
for (var j = 1; j < part.vertices.length; j++)
{
points.push(part.vertices[j].x - body.position.x, part.vertices[j].y - body.position.y);
}
points.push(part.vertices[0].x - body.position.x, part.vertices[0].y - body.position.y);
primitive.drawPoly(0, 0, points, fillStyle, strokeStyle, lineWidth);
// 角度指示器
if (options.showAngleIndicator || options.showAxes)
{
lineWidth = 1;
if (options.wireframes)
{
strokeStyle = '#CD5C5C';
}
else
{
strokeStyle = bodyRender.strokeStyle;
}
primitive.drawLine(part.position.x - body.position.x, part.position.y - body.position.y,
((part.vertices[0].x + part.vertices[part.vertices.length - 1].x) / 2 - body.position.x),
((part.vertices[0].y + part.vertices[part.vertices.length - 1].y) / 2 - body.position.y));
}
}
return sprite;
};
/**
* 绘制 constraint。
* @param {render} render
* @param {constraint} constraint
* @return {void}
*/
LayaRender.constraint = function(render, constraint)
{
var engine = render.engine,
bodyA = constraint.bodyA,
bodyB = constraint.bodyB,
pointA = constraint.pointA,
pointB = constraint.pointB,
container = render.container,
constraintRender = constraint.render,
primitiveId = 'c-' + constraint.id,
sprite = constraint.layaSprite;
// 如果sprite不存在,则初始化一个
if (!sprite)
sprite = constraint.layaSprite = new Laya.Sprite();
var primitive = sprite.graphics;
// constraint 没有两个终点时不渲染
if (!constraintRender.visible || !constraint.pointA || !constraint.pointB)
{
primitive.clear();
return;
}
// 如果sprite未在显示列表,则添加至显示列表
if (!container.contains(sprite))
container.addChild(sprite);
// 渲染 constraint
primitive.clear();
var fromX, fromY, toX, toY;
if (bodyA)
{
fromX = bodyA.position.x + pointA.x;
fromY = bodyA.position.y + pointA.y;
}
else
{
fromX = pointA.x;
fromY = pointA.y;
}
if (bodyB)
{
toX = bodyB.position.x + pointB.x;
toY = bodyB.position.y + pointB.y;
}
else
{
toX = pointB.x;
toY = pointB.y;
}
primitive.drawLine(fromX, fromY, toX, toY, constraintRender.strokeStyle, constraintRender.lineWidth);
};
window.LayaRender = LayaRender;
})();
二、官方例子
Laya示例-布
Laya示例-牛顿摆
Laya示例-投石射击
1.基本步骤
在Laya 物理引擎matter.js 系列一 matter基础中,看到原生的matter.js大致步骤如下:
var Engine = Matter.Engine,
Render = Matter.Render,
World = Matter.World,
Bodies = Matter.Bodies;
//第1步构造engine:
var engine = Engine.create();
Engine.run(engine);
//第2步构造render:
//render(渲染器)将要渲染的物理引擎是之前所创建的engine,而渲染的对象是html网页的body
var render = Render.create({
element: document.body,
engine: engine
});
Render.run(render);
//第3步在world中添加物体:
var boxA = Bodies.rectangle(200, 200, 80, 80);
var ground = Bodies.rectangle(400, 510, 810, 60, { isStatic: true });
World.add(engine.world, [boxA, ground]);
在Laya示例中,除了Render要换成LayaRender,其它基本一致
private stageWidth: number = 800;
private stageHeight: number = 600;
private Matter: any = Browser.window.Matter;
private LayaRender: any = Browser.window.LayaRender;
private mouseConstraint: any;
private engine: any;
private initMatter(): void {
//通过 enableSleeping: true 开启睡眠模式后,当刚体处于不受作用状态时,会进入睡眠状态,
//这样可以有效的提高引擎的性能,当物体被其他物体碰撞或者对刚体施加力时,
//刚体会被叫醒,引擎会继续对其进行计算模拟
this.engine = Matter.Engine.create({ enableSleeping: true });
Matter.Engine.run(this.engine);
var gameWorld: Sprite = new Sprite();
Laya.stage.addChild(gameWorld);
//如果用户没有指定contaienr,默认使用stage
var render = this.LayaRender.create({ engine: this.engine, container: gameWorld,
width: this.stageWidth, height: this.stageHeight, options: { wireframes: false } });
this.LayaRender.run(render);
this.mouseConstraint = Matter.MouseConstraint.create(this.engine, { element: Laya.Render.canvas });
Matter.World.add(this.engine.world, this.mouseConstraint);
render.mouse = this.mouseConstraint.mouse;
}
2.resize方法需要注意一下
Laya.stage.on("resize", this, this.onResize);
private onResize() {
// 设置鼠标的坐标缩放
Matter.Mouse.setScale(
this.mouseConstraint.mouse,
{
x: 1 / (Laya.stage.clientScaleX * Laya.stage._canvasTransform.a),
y: 1 / (Laya.stage.clientScaleY * Laya.stage._canvasTransform.d)
});
}
3.指定texture
在Laya示例-投石射击中,指定了游戏的背景图:
var render = this.LayaRender.create({
engine: this.engine, container: gameWorld, width: 800, height: 600,
options: { background: 'res/physics/img/background.png', wireframes: false }
});
this.LayaRender.run(render);
在LayaRender.js中可以看到针对background属性的解析:
/**
* 设置背景色或者背景图片。
* @param {render} render
* @param {string} background 16进制颜色字符串或者图片路径
*/
LayaRender.setBackground = function(render, background)
{
if (render.currentBackground !== background)
{
var isColor = background.indexOf && background.indexOf('#') !== -1;
render.container.graphics.clear();
if (isColor)
{
// 使用纯色背景
render.container.bgColor = background;
}
else
{
render.container.loadImage(background);
// 使用背景图片时把背景色设置为白色
render.container.bgColor = "#FFFFFF";
}
render.currentBackground = background;
}
}
创建世界如下:
private initWorld(): void {
//地面刚体,设置为不可见,因为背景图上有一个相同大小,但是更好看的绿色地面
var ground: any = this.Matter.Bodies.rectangle(395, 600, 815, 50, {
isStatic: true,
render: { visible: false }
});
//density密度,影响小球动能,同样力度,密度小时根本撞不倒那一堆积木
//texture会给小球加上皮肤
var rockOptions: any = {
density: 0.004, render: {
texture: 'res/physics/img/rock.png',
sprite: { xOffset: 23.5, yOffset: 23.5 }
}
};
var rock: any = this.Matter.Bodies.polygon(170, 450, 8, 20, rockOptions);
//弹性约束
var anchor: any = { x: 170, y: 450 };
var elastic: any = this.Matter.Constraint.create({
pointA: anchor,
bodyB: rock,
stiffness: 0.05,
render: { lineWidth: 5, strokeStyle: '#dfa417' }
});
//金字塔堆
var pyramid: any = this.Matter.Composites.pyramid(500, 300, 9, 10, 0, 0,
function (x, y, column): any {
var texture: any = column % 2 === 0 ?
'res/physics/img/block.png' : 'res/physics/img/block-2.png';
return this.Matter.Bodies.rectangle(x, y, 25, 40, {
render: {
sprite: { texture: texture, xOffset: 20.5, yOffset: 28 }
}
});
});
var ground2: any = this.Matter.Bodies.rectangle(610, 250, 200, 20, {
isStatic: true,
render: { fillStyle: '#edc51e', strokeStyle: '#b5a91c' }
});
var pyramid2: any = this.Matter.Composites.pyramid(550, 0, 5, 10, 0, 0,
function (x, y, column): any {
var texture: any = column % 2 === 0 ?
'res/physics/img/block.png' : 'res/physics/img/block-2.png';
return this.Matter.Bodies.rectangle(x, y, 25, 40, {
render: {
sprite: { texture: texture, xOffset: 20.5, yOffset: 28 }
}
});
});
this.Matter.World.add(this.engine.world, [this.mouseConstraint,
ground, pyramid, ground2, pyramid2, rock, elastic]);
this.Matter.Events.on(this.engine, 'afterUpdate', function (): any {
if (this.mouseConstraint.mouse.button === -1 &&
(rock.position.x > 190 || rock.position.y < 430)) {
rock = this.Matter.Bodies.polygon(170, 450, 7, 20, rockOptions);
this.Matter.World.add(this.engine.world, rock);
elastic.bodyB = rock;
}
}.bind(this));
}
三、其他问题
1.参考物理环境如何摧毁
Matter.World.remove 循环删除
Matter.Engine.clear(engine)
LayaRender.stop(render);
2.服务端做物理碰撞,参考Websocket做Matter.js状态同步
现在unity3d做的mmo是如何在服务器端做碰撞检测或nav mesh寻路的
MMO寻路的各种实现方式:
- 服务端执行寻路,客户端纯粹表现。
- 客户端执行寻路,服务端验证结果(可能抽样验证)。
- 客户端和服务端都执行寻路,并保证同步。
如果NPC需要寻路,服务端就必须有寻路功能。Unity本身的设计不适合用于MMO服务端,而一般公司也很难取得源码再实现相同的寻路规则。如果服务端就必须有寻路功能或验证功能,建议不要使用Unity本身的寻路功能。可以选择自行开发或使用一些中间件如PathEngine Autodesk Navigation
类桌球联网对战类游戏,服务端怎么防止一方作弊? 这种的话防作弊最简单的方式是服务器计算校验。当然,不能使用unity自带的碰撞检测什么的,需要自己实现不基于浮点数的碰撞(各个设备对浮点数的处理不一致)