同步官网文档8m_25d

This commit is contained in:
kwiilh
2025-08-25 18:36:29 +08:00
parent 4dc0ecf18d
commit 9e8855eeb4
5089 changed files with 8798 additions and 4799 deletions

View File

@@ -0,0 +1,17 @@
---
front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg
hard: 入门
time: 5分钟
---
# 摘要
我们曾早在第二章中就学习过了**粒子****Particle**)和**粒子发射器****Particle Emitter**),了解到了国际版的粒子和中国版的粒子特效与序列帧是基于同一个底层的两套不同的系统。在本章中,我们将一起通过我们曾经推荐过的网络应用**Snowstorm**来制作国际版的自定义粒子。
- 在第一节(*开始制作下雪粒子*我们将一起学习制作一个雪花粒子并通过雪花粒子的制作来学习Snowstorm的使用和粒子JSON文件的组成。
- 在第二节(*开始制作带有翻书动画的粒子*)中,我们将一起学习制作一个加载光环,通过加载光环的制作了解一种类似于中国版序列帧特效的国际版动态粒子——**翻书动画****Flipbook Animation**)粒子的制作。
- 在第三节(*使用Molang增加粒子动感*我们一起学习如何使用Molang为粒子添加更多的功能。
- 在第四节(*了解粒子事件*)中,我们将一起通过事件的学习掌握事件的定义和触发。
- 在最后一节(*挑战:设计一个烟花粒子*)中,我们将一起进行一个挑战,制作一个烟花粒子。
本章关键词:粒子 发射器 下雪 生命周期 运动 动力学 UV 翻书动画 变量 曲线 事件 烟花

View File

@@ -0,0 +1,332 @@
---
front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg
hard: 高级
time: 30分钟
---
# 开始制作下雪粒子
在本节中,我们将一起来学习如何制作一个国际版粒子。我们使用知名的粒子制作软件**Snowstorm**来进行粒子的制作。
## 认识Snowstorm
曾在第三章我们便介绍过Snowstorm软件的打开方法。我们打开Snowstorm观察它的界面。
![](./images/17.1_snowstorm.png)
我们可以看到Snowstorm的界面非常简单分为左右两个部分。左边的部分为**属性面板**,记录着该粒子可编辑的属性;右侧是**预览窗**,可以用于实时预览编辑的结果。
我们之前已经学习过,国际版的自定义粒子本质上并不是一个单独的粒子,而是一个带有粒子发射器的**粒子系统****Particle System**)。下面,我们将更详细地介绍这一概念。
正如Snowstorm属性面板中划分的那样一个粒子可以大致分为三部分。第一部分是该粒子的定义部分包括了自身元数据的定义即自己的名字的定义、各种后续要是用的Molang变量和曲线的定义。
第二部分用于定义一个**发射器****Emitter**)。一个发射器可以理解为**发射****Emit**)或**喷射****Burst**)粒子的一个来源或一个区域。在发射器具备形状和位置的同时,发射器本身还可以具备一个粒子发射逻辑,这包含了发射器发射模式、发射时间和是否循环等属性,这通常称为发射器的**生命周期****Lifetime**)属性,决定着发射器的存活和消逝。
第三部分便是定义**粒子****Particle**)的属性,这里的粒子往往又称作**粒子实例****Particle Instance**),也就是我们所说的单个粒子。一个单个粒子便是发射器用于发射的一个基本单位,往往具有**纹理**、**材质**、**面向****Facing**或**Orientation**)、**运动****Motion**)方式等属性和理所当然也具有的**生命周期**。
一个国际版粒子便可以理解为一个粒子系统,其中存在一个粒子发射器,而该发射器便用于发射该粒子系统中定义的粒子实例。每个被发射出的粒子便通过生命周期控制何时消亡,而发射器本身也有发射器的生命周期来控制何时销毁。一个粒子系统便可以通过从生成到销毁的过程在世界中展现其效果。
![](./images/17.1_code_mode.png)
在Snowstorm的右上角点击**Code****代码**)按钮,便可以从**Preview****预览**模式切换到Code模式。在Code模式中我们可以查看当前粒子的源代码。这极大地方便了我们学习各个粒子组件的进程。
我们可以看到,粒子的模式标识符不带有命名空间,是单纯的一个`particle_effect`。Snowstorm制作的粒子格式版本默认为`1.10.0`
![](./images/17.1_example.png)
Snowstorm提供一些预设示例我们可以在顶部的菜单栏中找到。
## 制作下雪粒子
我们接下来手把手地一起逐步制作一个和Snowstorm提供的预设下雪粒子效果相同的下雪粒子。
### 设置粒子定义
![](./images/17.1_meta.png)
我们在**Effect****效果**)栏组中找到**Meta****元**)栏,填写我们粒子的**Identifier****标识符**)。我们不妨将其填写为`tutroial_demo:snow`。这代表着我们对JSON文件进行了如下修改
```json
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "tutroial_demo:snow",
}
}
}
```
由于我们不需要设置其他的变量,因此我们暂时忽略其他的选项栏。
### 设置粒子发射器
![](./images/17.1_emitter.png)
相对于粒子来说,粒子发射器较为简单,我们首先为我们的粒子设置粒子发射器。**Emitter****发射器**)栏组便是用来设置发射器的位置。
我们来设想,我们的粒子发射器应该为什么形状。我们的想制作一个下雪的粒子效果。所以粒子发射器应该在我们的头顶上喷射大量的单一雪花粒子,所以我们的粒子发射器可以呈立方体,不过我们可以允许其没有厚度。紧接着,我们希望雪一直下,而不是在特定时间结束,所以我们可以将其设置为循环。最后,我们希望其喷射时能够把粒子稳定地喷射出来,而不是一股脑全部瞬间喷出。
因此,我们可以在**Rate****速率**)栏中将**Mode****模式**)设置为**Steady****稳定**)而非**Instant****瞬时**),调整好喷射**Rate****速率**)和**Maximum****最大**)粒子数。
在**Emitter Lifetime****发射器生命周期**)栏,中**Mode****模式**)设置为**Looping****循环****Active Time****激活时间**可以设置为1。这配合上述的稳定模式意味着在1秒内稳定以80个/秒的速率稳定最大喷出4000个粒子。当然由于我们的激活时间短所以最大粒子数是远远达不到的。由于我们不希望粒子的喷射有喷射间期我们将**Sleep Time****睡眠时间**保留不填写这等价于填写为0。
在**Shape****形状**)栏,将中**Mode****模式**)设置为**Box****立方体**)便可以使其粒子发射器具备立方体的发射区域,我们将**Box Size****立方体尺寸**的Y坐标设置为0同时将**Offset****偏移**的Y设置为正20这代表粒子发射器会在生成的位置想上20格的位置进行发射发射区域是一个36×36的平面。事实上我们的形状模式还有很多这里用不到的其他选择比如**Point****点**)、**Sphere****球**)、**Disc****圆盘**)和**Entity Bounding Box****实体包围盒**)等。
上述这些操作相当于在JSON文件中进行了如下的补充
```json
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "tutroial_demo:snow"
},
"components": {
"minecraft:emitter_rate_steady": {
"spawn_rate": 80,
"max_particles": 4000
}, // 发射器速率稳定模式的组件
"minecraft:emitter_lifetime_looping": {
"active_time": 1
}, // 发射器生命周期循环模式的组件
"minecraft:emitter_shape_box": {
"offset": [0, 20, 0],
"half_dimensions": [36, 0, 36]
} // 发射器形状立方体模式的组件
}
}
}
```
这样,我们便完成了粒子发射器的制作。接下来只需要制作好雪花粒子,即可将其组装为一个下雪的粒子发射器了。
### 制作粒子外观和纹理
![](./images/17.1_texture_0.png)
我们先选择粒子的纹理。我们找到**Particle****粒子**)栏组的**Texture****纹理**)栏。我们可以看到这里是一个用于设置纹理的面板。我们在**Texture****纹理**)中选择我们需要的纹理贴图。我们这里可以使用原版的`particles.png`文件也就是原版的粒子纹理的图集文件作为我们的粒子纹理贴图。我们可以选择这个图集中的第10行粒子的**精灵图****Sprite**)作为我们的雪花粒子。这可以通过指定该图片上的**UV**来实现。
UV说白了便是值U坐标和V坐标。$uOv$坐标系是一种用于在一个屏幕或图片上指定一个特定位置使用的坐标系,虽然类似于我们通常使用的$xOy$坐标系,但是坐标的方向是语气相反的。$uOv$坐标系的坐标原点在一张图的左上角。U坐标代表横轴向右为正方向V坐标代表竖轴向下为正方向。这里我们可以看到我们这张粒子纹理中每个精灵图尺寸为8×8像素为了使其定位到第10行我们在**UV Start****UV起始点**的V坐标指定了72即8的9倍然后在**UV Size****UV尺寸**的V坐标指定了8。也就是说我们指定了V坐标从8的9倍到8的10倍处的粒子因此代表第10行的粒子精灵图纹理。
![](./images/17.1_texture.png)
对于Snowstorm中每一个输入框我们都可以点击其右侧的箭头按钮来进行展开这是方便我们进行Molang表达式的书写的。目前在本节中我们并不想深入Molang表达式的学习我们只需要知道这里的意思为U坐标的起始点从8的0倍到8的7倍处选择其一。这样就保证了每次选出的粒子都在这一行的前8列中。
![](./images/17.1_appearance.png)
接下来我们设置**Appearance****外观**)。**Size****尺寸**是粒子的大小我们这里依旧使用了Molang表达式来代表一个动态的大小。**Material****材质**)是粒子使用的材质,我们这里选择了**Alpha****透明**)。**Facing****面向**)为粒子的面向相机模式,我们有相当多的选择。这里我们选择了**Rotate XYZ****旋转XYZ**代表在XYZ三个轴向上都进行一个旋转然后再生成。
到目前为止我们的操作又相当于在JSON中补充了如下内容
```json
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "tutroial_demo:snow",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particle/particles"
} // 该字段是必需字段,用于设置材质和纹理贴图
},
"components": {
"minecraft:emitter_rate_steady": {
"spawn_rate": 80,
"max_particles": 4000
},
"minecraft:emitter_lifetime_looping": {
"active_time": 1
},
"minecraft:emitter_shape_box": {
"offset": [0, 20, 0],
"half_dimensions": [36, 0, 36]
},
"minecraft:particle_appearance_billboard": {
"size": ["0.07+variable.particle_random_1/6", "0.07+variable.particle_random_1/6"],
"facing_camera_mode": "rotate_xyz",
"uv": {
"texture_width": 128,
"texture_height": 128,
"uv": ["math.floor(variable.particle_random_2*8)*8", 72],
"uv_size": [8, 8]
}
} // 粒子的纹理UV、大小和面向都是在粒子外观公告板模式组件中设置的
}
}
}
```
### 设置粒子运动
![](./images/17.1_dynamic.png)
我们接下来设置粒子的**Motion****运动**)。为了使粒子具有如真实世界一样的运动轨迹,我们将**Mode****模式**)设置为**Dynamic****动力**)。这样,我们便可以为粒子指定动力学参数。在粒子系统中,一旦选择动力运动模式,便意味着粒子将根据$\dot{x}=f(x,t)$的微分方程进行**模拟****Simulate**),然后根据欧拉方法进行物理量的计算。粒子的模拟指粒子实例在生成时和接下来的每一帧中被计算和分配速度的过程。粒子效果想要呈现在玩家面前,就必须经过模拟阶段和渲染阶段,所以粒子的模拟是非常重要的一环。而这里我们便可以填入一些动力学参数来使粒子能够正确模拟。
我们通过**Direction****方向**)来设置粒子三个轴向上的初方向向量分量,这里我们使用了随机数来使得粒子的初方向各不相同。然后我们通过**Speed****速度**)来设置粒子的初速度,粒子之后的速度将根据**Acceleration****加速度**)和**Air Drag****空气阻力**)设置的值通过欧拉方法进行模拟,所以我们只需要设置方向、速度和加速度的初值即可。
至此我们的JSON文件已经补充为如下内容
```json
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "tutroial_demo:snow",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particle/particles"
}
},
"components": {
"minecraft:emitter_rate_steady": {
"spawn_rate": 80,
"max_particles": 4000
},
"minecraft:emitter_lifetime_looping": {
"active_time": 1
},
"minecraft:emitter_shape_box": {
"offset": [0, 20, 0],
"half_dimensions": [36, 0, 36],
"direction": ["Math.random(-1, 1)", "-1.2-Math.random(0, 1)", "Math.random(-1, 1)"] // 粒子的初方向事实上由发射器管理,因此被填入了发射器的组件中
},
"minecraft:particle_initial_speed": 1, // 粒子初速度组件
"minecraft:particle_motion_dynamic": {
"linear_acceleration": [0, -0.2, 0]
}, // 粒子运动动力模式组件
"minecraft:particle_appearance_billboard": {
"size": ["0.07+variable.particle_random_1/6", "0.07+variable.particle_random_1/6"],
"facing_camera_mode": "rotate_xyz",
"uv": {
"texture_width": 128,
"texture_height": 128,
"uv": ["math.floor(variable.particle_random_2*8)*8", 72],
"uv_size": [8, 8]
}
}
}
}
}
```
## 设置粒子生命周期
![](./images/17.1_lifetime.png)
我们设置粒子的**Lifetime****生命周期**),用来指定粒子何时存在和消亡。我们在**Mode****模式**)中将其更改为**Time****时间**)模式,然后在**Max Age****最大年龄**处更改为25秒。这样我们的粒子就会在25秒后消失。
```json
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "tutroial_demo:snow",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particle/particles"
}
},
"components": {
"minecraft:emitter_rate_steady": {
"spawn_rate": 80,
"max_particles": 4000
},
"minecraft:emitter_lifetime_looping": {
"active_time": 1
},
"minecraft:emitter_shape_box": {
"offset": [0, 20, 0],
"half_dimensions": [36, 0, 36],
"direction": ["Math.random(-1, 1)", "-1.2-Math.random(0, 1)", "Math.random(-1, 1)"]
},
"minecraft:particle_lifetime_expression": {
"max_lifetime": 25
}, // 时间模式其实和终止表达式共用一个组件,即粒子生命周期表达式模式组件
"minecraft:particle_initial_speed": 1,
"minecraft:particle_motion_dynamic": {
"linear_acceleration": [0, -0.2, 0]
},
"minecraft:particle_appearance_billboard": {
"size": ["0.07+variable.particle_random_1/6", "0.07+variable.particle_random_1/6"],
"facing_camera_mode": "rotate_xyz",
"uv": {
"texture_width": 128,
"texture_height": 128,
"uv": ["math.floor(variable.particle_random_2*8)*8", 72],
"uv_size": [8, 8]
}
}
}
}
}
```
## 设置粒子颜色
![](./images/17.1_color.png)
我们的雪花应该是白色的,所以我们无需改动**Color & Light****颜色和光照**栏的设置相同的我们的JSON文件也将不会进行任何修改。
## 设置粒子旋转
![](./images/17.1_rotation.png)
最后,我们设置粒子的**Rotation****旋转**)。在**Dynamic****动力**)模式下,我们将**Start Rotation****起始转角**同样通过Molang进行一个随机。下面的参数都将影响粒子的角速度不过我们在本粒子中并不需要填写。
这意味着我们的最终JSON文件为
```json
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "snowstorm:snow",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particle/particles"
}
},
"components": {
"minecraft:emitter_rate_steady": {
"spawn_rate": 80,
"max_particles": 4000
},
"minecraft:emitter_lifetime_looping": {
"active_time": 1
},
"minecraft:emitter_shape_box": {
"offset": [0, 20, 0],
"half_dimensions": [36, 0, 36],
"direction": ["Math.random(-1, 1)", "-1.2-Math.random(0, 1)", "Math.random(-1, 1)"]
},
"minecraft:particle_lifetime_expression": {
"max_lifetime": 25
},
"minecraft:particle_initial_spin": {
"rotation": "variable.particle_random_3*360"
}, // 通过粒子初始自旋组件设置初始转角
"minecraft:particle_initial_speed": 1,
"minecraft:particle_motion_dynamic": {
"linear_acceleration": [0, -0.2, 0]
},
"minecraft:particle_appearance_billboard": {
"size": ["0.07+variable.particle_random_1/6", "0.07+variable.particle_random_1/6"],
"facing_camera_mode": "rotate_xyz",
"uv": {
"texture_width": 128,
"texture_height": 128,
"uv": ["math.floor(variable.particle_random_2*8)*8", 72],
"uv_size": [8, 8]
}
}
}
}
}
```
这样我们就完成了下雪粒子的制作我们可以直接在Snowstorm中预览到最终效果
![](./images/17.1_snow.png)
可以看到,制作出的粒子还是十分漂亮且逼真的!

