金沙国际官网_金沙国际平台登录

因为这个金沙国际官网_金沙国际平台登录网站与很多的大型澳门赌场都有合作,金沙国际官网_金沙国际平台登录尽职尽责,高效执行,保持好奇心,不断学习,追求卓越,点击进入金沙国际官网_金沙国际平台登录马上体验吧,所以现在也正式地开始了营业。

您的位置:金沙国际官网 > web前端 > 常见的2D碰撞检测,前端基础进阶

常见的2D碰撞检测,前端基础进阶

发布时间:2019-11-01 12:36编辑:web前端浏览(174)

    “等一下,我碰!”——常见的2D碰撞检测

    2017/02/22 · HTML5 · 1 评论 · 碰撞检测

    原文出处: 凹凸实验室   

    图片 1

    “碰乜鬼嘢啊,碰走晒我滴靓牌”。想到“碰”就自然联想到了“麻将”这一伟大发明。当然除了“碰”,洗牌的时候也充满了各种『碰撞』。

    好了,不废话。直入主题——碰撞检测。

    在 2D 环境下,常见的碰撞检测方法如下:

    • 外接图形判别法
      • 轴对称包围盒(Axis-Aligned Bounding Box),即无旋转矩形。
      • 圆形碰撞
    • 光线投射法
    • 分离轴定理
    • 其他
      • 地图格子划分
      • 像素检测

    下文将由易到难的顺序介绍上述各种碰撞检测方法:外接图形判别法 > 其他 > 光线投射法 > 分离轴定理。

    另外,有一些场景只要我们约定好限定条件,也能实现我们想要的碰撞,如下面的碰壁反弹:

    当球碰到边框就反弹(如x/y轴方向速度取反)。

    JavaScript

    if(ball.left < 0 || ball.right > rect.width) ball.velocityX = -ball.velocityX if(ball.top < 0 || ball.bottom > rect.height) ball.velocityY = -ball.velocityY

    1
    2
    if(ball.left < 0 || ball.right > rect.width) ball.velocityX = -ball.velocityX
    if(ball.top < 0 || ball.bottom > rect.height) ball.velocityY = -ball.velocityY

    再例如当一个人走到 100px 位置时不进行跳跃,就会碰到石头等等。

    因此,某些场景只需通过设定到适当的参数即可。

    理解JavaScript的原型属性

    2016/06/21 · JavaScript · 2 评论 · 原型

    本文由 伯乐在线 - alvendarthy 翻译,sunshinebuel 校稿。未经许可,禁止转载!
    英文出处:bytearcher。欢迎加入翻译组。

    理解 JavaScript 的prototype属性不太容易。你也许知道它同面向对象编程(OOP)和对象继承有关,但未必对其技术原理非常清楚。

    前端基础进阶(四):详细图解作用域链与闭包

    2017/02/24 · 基础技术 · 作用域链, 闭包

    原文出处: 波同学   

    图片 2

    攻克闭包难题

    初学JavaScript的时候,我在学习闭包上,走了很多弯路。而这次重新回过头来对基础知识进行梳理,要讲清楚闭包,也是一个非常大的挑战。

    闭包有多重要?如果你是初入前端的朋友,我没有办法直观的告诉你闭包在实际开发中的无处不在,但是我可以告诉你,前端面试,必问闭包。面试官们常常用对闭包的了解程度来判定面试者的基础水平,保守估计,10个前端面试者,至少5个都死在闭包上。

    可是为什么,闭包如此重要,还是有那么多人没有搞清楚呢?是因为大家不愿意学习吗?还真不是,而是我们通过搜索找到的大部分讲解闭包的中文文章,都没有清晰明了的把闭包讲解清楚。要么浅尝辄止,要么高深莫测,要么干脆就直接乱说一通。包括我自己曾经也写过一篇关于闭包的总结,回头一看,不忍直视[捂脸]。

    因此本文的目的就在于,能够清晰明了得把闭包说清楚,让读者老爷们看了之后,就把闭包给彻底学会了,而不是似懂非懂。

    外接图形判别法

    原型继承

    面向对象编程可以通过很多途径实现。其他的语言,比如 Java,使用基于类的模型实现: 类及对象实例区别对待。但在 JavaScript 中没有类的概念,取而代之的是一切皆对象。JavaScript 中的继承通过原型继承实现:一个对象直接从另一对象继承。对象中包含其继承体系中祖先的引用——对象的 prototype 属性。

    class 关键字是在 ES6 中首次引入 JavaScript 的。其实,它并没有为面向对象继承引入新模型, class 关键字通过语法糖,实现了本文介绍的原型特性和构造函数。

    一、作用域与作用域链

    在详细讲解作用域链之前,我默认你已经大概明白了JavaScript中的下面这些重要概念。这些概念将会非常有帮助。

    • 基础数据类型与引用数据类型
    • 内存空间
    • 垃圾回收机制
    • 执行上下文
    • 变量对象与活动对象

    如果你暂时还没有明白,可以去看本系列的前三篇文章,本文文末有目录链接。为了讲解闭包,我已经为大家做好了基础知识的铺垫。哈哈,真是好大一出戏。

    作用域

    • 在JavaScript中,我们可以将作用域定义为一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找。

      这里的标识符,指的是变量名或者函数名

    • JavaScript中只有全局作用域与函数作用域(因为eval我们平时开发中几乎不会用到它,这里不讨论)。

    • 作用域与执行上下文是完全不同的两个概念。我知道很多人会混淆他们,但是一定要仔细区分。

      JavaScript代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。编译阶段由编译器完成,将代码翻译成可执行代码,这个阶段作用域规则会确定。执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段创建。

    图片 3

    过程

    作用域链

    回顾一下上一篇文章我们分析的执行上下文的生命周期,如下图。

    图片 4

    执行上下文生命周期

    我们发现,作用域链是在执行上下文的创建阶段生成的。这个就奇怪了。上面我们刚刚说作用域在编译阶段确定规则,可是为什么作用域链却在执行阶段确定呢?

    之所有有这个疑问,是因为大家对作用域和作用域链有一个误解。我们上面说了,作用域是一套规则,那么作用域链是什么呢?是这套规则的具体实现。所以这就是作用域与作用域链的关系,相信大家都应该明白了吧。

    我们知道函数在调用激活时,会开始创建对应的执行上下文,在执行上下文生成的过程中,变量对象,作用域链,以及this的值会分别被确定。之前一篇文章我们详细说明了变量对象,而这里,我们将详细说明作用域链。

    作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。

    为了帮助大家理解作用域链,我我们先结合一个例子,以及相应的图示来说明。

    JavaScript

    var a = 20; function test() { var b = a + 10; function innerTest() { var c = 10; return b + c; } return innerTest(); } test();

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var a = 20;
     
    function test() {
        var b = a + 10;
     
        function innerTest() {
            var c = 10;
            return b + c;
        }
     
        return innerTest();
    }
     
    test();

    在上面的例子中,全局,函数test,函数innerTest的执行上下文先后创建。我们设定他们的变量对象分别为VO(global),VO(test), VO(innerTest)。而innerTest的作用域链,则同时包含了这三个变量对象,所以innerTest的执行上下文可如下表示。

    JavaScript

    innerTestEC = { VO: {...}, // 变量对象 scopeChain: [VO(innerTest), VO(test), VO(global)], // 作用域链 this: {} }

    1
    2
    3
    4
    5
    innerTestEC = {
        VO: {...},  // 变量对象
        scopeChain: [VO(innerTest), VO(test), VO(global)], // 作用域链
        this: {}
    }

    是的,你没有看错,我们可以直接用一个数组来表示作用域链,数组的第一项scopeChain[0]为作用域链的最前端,而数组的最后一项,为作用域链的最末端,所有的最末端都为全局变量对象。

    很多人会误解为当前作用域与上层作用域为包含关系,但其实并不是。以最前端为起点,最末端为终点的单方向通道我认为是更加贴切的形容。如图。

    图片 5

    作用域链图示

    注意,因为变量对象在执行上下文进入执行阶段时,就变成了活动对象,这一点在上一篇文章中已经讲过,因此图中使用了AO来表示。Active Object

    是的,作用域链是由一系列变量对象组成,我们可以在这个单向通道中,查询变量对象中的标识符,这样就可以访问到上一层作用域中的变量了。

    轴对称包围盒(Axis-Aligned Bounding Box)

    概念:判断任意两个(无旋转)矩形的任意一边是否无间距,从而判断是否碰撞。

    算法:

    JavaScript

    rect1.x < rect2.x + rect2.width && rect1.x + rect1.width > rect2.x && rect1.y < rect2.y + rect2.height && rect1.height + rect1.y > rect2.y

    1
    2
    3
    4
    rect1.x < rect2.x + rect2.width &&
    rect1.x + rect1.width > rect2.x &&
    rect1.y < rect2.y + rect2.height &&
    rect1.height + rect1.y > rect2.y

    两矩形间碰撞的各种情况:
    图片 6

    在线运行示例(先点击运行示例以获取焦点,下同):

    缺点:

    • 相对局限:两物体必须是矩形,且均不允许旋转(即关于水平和垂直方向上对称)。
    • 对于包含着图案(非填满整个矩形)的矩形进行碰撞检测,可能存在精度不足的问题。
    • 物体运动速度过快时,可能会在相邻两动画帧之间快速穿越,导致忽略了本应碰撞的事件发生。

    适用案例:

    • (类)矩形物体间的碰撞。

    JavaScript 实现继承的语言特性

    以下语言特性共同实现了 JavaScript 继承。

    • 当尝试访问 JavaScript 对象中不存在的属性时,解析器会查找匹配的对象原型。例如调用 car.toString(),如果 car 没有 toString 方法,就会调用 car 对象的原型。 这个查找过程会一直递归, 直到查找到匹配的原型或者继承链尽头。
    • 调用  new Car() 会创建一个新的对象,并初始化为 Car.prototype。 这样就允许为新对象设置原型链。需要注意的是,new Car() 只有当  Car 是函数时才有意义。 此类函数即所谓构造函数。
    • 调用对象的一个成员函数时, this 的值被绑定为当前对象。例如调用 "abc".toString()this 的值被设置为 "abc",然后调用 toString 函数。该技术支持代码重用:同样的代码,可在 this 为各种不同的值时调用。对象的成员函数,也被称为对象的方法。
    二、闭包

    对于那些有一点 JavaScript 使用经验但从未真正理解闭包概念的人来说,理解闭包可以看作是某种意义上的重生,突破闭包的瓶颈可以使你功力大增。

    • 闭包与作用域链息息相关;
    • 闭包是在函数执行过程中被确认。

    先直截了当的抛出闭包的定义:当函数可以记住并访问所在的作用域(全局作用域除外)时,就产生了闭包,即使函数是在当前作用域之外执行。

    简单来说,假设函数A在函数B的内部进行定义了,并且当函数A在执行时,访问了函数B内部的变量对象,那么B就是一个闭包。

    非常抱歉之前对于闭包定义的描述有一些不准确,现在已经改过,希望收藏文章的同学再看到的时候能看到吧,对不起大家了。

    在基础进阶(一)中,我总结了JavaScript的垃圾回收机制。JavaScript拥有自动的垃圾回收机制,关于垃圾回收机制,有一个重要的行为,那就是,当一个值,在内存中失去引用时,垃圾回收机制会根据特殊的算法找到它,并将其回收,释放内存。

    而我们知道,函数的执行上下文,在执行完毕之后,生命周期结束,那么该函数的执行上下文就会失去引用。其占用的内存空间很快就会被垃圾回收器释放。可是闭包的存在,会阻止这一过程。

    先来一个简单的例子。

    JavaScript

    var fn = null; function foo() { var a = 2; function innnerFoo() { console.log(a); } fn = innnerFoo; // 将 innnerFoo的引用,赋值给全局变量中的fn } function bar() { fn(); // 此处的保留的innerFoo的引用 } foo(); bar(); // 2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var fn = null;
    function foo() {
        var a = 2;
        function innnerFoo() {
            console.log(a);
        }
        fn = innnerFoo; // 将 innnerFoo的引用,赋值给全局变量中的fn
    }
     
    function bar() {
        fn(); // 此处的保留的innerFoo的引用
    }
     
    foo();
    bar(); // 2

    在上面的例子中,foo()执行完毕之后,按照常理,其执行环境生命周期会结束,所占内存被垃圾收集器释放。但是通过fn = innerFoo,函数innerFoo的引用被保留了下来,复制给了全局变量fn。这个行为,导致了foo的变量对象,也被保留了下来。于是,函数fn在函数bar内部执行时,依然可以访问这个被保留下来的变量对象。所以此刻仍然能够访问到变量a的值。

    这样,我们就可以称foo为闭包。

    下图展示了闭包fn的作用域链。

    图片 7

    闭包fn的作用域链

    我们可以在chrome浏览器的开发者工具中查看这段代码运行时产生的函数调用栈与作用域链的生成情况。如下图。

    图片 8

    从图中可以看出,chrome浏览器认为闭包是foo,而不是通常我们认为的innerFoo

    在上面的图中,红色箭头所指的正是闭包。其中Call Stack为当前的函数调用栈,Scope为当前正在被执行的函数的作用域链,Local为当前的局部变量。

    所以,通过闭包,我们可以在其他的执行上下文中,访问到函数的内部变量。比如在上面的例子中,我们在函数bar的执行环境中访问到了函数foo的a变量。个人认为,从应用层面,这是闭包最重要的特性。利用这个特性,我们可以实现很多有意思的东西。

    不过读者老爷们需要注意的是,虽然例子中的闭包被保存在了全局变量中,但是闭包的作用域链并不会发生任何改变。在闭包中,能访问到的变量,仍然是作用域链上能够查询到的变量。

    对上面的例子稍作修改,如果我们在函数bar中声明一个变量c,并在闭包fn中试图访问该变量,运行结果会抛出错误。

    JavaScript

    var fn = null; function foo() { var a = 2; function innnerFoo() { console.log(c); // 在这里,试图访问函数bar中的c变量,会抛出错误 console.log(a); } fn = innnerFoo; // 将 innnerFoo的引用,赋值给全局变量中的fn } function bar() { var c = 100; fn(); // 此处的保留的innerFoo的引用 } foo(); bar();

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    var fn = null;
    function foo() {
        var a = 2;
        function innnerFoo() {
            console.log(c); // 在这里,试图访问函数bar中的c变量,会抛出错误
            console.log(a);
        }
        fn = innnerFoo; // 将 innnerFoo的引用,赋值给全局变量中的fn
    }
     
    function bar() {
        var c = 100;
        fn(); // 此处的保留的innerFoo的引用
    }
     
    foo();
    bar();

    闭包的应用场景

    接下来,我们来总结下,闭包的常用场景。

    • 延迟函数setTimeout

    我们知道setTimeout的第一个参数是一个函数,第二个参数则是延迟的时间。在下面例子中,

    JavaScript

    function fn() { console.log('this is test.') } var timer = setTimeout(fn, 1000); console.log(timer);

    1
    2
    3
    4
    5
    function fn() {
        console.log('this is test.')
    }
    var timer =  setTimeout(fn, 1000);
    console.log(timer);

    执行上面的代码,变量timer的值,会立即输出出来,表示setTimeout这个函数本身已经执行完毕了。但是一秒钟之后,fn才会被执行。这是为什么?

    按道理来说,既然fn被作为参数传入了setTimeout中,那么fn将会被保存在setTimeout变量对象中,setTimeout执行完毕之后,它的变量对象也就不存在了。可是事实上并不是这样。至少在这一秒钟的事件里,它仍然是存在的。这正是因为闭包。

    很显然,这是在函数的内部实现中,setTimeout通过特殊的方式,保留了fn的引用,让setTimeout的变量对象,并没有在其执行完毕后被垃圾收集器回收。因此setTimeout执行结束后一秒,我们任然能够执行fn函数。

    • 柯里化

    在函数式编程中,利用闭包能够实现很多炫酷的功能,柯里化算是其中一种。关于柯里化,我会在以后详解函数式编程的时候仔细总结。

    • 模块

    在我看来,模块是闭包最强大的一个应用场景。如果你是初学者,对于模块的了解可以暂时不用放在心上,因为理解模块需要更多的基础知识。但是如果你已经有了很多JavaScript的使用经验,在彻底了解了闭包之后,不妨借助本文介绍的作用域链与闭包的思路,重新理一理关于模块的知识。这对于我们理解各种各样的设计模式具有莫大的帮助。

    JavaScript

    (function () { var a = 10; var b = 20; function add(num1, num2) { var num1 = !!num1 ? num1 : a; var num2 = !!num2 ? num2 : b; return num1 + num2; } window.add = add; })(); add(10, 20);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    (function () {
        var a = 10;
        var b = 20;
     
        function add(num1, num2) {
            var num1 = !!num1 ? num1 : a;
            var num2 = !!num2 ? num2 : b;
     
            return num1 + num2;
        }
     
        window.add = add;
    })();
     
    add(10, 20);

    在上面的例子中,我使用函数自执行的方式,创建了一个模块。方法add被作为一个闭包,对外暴露了一个公共方法。而变量a,b被作为私有变量。在面向对象的开发中,我们常常需要考虑是将变量作为私有变量,还是放在构造函数中的this中,因此理解闭包,以及原型链是一个非常重要的事情。模块十分重要,因此我会在以后的文章专门介绍,这里就暂时不多说啦。

    图片 9

    此图中可以观看到当代码执行到add方法时的调用栈与作用域链,此刻的闭包为外层的自执行函数

    为了验证自己有没有搞懂作用域链与闭包,这里留下一个经典的思考题,常常也会在面试中被问到。

    利用闭包,修改下面的代码,让循环输出的结果依次为1, 2, 3, 4, 5

    JavaScript

    for (var i=1; i<=5; i++) { setTimeout( function timer() { console.log(i); }, i*1000 ); }

    1
    2
    3
    4
    5
    for (var i=1; i<=5; i++) {
        setTimeout( function timer() {
            console.log(i);
        }, i*1000 );
    }

    关于作用域链的与闭包我就总结完了,虽然我自认为我是说得非常清晰了,但是我知道理解闭包并不是一件简单的事情,所以如果你有什么问题,可以在评论中问我。你也可以带着从别的地方没有看懂的例子在评论中留言。大家一起学习进步。

    2 赞 4 收藏 评论

    图片 10

    圆形碰撞(Circle Collision)

    概念:通过判断任意两个圆形的圆心距离是否小于两圆半径之和,若小于则为碰撞。

    两点之间的距离由以下公式可得:
    图片 11

    判断两圆心距离是否小于两半径之和:

    JavaScript

    Math.sqrt(Math.pow(circleA.x - circleB.x, 2) + Math.pow(circleA.y - circleB.y, 2)) < circleA.radius + circleB.radius

    1
    2
    3
    Math.sqrt(Math.pow(circleA.x - circleB.x, 2) +
    Math.pow(circleA.y - circleB.y, 2))
    < circleA.radius + circleB.radius

    图例:
    图片 12

    在线运行示例:

    缺点:

    • 与『轴对称包围盒』类似

    适用案例:

    • (类)圆形的物体,如各种球类碰撞。

    举个栗子

    我们用面向对象编程,实现一个计算矩形周长的例子。

    JavaScript

    function Rectangle(x, y) { this.x = x; this.y = y; } Rectangle.prototype.perimeter = function() { return 2 * (this.x + this.y); } var rect = new Rectangle(1, 2); console.log(rect.perimeter()); // outputs '6'

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function Rectangle(x, y) {
        this.x = x;
        this.y = y;
    }
     
    Rectangle.prototype.perimeter = function() {
        return 2 * (this.x + this.y);
    }
     
    var rect = new Rectangle(1, 2);
    console.log(rect.perimeter()); // outputs '6'

    首先,我们定义构造函数 Rectangle。 按照规范,我们大写构造函数名首字母,表明它可以用 new 调用,以示与其他常规函数的区别。构造函数自动将 this 赋值为一空对象,然后代码中用 xy 属性填充它,以备后用。

    然后, Rectangle.prototype 新增一个通过 xy 属性计算周长成员函数。 注意 this 的使用,在不同的对象中,this 会有不同的值,这些代码都可以正常工作。

    最后, 一个名为 rect 的对象创建出来了。 它继承了 Rectangle.prototype, 我们可以调用 rect.perimeter(), 然后将结果打印到控制台。

    其他

    prototype 属性名称带来的误解

    有一些关于 JavaScript 的原型的误解。 一个对象的原型与对象的 prototype 属性并非一回事。 前者用于在原型链中匹配不存在的属性。后者用于通过 new 关键字创建对象,它将作为新创建对象的原型。 理解二者的差异,将帮助你彻底理解 JavaScript 中的原型特性。

    在我们的例子中, Rectangle.prototype 是用 new Rectangle() 创建出来对象的原型, 而 Rectangle 的原型实际上是 JavaScript 的 Function.prototype。(子对象的原型是父对象的 prototype 属性)

    对象中保存原型的变量,也被称之为内部原型引用(the internal prototype link),历史上也曾称之为 __proto__ ,对这个称谓始终存在一些争议。 更精确的,它可以被称为 Object.getPrototypeOf(...) 的返回值。

    2 赞 5 收藏 2 评论

    地图格子划分

    概念:将地图(场景)划分为一个个格子。地图中参与检测的对象都存储着自身所在格子的坐标,那么你即可以认为两个物体在相邻格子时为碰撞,又或者两个物体在同一格才为碰撞。另外,采用此方式的前提是:地图中所有可能参与碰撞的物体都要是格子单元的大小或者是其整数倍。

    蓝色X 为障碍物:
    图片 13

    实现方法:

    JavaScript

    // 通过特定标识指定(非)可行区域 map = [ [0, 0, 1, 1, 1, 0, 0, 0, 0], [0, 1, 1, 0, 0, 1, 0, 0, 0], [0, 1, 0, 0, 0, 0, 1, 0, 0], [0, 1, 0, 0, 0, 0, 1, 0, 0], [0, 1, 1, 1, 1, 1, 1, 0, 0] ], // 设定角色的初始位置 player = {left: 2, top: 2}   // 移动前(后)判断角色的下一步的动作(如不能前行) ...

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 通过特定标识指定(非)可行区域
    map = [
    [0, 0, 1, 1, 1, 0, 0, 0, 0],
    [0, 1, 1, 0, 0, 1, 0, 0, 0],
    [0, 1, 0, 0, 0, 0, 1, 0, 0],
    [0, 1, 0, 0, 0, 0, 1, 0, 0],
    [0, 1, 1, 1, 1, 1, 1, 0, 0]
    ],
    // 设定角色的初始位置
    player = {left: 2, top: 2}
     
    // 移动前(后)判断角色的下一步的动作(如不能前行)
    ...

    在线运行示例:

    缺点:

    • 适用场景局限。

    适用案例:

    • 推箱子、踩地雷等

    关于作者:alvendarthy

    图片 14

    一个热爱生活的家伙! 个人主页 · 我的文章 · 16

    图片 15

    本文由金沙国际官网发布于web前端,转载请注明出处:常见的2D碰撞检测,前端基础进阶

    关键词: