# Vector Function [TOC] 下面两部分解释了什么是**向量(vectors)**和**归一化向量(normalised vectors)**,并且简要的介绍了如何在你的游戏中使用它们。 ## 1. What Is A Vector? 当某些时刻,你创造的游戏非常复杂的时候,你不得不使用向量。它被用于物理学、AI、三角形学(trigonometry)和其他各种情况,但是什么是向量?简单的说,向量是有指向(方向)的量。我们从**一维向量(1 dimensional vector)**开始看,它是一个带有数字的箭头,从数字0指向5。这是向量a,它的大小等于5,并且我们画另一个向量b,它从5开始到8结束,向量b等于3。 ![1D Vector](http://mimoe.cc/hj_gms2_f1/source/_build/3_scripting/4_gml_reference/maths/vector%20functions/images/1d_vector_image.png) 你应该注意到了,向量的开始并不重要,重要的是它的长度和方向。所以向量b从5开始、3个单位长并指向x轴(假设只有x轴)正方向和从0开始、长度为3并指向x+方向的向量是一样的。向量还可以相加,向量a加向量b结果是长为8的向量c。如果是负数呢?向量看得是方向和大小,而不是位置。 ![2D Vector](http://mimoe.cc/hj_gms2_f1/source/_build/3_scripting/4_gml_reference/maths/vector%20functions/images/2d_vector_image_1.png "2D Vector") 如上是,二维向量。三维向量如下图,z轴定义是**深度(depth)** ![3D Vector](http://mimoe.cc/hj_gms2_f1/source/_build/3_scripting/4_gml_reference/maths/vector%20functions/images/3d_vector_image.png "3D Version") ## 2. Normalised Vectors 通常,向量可以在很多情况下使用,但是有时你想要约束它们的值(就像处理角度),这就是为什么我们将它们归一化。这里使用了一个数学技巧,把向量的长度从n转化为1,这些转化过的向量被称为**单位向量**。 ![Unit Vector](http://mimoe.cc/hj_gms2_f1/source/_build/3_scripting/4_gml_reference/maths/vector%20functions/images/1d_norm_vector_image.png "Unit") 计算单位向量,我们首先得画出这个向量x、y轴的分量,并且计算出向量的长度(模)。接下来是计算过程。 ![normal vector](http://mimoe.cc/hj_gms2_f1/source/_build/3_scripting/4_gml_reference/maths/vector%20functions/images/2d_norm_vector_image_1.png) 首先根据坐标求出x、y边的长度: ```javascript vx = (x2 - x1) = (7 - 1) = 6 vy = (y2 - y1) = (4 - 1) = 3 ``` 接着求出向量长: ```javascript len = sqr(vx2 + vy2) = sqr(36 + 9) = sqr(45) = 6.708203932499369 ``` 最后,转化为单位长度: ```javascript vx = (vx/len) = (6 / 6.708203932499369) = 0.8944271909999159 vy = (vy/len) = (3 / 6.708203932499369) = 0.4472135954999579 a = 1 ``` 现在我们归一化了向量,那么我们如何在GMS2中使用它呢? **例:**假设你在设计一个设计游戏,角色必须射击敌军,你需要知道子弹在每一个step沿着x、y轴走多远才能击中目标: ![Game](http://mimoe.cc/hj_gms2_f1/source/_build/3_scripting/4_gml_reference/maths/vector%20functions/images/2d_norm_vector_image_2.png "Game") 为了这个你必须把角色和敌军的坐标归一化,并且得到x、y分量转化的小数值,这些数值将乘以子弹在每一个step中移动的速度,并在最后存储起来,作为下一步的初始坐标。让我们看看例子(数值已经四舍五入) ```javascript px = 100; py = 425; ex = 356; ey = 83; bullet_speed = 5; vx = (ex - px) = 256 vy = (ey - py) = -342 len = sqrt(vx2 + vy2) = sqrt(65536 + 116964) = 427.2 vx = vx / len = 0.6 vy = vy / len = 0.8 speed_x = vx * bullet_speed = 3 speed_y = vy * bullet_speed = 4 ``` 所以,为了达到目标,我们应该在每一步在子弹的x轴加3,y轴加4。 ## 3. Function GMS2也拥有一些基于向量的函数,下面是介绍使用方法 ### point_direction **语法** `point_direction(x1, y1, x2, y2)` |参数|描述| |:----|------ | |x1 | 向量的第一个分量的x坐标| |y1 | 向量的第一个分量的y坐标| |x2 | 向量的第二个分量的x坐标| |y2 | 向量的第二个分量的y坐标| **返回类型:** Real **描述** 这个函数返回由指定的组件[x1,y1]和[x2,y2]形成的向量的方向,与房间的固定x/y坐标有关。如图,如果我们要让导弹朝着敌人的方向前进,就可以使用下面例子的代码。 ![point_direction](http://mimoe.cc/hj_gms2_f1/source/_build/3_scripting/4_gml_reference/maths/vector%20functions/images/point_direction.png "point_direction") **举例** ```javascript var ex, ey; ex = instance_nearest(x, y, enemy).x; ey = instance_nearest(x, y, enemy).y; with (instance_create_layer(x, y, "Bullets", obj_Missile)) { direction = point_direction(x, y, ex, ey); } ``` **理解** 多次试验之后我发现这个函数返回的结果是度数,设向量与x轴正半轴的夹角为α。 例如: 1. 初始坐标[0, 0] ----》 [1, 1] 得到结果315 2. 初始坐标[0, 0] ----》 [1, 0] 得到结果270 3. 初始坐标[0, 0] ----》 [0,-1] 得到结果90 4. 初始坐标[0, 0] ----》 [-1,0] 得到结果180 5. 初始坐标[0, 0] ----》 [1,-1] 得到结果45 ![my](http://www.showdoc.cc/server/api/common/visitfile/sign/667a5c7d5775664d345affeec4276ffb?showdoc=.jpg "my") 如果把这些结果做成图形我们会清晰的发现,返回的度数是`360-α` ------------ ### point_distance **语法** `point_distance(x1, y1, x2, y2);` |参数|描述| |:----|------ | |x1 | 向量的第一个分量的x坐标| |y1 | 向量的第一个分量的y坐标| |x2 | 向量的第二个分量的x坐标| |y2 | 向量的第二个分量的y坐标| **返回类型:**Real **描述** 返回向量的长度 ![length](http://mimoe.cc/hj_gms2_f1/source/_build/3_scripting/4_gml_reference/maths/vector%20functions/images/point_distance.png "length") **举例** ```javascript var ex, ey; ex = instance_nearest(x, y, enemy).x; ey = instance_nearest(x, y, enemy).y; if point_distance(x, y, ex, ey) < 200 { instance_create_layer(x, y, "Bullets", obj_Missile) } ``` 上面代码自动检测最近的敌军的位置,如果角色与敌军的距离小于200,就会自动生成导弹。 ------------ ### point_distance_3d **语法** `point_distance_3d(x1, y1, z1, x2, y2, z2);` |参数|描述| |:----|------ | |x1 | 向量的第一个分量的x坐标| |y1 | 向量的第一个分量的y坐标| |z1 | 向量的第一个分量的z坐标| |x2 | 向量的第二个分量的x坐标| |y2 | 向量的第二个分量的y坐标| |z2 | 向量的第二个分量的z坐标| **返回类型:**Real **描述** 这个方法使用方式和point_distance一样。加上了z轴用于3D空间。 **举例** ```javascript var inst, ex, ey, ez; inst = instance_nearest(x, y, enemy); if inst { ex = inst.x; ey = inst.y; ez = inst.z; if point_distance_3d(x, y, z, ex, ey, ez) < 200 { instance_create_layer(x, y, "Bullets", obj_Missile) } } ``` 上面代码自动检测最近的敌军的位置,如果角色与敌军的距离小于200,就会自动生成导弹。 ------------ ### distance_to_object **语法** `distance_to_object( obj );` |参数|描述| |:----|------ | |obj |要检查的对象 | **返回类型:**Real **描述** 这个函数计算两个实例包围盒(边框)间的距离:你在哪个obj使用这个函数---》输入的obj间的距离。输入的obj的内容可以是它的索引或者ID,也可以是关键字**other**,并且返回的距离以像素为单位。请注意,如果两个obj的任意一个都没有使用精灵(sprite)或者遮罩(mask),那么返回结果是不正确的。 **举例** ```javascript if distance_to_object(obj_Player) < range { canshoot = true; } ``` 上面的代码检查到player对象的距离,如果它小于在变量“range”中存储的值,那么变量“canshoot”将被设置为true。 ------------ ### distance_to_point **语法** `distance_to_point(x, y);` |参数|描述| |:----|------ | |x |要检查位置的x坐标 | |y |要检查位置的y坐标 | **返回类型:**Real **描述** 这个函数计算调用实例的包围盒到指定x、y坐标的距离,返回值为像素。请注意,如果调用实例没有使用精灵(sprite)或者遮罩(mask),那么返回结果是不正确的。 **举例** ```javascript if distance_to_point(obj_Player.x, obj_Player.y) < range { canshoot = true; } ``` 上面的代码检查到player对象当前坐标的距离,如果它小于在变量“range”中存储的值,那么变量“canshoot”将被设置为true。 ------------ ### dot_product **语法** `dot_product(x1, y1, x2, y2)` |参数|描述| |:----|------ | |x1 | 向量的第一个分量的x坐标| |y1 | 向量的第一个分量的y坐标| |x2 | 向量的第二个分量的x坐标| |y2 | 向量的第二个分量的y坐标| **返回类型:**Real **描述** 点积(dot product)是表示两个向量间角度的关系的值,通过取两个向量,将他们相乘然后相加。"点积"这个名字由来是从中心点"·",它经常被用来指代这个运算(替代名称“标量积”强调结果的标量而不是向量性质) 实际的数学公式可以写成这样: ![dot product](http://mimoe.cc/hj_gms2_f1/source/_build/3_scripting/4_gml_reference/maths/vector%20functions/images/dot_product_image.png "dot product") 因此,在2D中,向量a[x1,y1]和b[x2,y2]的点积结果是x1x2+y1y2,在GMS2中计算 ```javascript a · b = (x1*x2)+(y1*y2); ``` 点积的奇怪之处在于它的输入向量与向量形成的角度间的关系,可以表示为: ```javascript a · b = (length of a) * (length of b) * cos(angle) ``` 即,点积等于两个向量的模乘以向量夹角的余弦,如下图 ![angle](http://mimoe.cc/hj_gms2_f1/source/_build/3_scripting/4_gml_reference/maths/vector%20functions/images/dot_product_image_2.png "angle") 我们可以从向量点积的结果得出如下结论 - 向量a与向量b垂直,点积为0,因为cos90为0 - 向量a与向量b间的夹角小于90度,点积将是正的(大于零),因为cos(ang)是正的。 - 向量a与向量b间的夹角大于90度,点积将为负(小于零),因为cos(ang)将为负。 所以我们如何将其运用到游戏中呢?这种数学关系能够运用到相当多种情况,但最好的方式是你自己建立一个实际的场景,看看会发生什么。简单的例子,角色位于敌人正前方90度之内就会被发现。 ![dot product Use](http://mimoe.cc/hj_gms2_f1/source/_build/3_scripting/4_gml_reference/maths/vector%20functions/images/dot_product_image_1.png "dot product Use") 基本上,我们要得到敌人的法线(包括方向和视线距离),之后我们需要得到角色和敌人间的向量。并且得到这些向量的点积,若结果为正,角色会被看到,负则不会。实际的代码如下 **举例** ```javascript var x1, y1, x2, y2; x1 = lengthdir_x(1, image_angle); y1 = lengthdir_y(1, image_angle); x2 = o_Player.x - x; y2 = o_Player.y - y; if dot_product(x1, y1, x2, y2) > 0 seen=true else seen=false; ``` 上面的代码在敌人中用实例图像的角度创建向量,接着得到角色和自己本身的距离。最后,计算出这两个向量的点积,如果大于可以被看见,否则不行。 ------------ ### dot_product_3d **语法** `dot_product_3d(x1, y1, z1, x2, y2, z2)` |参数|描述| |:----|------ | |x1 | 向量的第一个分量的x坐标| |y1 | 向量的第一个分量的y坐标| |z1 | 向量的第一个分量的z坐标| |x2 | 向量的第二个分量的x坐标| |y2 | 向量的第二个分量的y坐标| |z2 | 向量的第二个分量的z坐标| **返回类型:**Real **描述** 点积(dot product)是表示两个向量间角度的关系的值,通过取两个向量,将他们相乘然后相加。"点积"这个名字由来是从中心点"·",它经常被用来指代这个运算(替代名称“标量积”强调结果的标量而不是向量性质) 实际的数学公式可以写成这样: ![dot product](http://mimoe.cc/hj_gms2_f1/source/_build/3_scripting/4_gml_reference/maths/vector%20functions/images/dot_product_image.png "dot product") 因此,在2D中,向量a[x1,y1]和b[x2,y2]的点积结果是x1x2+y1y2,在GMS2中计算 ```javascript a · b = (x1*x2)+(y1*y2); ``` 点积的奇怪之处在于它的输入向量与向量形成的角度间的关系,可以表示为: ```javascript a · b = (length of a) * (length of b) * cos(angle) ``` 即,点积等于两个向量的模乘以向量夹角的余弦,如下图 ![angle](http://mimoe.cc/hj_gms2_f1/source/_build/3_scripting/4_gml_reference/maths/vector%20functions/images/dot_product_image_2.png "angle") 我们可以从向量点积的结果得出如下结论 - 向量a与向量b垂直,点积为0,因为cos90为0 - 向量a与向量b间的夹角小于90度,点积将是正的(大于零),因为cos(ang)是正的。 - 向量a与向量b间的夹角大于90度,点积将为负(小于零),因为cos(ang)将为负。 所以这对我们做游戏的人来说意味着什么呢?这个数学关系式能在许多种情况下运用,但是最好的方法是你亲自建立一个模拟的场景,并且看看会发生什么。最简单的方法是为视野内的敌人生成一个简单的“高度”检测,敌人将会看到角色,如果敌人建立在法线和3d平面上。 ![](http://mimoe.cc/hj_gms2_f1/source/_build/3_scripting/4_gml_reference/maths/vector%20functions/images/dot_product_3d_image.png) 基本上,我们根据敌人和地板的角度得到法向量,并且还得到角色到敌人的向量。我们接着可以得到这些向量的点积。根据前面的推论,如果结果为正(夹角小于90°),角色在敌人平面之上;如果为负,则在其之下。实际代码示例如下: **举例** ```javascript //在enemy中 var x1, y1, x2, y2; x1 = 0; y1 = 1; z1 = 0; x2 = o_Player.x - x; y2 = o_Player.y - y; z2 = o_Player.z - z; if dot_product_3d(x1, y1, z1, x2, y2, z2) > 0 above=true else above=false; ``` 上面的代码沿着实例y(上半)轴创建了一个向量1(法向量),接着根据o_Player到自身又创建了向量2。最后计算这两个向量的点积,并判断是否大于0。 ------------ ### dot_product_normalised **语法** `dot_product_normalised(x1, y1, x2, y2)` |参数|描述| |:----|------ | |x1 | 向量的第一个分量的x坐标| |y1 | 向量的第一个分量的y坐标| |x2 | 向量的第二个分量的x坐标| |y2 | 向量的第二个分量的y坐标| **返回类型:**Real **描述** 点积(dot product)是表示两个向量间角度的关系的值,通过取两个向量,将他们相乘然后相加。"点积"这个名字由来是从中心点"·",它经常被用来指代这个运算(替代名称“标量积”强调结果的标量而不是向量性质) 实际的数学公式可以写成这样: ![dot product](http://mimoe.cc/hj_gms2_f1/source/_build/3_scripting/4_gml_reference/maths/vector%20functions/images/dot_product_image.png "dot product") 因此,在2D中,向量a[x1,y1]和b[x2,y2]的点积结果是x1x2+y1y2,在GMS2中计算 ```javascript a · b = (x1*x2)+(y1*y2); ``` 那么标准化点积呢?标准化点积得到修正,返回值在-1~1间(详情见Normalised Vectors),在很多情况下是非常有用的,特别是在处理照明和其他3D功能时。 **举例** ```javascript var x1, y1, x2, y2; x1 = lengthdir_x(1, image_angle); y1 = lengthdir_y(1, image_angle); x2 = o_Player.x - x; y2 = o_Player.y - y; if dot_product_normalised(x1, y1, x2, y2) > 0 seen=true else seen=false; ``` 上面的代码在敌人中用实例图像的角度创建向量,接着得到角色和自己本身的距离。最后,计算出这两个向量的点积,如果大于可以被看见,否则不行。 ------------ ### dot_product_3d_normalised **语法** `dot_product_3d(x1, y1, z1, x2, y2, z2)` |参数|描述| |:----|------ | |x1 | 向量的第一个分量的x坐标| |y1 | 向量的第一个分量的y坐标| |z1 | 向量的第一个分量的z坐标| |x2 | 向量的第二个分量的x坐标| |y2 | 向量的第二个分量的y坐标| |z2 | 向量的第二个分量的z坐标| **返回类型:**Real **描述** 点积(dot product)是表示两个向量间角度的关系的值,通过取两个向量,将他们相乘然后相加。"点积"这个名字由来是从中心点"·",它经常被用来指代这个运算(替代名称“标量积”强调结果的标量而不是向量性质) 实际的数学公式可以写成这样: ![dot product](http://mimoe.cc/hj_gms2_f1/source/_build/3_scripting/4_gml_reference/maths/vector%20functions/images/dot_product_image.png "dot product") 因此,在2D中,向量a[x1,y1]和b[x2,y2]的点积结果是x1x2+y1y2,在GMS2中计算 ```javascript a · b = (x1*x2)+(y1*y2); ``` 那么标准化点积呢?标准化点积得到修正,返回值在-1~1间(详情见Normalised Vectors),在很多情况下是非常有用的,特别是在处理照明和其他3D功能时。 **举例** ```javascript var x1, y1, x2, y2; x1 = 0; y1 = 1; z1 = 0; x2 = o_Player.x - x; y2 = o_Player.y - y; z2 = o_Player.z - z; if dot_product_3d_normalised(x1, y1, z1, x2, y2, z2) > 0 above=true else above=false; ``` 上面的代码沿着实例y(上半)轴创建了一个向量1(法向量),接着根据o_Player到自身又创建了向量2。最后计算这两个向量的点积,并判断是否大于0。 ------------ ### angle_difference **语法** `angle_difference(ang1, ang2)` |参数|描述| |:----|------ | |ang1 | 第一个角度| |y1 | 第二个角度| **返回类型:**Real **描述** 这个函数将返回两个角度之间最小的角度差值,在-180度和180度之间(正角是逆时针,负角是顺时针)。 **举例** ```javascript var pd = point_direction(x, y, mouse_x, mouse_y); var dd = angle_difference(image_angle, pd); image_angle -= min(abs(dd), 10) * sign(dd); ``` pd为当前位置到鼠标位置与x(正半)轴间的角度,dd为当前image_angle与pd的差别度数。功能为可以让图片缓慢朝着鼠标位置旋转。