Skip to content

Latest commit

 

History

History
175 lines (121 loc) · 7.8 KB

5-3.md

File metadata and controls

175 lines (121 loc) · 7.8 KB

5-3 粒子效果

!> 注意兼容性
本节中的部分内容可能不与旧版本(1.12.2 及以下)兼容,请使用 1.13 或更高版本!

粒子效果是 Minecraft 中常见的艺术效果。虽然大部分工作都在客户端完成,但粒子效果仍然是在服务端被计算的,并且推送到客户端。

值得注意的是,如果你在客户端的设置中将「粒子效果」设为了「关闭」,那么插件定义的粒子效果将不一定能生效。之所以说「不一定」,是因为不同服务端的实现不一样。总之,粒子效果只应当被用来进行装饰

播放粒子效果是 World 接口的一个方法,要获得 World 对象,可通过 Location 类的 getWorld 方法,或者先前介绍到的 Bukkit.getWorlds 方法。

Location location = e.getPlayer().getLocation();
World playerWorld = location.getWorld();
// 适合获取一个玩家的世界
World nether = Bukkit.getWorlds().get(1);
// 适合固定获取一个世界

播放一个粒子

播放粒子效果的方法叫做 spawnParticle。值得注意的是,它有非常多的重载版本,这次我们只介绍其中最简单的,签名如下:

void spawnParticle(Particle particle, double x, double y, double z, int count);
// 最简单的,在指定位置播放指定次数的效果

建议参考该方法的 JavaDocs 以确保你没有错过精彩的内容!

Particleorg.bukkit.Particle)枚举中含有许多粒子形状。WATER_BUBBLE 是泡泡,LAVA 是岩浆蹦出的火星,ITEM_CRACK 是物品损坏时碎裂的粒子,还有很多,你可以查询 Particle 枚举。

这可以播放一个粒子,但只播放一个怎么够呢?

播放一堆粒子(难看的)

这里需要说明的一点是,spawnParticle 方法一次只能画出一个粒子,Minecraft 原版中的粒子也是通过多次调用 spawnParticle 方法画出的。那么只需要传入几个随机数就可以啦~哇,别生气,我是开玩笑的。

播放一堆粒子(好看的)

我们通过有规则地画出一些粒子来画出形状。鉴于计算出需要的位置可能会用到数学库,可以使用 java.lang.Math 类中的方法,不过,下面我们使用几何的力量来解决(笑)。

粒子效果很多,我们举一个例子:当玩家登录时,在她的脚下播放一个六角星。

首先我们要画出图:

SKETCH.jpg

有鉴于图床失效、原图丢失,上图系定稿后再行修补,图文未必贴合。

绿色为原点(玩家脚下),红色为顶点,黑色为边,蓝色为辅助的直角坐标系。

由于一边计算一边画很慢,我们先将各个点都计算好。为了凸显计算的重要性,我们使用平面几何方法计算出它们(而不使用测量):

首先,我们需要计算出 A、B、C、D、E、F 的坐标。

我们将粒子效果的半径设为 3,即 B(0,3)。

那么 E 就是(0,-3)。

A 的横坐标为 cos 30° * 3 ≈ 2.5981(这样的精度已经过分了)

A 的纵坐标为 sin 30° * 3 = 1.5

所以 A(2.5981,1.5)

那么我们求得 C(-2.5981, 1.5),F(2.5981,-1.5),D(-2.5981, -1.5)

这样六个点就算出来了。

由于我们是在水平地面上画图,因此 Y 方向偏移量始终为 0,我们在上面的平面图中计算出来的是 X 和 Z 值。(Minecraft 的 Y 是立轴)

接下来我们只要把线画出来就行了。由于粒子效果只有一个点,因此我们通过点动成线的方法画出线。

每条线段我们使用 300 个点构成,这个数量可以根据服务器的性能适当调整,下面的算法表示了如何计算出线:

