从零开始的MC特效(二 | 向量基础)

目录:

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

导读

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

<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的向量叫做零向量


向量的基本运算

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

image

在上方我们给一个向量乘以-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
图 2-1

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

思路:

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

我们把上方的思路转为几何来理解一下
image
有了上面的思路,我们就可以打代码了

首先我们要获取它们向量,这里我调用了location的clone函数,因此不会改变locB的值

之后我们先获取一下该向量的长度,等下要用
```double vectorLength = vectorAB.length();

然后我们再把该向量转为单位向量

然后我们就需要利用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出来后的点的**间距** (这里我不懂怎么解释,直接画了个图给你们,按照图来理解会很好哦~)
![image](http://upload-images.jianshu.io/upload_images/8109631-03035b9b0cddc4f0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
那么我就会得到每一次从点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);
}
}

游戏内的效果:
![image](http://upload-images.jianshu.io/upload_images/8109631-c32ef6631b2344a8.gif?imageMogr2/auto-orient/strip)

> **注: 以下的内容则是对于画线的一些实践,跟上方的理论知识有部分的联系,所以可看可不看,不属于应掌握内容**

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

这里顺便讲个小故事,在很久以前有个人叫**阿基米德**,他使用了一种叫做**割圆法**的方式计算出了圆周率,那么这个**割圆法的步骤**可以[看这个视频](https://www.bilibili.com/video/av25406675)来了解一下

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

首先既然是多边形,就会有点,我们找到点之后就成功了一半,我们来看一张图
![图 3-1](http://upload-images.jianshu.io/upload_images/8109631-7ec23efd4f399402.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

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

我们把它放入代码中看看要怎么操作
我们依然使用玩家的location作为原点O,之后我们实例化一个List用于保存这五个点

Location playerLocation = player.getLocation();
List 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个点了
如果我们现在对这五个点播放粒子的话你可以看到以下这样的图
![image](http://upload-images.jianshu.io/upload_images/8109631-e8916358317f7123.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
那么我们剩下的就是每个点依次顺序进行画线操作,然后你就可以看到以下的效果了
![image](http://upload-images.jianshu.io/upload_images/8109631-c9268d1984e316c6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

#### 4.一个五角星
其实也没什么好说的...看代码吧...

Location playerLocation = player.getLocation();
List 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的画线操作我们可以利用图来理解一下就可以

image

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

如果我们要画一个五角星,可以这么画
连接AC, AD,即0 -> 2,0 -> 3
连接BD, BE,即1 -> 3,1 -> 4
连接CE,即 2 -> 4
然后你就可以看到这样的五角星了
image