unity初探之黑暗之光,js源码阅读笔记_基础知识

日期:2019-12-17编辑作者:澳门金莎娱乐网站

“等一下,我碰!”——常见的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 位置时不进行跳跃,就会碰到石头等等。

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

Three.js是一个比较伟大的webgl开源库,它简化了浏览器3D编程,使得使用JavaScript在浏览器中创建复杂的场景变得容易很多。Github上众多webgl demo令我兴奋不已,跃跃欲试。由于这个库还处在开发阶段,因此资料非常匮乏,爱好者大部分时间不得不通过阅读该库的源码进行学习,我现在也准备这样做。 这是第一篇笔记,先从最基础的核心对象开始。 Core::Vector2 该构造函数用来创建一个表示二维向量的对象 复制代码 代码如下: THREE.Vector2 = function { this.x = x || 0; this.y = y || 0; }; Vector2对象的功能函数采用定义构造函数的原型对象来实现,形如: 复制代码 代码如下: THREE.Vector2.prototype = { constructor: THREE.Vector2, set: function { this.x = x; this.y = y; return this; }, copy: function { this.x = v.x; this.y = v.y; return this; }, ...... // 更多的函数 }; 函数set用以指定向量的值,调用者本身的x,y值被影响了,而该方法本身又返回调用者本身,这种情况很常见,以下不再说明。通过文字能够表述清楚功能的函数不再引用源代码,这一点以下也不再说明。 函数copy用来将向量v复制进调用者。 函数add分别表示对向量a,b相加和相减。 函数addSelf分别表示对调用者本身加上或减去向量v。 函数multiplyScale分别表示对调用者本身乘以或除以s。 函数lerpSelf将调用者向v所指的方向旋转alpha,当alpha为1时,调用者最终等于v,而当alpha=0时,调用者还等于原来。 复制代码 代码如下: lerpSelf: function { this.x += * alpha; this.y += * alpha; return this; }, 函数negate返回float类型的调用者和向量v的点乘。 函数lengthSq返回float类型的调用者长度平方或长度。 函数normalize()将调用者本身归一化。 函数distanceToSquared将返回调用者和向量v的距离。这里的距离其实是两向量起点都在原点时,终点之间的距离,也就是向量this-v的长度。 函数setLength将向量的长度缩放至为s,方向不变。 函数equals判断调用者与向量v的值是否相同。 函数isZero()判断调用者是否是零向量。 函数clone()返回一个与调用者值一样的新向量,相当于将其复制出去,注意与copy的区别。 Core::Vector3 该构造函数创建一个表示三维向量的对象 复制代码 代码如下: THREE.Vector3 = function { this.x = x || 0; this.y = y || 0; this.z = z || 0; }; 三维向量和二维向量有许多共通之处,比如set,add,dot,length,clone等,此处尽数略去,只记录三维向量比二维向量多出的部分函数。 函数setX用来单独设置某一分量的值。 函数cross分别使调用者变为a,b的叉乘或者调用者本身与v的叉乘。叉乘是一个向量,垂直于参与叉乘的两个向量并呈右手螺旋法则。 函数getPositionFromMatrix,getRotationFromMatrix,getScaleFromMatrix从4×4的模型矩阵中提取位置分量,旋转分量和缩放分量。模型矩阵表示了一系列平移、旋转、缩放变换的叠加效果。(这里第二个函数出现在文档中,在源码中被另外两个函数代替了,也许还没来得及更新)。 函数angleTo计算调用者和向量v的夹角。 Core::Vector4 该构造函数创建一个表示四维向量的对象 复制代码 代码如下: THREE.Vector4 = function { this.x = x || 0; this.y = y || 0; this.z = z || 0; this.w = ? w : 1; }; 四维向量用来表示齐次坐标,其函数和Vector2,Vector3中的函数功能重合,仅仅是多一个分量而已,这里不再记录。 Core::Matrix3 该构造函数创建一个表示3×3矩阵的对象 THREE.Matrix3 = function () { this.elements = new Float32Array; }; 3×3矩阵有9个元素,存储在矩阵对象的属性elements中,elements是一个数组。 函数getInverse返回矩阵m的逆矩阵,同时改变调用者本身。 函数transpose()转置调用者。 函数transposeToArray将调用者转置进数组r而不改变自身。(这个地方似乎源码错了,var m=this.m应该为var m=this.elements。) Core::Matrix4 该构造函数创建一个表示4×4矩阵的对象,4×4矩阵在三维图形学中非常重要,模型矩阵、视图矩阵和投影矩阵都是这样的矩阵。 复制代码 代码如下: THREE.Matrix4 = function ( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { this.elements = new Float32Array; this.set( ? n11 : 1, n12 || 0, n13 || 0, n14 || 0, n21 || 0, ? n22 : 1, n23 || 0, n24 || 0, n31 || 0, n32 || 0, ? n33 : 1, n34 || 0, n41 || 0, n42 || 0, n43 || 0, ? n44 : 1 ); }; 在Matrix3对象中出现的几个函数在Matrix4中有相同的作用,这里也略去。 函数identity()将对象重置为单位阵。 函数lookAt将对象设定为一个视图矩阵,参数都是Vector3对象,该矩阵只会用到eye和center的相对位置。该视图矩阵表示,摄像机在eye位置看向center位置,且向上的向量为up时的视图矩阵。视图矩阵又可以看做摄像机的模型矩阵,所以该函数产生的矩阵又可以表示以下变换:将物体从原点平移至位置center-eye,再将其旋转至向上的向量为up。向上的向量up用来固定相机,可以想象当相机固定在一点,镜头朝向固定方向的时候,还是可以在一个维度里自由旋转的,up向量固定相机的这个维度。 复制代码 代码如下: lookAt: function { var te = this.elements; var x = THREE.Matrix4.__v1; // 空Vector3对象,下同 var y = THREE.Matrix4.__v2; var z = THREE.Matrix4.__v3; z.sub.normalize === 0 ) { z.z = 1; } x.cross; if { z.x += 0.0001; x.cross; } y.cross; te[0] = x.x; te[4] = y.x; te[8] = z.x; te[1] = x.y; te[5] = y.y; te[9] = z.y; te[2] = x.z; te[6] = y.z; te[10] = z.z; return this; }, 函数multiply和multiplyToArray将两个矩阵相乘。 函数multiplyScale将对象所有16个元素都乘以s。 函数multiplyVector3将对象矩阵左乘四维行向量,返回vector3和vector4类型的行向量。如果对象矩阵是模型视图矩阵,输入的向量是点位置信息,则输出的向量则是经过模型变换和相机变换后,该点相对于相机的位置。输入vector3类型向量时,自动补足为齐次坐标,返回时再砍掉第四个分量成为普通坐标。 函数rotateAxis使用对象矩阵左上角的3×3子矩阵左乘行向量v,得到一个新的行向量并归一化,返回这个新行向量。该函数同时更新了向量v的值。模型视图矩阵左上角3×3的子矩阵包含了模型矩阵中的旋转信息,将该子矩阵左乘一个向量,得到的新向量实际上就是原向量经过旋转得到的。因此该函数名为rotateAxis。 复制代码 代码如下: rotateAxis: function { var te = this.elements; var vx = v.x, vy = v.y, vz = v.z; v.x = vx * te[0] + vy * te[4] + vz * te[8]; v.y = vx * te[1] + vy * te[5] + vz * te[9]; v.z = vx * te[2] + vy * te[6] + vz * te[10]; v.normalize(); return v; }, 函数crossVector和v的叉乘,实际上就是对象矩阵左乘四维行向量v,返回向量。这个具体是做什么的,我还没弄明白。 复制代码 代码如下: crossVector: function { var te = this.elements; var v = new THREE.Vector4(); v.x = te[0] * a.x + te[4] * a.y + te[8] * a.z + te[12] * a.w; v.y = te[1] * a.x + te[5] * a.y + te[9] * a.z + te[13] * a.w; v.z = te[2] * a.x + te[6] * a.y + te[10] * a.z + te[14] * a.w; v.w = ? te[3] * a.x + te[7] * a.y + te[11] * a.z + te[15] * a.w : 1; return v; }, 函数determinant()计算矩阵的行列式值。 函数flattenToArray和函数flattenToArrayOfset将矩阵转存到一维数组中,前一个函数从flat[0]存储到flat[15],后一个函数允许指定开始存储的位置,从flat[offset]存储到flat[offset+15]。函数getPosition用来获取或设置矩阵对象的位置分量。正如旋转分量存储在左上角3×3的子矩阵中,位置分量存储在第四行前三个分量上,即element[12], element[13], element[14]中。 函数getColumeX,getColumeZ()分别提取左上角3×3子矩阵的三列。 函数compose(translate,rotation,scale)将对象矩阵设置为由vector3类型translate对象表示的平移、由matrix3类型rotation对象表示的旋转、由vector3类型scale对象表示的缩放这三个变换组合到一起的变换矩阵。实际上就是讲其直接填充到模型矩阵的相应子空间。 函数decompose(translate,rotation,scale)将矩阵对象拆开到三个对象中,和上一个函数正好相反。 函数extractPosition将矩阵对象m中表示位置或旋转的分量抽取到调用者对象中,比如两个物体经过多次各不相同的变换,只需要一个物体的模型视图矩阵extractRotation另一个物体的模型视图矩阵,则调用者就和另外一个物体保持着变换之处相同的旋转方位。函数translate是模型矩阵最基本的变换之一:平移变换,将模型矩阵从属的物体平移向量v。 函数rotateX,rotateZ分别将模型矩阵从属的物体绕X,Y,Z轴旋转角度angle。 函数rotateByAxis将模型矩阵从属的物体绕一个任意轴axis旋转角度angle,这是上面两条所涉及的变换的多次叠加,我在《模型视图矩阵和投影矩阵:webgl笔记》中曾讨论到绕任意轴旋转的问题。 这里不应该有一个scale函数吗?可是我在源码中没找到。 函数makeTranslate,makeRotationX,makeRotationY,makeRotationZ,makeRotationAxis,makeScale函数将对象矩阵直接重置为单位阵经过一次平移、或绕某轴旋转、或单纯某次缩放后的矩阵。该函数更新对象本身的值,而且更新的结果与对象之前的值毫无关联。 函数makeFrustum,makePerspective,makeOrthographic也是用来初始化新矩阵,具体含义到相机类里面再讨论,我想相机类的构造函数里一定会调用这些函数的。 函数clone()将矩阵对象复制出来并返回。 Core::Face3 该函数创建一个三角形平面对象 复制代码 代码如下: THREE.Face3 = function ( a, b, c, normal, color, materialIndex ) { this.a = a; this.b = b; this.c = c; this.normal = normal instanceof THREE.Vector3 ? normal : new THREE.Vector3(); this.vertexNormals = normal instanceof Array ? normal : [ ]; this.color = color instanceof THREE.Color ? color : new THREE.Color(); this.vertexColors = color instanceof Array ? color : []; this.vertexTangents = []; this.materialIndex = materialIndex; this.centroid = new THREE.Vector3(); }; 对象的a,b,c值是三个顶点的索引(后面会说到,Mesh对象中将所有点存储为一个数组);顾名思义normal是法线;color是颜色;materialIndex是顶点材质索引:这几个参数即可以传入vector3类型又可以传入数组类型。 clone方法返回一个新的,具有相同值的对象。 Core::Face4 该函数创建一个四个顶点的面,和Face3几乎一样,略去。 Core::Math THREE.Math是一个“静态类”,没有构造函数因此也不需要通过new关键字初始化。该类提供一些必要的数学工具。 函数clamp将x夹在区间[a,b]中。clampBottom的作用类似,只不过只夹一边。 函数mapLinear计算出一个值y,使得点连成的直线上。 复制代码 代码如下: mapLinear: function { return b1 + * / ; }, 函数random16,randFloat,randFloatSpread分别产生[0,1]区间的16位随机浮点数,[low,high]区间随机整数,[low,high]区间随机浮点数,[-range/2,range/2]区间随机浮点数。 函数sigh根据x的符号返回+1或-1。 Core::Clock 该构造函数创建时钟对象 复制代码 代码如下: THREE.Clock = function { this.autoStart = ( autoStart !== undefined ) ? autoStart : true; this.startTime = 0; this.oldTime = 0; this.elapsedTime = 0; this.running = false; }; 函数start用来开始计时或停止计时。 函数getDelta()返回调用该函数时距离上一次调用该函数时的时间长度,如果是第一次调用该函数,则返回此时距离开始计时时的时间长度。如果autoStart值为真,若在调用getDelta函数或者已经调用过stop()函数,则自动开始计时并返回0。如果autoStart之前或stop返回0。函数getElapsedTime()返回调用该函数时距离开始计时时的时间。 Core::Color 该构造函数构造一个表示颜色的对象 复制代码 代码如下: THREE.Color = function { if this.setHex; return this; }; 函数setHex以十六进制序列设置对象的r,g,b属性。实际上在对象中,最终是以这三个属性存储颜色的。 复制代码 代码如下: setHex: function { hex = Math.floor; this.r = / 255; this.g = / 255; this.b = / 255; return this; }, 函数setRGB以RGB值或HSV值设置对象。 函数getHex()返回16进制颜色值。 函数copyGammaToLinear,copyLinearToGamma将color的rgb值分别平方或开方,赋给调用者对象。 函数convertGammaToLinear()和convertLinearToGamma()分别对调用者自身的rgb值平方或开放。

系列简介 也许,三百年前的艾萨克·牛顿爵士(Sir Issac Newton, 1643-1727)并没幻想过,物理学广泛地应用在今天许多游戏、动画中。为什么在这些应用中要使用物理学?笔者认为,自我们出生以来,一直感受着物理世界的规律,意识到物体在这世界是如何"正常移动",例如射球时球为抛物线 、石子系在一根线的末端会以固定频率摆动等等。要让游戏或动画中的物体有真实感,其移动方式就要符合我们对"正常移动"的预期。 今天的游戏动画应用了多种物理模拟技术,例如运动学模拟(kinematics simulation)、刚体动力学模拟(rigid body dynamics simulation)、绳子/布料模拟(string/cloth simulation)、柔体动力学模拟(soft body dynamics simulation)、流体动力学模拟(fluid dynamics simulation)等等。另外碰撞侦测是许多模拟系统里所需的。 本系列希望能介绍一些这方面最基础的知识,继续使用JavaScript做例子,以即时互动方式体验。 本文简介 作为系列第一篇,本文介绍最简单的运动学模拟,只有两条非常简单的公式。运动学模拟可以用来模拟很多物体运动,本文将会配合粒子系统做出一些视觉特效(粒子系统其实也可以用来做游戏的玩法,而不单是视觉特效)。 运动学模拟 运动学研究物体的移动,和动力学不同之处,在于运动学不考虑物体的质量/转动惯量,以及不考虑加之于物体的力。 我们先回忆牛顿第一运动定律: 当物体不受外力作用,或所受合力为零时,原先静止者恒静止,原先运动者恒沿着直线作等速度运动。该定律又称为「惯性定律」。此定律指出,每个物体除了其位置外,还有一个线性速度的状态。然而,只模拟不受力影响的物体并不有趣。撇开力的概念,我们可以用线性加速度去影响物体的运动。例如,要计算一个自由落体在任意时间t的y轴座标,可以使用以下的分析解: 当中,和分别是t=0时的y轴起始座标和速度,而g则是重力加速度(gravitational acceleration)。 这分析解虽然简单,但是有一些缺点,例如g是常数,在模拟过程中不能改变;另外,当物体遇到障碍物,产生碰撞时,这公式也很难处理这种不连续性 。 在计算机模拟中,通常需要计算连续的物体状态。用游戏的用语,就是计算第一帧的状态、第二帧的状态等等。设物体在任意时间t的状态:位置矢量为、速度矢量为、加速度矢量为。我们希望从时间的状态,计算下一个模拟时间的状态。最简单的方法,是采用欧拉方法作数值积分(numerical integration): 欧拉方法非常简单,但有准确度和稳定性问题,本文会先忽略这些问题。本文的例子采用二维空间,我们先实现一个JavaScript二维矢量类: 复制代码 代码如下: // Vector2.js Vector2 = function { this.x = x; this.y = y; }; Vector2.prototype = { copy : function() { return new Vector2; }, length : function() { return Math.sqrt(this.x * this.x + this.y * this.y); }, sqrLength : function() { return this.x * this.x + this.y * this.y; }, normalize : function() { var inv = 1/this.length(); return new Vector2(this.x * inv, this.y * inv); }, negate : function() { return new Vector2; }, add : function { return new Vector2(this.x + v.x, this.y + v.y); }, subtract : function { return new Vector2(this.x - v.x, this.y - v.y); }, multiply : function { return new Vector2(this.x * f, this.y * f); }, divide : function { var invf = 1/f; return new Vector2(this.x * invf, this.y * invf); }, dot : function { return this.x * v.x + this.y * v.y; } }; Vector2.zero = new Vector2; 然后,就可以用HTML5 Canvas去描绘模拟的过程: 复制代码 代码如下: var position = new Vector2; var velocity = new Vector2; var acceleration = new Vector2; var dt = 0.1; function step() { position = position.add); velocity = velocity.add(acceleration.multiply; ctx.strokeStyle = "#000000"; ctx.fillStyle = "#FFFFFF"; ctx.beginPath(); ctx.arc(position.x, position.y, 5, 0, Math.PI*2, true); ctx.closePath; ctx.stroke(); } start("kinematicsCancas", step); Run Stop Clear

unity初探之黑暗之光(2)

外接图形判别法

修改代码试试看

改变起始位置 改变起始速度 改变加速度

一、设置角色跟随鼠标点击移动

思路:使用charactercollider的SimpleMove方法来控制角色的移动。通过摄像机的射线投射到地面,通过屏幕上的一个点也就是鼠标单击的点。该射线与地面发生碰撞返回发生碰撞的点,然后让角色转向该点,开始移动。当移动到一定范围时停止移动。

使用到的方法:

轴对称包围盒(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

两矩形间碰撞的各种情况:
澳门金莎娱乐网站 2

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

缺点:

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

适用案例:

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

这程序的核心就是step()函数头两行代码。很简单吧? 粒子系统 粒子系统是图形里常用的特效。粒子系统可应用运动学模拟来做到很多不同的效果。粒子系统在游戏和动画中,常常会用来做雨点、火花、烟、爆炸等等不同的视觉效果。有时候,也会做出一些游戏性相关的功能,例如敌人被打败后会发出一些闪光,主角可以把它们吸收。 粒子的定义 粒子系统模拟大量的粒子,并通常用某些方法把粒子渲染。粒子通常有以下特性:

Camera.ScreenPointToRay 屏幕位置转射线

 

function ScreenPointToRay (position : Vector3) : Ray

Description描述

Returns a ray going from camera through a screen point.

返回一条射线从摄像机通过一个屏幕点。

Resulting ray is in world space, starting on the near plane of the camera and going through position's (x,y) pixel coordinates on the screen (position.z is ignored).

产生的射线是在世界空间中,从相机的近裁剪面开始并穿过屏幕position(x,y)像素坐标(position.z被忽略)。

Screenspace is defined in pixels. The bottom-left of the screen is (0,0); the right-top is (pixelWidth,pixelHeight).

屏幕空间以像素定义。屏幕的左下为(0,0);右上是(pixelWidth,pixelHeight)。

 

圆形碰撞(Circle Collision)

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

两点之间的距离由以下公式可得:
澳门金莎娱乐网站 3

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

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

图例:
澳门金莎娱乐网站 4

在线运行示例:

缺点:

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

适用案例:

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

粒子是独立的,粒子之间互不影响

Physics.Raycast 光线投射

 

static function Raycast (origin : Vector3, direction : Vector3, 澳门金莎娱乐网站,distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : bool

其他

粒子有生命周期,生命结束后会消失

Parameters参数

  • origin

    The starting point of the ray in world coordinates.
    在世界坐标,射线的起始点。

  • direction

    The direction of the ray.
    射线的方向。

  • distance

    The length of the ray
    射线的长度。

  • layerMask

    A Layer mask that is used to selectively ignore colliders when casting a ray.
    只选定Layermask层内的碰撞器,其它层内碰撞器忽略。

• static function Raycast (origin : Vector3, direction : Vector3, out hitInfo : RaycastHit, distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : bool

地图格子划分

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

蓝色X 为障碍物:
澳门金莎娱乐网站 5

实现方法:

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}
 
// 移动前(后)判断角色的下一步的动作(如不能前行)
...

在线运行示例:

缺点:

  • 适用场景局限。

适用案例:

  • 推箱子、踩地雷等

粒子可以理解为空间的一个点,有时候也可以设定半径作为球体和环境碰撞

Parameters参数

  • origin

    The starting point of the ray in world coordinates.
    在世界坐标,射线的起始点。

  • direction

    The direction of the ray.
    射线的方向。

  • distance

    The length of the ray
    射线的长度。

  • hitInfo

    If true is returned, hitInfo will contain more information about where the collider was hit (See Also: RaycastHit).
    如果返回true,hitInfo将包含碰到器碰撞的更多信息。

  • layerMask

    A Layer mask that is used to selectively ignore colliders when casting a ray.
    只选定Layermask层内的碰撞器,其它层内碰撞器忽略。 

 

    

像素检测

概念:以像素级别检测物体之间是否存在重叠,从而判断是否碰撞。

实现方法有多种,下面列举在 Canvas 中的两种实现方式:

  1. 如下述的案例中,通过将两个物体在 offscreen canvas 中判断同一位置(坐标)下是否同时存在非透明的像素。
  2. 利用 canvas 的 globalCompositeOperation = 'destination-in' 属性。该属性会让两者的重叠部分会被保留,其余区域都变成透明。因此,若存在非透明像素,则为碰撞。

注意,当待检测碰撞物体为两个时,第一种方法需要两个 offscreen canvas,而第二种只需一个。

offscreen canvas:与之相关的是 offscreen rendering。正如其名,它会在某个地方进行渲染,但不是屏幕。“某个地方”其实是内存。渲染到内存比渲染到屏幕更快。—— Offscreen Rendering

当然,我们这里并不是利用 offscreen render 的性能优势,而是利用 offscreen canvas 保存独立物体的像素。换句话说:onscreen canvas 只是起展示作用,碰撞检测是在 offscreen canvas 中进行

另外,由于需要逐像素检测,若对整个 Canvas 内所有像素都进行此操作,无疑会浪费很多资源。因此,我们可以先通过运算得到两者相交区域,然后只对该区域内的像素进行检测即可。

图例:
澳门金莎娱乐网站 6

下面示例展示了第一种实现方式:

缺点:

  • 因为需要检查每一像素来判定是否碰撞,性能要求比较高。

适用案例:

  • 需要以像素级别检测物体是否碰撞。

粒子带有运动状态,也有其他外观状态

Transform.LookAt 注视

function LookAt (target : Transform, worldUp : Vector3 = Vector3.up) : void

Description描述

Rotates the transform so the forward vector points at /target/'s current position.

旋转物体,这样向前向量指向target的当前位置。简单说,

旋转物体使z轴指向目标物体。

当该物体设置了LookAt并指定了目标物体时,该物体的z轴将始终指向目标物体,在设置了worldUp轴向时,该物体在更接近指定的轴向是旋转便的灵活,注意worldUp指的是世界空间,不论你物体在什么位置,只要接近指定的轴方向,旋转会变的更灵活。


光线投射法(Ray Casting)

概念:通过检测两个物体的速度矢量是否存在交点,且该交点满足一定条件。

对于下述抛小球入桶的案例:画一条与物体的速度向量相重合的线(#1),然后再从另一个待检测物体出发,连线到前一个物体,绘制第二条线(#2),根据两条线的交点位置来判定是否发生碰撞。

抛球进桶图例:
澳门金莎娱乐网站 7

在小球飞行的过程中,需要不断计算两直线的交点。

当满足以下两个条件时,那么应用程序就可以判定小球已落入桶中:

  • 两直线交点在桶口的左右边沿间
  • 小球位于第二条线(#2)下方

在线运行示例:

优点:

  • 适合运动速度快的物体

缺点:

  • 适用范围相对局限。

适用案例:

  • 抛球运动进桶。

粒子可以只有线性运动,而不考虑旋转运动

二、绕物相机旋转,拉近拉远

 1    public float maxDis = 20f;
 2     public float minDis = 2f;
 3     public int scrollSpeed = 10;
 4     public float distance=0;
 5     public int rotateSpeed = 1;
 6 
 7     private Transform player;
 8     private Vector3 offSet;
 9     private bool isRotate = false;
10     // Use this for initialization
11     void Start () {
12         player = GameObject.FindGameObjectWithTag(Tags.Player).transform;
13         transform.LookAt(player);
14         offSet = transform.position - player.position;//偏位
15     }
16     
17     // Update is called once per frame
18     void Update () {
19         transform.position = player.position + offSet;
20         
21         Rotate();
22         scroll();
23     }
24     //右键控制相机围绕对象旋转
25     void Rotate()
26     {
27         if (Input.GetMouseButtonDown(1))
28         {
29             isRotate = true;
30         }
31         if (Input.GetMouseButtonUp(1))
32         {
33             isRotate = false;
34         }
35 
36         if (isRotate)
37         {
38             Vector3 originalPosion = transform.position;
39             Quaternion originalRotate = transform.rotation;
40 
41             transform.RotateAround(player.position, Vector3.up, rotateSpeed * Input.GetAxis("Mouse X"));
42 
43             print(rotateSpeed * Input.GetAxis("Mouse Y"));
44             transform.RotateAround(player.position, Vector3.right, rotateSpeed * Input.GetAxis("Mouse Y"));
45             float x = transform.eulerAngles.x;
46             if (x > 80||x<10)
47             {
48                 transform.position = originalPosion;
49                 transform.rotation = originalRotate;
50             }
51         }
52         offSet = transform.position - player.position;//重新得到相机与当前人物对象的向量
53     }
54     //滑轮控制镜头远近
55     void scroll()
56     {
57         distance = offSet.magnitude;
58         distance -= Input.GetAxis("Mouse ScrollWheel")*scrollSpeed;
59         distance = Mathf.Clamp(distance, minDis, maxDis);
60         offSet = offSet.normalized * distance;
61     }

使用到的方法:

分离轴定理(Separating Axis Theorem)

概念:通过判断任意两个 凸多边形 在任意角度下的投影是否均存在重叠,来判断是否发生碰撞。若在某一角度光源下,两物体的投影存在间隙,则为不碰撞,否则为发生碰撞。

图例:
澳门金莎娱乐网站 8

在程序中,遍历所有角度是不现实的。那如何确定 投影轴 呢?其实投影轴的数量与多边形的边数相等即可。

澳门金莎娱乐网站 9

以较高抽象层次判断两个凸多边形是否碰撞:

JavaScript

function polygonsCollide(polygon1, polygon2) { var axes, projection1, projection2   // 根据多边形获取所有投影轴 axes = polygon1.getAxes() axes.push(polygon2.getAxes())   // 遍历所有投影轴,获取多边形在每条投影轴上的投影 for(each axis in axes) { projection1 = polygon1.project(axis) projection2 = polygon2.project(axis)   // 判断投影轴上的投影是否存在重叠,若检测到存在间隙则立刻退出判断,消除不必要的运算。 if(!projection1.overlaps(projection2)) return false } return true }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function polygonsCollide(polygon1, polygon2) {
var axes, projection1, projection2
 
// 根据多边形获取所有投影轴
axes = polygon1.getAxes()
axes.push(polygon2.getAxes())
 
// 遍历所有投影轴,获取多边形在每条投影轴上的投影
for(each axis in axes) {
projection1 = polygon1.project(axis)
projection2 = polygon2.project(axis)
 
// 判断投影轴上的投影是否存在重叠,若检测到存在间隙则立刻退出判断,消除不必要的运算。
if(!projection1.overlaps(projection2))
return false
}
return true
}

上述代码有几个需要解决的地方:

  • 如何确定多边形的各个投影轴
  • 如何将多边形投射到某条投影轴上
  • 如何检测两段投影是否发生重叠

以下是本文例子里实现的粒子类: 复制代码 代码如下:// Particle.js Particle = function(position, velocity, life, color, size) { this.position = position; this.velocity = velocity; this.acceleration = Vector2.zero; this.age = 0; this.life = life; this.color = color; this.size = size; }; 游戏循环 粒子系统通常可分为三个周期: 发射粒子 模拟粒子 渲染粒子 在游戏循环中,需要对每个粒子系统执行以上的三个步骤。 生与死 在本文的例子里,用一个JavaScript数组particles储存所有活的粒子。产生一个粒子只是把它加到数组末端。代码片段如下: 复制代码 代码如下://ParticleSystem.js function ParticleSystem() { // Private fields var that = this; var particles = new Array(); // Public fields this.gravity = new Vector2; this.effectors = new Array(); // Public methods this.emit = function { particles.push; }; // ... } 粒子在初始化时,年龄则是固定的。年龄和生命的单位都是秒。每个模拟步,都会把粒子老化,即是把年龄增加Delta t,年龄超过生命,就会死亡。代码片段如下: 复制代码 代码如下:function ParticleSystem() { // ... this.simulate = function; applyGravity; kinematics; }; // ... // Private methods function aging { for (var i = 0; i < particles.length; ) { var p = particles[i]; p.age += dt; if kill; else i++; } } function kill { if particles[index] = particles[particles.length - 1]; particles.pop(); } // ... } 在函数kill()里,用了一个技巧。因为粒子在数组里的次序并不重要,要删除中间一个粒子,只需要复制最末的粒子到那个元素,并用pop()移除最末的粒子就可以。这通常比直接删除数组中间的元素快(在C++中使用数组或std::vector亦是)。 运动学模拟 把本文最重要的两句运动学模拟代码套用至所有粒子就可以。另外,每次模拟会先把引力加速度写入粒子的加速度。这样做是为了将来可以每次改变加速度。 复制代码 代码如下:function ParticleSystem() { // ... function applyGravity() { for particles[i].acceleration = that.gravity; } function kinematics { for { var p = particles[i]; p.position = p.position.add(p.velocity.multiply; p.velocity = p.velocity.add(p.acceleration.multiply; } } // ... } 渲染 粒子可以用很多不同方式渲染,例如用圆形、线段、影像、精灵等等。本文采用圆形,并按年龄生命比来控制圆形的透明度,代码片段如下: 复制代码 代码如下:function ParticleSystem() { // ... this.render = function { for { var p = particles[i]; var alpha = 1 - p.age / p.life; ctx.fillStyle = "rgba("

Transform.RotateAround 围绕旋转

function RotateAround (point : Vector3, axis : Vector3, angle : float) : void

Description描述

Rotates the transform about axis passing through point in world coordinates by angle degrees.

按照angle度通过在世界坐标的point轴旋转物体。

简单的说,按照多少度在世界坐标的某位置轴旋转物体。

This modifies both the position and the rotation of the transform.

这个修改变换的位置和旋转角度。

 

Input.GetAxis("Mouse X")和Input.GetAxis("Mouse Y")可以分别得到鼠标在水平和垂直方向上拖动的动作信息。

Input.GetAxis("Mouse ScrollWheel")可以获得鼠标滚轮的前后滚动的动作信息。

 

 

投影轴

如下图所示,我们使用一条从 p1 指向 p2 的向量来表示多边形的某条边,我们称之为边缘向量。在分离轴定理中,还需要确定一条垂直于边缘向量的法向量,我们称之为“边缘法向量”。

投影轴平行于边缘法向量。投影轴的位置不限,因为其长度是无限的,故而多边形在该轴上的投影是一样的。该轴的方向才是关键的。

澳门金莎娱乐网站 10

JavaScript

// 以原点(0,0)为始,顶点为末。最后通过向量减法得到 边缘向量。 var v1 = new Vector(p1.x, p1.y) v2 = new Vector(p2.x, p2.y)   // 首先得到边缘向量,然后再通过边缘向量获得相应边缘法向量(单位向量)。 // 两向量相减得到边缘向量 p2p1(注:上面应该有个右箭头,以表示向量)。 // 设向量 p2p1 为(A,B),那么其法向量通过 x1x2+y1y2 = 0 可得:(-B,A) 或 (B,-A)。 axis = v1.edge(v2).normal()

1
2
3
4
5
6
7
8
// 以原点(0,0)为始,顶点为末。最后通过向量减法得到 边缘向量。
var v1 = new Vector(p1.x, p1.y)
v2 = new Vector(p2.x, p2.y)
 
// 首先得到边缘向量,然后再通过边缘向量获得相应边缘法向量(单位向量)。
// 两向量相减得到边缘向量 p2p1(注:上面应该有个右箭头,以表示向量)。
// 设向量 p2p1 为(A,B),那么其法向量通过 x1x2+y1y2 = 0 可得:(-B,A) 或 (B,-A)。
axis = v1.edge(v2).normal()

以下是向量对象的部分实现,具体可看源码。

JavaScript

var Vector = function(x, y) { this.x = x this.y = y }   Vector.prototype = { // 获取向量大小(即向量的模),即两点间距离 getMagnitude: function() { return Math.sqrt(Math.pow(this.x, 2), Math.pow(this.y, 2)) }, // 点积的几何意义之一是:一个向量在平行于另一个向量方向上的投影的数值乘积。 // 后续将会用其计算出投影的长度 dotProduct: function(vector) { return this.x * vector.x + this.y + vector.y }, // 向量相减 得到边 subtarct: function(vector) { var v = new Vector() v.x = this.x - vector.x v.y = this.y - vector.y return v }, edge: function(vector) { return this.substract(vector) }, // 获取当前向量的法向量(垂直) perpendicular: function() { var v = new Vector() v.x = this.y v.y = 0 - this.x return v }, // 获取单位向量(即向量大小为1,用于表示向量方向),一个非零向量除以它的模即可得到单位向量 normalize: function() { var v = new Vector(0, 0) m = this.getMagnitude() if(m !== 0) { v.x = this.x / m v.y = this.y /m } return v }, // 获取边缘法向量的单位向量,即投影轴 normal: function() { var p = this.perpendicular() return p .normalize() } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
var Vector = function(x, y) {
this.x = x
this.y = y
}
 
Vector.prototype = {
// 获取向量大小(即向量的模),即两点间距离
getMagnitude: function() {
return Math.sqrt(Math.pow(this.x, 2),
Math.pow(this.y, 2))
},
// 点积的几何意义之一是:一个向量在平行于另一个向量方向上的投影的数值乘积。
// 后续将会用其计算出投影的长度
dotProduct: function(vector) {
return this.x * vector.x + this.y + vector.y
},
// 向量相减 得到边
subtarct: function(vector) {
var v = new Vector()
v.x = this.x - vector.x
v.y = this.y - vector.y
return v
},
edge: function(vector) {
return this.substract(vector)
},
// 获取当前向量的法向量(垂直)
perpendicular: function() {
var v = new Vector()
v.x = this.y
v.y = 0 - this.x
return v
},
// 获取单位向量(即向量大小为1,用于表示向量方向),一个非零向量除以它的模即可得到单位向量
normalize: function() {
var v = new Vector(0, 0)
m = this.getMagnitude()
if(m !== 0) {
v.x = this.x / m
v.y = this.y /m
}
return v
},
// 获取边缘法向量的单位向量,即投影轴
normal: function() {
var p = this.perpendicular()
return p .normalize()
}
}

澳门金莎娱乐网站 11
向量相减

更多关于向量的知识可通过其它渠道学习。

  • Math.floor + "," + Math.floor + "," + Math.floor + "," + alpha.toFixed"; ctx.beginPath(); ctx.arc(p.position.x, p.position.y, p.size, 0, Math.PI * 2, true); ctx.closePath; } } // ... } 基本粒子系统完成 以下的例子里,每帧会发射一个粒子,其位置在画布中间,发射方向是360度,速率为100,生命为1秒,红色、半径为5象素。 复制代码 代码如下: var ps = new ParticleSystem(); var dt = 0.01; function sampleDirection() { var theta = Math.random() * 2 * Math.PI; return new Vector2, Math.sin; } function step() { ps.emit(new Particle, sampleDirection, 1, Color.red, 5)); ps.simulate; ps.render; } start("basicParticleSystemCanvas", step);

Vector3.normalized 规范化

var normalized : Vector3

Description描述

Returns this vector with a magnitude of 1 (Read Only).

返回向量的长度为1(只读)。

When normalized, a vector keeps the same direction but its length is 1.0.

当规格化后,向量保持同样的方向,但是长度变为1.0。

Note that the current vector is unchanged and a new normalized vector is returned. If you want to normalize the current vector, use Normalize function.

注意,当前向量是不改变的并且返回一个新的规范化的向量。如果你想规范化当前向量,使用Normalize函数。

If the vector is too small to be normalized a zero vector will be returned.

如果这个向量太小而不能被规范化,一个零向量将会被返回。

 

 

投影

投影的大小:通过将一个多边形上的每个顶点与原点(0,0)组成的向量,投影在某一投影轴上,然后保留该多边形在该投影轴上所有投影中的最大值和最小值,这样即可表示一个多边形在某投影轴上的投影了。

判断两多边形的投影是否重合:projection1.max > projection2.min && project2.max > projection.min

澳门金莎娱乐网站 12
为了易于理解,示例图将坐标轴原点(0,0)放置于三角形边1投影轴的适当位置。

由上述可得投影对象:

JavaScript

// 用最大和最小值表示某一凸多边形在某一投影轴上的投影位置 var Projection = function (min, max) { this.min this.max } projection.prototype = { // 判断两投影是否重叠 overlaps: function(projection) { return this.max > projection.min && projection.max > this.min } }

1
2
3
4
5
6
7
8
9
10
11
// 用最大和最小值表示某一凸多边形在某一投影轴上的投影位置
var Projection = function (min, max) {
    this.min
    this.max
}
projection.prototype = {
    // 判断两投影是否重叠
    overlaps: function(projection) {
        return this.max > projection.min && projection.max > this.min
    }
}

如何得到向量在投影轴上的长度?
向量的点积的其中一个几何含义是:一个向量在平行于另一个向量方向上的投影的数值乘积。
由于投影轴是单位向量(长度为1),投影的长度为 x1 * x2 + y1 * y2

澳门金莎娱乐网站 13

JavaScript

// 根据多边形的每个定点,得到投影的最大和最小值,以表示投影。 function project = function (axis) { var scalars = [], v = new Vector()   this.points.forEach(function (point) { v.x = point.x v.y = point.y scalars.push(v.dotProduct(axis)) }) return new Projection(Math.min.apply(Math, scalars), Math.max,apply(Math, scalars)) }

1
2
3
4
5
6
7
8
9
10
11
12
// 根据多边形的每个定点,得到投影的最大和最小值,以表示投影。
function project = function (axis) {
var scalars = [], v = new Vector()
 
this.points.forEach(function (point) {
v.x = point.x
v.y = point.y
scalars.push(v.dotProduct(axis))
})
return new Projection(Math.min.apply(Math, scalars),
Math.max,apply(Math, scalars))
}

Run

圆形与多边形之间的碰撞检测

由于圆形可近似地看成一个有无数条边的正多边形,而我们不可能按照这些边一一进行投影与测试。我们只需将圆形投射到一条投影轴上即可,这条轴就是圆心与多边形顶点中最近的一点的连线,如图所示:

澳门金莎娱乐网站 14

因此,该投影轴和多边形自身的投影轴就组成了一组待检测的投影轴了。

而对于圆形与圆形之间的碰撞检测依然是最初的两圆心距离是否小于两半径之和。

分离轴定理的整体代码实现,可查看以下案例:

优点:

  • 精确

缺点:

  • 不适用于凹多边形

适用案例:

  • 任意凸多边形和圆形。

更多关于分离轴定理的资料:

  • Separating Axis Theorem (SAT) explanation
  • Collision detection and response
  • Collision detection Using the Separating Axis Theorem
  • SAT (Separating Axis Theorem)
  • Separation of Axis Theorem (SAT) for Collision Detection

Stop

延伸:最小平移向量(MIT)

通常来说,如果碰撞之后,相撞的双方依然存在,那么就需要将两者分开。分开之后,可以使原来相撞的两物体彼此弹开,也可以让他们黏在一起,还可以根据具体需要来实现其他行为。不过首先要做的是,还是将两者分开,这就需要用到最小平移向量(Minimum Translation Vector, MIT)。

澳门金莎娱乐网站 15

修改代码试试看

改变发射位置 向上发射,发射范围在90度内 改变生命 改变半径 每帧发射5个粒子

碰撞性能优化

若每个周期都需要对全部物体进行两两判断,会造成浪费(因为有些物体分布在不同区域,根本不会发生碰撞)。所以,大部分游戏都会将碰撞分为两个阶段:粗略和精细(broad/narrow)。

简单碰撞 为了说明用数值积分相对于分析解的优点,本文在粒子系统上加简单的碰撞。我们想加入一个需求,当粒子碰到长方形室的内壁,就会碰撞反弹,碰撞是完全弹性的(perfectly elastic collision)。 在程序设计上,我把这功能用回调方式进行。 ParticleSystem类有一个effectors数组,在进行运动学模拟之前,先执行每个effectors对象的apply()函数: 而长方形室就这样实现: 复制代码 代码如下:// ChamberBox.js function ChamberBox { this.apply = function { if (particle.position.x - particle.size < x1 || particle.position.x + particle.size > x2) particle.velocity.x = -particle.velocity.x; if (particle.position.y - particle.size < y1 || particle.position.y + particle.size > y2) particle.velocity.y = -particle.velocity.y; }; } 这其实就是当侦测到粒子超出内壁的范围,就反转该方向的速度分量。 此外,这例子的主循环不再每次把整个Canvas清空,而是每帧画一个半透明的黑色长方形,就可以模拟动态模糊的效果。粒子的颜色也是随机从两个颜色中取样。 复制代码 代码如下: var ps = new ParticleSystem(); ps.effectors.push(new ChamberBox; // 最重要是多了这语句 var dt = 0.01; function sampleDirection { var t = Math.random(); var theta = angle1 * t + angle2 * ; return new Vector2, Math.sin; } function sampleColor { var t = Math.random(); return color1.multiply.add(color2.multiply; } function step() { ps.emit(new Particle, sampleDirection(Math.PI * 1.75, Math.PI * 2).multiply, 3, sampleColor(Color.blue, Color.purple), 5)); ps.simulate; ctx.fillStyle="rgba"; ctx.fillRect(0,0,canvas.width,canvas.height); ps.render; } start("collisionChamberCanvas", step);

粗略阶段(Broad Phase)

Broad phase 能为你提供有可能碰撞的实体列表。这可通过一些特殊的数据结构实现,它们能为你提供信息:实体存在哪里和哪些实体在其周围。这些数据结构可以是:四叉树(Quad Trees)、R树(R-Trees)或空间哈希映射(Spatial Hashmap)等。

读者若感兴趣,可以自行查阅相关信息。

Run

精细阶段(Narrow Phase)

当你有了较小的实体列表,你可以利用精细阶段的算法(如上述讲述的碰撞算法)得到一个确切的答案(是否发生碰撞)。

Stop

最后

无论你碰不碰,我都会自摸️✌️。

完!

互动发射 最后一个例子加入互动功能,在鼠标位置发射粒子,粒子方向是按鼠标移动速度再加上一点噪音。粒子的大小和生命都加入了随机性。 复制代码 代码如下:var ps = new ParticleSystem(); ps.effectors.push(new ChamberBox; var dt = 0.01; var oldMousePosition = Vector2.zero, newMousePosition = Vector2.zero; function sampleDirection { var t = Math.random(); var theta = angle1 * t + angle2 * ; return new Vector2, Math.sin; } function sampleColor { var t = Math.random(); return color1.multiply.add(color2.multiply; } function sampleNumber { var t = Math.random(); return value1 * t + value2 * ; } function step() { var velocity = newMousePosition.subtract.multiply; velocity = velocity.add(sampleDirection.multiply; var color = sampleColor(Color.red, Color.yellow); var life = sampleNumber; var size = sampleNumber; ps.emit(new Particle(newMousePosition, velocity, life, color, size)); oldMousePosition = newMousePosition; ps.simulate; ctx.fillStyle="rgba"; ctx.fillRect(0,0,canvas.width,canvas.height); ps.render; } start("interactiveEmitCanvas", step); canvas.onmousemove = function { if (e.layerX || e.layerX == 0) { // Firefox e.target.style.position='relative'; newMousePosition = new Vector2; } else newMousePosition = new Vector2; };

参考资料

  • MDN:2D collision detection
  • 《HTML5 Canvas 核心技术:图形、动画与游戏开发》

    2 赞 3 收藏 1 评论

澳门金莎娱乐网站 16

Run

Stop

总结 本文介绍了最简单的运动学模拟,使用欧拉方法作数值积分,并以此法去实现一个有简单碰撞的粒子系统。本文的精华其实只有两条简单公式,希望让读者明白,其实物理模拟可以很简单。虽然本文的例子是在二维空间,但这例子能扩展至三维空间,只须把Vector2换成Vector3。本文完整源代码可下载。 续篇会谈及在此基础上加入其他物理现象,有机会再加入其他物理模拟课题。希望各位支持,并给本人更多意见。

本文由澳门金莎娱乐网站发布于澳门金莎娱乐网站,转载请注明出处:unity初探之黑暗之光,js源码阅读笔记_基础知识

关键词:

JS原型链和访问对象原型的方法,JS核心系列

JS核心系列:浅谈 原型对象和原型链 2016/03/01 · JavaScript· 2 评论 ·原型对象,原型链 原文出处: 一像素    在Javasc...

详细>>

利用AJAX实现搜索提示,迭代器和生成器

JS原生Date类型方法的一些冷知识 2015/09/07 · JavaScript· Date 原文出处:chitanda    一个多月没更新了--偷懒中。这个东西...

详细>>

澳门金莎娱乐网站的原型属性,理解JavaScript的原

精晓JavaScript的原型属性 2016/06/21 · JavaScript· 2 评论 ·原型 本文由 伯乐在线 -alvendarthy翻译,sunshinebuel校稿。未经许可...

详细>>

jquery基础教程,移动端h5开发相关内容总结

移动端h5开发相关内容总结(四) 2017/02/06 · HTML5 · 1评论 ·移动端 本文作者: 伯乐在线 -zhiqiang21。未经作者许可,禁...

详细>>