public static List<double[]> generateLine(double startX, double startZ, double endX, double endZ, int resolution) {
    double XStep = (endX - startX) / (double) resolution;
    // X 方向的「单元」
    double ZStep = (endZ - startZ) / (double) resolution;
    // Z 方向的「单元」
    List<double[]> result = new ArrayList<>();
    for (int i = 0; i <= resolution; i++) {
        double[] point = new double[2];
        // {x, z} 这样的数组
        point[0] = startX;
        point[1] = startZ;
        result.add(point);
        // 加入点阵
        startX += XStep;
        startZ += ZStep;
        // 移动到下一个单元

    }
    return result;
}

选用 List 只是编写方便,double[] 是坐标。事实上 spawnParticle 方法可以使用 Location 的实例,但考虑到我们要画的点很多,相比之下,使用 double 数组能节省一些内存开销。

其实算法很简单(就是模拟),X 方向上分成许多段,Z 方向上分成许多段,每次加上「一个单元」就可以画出直线了。a += ba = a + b 的缩写。

在正式绘画前,我们还需要将六条线画出来:

List<double[]> AC = generateLine(2.5981, 1.5, -2.5981, 1.5, 300);
List<double[]> CE = generateLine(-2.5981, 1.5, 0, -3, 300);
List<double[]> EA = generateLine(0, -3, 2.5981, 1.5, 300);
List<double[]> BD = generateLine(0, 3, -2.5981, -1.5, 300);
List<double[]> DF = generateLine(-2.5981, -1.5, 2.5981, -1.5, 300);
List<double[]> FB = generateLine(2.5981, -1.5, 0, 3, 300);

这样就做好啦!

下面的方法用于真正「绘制」每个粒子:

public void playMagic(Location base) {
    World world = base.getWorld();
    for (double[] point : AC) {
        world.spawnParticle(Particle.HEART, base.add(point[0], 0, point[1]), 1);
        base.subtract(point[0], 0, point[1]);
    }
    for (double[] point : CE) {
        world.spawnParticle(Particle.HEART, base.add(point[0], 0, point[1]), 1);
        base.subtract(point[0], 0, point[1]);
    }
    for (double[] point : EA) {
        world.spawnParticle(Particle.HEART, base.add(point[0], 0, point[1]), 1);
        base.subtract(point[0], 0, point[1]);
    }
    for (double[] point : BD) {
        world.spawnParticle(Particle.HEART, base.add(point[0], 0, point[1]), 1);
        base.subtract(point[0], 0, point[1]);
    }
    for (double[] point : DF) {
        world.spawnParticle(Particle.HEART, base.add(point[0], 0, point[1]), 1);
        base.subtract(point[0], 0, point[1]);
    }
    for (double[] point : FB) {
        world.spawnParticle(Particle.HEART, base.add(point[0], 0, point[1]), 1);
        base.subtract(point[0], 0, point[1]);
    }
}

add 是就地修改(Bukkit 缺德 +1),所以我们要用 subtract 减回去。

Particle.HEART 是心形粒子(其它粒子的效果似乎并不好),1 是次数。

最后,当我们需要播放效果时:

playMagic(e.getPlayer.getLocation());

就可以啦~

测试效果大概是这样的(OptiFine HD U G6,材质包 Love-And-Tolerance,光影 Sildurs Vibrant Shaders v1.281 Extreme-VL):

EFFECT

在实际截图时,我将粒子出现的位置向下调了 2 格以便操作。


因此我们总结出粒子 绘画的方法:

  1. 画出图,计算各点位置
  2. 确定分辨率(多少个粒子构成),用函数计算出直线,对于圆锥曲线(双曲线,抛物线,椭圆,圆形)可以使用对应的参数方程来绘制
  3. 在需要播放时,以玩家脚底为原点,添加偏移量(add)后播放即可

?> 我不想计算!
对于直线图形,如果你不想通过平面几何或解析几何计算的话,你可以使用你的画图软件(如「GeoGebra」,一个免费的数学画图工具)测量各个点的位置,再通过乘法得到适当的大小。上面我们编写的函数,对任何直线都适用(需要合适的分辨率)!