Unity学习记录——粒子系统与流动效果
前言
本文是中山大学软件工程学院2020级3d游戏编程与设计的最终作业
编程题:粒子光环
1.概述
粒子系统在unity官网与老师课件中的概念定义如下:
粒子系统 - Unity 手册:一个粒子系统可以模拟并渲染许多称为粒子的小图像或网格以产生视觉效果。系统中的每个粒子代表效果中的单个图形元素。系统共同模拟每个粒子以产生完整效果的印象。
粒子系统与流动效果 | 3D Game Programming & Design (pmlpml.github.io):粒子系统是模拟一些不确定、流动现象的技术。它采用许多形状简单且赋予生命的微小粒子作为基本元素来表示物体(一般由点或很小的多边形通过纹理贴图表示),表达物体的总体形态和特征的动态变化。人们经常使用粒子系统模拟的现象有火、爆炸、烟、水流、火花、落叶、云、雾、雪、尘、流星尾迹或者象发光轨迹这样的抽象视觉效果等等。
2.题目要求
参考 http://i-remember.fr/en 这类网站,使用粒子流编程控制制作一些效果, 如“粒子光环”
最终作业附加要求:必须带一个控制组件,控制粒子呈现效果
3.操作
按照如下结构建立一个对象HaloSystem
其中:
- HaloSystem为Empty,作为承载粒子系统的父类
- Collider为Empty,挂在了粒子系统所需要的碰撞体
- halo为Empty,用于作为粒子系统的父类
- in和out都为Particle System,即粒子系统,本次粒子系统的主角,通过两者不同地方的设置呈现效果
建立好HaloSystem首先给Collider添加碰撞体,设置如下:
分别将in和out两个粒子系统的数值修改如下:
对于此处冗长的设置,根据粒子系统模块 - Unity 手册,此处介绍粒子系统的中我们勾选了的模块:
-
Particle System 模块
包含影响整个系统的全局属性。大多数这些属性用于控制新创建的粒子的初始状态。其中各个属性如下:
属性 | 功能 |
---|---|
Duration | 系统运行的时间长度。 |
Looping | 如果启用此属性,系统将在其持续时间结束时再次启动并继续重复该循环。 |
Prewarm | 如果启用此属性,系统将初始化,就像已经完成一个完整周期一样(仅当 Looping 也启用时才有效)。 |
Start Delay | 启用此属性后,系统开始发射前将延迟一段时间(以秒为单位)。 |
Start Lifetime | 粒子的初始生命周期。 |
Start Speed | 每个粒子在适当方向的初始速度。 |
3D Start Size | 如果要分别控制每个轴的大小,请启用此属性。 |
Start Size | 每个粒子的初始大小。 |
3D Start Rotation | 如果要分别控制每个轴的旋转,请启用此属性。 |
Start Rotation | 每个粒子的初始旋转角度。 |
Flip Rotation | 使一些粒子以相反的方向旋转。 |
Start Color | 每个粒子的初始颜色。 |
Gravity Modifier | 缩放 Physics 窗口中设置的重力值。值为零会关闭重力。 |
Simulation Space | 控制粒子的运动位置是在父对象的局部空间中(因此与父对象一起移动)、在世界空间中还是相对于自定义对象(与您选择的自定义对象一起移动)。 |
Simulation Speed | 调整整个系统更新的速度。 |
Delta Time | 在 Scaled 和 Unscaled 之间进行选择,其中的 Scaled 使用 Time 窗口中的 Time Scale 值,而 Unscaled 将忽略该值。此属性对于出现在暂停菜单 (Pause Menu) 上的粒子系统非常有用。 |
Scaling Mode | 选择如何使用变换中的缩放。设置为 Hierarchy、Local 或 Shape。Local 仅应用粒子系统变换缩放,忽略任何父级。Shape 模式将缩放应用于粒子起始位置,但不影响粒子大小。 |
Play on Awake | 如果启用此属性,则粒子系统会在创建对象时自动启动。 |
Emitter Velocity | Choose how the Particle System calculates the velocity used by the Inherit Velocity and Emission modules. The system can calculate the velocity using a Rigidbody component, if one exists, or by tracking the movement of the Transform component. If no Rigidbody component exists, the system uses its Transform component by default. |
Max Particles | 系统中同时允许的最多粒子数。如果达到限制,则移除一些粒子。 |
Auto Random Seed | 如果启用此属性,则每次播放时粒子系统看起来都会不同。设置为 false 时,每次播放时系统都完全相同。 |
Random Seed | 禁用自动随机种子时,此值用于创建唯一的可重复效果。 |
Stop Action | 当属于系统的所有粒子都已完成时,可使系统执行某种操作。当一个系统的所有粒子都已死亡,并且系统存活时间已超过 Duration 设定的值时,判定该系统已停止。对于循环系统,只有在通过脚本停止系统时才会发生这种情况。 |
Disable | 禁用游戏对象。 |
Destroy | 销毁游戏对象。 |
Callback | 将 OnParticleSystemStopped 回调发送给附加到游戏对象的任何脚本。 |
Culling Mode | 选择粒子在屏幕外时是否暂停粒子系统模拟。在屏幕外时进行剔除具有最高效率,但您可能希望继续进行非一次性 (off-one) 效果的模拟。 |
Automatic | 循环系统使用 Pause__,而所有其他系统使用Always Simulate。 |
Disabled | 禁用 Ring Buffer Mode__,以便系统在粒子生命周期终结时删除粒子。 | | Pause Until Replaced | 在粒子生命周期结束时暂停旧粒子,直至达到 Max Particle__ 限制,此时系统会进行粒子再循环,因此旧粒子会重新显示为新粒子。 |
Loop Until Replaced | 在粒子生命周期结束时,粒子将倒回到其生命周期的指定比例,直至达到 Max Particle 限制,此时系统会进行粒子再循环,因此旧粒子会重新显示为新粒子。 |
- Emission 模块:模块中的属性会影响粒子系统发射的速率和时间。
- Shape模块:该模块定义发射粒子的体积或表面,以及起始速度的方向。Shape 属性定义发射体积的形状,其余模块属性因选择的形状而异。
- Renderer模块:决定了粒子的图像或网格如何被其他粒子变换、着色和过度绘制。
之后就是编写代码以及挂载的事情了。
4.代码
代码部分参考了老师给的学长优秀作品:2016 粒子光环:大神神作,无人超
粒子光环的思路很简单,也就是粒子在一个圆环之中向着一个方向运动;当鼠标移动到圆环中心点(之前设置的Collider)时,圆环就为向中心收缩。
- CirclePosition类,用于记录光环的位置与大小信息,以及时间
public class CirclePosition
{
public float radius = 0f, angle = 0f, time = 0f;
public CirclePosition(float radius, float angle, float time)
{
this.radius = radius; // 半径
this.angle = angle; // 角度
this.time = time; // 时间
}
}
- 各种记录光环的各种信息
private ParticleSystem particleSys; // 粒子系统
private ParticleSystem.Particle[] particleArr; // 粒子数组
private CirclePosition[] circle; // 极坐标数组
public int pCount = 5000; // 粒子数量
public float pSize = 0.15f; // 粒子大小
public float minRadius = 4.0f; // 最小半径
public float maxRadius = 11.0f; // 最大半径
public bool clockwise = true; // 旋转方向
public float speed = 1f; // 速度
public float pingPong = 0.01f; // 游离范围
public int tier = 10; // 速度差分层数
public Camera camera; // 主摄像机
private Ray ray; // 射线
private RaycastHit hit; // 碰撞信息
private float[] before; // 收缩前粒子位置
private float[] after; // 收缩后粒子位置
public float shrinkSpeed = 5f; // 粒子缩放的速度
private bool ischange = false; // 是否收缩
public int type; // 光环类型
public Gradient colorGradient; // 颜色渐变
private GradientAlphaKey[] alphaKeys; // 透明度
private GradientColorKey[] colorKeys; // 颜色
- 粒子的游离运动函数
void RandomlySpread()
{
// 初始化各粒子位置
// 随机每个粒子距离中心的半径,同时希望粒子集中在平均半径附近
for (int i = 0; i < pCount; ++i)
{
float midRadius = (maxRadius + minRadius) / 2;
float minRate = Random.Range(1.0f, midRadius / minRadius);
float maxRate = Random.Range(midRadius / maxRadius, 1.0f);
float radius = Random.Range(minRadius * minRate, maxRadius * maxRate);
// 随机每个粒子的角度
float angle = Random.Range(0.0f, 360.0f);
float theta = angle / 180 * Mathf.PI;
// 随机每个粒子的游离起始时间
float time = Random.Range(0.0f, 360.0f);
circle[i] = new CirclePosition(radius, angle, time);
before[i] = radius;
after[i] = 0.7f * radius;
if (after[i] < minRadius * 1.1f)
{
after[i] = Random.Range(Random.Range(minRadius, midRadius), (minRadius * 1.1f));
}
particleArr[i].position = new Vector3(circle[i].radius * Mathf.Cos(theta), 0f, circle[i].radius * Mathf.Sin(theta));
}
particleSys.SetParticles(particleArr, particleArr.Length);
}
- Update函数,每一帧检测碰撞事件,此处为光环的收缩与还原的控制
void Update()
{
for (int i = 0; i < pCount; i++)
{
if (ischange)
{
// 开始收缩
if (circle[i].radius > after[i])
{
circle[i].radius -= shrinkSpeed * (circle[i].radius / after[i]) * Time.deltaTime;
}
}
else
{
// 开始还原
if (circle[i].radius < before[i])
{
circle[i].radius += shrinkSpeed * (before[i] / circle[i].radius) * Time.deltaTime;
}
}
if (clockwise) circle[i].angle -= (i % tier + 1) * (speed / circle[i].radius / tier);
else circle[i].angle += (i % tier + 1) * (speed / circle[i].radius / tier);
// 保证angle在0~360度
circle[i].angle = (360.0f + circle[i].angle) % 360.0f;
float theta = circle[i].angle / 180 * Mathf.PI;
// 粒子在半径方向上游离
circle[i].time += Time.deltaTime;
circle[i].radius += Mathf.PingPong(circle[i].time, pingPong) - pingPong / 2.0f;
//particleArr[i].startColor = colorGradient.Evaluate(circle[i].angle / 360.0f);
particleArr[i].position = new Vector3(circle[i].radius * Mathf.Cos(theta), 0f, circle[i].radius * Mathf.Sin(theta));
}
ChangeColor();
particleSys.SetParticles(particleArr, particleArr.Length);
// 碰撞检测
ray = camera.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit) && hit.collider.gameObject.tag == "button") ischange = true;
else ischange = false;
}
- Start,初始化函数,此处根据设定的类型给光环设置颜色
void Start()
{
particleArr = new ParticleSystem.Particle[pCount];
circle = new CirclePosition[pCount];
before = new float[pCount];
after = new float[pCount];
// 初始化粒子系统
particleSys = this.GetComponent<ParticleSystem>();
var main = particleSys.main;
main.startSpeed = 0;
main.startSize = pSize;
main.loop = false;
main.maxParticles = pCount; // 粒子数量
particleSys.Emit(pCount); // 发射粒子
particleSys.GetParticles(particleArr);
alphaKeys = new GradientAlphaKey[5];
colorKeys = new GradientColorKey[2];
// 初始化梯度颜色控制器
alphaKeys[0].time = 0.0f; alphaKeys[0].alpha = 0.2f;
alphaKeys[1].time = 0.25f; alphaKeys[1].alpha = 1f;
alphaKeys[2].time = 0.5f; alphaKeys[2].alpha = 0.2f;
alphaKeys[3].time = 0.75f; alphaKeys[3].alpha = 1f;
alphaKeys[4].time = 1.0f; alphaKeys[4].alpha = 0.2f;
if (clockwise)
{
colorKeys[0].time = 0.25f;
colorKeys[1].time = 0.75f;
switch (type) // 根据设定的颜色组合改变
{
case 1:
colorKeys[0].color = new Color(221f / 255, 49f / 255, 221f / 255);
colorKeys[1].color = new Color(24f / 255, 177f / 255, 224f / 255);
break;
case 2:
colorKeys[0].color = new Color(255 / 255, 30 / 255, 57 / 255);
colorKeys[1].color = new Color(24f / 255, 224f / 255, 43f / 255);
break;
}
}
else
{
colorKeys[0].time = 0.25f;
colorKeys[1].time = 0.75f;
switch (type)
{
case 1:
colorKeys[1].color = new Color(221f / 255, 49f / 255, 221f / 255);
colorKeys[0].color = new Color(24f / 255, 177f / 255, 224f / 255);
break;
case 2:
colorKeys[1].color = new Color(255 / 255, 30 / 255, 57 / 255);
colorKeys[0].color = new Color(24f / 255, 224f / 255, 43f / 255);
break;
}
}
colorGradient.SetKeys(colorKeys, alphaKeys);
RandomlySpread();
}
5.补充
完善代码之后将代码挂载到粒子系统上,数值设置如下:
- in
- out
之后我们可以将halo再复制一份,增加到HaloSystem中
修改MinRadius与Max Radius的大小,再增加颜色种类,呈现多光环的效果
最后,修改摄像头的背景,改为全黑色,就可以明显看到粒子效果了
演示
gif图如下:
由于超出csdn文件大小限制,见gitee
演示视频已经上传至B站unity粒子系统——简单粒子光环_哔哩哔哩_bilibili
代码位置
代码以及文档均已经上传至hw_fina· XiaoChen04_3/3D_Computer_Game_Programming - gitee