粒子编程初试——粒子光环制作

写在前面

实验步骤

首先回顾粒子海洋的制作过程:

  1. 添加空对象并且添加粒子系统的部件
  2. 简单调整一下粒子部件中的粒子材料(选个默认粒子材料就好,不要让其为空)
  3. 写代码编程,挂到空对象上。并且将粒子系统部件拖入代码的公共变量中去。
  4. 添加颜色渐变部件,手动修改颜色
  5. 。。。。

所以其实对于粒子系统部件,我们不需要太多的操作,大部分的变化都是在代码中完成的,只要清楚各个属性对应的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 ++;
    
    •  
    这部分就是来计算的,首先利用实时时间,也就是程序运行的时间模上一个浮点数(注意是模浮点数,这样得出来的结果才会是浮点数),得到一个区间内的数,这里模1.0,说明颜色变化会在一秒内跑一圈,如果是模比一大的数,还需要进行归一化处理,也就是模多少就除以多少。
    然后还需要加上当前角度归一化的结果,最后利用一个循环加减令其最终结果在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,也就是之前提到了变化过程不是对称的,也就是有的变化不可逆,可能变着变着就和原来的有很大区别了,这也是需要不断调整参数,不断研究的一点,不过鉴于粒子编程太多可钻研的了,同时也有大量的资源、插件可使用,这一点也不必太担心吧。

猜你喜欢

转载自blog.csdn.net/weixin_40552127/article/details/112786378