学习Unity(10)利用粒子系统制作光环

粒子系统概述

根据官方文档的解释,粒子系统管理大量既小又简单的图片或网格(mesh)一起运动,组成一个整体的效果。比如利用粒子系统可以制作出烟雾效果,每一个粒子是一个微小的烟云的图片,成千上万个这样的粒子就组成了一整块烟雾的效果,用普通的方法就很难做出烟雾的效果。

粒子系统的重要属性

  • 生命周期lifetime:每一个粒子都有一个生命周期,表示粒子被发射(emit)出来以后能存活多长时间。你可以在Inspector中设置Start lifetime来设置粒子的生命周期:


    Inspector中设置lifetime

之所以叫start是因为你设置的只是粒子默认的生命周期,在运行过程中lifetime还有可能被脚本改变。

  • 发射速率emission rate:每秒钟发射多少个粒子。你可以在Inspector中找到Emission模块的rate over time中设置发射速率。

实际发射粒子的时机有一定的随机性,不一定是间隔均匀地发射。

以上两个属性描述的是整个粒子系统的状态。我们还可以控制单个粒子的样式和行为。

  • 粒子个体的属性:速度矢量、颜色、大小、朝向等。有一些属性除了可以设置常量值以外,还可以设置成随着时间变化的值,或者在一定范围内随机的值。你只需要点击输入框右边的下拉三角按钮,就可以改变设置方式。

由于整个粒子系统有很多的属性可以自定义,因此Unity将它划分成了很多个模块(Particle System modules),方便查找。这是粒子系统模块的参考文档

项目概述

这个项目制作一个简单的粒子光环,光环中的粒子缓慢移动,看起来像太阳系的小行星带。效果尽量类似http://i-remember.fr/en。要实现这个效果,需要使用脚本来控制每一个粒子。

效果图

在自己的电脑上运行!

我的github下载项目资源,将所有文件放进你的项目的Assets文件夹(如果有重复则覆盖),然后在Unity3D中双击“hw9”,就可以运行了!

代码

ParticleStatus 用于记录某个粒子的状态:

public class ParticleStatus {
    public float radius = 0f, angle = 0f, time = 0f, radiusChange = 0.02f; // 粒子轨道变化范围;  
    public ParticleStatus(float radius, float angle, float time, float radiusChange)  
    {  
        this.radius = radius;   // 半径  
        this.angle = angle;     // 角度
        this.time = time;       // 变化的进度条,用于轨道半径的变化
        radiusChange = this.radiusChange;       // 轨道半径的变化程度
    }  
}

ParticleHalo控制一个含有粒子系统的对象,生成一个光环:

using UnityEngine;

public class ParticleHalo : MonoBehaviour
{

    private ParticleSystem particleSys;  // 粒子系统组件
    private ParticleSystem.Particle[] particleArr;  // 粒子数组  
    private ParticleStatus[] StatusArr; // 记录粒子状态的数组
    public int particleNum = 10000; // 粒子数量
    public float minRadius = 8.0f; // 光环最小半径
    public float maxRadius = 12.0f; // 光环最大半径
    public float maxRadiusChange = 0.02f; // 粒子轨道变化的平均值
    public bool clockwise = true;  // 光环是否顺时针旋转
    public float rotateSpeed = 0.3f;  // 光环旋转速度
    public int speedLevel = 5; // 速度有多少个层次
    private NormalDistribution normalGenerator; // 高斯分布生成器
    public Gradient colorGradient;  // 控制粒子的透明度


    void Start()
    {
        particleSys = GetComponent<ParticleSystem>();
        particleArr = new ParticleSystem.Particle[particleNum];
        StatusArr = new ParticleStatus[particleNum];

        var ma = particleSys.main;  // 通过ma来设置粒子系统的maxParticles
        ma.maxParticles = particleNum;

        particleSys.Emit(particleNum);  // 同时发射particleNum个粒子
        particleSys.GetParticles(particleArr);  // 将发射的粒子存在particleArr数组中
        normalGenerator = new NormalDistribution(); // 初始化高斯分布生成器

        // 初始化梯度颜色控制器  
        GradientAlphaKey[] alphaKeys = new GradientAlphaKey[5];
        alphaKeys[0].time = 0.0f; alphaKeys[0].alpha = 1.0f;
        alphaKeys[1].time = 0.4f; alphaKeys[1].alpha = 0.4f;
        alphaKeys[2].time = 0.6f; alphaKeys[2].alpha = 1.0f;
        alphaKeys[3].time = 0.9f; alphaKeys[3].alpha = 0.4f;
        alphaKeys[4].time = 1.0f; alphaKeys[4].alpha = 0.9f;
        GradientColorKey[] colorKeys = new GradientColorKey[2];
        colorKeys[0].time = 0.0f; colorKeys[0].color = Color.white;
        colorKeys[1].time = 1.0f; colorKeys[1].color = Color.white;
        colorGradient.SetKeys(colorKeys, alphaKeys);

        initParticle();
    }

