写在前面
- 实验参考博客:感谢指导!
- Unity3D学习笔记(9)—— 粒子光环
- Unity3d——ParticleSystem粒子光环
- 堂上编程练习(有错误,仅作参考):Unity制作神奇的粒子海洋!
实验步骤
首先回顾粒子海洋的制作过程:
- 添加空对象并且添加粒子系统的部件
- 简单调整一下粒子部件中的粒子材料(选个默认粒子材料就好,不要让其为空)
- 写代码编程,挂到空对象上。并且将粒子系统部件拖入代码的公共变量中去。
- 添加颜色渐变部件,手动修改颜色
- 。。。。
所以其实对于粒子系统部件,我们不需要太多的操作,大部分的变化都是在代码中完成的,只要清楚各个属性对应的api。
粒子光环制作
首先添加一个空对象,并且添加部件ParticleSystem,然后简单设置一下属性:
还有发射器Render的属性,这里最重要就是选择材料!选择材料!选择材料!不然的话就会出现一堆粉紫色的小方块,不免太难看了:
至于其他的属性可以先不用管,我们开始编程:
粒子光环的属性
public ParticleSystem myparticleSystem;
private ParticleSystem.Particle[] particleArray;
private SingleParticle[] points;
public Gradient grad;
int count = 1000;
public float size = 0.5f;
public float minRadius = 3.0f;
public float maxRadius = 6.0f;
public bool rotate_way = false; // 决定圈扩大还是缩小
private float rotate_speed = -1; // 颜色旋转速度(正负代表方向)
public float speed = 0.5f; // 速度参数
private float time = 0;
ParticleSystem myparticleSystem
首先最重要肯定是粒子系统本身,没有这个类又谈何粒子编程呢?ParticleSystem.Particle[] particleArray
其次是粒子数组,保存了每一个粒子的状态,这里需要规定一个数量count
,也就是数组的大小,我设置为了1000.SingleParticle[] points
由于是光环,所以每个粒子的位置肯定需要一定规律地排序,而不是随机乱跑,所以这里记录了每个粒子对于整个光环的状态,两个最重要的属性角度与半径,因为每个粒子都是绕中心点运动,所以运动轨迹会有一个半径,运动到什么地方呢?就需要角度来记录。这里用到自己定义的一个类SingleParticle,稍后会说明。Gradient grad
颜色渐变器,Unity自带的渐变器,只要选定颜色变化的区间就能在有一段渐变的夜色。- 还有一些比较杂的变量属性:粒子大小,光环内径外径,转圈速度,旋转方向等。
粒子的位置属性:
public class SingleParticle {
public float angle;
public float radius;
private float x = 0.0f;
private float y = 0.0f;
public void CalPosition() {
float temp = angle / 180.0f * Mathf.PI;
y = radius * Mathf.Sin(temp);
x = radius * Mathf.Cos(temp);
}
public SingleParticle(float angle, float radius) {
this.angle = angle;
this.radius = radius;
}
public float getX() {
return x;
}
public float getY() {
return y;
}
}
主要是靠角度和半径来估算粒子所在的xy平面的面积,xy坐标就是靠基本的数学三角函数来计算出来。其中定义了一些外面能够调用的方法。
粒子系统初始化
在Start函数中,首先设置粒子系统的一些基本属性,如粒子数目等,这些需要在一开始就规定好并且不再改变。
void Start () {
// myparticleSystem = this.GetComponent<ParticleSystem>();
particleArray = new ParticleSystem.Particle[count];
points = new SingleParticle[count];
var m = myparticleSystem.main;
m.startSpeed = 0;
m.startSize = size;
m.maxParticles = count;
myparticleSystem.Emit(count);
myparticleSystem.GetParticles(particleArray);
Init();
}
注意到代码第一行,对于粒子系统的赋值,我们可以直接通过GetComponent
的方法来获取挂载在同一对象上的部件,但是这里我们用手动拖的方式,给脚本得公共变量赋值,好像更有体验感一点
然后就是各种初始化,新建一个粒子位置的数组,由于我们数量设置了1000,所以数组长度就是1000,除了这个数组外,还有粒子系统中的真正的粒子数组,设定size、最大粒子数、发射器发射数量,设置完之后还需要使用粒子系统Get一下,才能真正设置成功了。
这里有一个点值得注意的,就是旧版本里面通常使用ParticleSystem.startSize
来设置,但是在新版中,需要先获取用一个变量获取ParticleSystem.main
,再对main的startSize、maxParticles 进行设置。
其中,Init的函数就是作为粒子位置的初始化:
private void Init() {
int i;
for (i = 0; i < count; i++) {
float midRadius = (minRadius + maxRadius) / 2.0f;
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);
points[i] = new SingleParticle(angle, radius);
points[i].CalPosition();
particleArray[i].position = new Vector3(points[i].getX(), points[i].getY(), 0f);
}
myparticleSystem.SetParticles(particleArray, particleArray.Length);
}
通过内径和外径,中间划分一个分截,粒子分别在这两个部分中随机分布,角度则是360度随机。
然后就是每次Update时候的变化了:
void Update () {
int i;
int level = 10;
for (i = 0; i < count; i++) {
if (i % level < 3 || i % level > 6)
{
points[i].angle -= rotate_speed * (i % level + 1) * speed;
} else {
points[i].angle += rotate_speed * (i % level + 1) * speed;
}
points[i].angle = (points[i].angle + 360.0f) % 360.0f;
points[i].CalPosition();
float value = Time.realtimeSinceStartup % 1.0f;
value -= rotate_speed * points [i].angle /360.0f;
while (value > 1)
value--;
while (value < 0)
value ++;
particleArray[i].startColor = grad.Evaluate(value);
particleArray[i].position = new Vector3(points[i].getX(), points[i].getY(), 0.0f);
}
myparticleSystem.SetParticles(particleArray, particleArray.Length);
}
每次更新都需要循环遍历1000个粒子,对于每个粒子的状态都做一次改变,像角度颜色等。具体操作如下
- 利用取模的方式将1000个粒子分为10层,每一层转动的角度(相当于速度)都略微不一样,然后又将整体分成3部分:第一部分和第三部分是同一个方向旋转,第二部分是另一个方向旋转。使得整体的转动效果不太单一。当然最后需要对增加后的角度取模,以免溢出360度,或者小于0度。最后利用粒子位置的类中的方法,算出xy坐标。
- 然后是颜色的改变,首先我们需要利用渐变器进行颜色的调整,其实这一步也依然是可以通过代码来调整的,但是跟前面说的一样,~~增强体验感(其实是麻烦)~~使用手动调整的方法。首先看下渐变器的基本操作:
这个就是主体部分,可以看到一个类似进度条的东西,上下都有一些点,其中上方的点表示透明度的渐变(可以理解为亮度的变化),下方的点是颜色的选择,添加点只需要在对应空白的地方点击一下,删除点就需要点中某个点按删除键即可。
然后需要更改颜色就需要点击下方的color栏,然后选择适合的颜色,全部选择完毕之后就可以看到两个颜色点之间自动渐变。透明度也类似。
调整好之后就可以通过代码来选择其中的颜色,从0~1就是开始的颜色到最后的颜色了。
所以我们的想法是不仅颜色随着角度的变化而变化,而且要随着时间的变化而变化,所以需要对时间进行一个估算,以上代码中:float value = Time.realtimeSinceStartup % 1.0f; value -= rotate_speed * points [i].angle /360.0f; while (value > 1) value--; while (value < 0) value ++;
然后还需要加上当前角度归一化的结果,最后利用一个循环加减令其最终结果在0~1之间。用Gradient渐变器的Evaluate获取对应的颜色 - 最后,最关键的一步当然就是将这些计算出来的颜色,和位置设置到粒子本身中去,
SetParticles
函数就是这里使用的。
好了大功告成之后看看效果吧~
基本效果
就看到光圈在转呀转~ 转呀转~
其实仔细看的话还是能够看到有的粒子慢有的粒子快,还能分出顺时针和逆时针变化。
改进版本
既然光转圈不过瘾,那么就来个放大缩小吧,转着转着就放大,有转着转着缩小,总比光转有趣多了吧。
那么就需要先利用几个变量来帮助我们:
public bool rotate_way = false; // 决定圈扩大还是缩小
private float rotate_speed = -1; // 颜色旋转速度(正负代表方向)
private float time = 0;
这是之前就定义了可是没有怎么用到的变量,首先需要知道什么是否放大,什么时候缩小,其次旋转的速度是可变的,就不会呆板的转圈,有必要还可以反方向转动。至于时间的话,作为一个计时器用,决定了放大缩小的时间。
好下面就来写代码吧!
在Update的开头加入以下代码:
time += Time.deltaTime;
if (time < 10) {
if (time < 5) {
rotate_way = false;
rotate_speed += 0.01f;
}
else {
rotate_way = true;
rotate_speed -= 0.01f;
}
} else {
time = 0;
rotate_speed = -1;
}
总时长为10,然后分成两段,一段是放大,一段是缩小的,在此期间,旋转速度也做一点变化,随着时间增加或者减少,但是这个没有固定的变化规律,也就是说不一定增加的量和减少的量能够统一(值得改进),所以在每段时间结束的时候都需要进行一个reset处理,也就是将旋转速度回归原始状态,至于光环的大小就不一定了。。。
在对粒子属性的循环中,加入以下代码:
if (i % level > 5) {
float tmp = rotate_way? 1 : -1;
points[i].radius += tmp * 0.05f;
}
if (i % level <= 5) {
float tmp = rotate_way? 1 : -1;
points[i].radius += tmp * 0.052f;
}
依然是将粒子分成两部分(还是熟悉的配方,不知道为什么这么执迷于分层),然后一部分圈半径改变的速度快一点,一部分改变得慢一点,其实差别不大,因为差别大了的话会出现断层的现象。
添加完之后看看效果:
感觉好很多了,不过这里有一点问题就是圈的扩大缩小过程是不对称的,而且因为是Update函数中计时执行,所以不同电脑也会有变化细节,但是总体的变化是一定的,就是放大缩小,然后转~~。
添加粒子轨迹
做到这里感觉还是不太好看,于是就自己东搞搞西搞搞,然后想到了轨迹这个东西,也就是说,让我们的粒子加上一条尾巴,看起来会不会好看一点呢?
首先在粒子系统的Render属性里面,添加轨迹材料(别忘了设置,不然就是很丑的粉紫色长条):
然后再轨迹的选项里,调整一下参数:
重点调整一下轨迹与粒子的大小比例,还有轨迹寿命,也就是轨迹能拖多长。
设置好之后就运行看看效果吧。
看起来酷炫多了!
最后总结一下,其实这个编程有一些bug,也就是之前提到了变化过程不是对称的,也就是有的变化不可逆,可能变着变着就和原来的有很大区别了,这也是需要不断调整参数,不断研究的一点,不过鉴于粒子编程太多可钻研的了,同时也有大量的资源、插件可使用,这一点也不必太担心吧。