Laya 物理引擎系列二 如何在Laya中使用matter.js

一、在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自带的碰撞检测什么的,需要自己实现不基于浮点数的碰撞(各个设备对浮点数的处理不一致)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容