View File

@@ -0,0 +1,101 @@
---
front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg
hard: 高级
time: 20分钟
---
# 开始制作带有翻书动画的粒子
在Snowstorm中我们不仅可以制作静态纹理的粒子还可以制作带有**翻书动画****Flipbook Animation**的动态纹理粒子。类似于中国版序列帧动画翻书动画是国际版支持的一种通过帧变换进行的纹理动画模式在方块、物品、粒子等功能中都可以得到应用。本节中我们将一起学习如何制作一个和Snowstorm自带的原版着火粒子预设一样的着火粒子。
## 设置翻书纹理
![](./images/17.2_fire_texture.png)
翻书纹理需要用到一张动态精灵图作为图集,原版的`flame_atlas.png`恰好可以作为我们的图集文件。我们将**UV Mode****UV模式**)设置为**Animated****动画**),然后可以看到下面多出了一个**UV Step****UV步长**),我们将其设置为`flame_atlas.png`中一个精灵的尺寸即U步长为0V步长为16。然后我们开启**Stretch To Lifetime****拉伸至生命周期**),这意味着整个动画的时间将拉伸到与生命周期相同。正因如此,我们无需设置**FPS**因为它将自动设置我们将其保持为0即可。
## 设置局部空间
![](./images/17.2_fire_space.png)
在顶部的**Space****空间**)栏中,我们可以为粒子设置**局部空间****Local Space**)属性。“局部”二字其实是形容粒子的**模拟空间****Simulation Space**)。我们上一节中讲了,粒子的模拟是指粒子在生成前那一刻或之后的每一帧时计算应该其具备的速度的过程。一般而言,粒子的模拟是相对于世界的,也就是绝对坐标的,因此粒子生成后会相对整个世界运动。然后,有时我们希望粒子是跟随其挂接的实体或者定位器运动,那么就要将模拟空间设置为**局部的****Local**)。这里的三个选项分别可以将粒子的位置模拟、旋转模拟和速度模拟分别设置为局部空间。
## 设置其他属性
![](./images/17.2_fire_life.png)
我们将生命周期通过Molang设置为一个随机数。这样不同的火焰粒子实例将保持不同的燃烧速度。
![](./images/17.2_fire_appearance.png)
我们将面向设置为**Look at XYZ****看向XYZ**),这意味着粒子的三个轴向将始终面向看向它的实体。
![](./images/17.2_fire_motion.png)
我们将粒子运动方向设置为**Outwards****向外**Y轴初加速度恒定其余两轴初加速度随机。这样能够制作出一个向外“蔓延”的效果
![](./images/17.2_fire_emitter.png)
配置好发射器,将其设为**Dics****圆盘**)形状,**Radius****半径**设为1.2米;循环;稳定发射。
因此我们得到了最终的JSON文件
```json
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "tutorial_demo:fire",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particle/flame_atlas"
}
},
"components": {
"minecraft:emitter_local_space": {
"position": true,
"rotation": true,
"velocity": true
},
"minecraft:emitter_rate_steady": {
"spawn_rate": 20,
"max_particles": 1000
},
"minecraft:emitter_lifetime_looping": {
"active_time": 1
},
"minecraft:emitter_shape_disc": {
"offset": [0, 0.4, 0],
"radius": 1.2,
"direction": "outwards"
},
"minecraft:particle_lifetime_expression": {
"max_lifetime": "Math.random(1, 1.4)"
},
"minecraft:particle_initial_speed": 1,
"minecraft:particle_motion_dynamic": {
"linear_acceleration": ["(variable.particle_random_1-0.5)", 1.2, "(variable.particle_random_1-0.5)"]
},
"minecraft:particle_appearance_billboard": {
"size": [0.4, 0.4],
"facing_camera_mode": "lookat_xyz",
"uv": {
"texture_width": 16,
"texture_height": 512,
"flipbook": {
"base_UV": [0, 0],
"size_UV": [16, 16],
"step_UV": [0, 16],
"max_frame": 32,
"stretch_to_lifetime": true
}
}
}
}
}
}
```
![](./images/17.2_fire.png)
我们在Snowstorm的预览窗中将看到最终的效果非常理想。

