从零开始的MC特效 (二)
本教程转自 [Tutorial][Bone Studio][应该全核心]从零开始的MC特效 (二)
原作者是我 (小声
目录:
- 导读
- 数学上的向量与BukkitAPI中的向量
- 利用向量进行画线操作
- 利用画线进行多边形的绘制
- 一个五角星
导读
本教程需要读者有一定的空间想象能力(因为我也懒得画图了233)
本教程使用的 Spigot1.10.2-R0.1-SNAPSHOT 核心
在阅读之前请确保你具有高中数学必修4和Java基础的知识
(没有我也会适当的解释的)
<To初中生>: 如果你是初中的话,别慌,你有趋向的概念就可以读懂本教程(应该吧...)
<To高中生>: 如果你还未学到关于上面的那本书,别慌学到了再来看也行233 (雾
<To大学生>: 没什么好说的...
1.数学上的向量与BukkitAPI中的向量
此处只讲平面向量
在教程开始之前我们来谈一下数学上对向量的定义
**定义:
**
向量由长度和方向组成,总是用来描述从一个点到另一个的移动。
和我们平常所说的数量不同的是,向量则是有了方向这一概念
那么我们利用图来理解一下向量
在 图 1-1 当中我们定义两个点一个是点A一个是点B,那么我们以A为起点,B为终点作一条有向线段,得到下图
在 图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的向量,我们可以使用以下的代码,即终点减起点
/**
* 取第一个坐标到第二个坐标的向量
*
* @param firstLocation 坐标1
* @param secondLocation 坐标2
* @return {@link Vector}
*/
public static Vector getVector(Location firstLocation, Location secondLocation) {
return secondLocation.subtract(firstLocation).toVector();
}
请注意!这里的 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的值
Vector vectorAB = locB.clone().subtract(locA).toVector();
之后我们先获取一下该向量的长度,等下要用
double vectorLength = vectorAB.length();
然后我们再把该向量转为单位向量
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
locA.add(vector);
locA.getWorld().playEffect(locA, Effect.HAPPY_VILLAGER, 1); // 这里是播放粒子的代码...
locA.subtract(vector);
完整代码:
public static void buildLine(Location locA, Location locB) {
Vector vectorAB = locB.clone().subtract(locA).toVector();
double vectorLength = vectorAB.length();
vectorAB.normalize();
for (double i = 0; i < vectorLength; i += 0.1) {
Vector vector = vectorAB.clone().multiply(i);
locA.add(vector);
locA.getWorld().playEffect(locA, Effect.HAPPY_VILLAGER, 1);
locA.subtract(vector);
}
}
游戏内的效果:
注: 以下的内容则是对于画线的一些实践,跟上方的理论知识有部分的联系,所以可看可不看,不属于应掌握内容
3.利用画线进行多边形的绘制
在第一章的时候,我们利用三角函数画出了一个圆,如果我们向画的是一个多边形该怎么做呢?
这里顺便讲个小故事,在很久以前有个人叫阿基米德,他使用了一种叫做割圆法的方式计算出了圆周率,那么这个割圆法的步骤可以看这个视频来了解一下
那么我们提到这个割圆法有什么用呢,首先这个割圆法利用的是内接六边形,再使用勾股定理求出来的,之后再内接八边形,十六边形一直下去...那么我们多边形始终都离不开一个圆,当然了是内接的多边形,而且还是规则的多边形,那么如果我们要画一个五边形或者三角形该怎么画呢?
首先既然是多边形,就会有点,我们找到点之后就成功了一半,我们来看一张图
在上图中我们找到了五个点,这五个点,每两两之间的夹角都为72°,这72°是怎么来的呢?我们利用360° / 5就可以得到72°,那么从360°/5得到的是一个单位圆中平均分成五块,且每块跟X轴正半轴的夹角都为72的倍数,那么这五个点就是这么找出来的
我们把它放入代码中看看要怎么操作
我们依然使用玩家的location作为原点O,之后我们实例化一个List用于保存这五个点
Location playerLocation = player.getLocation();
List<Location> locations = Lists.newArrayList();
因为我们只是需要72的倍数的角度,所以我们把步长修改为 i += 72 (等价于 i = i + 72)
for (int i = 0; i < 360; i += 72) {
// 转弧度制
double radians = Math.toRadians(i);
locations.add(playerLocation.clone().add(3 *
Math.cos(radians), 0D, 3 * Math.sin(radians)));
}
为了以便于观看我将每次cos和sin计算的值都乘以3来扩大点到原点的距离
之后我们的locations里就存放有那5个点了
如果我们现在对这五个点播放粒子的话你可以看到以下这样的图
那么我们剩下的就是每个点依次顺序进行画线操作,然后你就可以看到以下的效果了
4.一个五角星
其实也没什么好说的...看代码吧...
Location playerLocation = player.getLocation();
List<Location> locations = Lists.newArrayList();
for (int i = 0; i < 360; i += 72) {
// 转弧度制
double radians = Math.toRadians(i);
locations.add(playerLocation.clone().add(3 * Math.cos(radians), 0D, 3 * Math.sin(radians)));
}
buildLine(locations.get(0), locations.get(2));
buildLine(locations.get(0), locations.get(3));
buildLine(locations.get(1), locations.get(3));
buildLine(locations.get(1), locations.get(4));
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
然后你就可以看到这样的五角星了
首先我承认这种方法并不是最好的方法,如果你有更好的方法的话不妨在楼下发出来,大家可以一起讨论讨论