本帖最后由 602723113 于 2018-12-15 20:07 编辑


从零开始的MC特效 (二)

目录:
  • 导读
  • 数学上的向量与BukkitAPI中的向量
  • 利用向量进行画线操作
  • 利用画线进行多边形的绘制
  • 一个五角星

导读

本教程需要读者有一定的空间想象能力(因为我也懒得画图了233)
本教程使用的 Spigot1.10.2-R0.1-SNAPSHOT 核心
在阅读之前请确保你具有高中数学必修4Java基础知识
(没有我也会适当的解释的)

<To初中生>: 如果你是初中的话,别慌,你有趋向的概念就可以读懂本教程(应该吧...)
<To高中生>: 如果你还未学到关于上面的那本书,别慌学到了再来看也行233 (雾
<To大学生>: 没什么好说的...

1.数学上的向量与BukkitAPI中的向量

此处只讲平面向量

在教程开始之前我们来谈一下数学上对向量的定义
定义:
向量由长度和方向组成,总是用来描述从一个点到另一个的移动。
和我们平常所说的数量不同的是,向量则是有了方向这一概念
那么我们利用图来理解一下向量

图 1-1
在 图 1-1 当中我们定义两个点一个是点A一个是点B,那么我们以A为起点,B为终点作一条有向线段,得到下图

图 1-2
在 图1-2 当中我们作了一条有向线段,而这条有向线段我们称之为向量AB,那么这就是向量的一个基本定义

向量的坐标表示: 假设A = (1, 3), B = (2, 4),则向量AB = (2 - 1, 4 - 3),即终点减起点
(在BukkitAPI中,向量以坐标来表示所以突出这一段)

我们再来谈谈关于向量的一些相关概念
  • 向量的模: 若我们有一个向量AB,那么它的模可以使用 |AB| 来进行表示,表示的则是AB之间的距离即向量AB的长度
  • 单位向量: 我们把一个模等于1个单位长度的向量叫做单位向量
  • 相反向量: 与向量A长度相等,方向相反的向量,叫做向量A的相反向量
  • 零向量:我们把模长等于0的向量叫做零向量



向量的基本运算
向量是有加和乘的,那么我们根据以下的一些图来了解一下它们的基本运算,
向量相加应满足平行四边形定则或三角形定则
若向量A = (1, 3), 向量B = (2, 4),则向量A+向量B = (3, 7)

在上方我们给一个向量乘以-1,得到了反方向的向量,这个我们称之为相反向量
若向量A = (1, 3),向量A * 2 = (2, 4)
若向量A = (1, 3),向量A * -1 = (-1, -3)



BukkitAPI上的向量 —— Vector
那么BukkitAPI上对向量的定义又是怎么样的呢?首先我们在 org.bukkit.util 包下找到一个叫Vector的类
那么在BukkitAPI上,Vector类用于表示从一个Location到另一个Location的"趋势"。

若我们想获取从LocationA到LocationB的向量,我们可以使用以下的代码,即终点减起点
  1. /**
  2.      * 取第一个坐标到第二个坐标的向量
  3.      *
  4.      * @param firstLocation  坐标1
  5.      * @param secondLocation 坐标2
  6.      * @return {@link Vector}
  7.      */
  8.     public static Vector getVector(Location firstLocation, Location secondLocation) {
  9.         return secondLocation.subtract(firstLocation).toVector();
  10.     }
复制代码
请注意!这里的 secondLocation.subtract() 将会改变secondLocation的值


2.利用向量进行画线操作

首先我们来看张图

图 2 - 1

在上方的图中,我们定义两个点,一个是A一个是B,那么对应到MC中它们就是两个Location,LocationA和LocationB,那么我们要怎么使用向量来给这两个点进行画线的操作呢?

思路:
  • 首先我们要获取一下这两个点的一个向量,我们称之为向量AB
  • 之后我们把向量AB转为单位向量,方向不变
  • 在Vector类里有个叫multiply的函数,这个函数是对Vector进行乘的操作
  • 若向量AB乘以2,那么它的长度就会乘以了2,之后我们再使用locationA的add函数进行增加Vector,这样我们就可以获得在AB上期中的一个点了,然后我们进行遍历的操作以此类推...(看不懂的话喝杯茶...)


我们把上方的思路转为几何来理解一下

有了上面的思路,我们就可以打代码了

首先我们要获取它们向量,这里我调用了location的clone函数,因此不会改变locB的值
  1. Vector vectorAB = locB.clone().subtract(locA).toVector();
复制代码
之后我们先获取一下该向量的长度,等下要用
  1. double vectorLength = vectorAB.length();
复制代码
然后我们再把该向量转为单位向量
  1. vectorAB.normalize();
复制代码
然后我们就需要利用for循环来计算出一个每一个需要multiply的值
for (double i = 0; i < vectorLength; i += 0.1) {
    // 这里如果我们直接进行multiply的话则会修改vectorAB的值,所以我们先clone再multiply
    Vector vector = vectorAB.clone().multiply(i);
}
不要问我为什么上面没有代码格式...因为论坛吞代码
i的类型:
  • 首先我们来解释为什么这里的i的类型为double
  • 因为在坐标运算中,精确度是比较高的,当两个点相近时可能他们的值还没有到1,可能只有0.8左右的长度,所以我们使用double来修饰

结束值: 之后我们来谈谈这里为什么要循环到向量的模长的时候就结束,在上面的思路我们只举了当i == 2时的一个情况,而我们想获得这两个点之间的点,所以我们要填入向量的模长

步长: 这里我们依然修改了循环的步长,那么它的作用是什么?其实就是在设定每一个add出来后的点的间距 (这里我不懂怎么解释,直接画了个图给你们,按照图来理解会很好哦~)


那么我就会得到每一次从点A往点B进行逐步偏移的一个效果,然后我们每次给locA,进行add的操作就可以得到当前循环应该得到的Location
  1. locA.add(vector);
  2. locA.getWorld().playEffect(locA, Effect.HAPPY_VILLAGER, 1); // 这里是播放粒子的代码...
  3. locA.subtract(vector);
复制代码
完整代码:
  1. public static void buildLine(Location locA, Location locB) {
  2.     Vector vectorAB = locB.clone().subtract(locA).toVector();
  3.     double vectorLength = vectorAB.length();
  4.     vectorAB.normalize();
  5.     for (double i = 0; i < vectorLength; i += 0.1) {
  6.         Vector vector = vectorAB.clone().multiply(i);
  7.         locA.add(vector);
  8.         locA.getWorld().playEffect(locA, Effect.HAPPY_VILLAGER, 1);
  9.         locA.subtract(vector);
  10.     }
  11. }
复制代码
游戏内的效果:


注: 以下的内容则是对于画线的一些实践,跟上方的理论知识有部分的联系,所以可看可不看,不属于应掌握内容
3.利用画线进行多边形的绘制

在第一章的时候,我们利用三角函数画出了一个圆,如果我们向画的是一个多边形该怎么做呢?

这里顺便讲个小故事,在很久以前有个人叫阿基米德,他使用了一种叫做割圆法的方式计算出了圆周率,那么这个割圆法的步骤可以看这个视频来了解一下

那么我们提到这个割圆法有什么用呢,首先这个割圆法利用的是内接六边形,再使用勾股定理求出来的,之后再内接八边形十六边形一直下去...那么我们多边形始终都离不开一个圆,当然了是内接的多边形,而且还是规则的多边形,那么如果我们要画一个五边形或者三角形该怎么画呢?

首先既然是多边形,就会有,我们找到之后就成功了一半,我们来看一张图
图 3-1

在上图中我们找到了五个点,这五个点,每两两之间的夹角都为72°,这72°是怎么来的呢?我们利用360° / 5就可以得到72°,那么从360°/5得到的是一个单位圆中平均分成五块,且每块跟X轴正半轴的夹角都为72的倍数,那么这五个点就是这么找出来的

我们把它放入代码中看看要怎么操作
我们依然使用玩家的location作为原点O,之后我们实例化一个List用于保存这五个点
  1. Location playerLocation = player.getLocation();
  2. List<Location> locations = Lists.newArrayList();
复制代码
因为我们只是需要72的倍数的角度,所以我们把步长修改为 i += 72 (等价于 i = i + 72)
  1. for (int i = 0; i < 360; i += 72) {
  2. // 转弧度制
  3. double radians = Math.toRadians(i);
  4. locations.add(playerLocation.clone().add(3 * Math.cos(radians), 0D, 3 * Math.sin(radians)));
复制代码
为了以便于观看我将每次cos和sin计算的值都乘以3来扩大点到原点的距离
之后我们的locations里就存放有那5个点了
如果我们现在对这五个点播放粒子的话你可以看到以下这样的图
那么我们剩下的就是每个点依次顺序进行画线操作,然后你就可以看到以下的效果了


4.一个五角星

其实也没什么好说的...看代码吧...
  1. Location playerLocation = player.getLocation();
  2.                 List<Location> locations = Lists.newArrayList();
  3.                 for (int i = 0; i < 360; i += 72) {
  4.                     // 转弧度制
  5.                     double radians = Math.toRadians(i);
  6.                     locations.add(playerLocation.clone().add(3 * Math.cos(radians), 0D, 3 * Math.sin(radians)));
  7.                 }

  8.                 buildLine(locations.get(0), locations.get(2));
  9.                 buildLine(locations.get(0), locations.get(3));
  10.                 buildLine(locations.get(1), locations.get(3));
  11.                 buildLine(locations.get(1), locations.get(4));
  12.                 buildLine(locations.get(2), locations.get(4));
复制代码
备注: 这里的buildLine是上方我所发出来的一个方法哦


上方的locations的画线操作我们可以利用图来理解一下就可以


为了方便解释我给每个点都起了名字括号内的是它在locations中的下标

如果我们要画一个五角星,可以这么画

连接AC, AD,即0 -> 2,0 -> 3

连接BD, BE,即1 -> 3,1 -> 4

连接CE,即 2 -> 4

然后你就可以看到这样的五角星了



首先我承认这种方法并不是最好的方法,如果你有更好的方法的话不妨在楼下发出来,大家可以一起讨论讨论

结语

内容还是非常的少...然后...没什么好说的
—— 撰写: 一个来自普高文科的学生

[groupid=1306]Bone Studio[/groupid]