View File

@@ -0,0 +1,138 @@
---
front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg
hard: 高级
time: 25分钟
---
# 使用Molang增加粒子动感
在本节中我们将通过Snowstorm自带的各种示例来学习如何在粒子中使用Molang。
## 设置变量
![](./images/17.3_loading.png)
我们打开Loading加载粒子来观察如何进行变量设置。
![](./images/17.3_loading_var.png)
在左侧的**Variables****变量**栏中我们可以设置两种Molang变量分别是**Start Variables****起始变量**)和**Tick Variables****滴答变量**)。起始变量是粒子**初始化****Initialize**)时便会设置的变量,只会设置一次。滴答变量是粒子每次跟随游戏滴答主循环进行设置的变量,每游戏刻都会设置一次。
这里,我们设置了两个起始变量`variable.size = 0.08``variable.radius = 0.6`。这在JSON中可以写为
```json
"minecraft:emitter_initialization": {
"creation_expression": "
variable.size = 0.08;
variable.radius = 0.6;
",
"per_update_expression": "" // 没有设置逐更新变量,也就是滴答变量
} // 发射器初始化组件
```
## 动态设置粒子大小
粒子可以通过Molang动态设置大小我们继续看加载粒子的示例。
![](./images/17.3_loading_size.png)
这里使用了先前自定义的变量`variable.size`和一个粒子自带的原生变量`variable.particle_age`。事实上虽然粒子是组件化的但是粒子并不被认为是ECS框架中的一个*实体*。所以,粒子本身并不具备查询函数,粒子所有的查询函数都来自于其挂接的实体。如果粒子挂接的实体消亡,那么粒子也将无法继续使用实体的查询函数。而粒子本身自带的一些变量值皆通过`variable.`前缀来提供。也就是说,虽然粒子没有查询,但是粒子依旧拥有一些原生变量。这里的`variable.particle_age`就是一个例子,它代表粒子从生成带当前为止的时间。
这段内容通过JSON来表示便为
```json
"minecraft:particle_appearance_billboard": {
"size": [
"variable.size*(1-variable.particle_age)",
"variable.size*(1-variable.particle_age)"
],
"facing_camera_mode": "rotate_xyz",
"uv": {
// 纹理UV的相关信息于此处无关
}
},
```
## 动态设置发射器位置
我们可以通过Molang动态设置发射器位置。我们继续看加载粒子。
![](./images/17.3_loading_emitter_shape.png)
我们在发射器的Shape形状栏中可以看到这里用了另一个自定义变量`variable.radius`。这是将后面通过`math.sin``math.cos`构造的单位圆周运动放缩成我们想要的大小。这个圆周运动是对于粒子发射器来说的,也就是说粒子除了上面所说的外观随着粒子年龄而变小之外,还会因为发射器在做圆周运动而在圆周上周期生成。
```json
"minecraft:emitter_shape_point": {
"offset": [
"variable.radius*-math.sin(variable.emitter_age*360)",
"variable.radius*math.cos(variable.emitter_age*360)",
0
]
},
```
接下来我们看另一种粒子的情况。我们来看Magic魔法粒子。
![](./images/17.3_magic.png)
![](./images/17.3_magic_emitter_shape.png)
我们可以看到相对于加载粒子魔法粒子的参数更加丰富。这是因为魔法粒子使用了Disc圆盘形状的发射器。圆盘在数学上一般指一个二维的圆面但是我们的粒子是发射在三维空间中的所以这里的圆盘必须指定一个**法向****Normal**),有了法向我们就可以将圆盘放置在垂直于法向的平面上。法向的方向我们可以用**法向量****Normal Vector**)来表示。**Plane Normal****平面法向**)便是用于指定这个法向量三个轴向分量的属性。
可以看到魔法粒子除了Offset偏移使用了Molang做圆周运动外还在法向量上使用了相同频率的简谐振动。也就是说圆盘所在的平面会在做简谐振动的同时做圆周运动这两个运动的叠加组成了圆盘最终的运动。
## 动态设置粒子自旋
为了和一般的旋转运动(比如上述我们通过圆周运动进行的伪旋转)分开,我们将粒子自身围绕一个轴所做的旋转运动称为**自旋****Spin**)。我们继续来看魔法粒子。
![](./images/17.3_magic_rotate.png)
我们可以看到魔法粒子的单体实例在整体运动的同时还会做自旋运动。这里便通过Molang指定了一个随机的自旋初速度。
## 动态设置粒子线性运动
粒子单体除了自旋,也就是做棱角运动且具有角速度之外,还会做线性运动且具有线速度。我们继续来看魔法粒子。
![](./images/17.3_magic_motion.png)
这里我们发现,我们使用了一个`variable.particle_random_3`变量来进行了线性加速度的随机。事实上,`variable.particle_random_3`是粒子自带的一个原生变量,同样的变量还有`variable.particle_random_1``variable.particle_random_2``variable.particle_random_4``variable.emitter_random_1``variable.emitter_random_2``variable.emitter_random_3``variable.emitter_random_4`。他们分别是0至1之间不同的原生随机变量互不相同可以直接引用。
```json
"minecraft:particle_motion_dynamic": {
"linear_acceleration": [
"math.random(0, 4)",
"math.random(0, 8)",
"variable.particle_random_3>0.2 ? -10 : -4"
]
}
```
## 动态设置粒子碰撞
粒子的碰撞也可以通过Molang控制只不过目前Snowstorm还不支持这一功能。事实上粒子的碰撞即粒子与世界中地形的碰撞是通过`minecraft:particle_motion_collision`组件来实现的虽然Snowstorm的粒子中有一个**Collision****碰撞**栏来支持碰撞参数的修改但是它不支持该组件中唯一支持Molang的`enabled`字段的修改。
```json
"minecraft:particle_motion_collision": {
"enabled": "/* Molang Expression */"
}
```
通过该字段的Molang控制可以动态控制一个粒子是否具备碰撞特性。
## 设计粒子颜色渐变
![](./images/17.3_rainbow.png)
最后我们来通过Rainbow彩虹粒子来学习设置颜色渐变。
![](./images/17.3_rainbow_curve.png)
![](./images/17.3_rainbow_color.png)
彩虹粒子在Color & Light颜色和光照栏中将**Color Mode****配色模式**)更改为了**Gradient****渐变**),并使用了**Interpolant****插值**)变量`variable.rainbow`。而可以看到,在最上部的**Curve****曲线**)栏中,我们定义了变量`variable.rainbow`的曲线。它使用`variable.particle_random_2`原生变量作为**Input****输入**)变量,通过**Catmull-Rom插值**产生了一个曲线,从而产生了预览窗中显示的彩虹效果。
至此我们基本了解了Molang在粒子中的应用。事实上在Snowstorm的菜单栏中我们可以获取到所有粒子的原生Molang变量的信息以供后续制作参考
![](./images/17.3_molang_ref.png)
![](./images/17.3_molangs.png)