    void initParticle()
    {
        for (int i = 0; i < particleNum; 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);

            // 使用高斯分布生成半径, 均值为midRadius,标准差为0.7
            float midRadius = (maxRadius + minRadius) / 2;
            float radius = (float)normalGenerator.NextGaussian(midRadius, 0.7);

            float angle = Random.Range(0.0f, 360.0f);
            float theta = angle / 180 * Mathf.PI;
            float time = Random.Range(0.0f, 360.0f);    // 给粒子生成一个随机的初始进度
            float radiusChange = Random.Range(0.0f, maxRadiusChange);   // 随机生成一个轨道变化大小
            StatusArr[i] = new ParticleStatus(radius, angle, time, radiusChange);
            particleArr[i].position = computePos(radius, theta);
        }
        particleSys.SetParticles(particleArr, particleArr.Length);
    }

    Vector3 computePos(float radius, float theta)
    {
        return new Vector3(radius * Mathf.Cos(theta), 0f, radius * Mathf.Sin(theta));
    }

    void Update()
    {
        for (int i = 0; i < particleNum; i++)
        {
            // 将所有粒子根据下标i,给5个不同的速度,分别是rotateSpeed的1/5、2/5……5/5
            if (!clockwise)
            {
                StatusArr[i].angle += (i % speedLevel + 1) * (rotateSpeed / speedLevel);
            }
            else
            {
                StatusArr[i].angle -= (i % speedLevel + 1) * (rotateSpeed / speedLevel);
            }

            // angle range guarantee
            StatusArr[i].angle = (360.0f + StatusArr[i].angle) % 360.0f;
            float theta = StatusArr[i].angle / 180 * Mathf.PI;

            StatusArr[i].time += Time.deltaTime;    // 增加粒子的进度
            StatusArr[i].radius += Mathf.PingPong(StatusArr[i].time / maxRadius / maxRadius, StatusArr[i].radiusChange) - StatusArr[i].radiusChange / 2.0f; // 根据粒子的进度,给粒子的半径赋予不同的值,这个值在0与StatusArr[i].radiusChange之间来回摆动

            particleArr[i].position = computePos(StatusArr[i].radius, theta);

            particleArr[i].color = colorGradient.Evaluate(StatusArr[i].angle / 360.0f); // 根据粒子的angle,给粒子赋予不同的透明度(颜色),使某一些角度上的粒子暗一些
        }

        particleSys.SetParticles(particleArr, particleArr.Length);
    }
}

NormalDistribution 用于产生正态分布的随机数,原理是Marsaglia polar method

using System;

public class NormalDistribution {
    // use Marsaglia polar method to generate normal distribution
    private bool _hasDeviate;
    private double _storedDeviate;
    private readonly Random _random;

    public NormalDistribution(Random random = null)
    {
        _random = random ?? new Random();
    }

    public double NextGaussian(double mu = 0, double sigma = 1)
    {
        if (sigma <= 0)
            throw new ArgumentOutOfRangeException("sigma", "Must be greater than zero.");

        if (_hasDeviate)
        {
            _hasDeviate = false;
            return _storedDeviate*sigma + mu;
        }

        double v1, v2, rSquared;
        do
        {
            // two random values between -1.0 and 1.0
            v1 = 2*_random.NextDouble() - 1;
            v2 = 2*_random.NextDouble() - 1;
            rSquared = v1*v1 + v2*v2;
            // ensure within the unit circle
        } while (rSquared >= 1 || rSquared == 0);

        // calculate polar tranformation for each deviate
        var polar = Math.Sqrt(-2*Math.Log(rSquared)/rSquared);
        // store first deviate
        _storedDeviate = v2*polar;
        _hasDeviate = true;
        // return second deviate
        return v1*polar*sigma + mu;
    }
}

代码意义已经在注释中解释


两层光环

为了做出两层光环,只需要将同一份脚本挂载在两个具有Particle System的对象上,然后在Inspector中调整一下参数,就可以实现内层逆时针、外层顺时针、内层快、外层慢、内层稠密、外层稀疏的效果了。

外层参数

内层参数
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容