View File

@@ -0,0 +1,223 @@
---
front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg
hard: 高级
time: 20分钟
---
# 了解粒子事件
我们之前一起学习过,数据驱动实体具有定义事件和触发事件的功能。事实上,我们的粒子也可以定义和触发事件。在本节中,我们一起来学习如何定义和触发粒子的事件。
## 定义事件
目前Snowstorm并不支持事件的定义和触发因此我们需要手动编辑JSON文件。事件和组件平级是定义在模式标识符下的。
```json
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "tutorial_demo:some_particle",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particle/particles"
}
},
"events": {
// 事件
},
"curves": {
// 曲线
},
"components": {
// 组件
}
}
}
```
`events`字段中,我们可以手动定义一些粒子的事件。和实体类似,每个粒子事件也是由一个对象组成的,对象的键名便是粒子的事件名。粒子事件也可以使用`sequence`来触发多个事件,或者使用`randomize`来加权随机触发一个事件。比如,我们可以分析如下杜撰的示例:
```json
"events": {
"<event_name_1>": {
"sequence": [
{
"particle_effect": {
"effect": "tutorial_demo:particle_1",
"type": "emitter"
},
"sound_effect": {
"event_name": "some.level.sound.event.name"
}
},
{
"particle_effect": {
"effect": "tutorial_demo:particle_2",
"type": "emitter"
},
"expression": "/* Some Molang Expression */"
},
{
"sequence": [
// 可以无限嵌套
]
},
{
"randomize": [
// 加权随机触发
]
}
] // 可以写一个事件触发序列
},
"<event_name_2>": {
"randomize": [
{
"weight": 1,
"particle_effect": {
"effect": "tutorial_demo:particle_2",
"type": "particle"
},
"expression": "/* Another Molang Expression */"
},
{
"weight": 1,
"particle_effect": {
"effect": "tutorial_demo:particle_4",
"type": "particle_with_velocity"
}
}
] // 可以写一个加权随机触发
},
"<event_name_3>": {
"particle_effect": {
"effect": "tutorial_demo:particle_5",
"type": "emitter_bound"
} // 也可以直接写一个事件触发响应
}
}
```
可以看到,这个事件的结构几乎和实体中的相一致,只是事件可以响应的内容有所不同。`particle_effect`用于在自己发射器的初始坐标处再生成另一个粒子,其中`effect`填写粒子的赋命名空间标识符,`type`填写生成的类型,可以填写`emitter``emitter_bound``particle``particle_with_velocity`,分别代表着生成粒子的发射器、绑定生成粒子的发射器(即如果当前粒子绑定在了一个实体或定位器上,新粒子会继承这个绑定关系)、生成一个粒子实例和生成一个继承了当前发射器速度的粒子实例。
`expression`用于在触发事件时执行一个Molang表达式一般用于更改变量的值。
`sound_effect`用于触发一个系统声音,或者称为存档声音事件。存档声音事件即是定义在资源包`sounds.json`中的声音,不过,对于粒子来说,只能触发定义在`individual_event_sounds`字段中的系统声音,即独立存档声音事件。比如,我们看原版的蜂蜜滴粒子的示例:
```json
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "minecraft:honey_drip_particle",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particle/particles"
}
},
"events": {
"hit_ground": {
"sound_effect": {
"event_name": "block.beehive.drip"
}
}
},
"components": {
// ...
"minecraft:particle_motion_collision": {
"coefficient_of_restitution": 0.1,
"collision_drag": 10.0,
"collision_radius": 0.01,
"events": [
{
"event": "hit_ground",
"min_speed": 0.5
}
]
}
// ...
}
}
}
```
以及对应的`sounds.json`文件:
```json
{
"individual_event_sounds": {
"events": {
"block.beehive.enter": {
"sound": "block.beehive.enter",
"volume": 0.75,
"pitch": [ 0.7, 0.8 ]
},
"block.beehive.exit": {
"sound": "block.beehive.exit",
"volume": 0.75,
"pitch": [0.9, 1.1]
},
"block.beehive.shear": {
"sound": "block.beehive.shear",
"volume": 0.8,
"pitch": [0.8, 1.0]
},
"block.beehive.work": {
"sound": "block.beehive.work",
"volume": 0.6,
"pitch": 1.0
},
"block.beehive.drip": {
"sound": "block.beehive.drip",
"volume": 0.3,
"pitch": [0.7, 0.9]
} // 此处定义了block.beehive.drip系统声音
}
},
"entity_sounds": {
// ...
}
// ...
}
```
## 触发事件
粒子在多个组件中都能触发事件,其中最为主要的便是两个生命周期事件组件`minecraft:emitter_lifetime_events``minecraft:particle_lifetime_events`
`minecraft:emitter_lifetime_events`组件为**发射器的生命周期事件**,其下具有`creation_event``expiration_event``timeline``travel_distance_events``looping_travel_distance_events`五个字段,每个字段都能以特定的方式触发组件。
```json
"minecraft:emitter_lifetime_events": {
"creation_event": [ "<event_name>", ...],
// "creation_event": "<event_name>",
"expiration_event": [ "<event_name>", ...],
// "expiration_event": "<event_name>",
"timeline": {
"some_time": [ "<event_name>", ... ],
"another_time": "<event_name>"
},
"travel_distance_events": {
"some_distance": [ "<event_name>", ... ],
"another_distance": "<event_name>"
},
"looping_travel_distance_events": [
{
"distance": 1.0,
"effects": [ "<effect_event_one>" ]
},
{
"distance": 2.0,
"effects": [ "<effect_event_two>" ]
}
]
}
```
我们可以看到,创建事件`creation_event`和过期事件`expiration_event`都可以指定一个事件名或者以数组的形式指定多个事件名。时间轴`timeline`和移动距离事件`travel_distance_events`分别代表发射器经过特定时间或者走过特定距离后触发一个或多个事件。其中`some_time``another_time``some_distance``another_distance`都可以像动画的关键帧那样填写一个数字。循环移动距离事件`looping_travel_distance_events`用于定义每走过多远触发一次的事件,这样的事件必须是生成一个新的粒子效果的事件,也就这里触发的事件必须具有`particle_effect`字段的定义。
`minecraft:particle_lifetime_events`为**粒子的生命周期事件**,不过其下只能定义创建事件`creation_event`、过期事件`expiration_event`和时间轴`timeline`。所有的语法是相同的。
至此,我们已经基本学习了粒子的大部分内容,接下来的一节,我们一起挑战创建一个烟花粒子,来完成我们本章的学习!

View File

@@ -0,0 +1,202 @@
---
front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg
hard: 高级
time: 25分钟
---
# 挑战:设计一个烟花粒子
在本节中,我们一起来制作一个烟花粒子。根据我们的生活经验,烟花的绽放一般分为升起和炸裂两部分。因此,我们分别制作升起和炸裂粒子,然后通过粒子事件将它们联系在一起。
## 制作升起粒子
我们在Snowstorm中新建一个粒子命名为`tutorial_demo:firework_rise`
![](./images/17.4_rise_effect.png)
我们模仿加载粒子,设置一个变量以代表粒子大小。
![](./images/17.4_rise_emitter.png)
我们设置为稳定喷射粒子的模式,并将发射器生命周期改为**Once****单次**。毕竟我们不像让烟花升起后“再升起一次”。激活时间设为5秒同时形状的偏移量的纵向上设置为`variable.emitter_age`。所以我们的烟花会上升5米速度1米/秒。
![](./images/17.4_rise_particle.png)
我们采用和加载粒子相同的纹理然后每个粒子的最大年龄设置为1并采用和加载粒子相同的粒子大小变化方式即1秒后由大到小完全消失。最后我们将颜色设置成红色。
因此我们得到了如下的JSON文件
```json
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "tutorial_demo:firework_rise",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particle/particles"
}
},
"components": {
"minecraft:emitter_initialization": {
"creation_expression": "variable.size = 0.08;"
},
"minecraft:emitter_rate_steady": {
"spawn_rate": 31,
"max_particles": 60
},
"minecraft:emitter_lifetime_once": {
"active_time": 5
},
"minecraft:emitter_shape_point": {
"offset": [0, "variable.emitter_age", 0]
},
"minecraft:particle_lifetime_expression": {
"max_lifetime": 1
},
"minecraft:particle_initial_speed": 0,
"minecraft:particle_motion_dynamic": {},
"minecraft:particle_appearance_billboard": {
"size": ["variable.size*(1-variable.particle_age)", "variable.size*(1-variable.particle_age)"],
"facing_camera_mode": "rotate_xyz",
"uv": {
"texture_width": 128,
"texture_height": 128,
"uv": [32, 88],
"uv_size": [8, 8]
}
},
"minecraft:particle_appearance_tinting": {
"color": [0.93725, 0.32157, 0.32157, 1]
}
}
}
}
```
![](./images/17.4_rise.png)
## 制作炸裂粒子
我们在Snowstorm中新建第二个粒子命名为`tutorial_demo:firework_crack`
![](./images/17.4_crack_effect.png)
同样,我们新建一个`variable.size`变量。
![](./images/17.4_crack_emitter.png)
我们希望很多粒子同时在某一点炸开所以我们设置为Instant瞬时模式。同时发射器向上偏移5米因为这是升起粒子的终点位置。
![](./images/17.4_crack_particle.png)
除了和升起粒子一样的部分外我们设置粒子的运动模拟。我们将运动方向设置为Outwards向外然后加速度三个轴上皆设置为-2。
这样,我们便得到了炸裂的粒子:
```json
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "tutorial_demo:firework_crack",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particle/particles"
}
},
"components": {
"minecraft:emitter_initialization": {
"creation_expression": "variable.size = 0.08;"
},
"minecraft:emitter_rate_instant": {
"num_particles": 600
},
"minecraft:emitter_lifetime_once": {
"active_time": 1
},
"minecraft:emitter_shape_sphere": {
"offset": [0, 5, 0],
"radius": 2,
"direction": "outwards"
},
"minecraft:particle_lifetime_expression": {
"max_lifetime": 1
},
"minecraft:particle_initial_speed": 2,
"minecraft:particle_motion_dynamic": {
"linear_acceleration": [-2, -2, -2]
},
"minecraft:particle_appearance_billboard": {
"size": ["variable.size*(1-variable.particle_age)", "variable.size*(1-variable.particle_age)"],
"facing_camera_mode": "lookat_xyz",
"uv": {
"texture_width": 128,
"texture_height": 128,
"uv": [32, 88],
"uv_size": [8, 8]
}
},
"minecraft:particle_appearance_tinting": {
"color": [0.93725, 0.32157, 0.32157, 1]
}
}
}
}
```
![](./images/17.4_crack.png)
最后,我们将两个粒子拼合在一起。我们通过`minecraft:emitter_lifetime_events`组件来触发执行两个粒子的事件。我们手动创建`firework.particle.json`文件,并写入如下内容:
```json
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "tutorial_demo:firework",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particle/particles"
}
},
"components": {
"minecraft:emitter_lifetime_events": {
"creation_event": "rise",
"timeline": {
"5.0": "crack"
}
},
"minecraft:emitter_rate_instant": {
"num_particles": 0
},
"minecraft:emitter_lifetime_once": {
"active_time": 6
},
"minecraft:emitter_shape_point": {
"offset": [0, "variable.emitter_age", 0]
}
},
"events": {
"rise": {
"particle_effect": {
"effect": "tutorial_demo:firework_rise",
"type": "emitter"
}
},
"crack": {
"particle_effect": {
"effect": "tutorial_demo:firework_crack",
"type": "emitter"
}
}
}
}
}
```
`basic_render_parameters`是必需存在的字段,不过我们用不到它,填写默认的材质和纹理即可。重点在于`minecraft:emitter_lifetime_events`组件。我们通过`creation_event`在粒子创建时便执行`rise`事件,而`rise`事件用于生成`tutorial_demo:firework_rise`粒子。我们通过`timeline`在5.0s处触发`crack`事件,而`crack`事件则用于生成`tutorial_demo:firework_crack`粒子。这样,我们的烟花粒子就完成了。
![](./images/17.4_in-game.gif)
我们可以进入游戏通过`/particle`命令来测试效果。可以看到,粒子果然如同我们设想的那样呈现了烟花的形状!

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB