Fork me on GitHub

Unity-ParticleRing

ParticleRing

做完了两三天了,终于挤出时间来写写博客。

我们仿照remember的粒子光环效果。
其中,鼠标悬浮光环中心,光环会收缩,移开后恢复。

效果视频

参数设置

parameter

  • Start Lifetime
    我们将粒子的生命时间(生命周期)设置为100,让粒子可以较久的保留在界面上,避免圆环上的粒子太过稀疏。
  • Start Size
    粒子大小,在参数面板设置。
  • Start Speed
    将粒子的初始速度设为零,我们在代码中会对粒子速度进行设置。
  • Max Particles
    粒子最大数目,在代码中可以设置。

代码讲解

我们依旧在了解参数面板参数的含义下,使用代码完成任务。首先,将粒子和粒子系统分开,类似 model 与 actionManager 或工厂模式的设计思路。

  1. 粒子
    我们分解一下粒子状态:初始态,收缩态和收缩前态。在这三个状态下,我们关注粒子的初始发射角度,初始旋转半径,收缩前的旋转半径,收缩后的旋转半径。所以建立如下数据结构:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class ParticleData {
    public float angle; // 粒子初始角度
    public float radius; // 当前粒子半径
    public float beforeRadius; // 粒子收缩前半径
    public float shringRadius; // 粒子收缩后半径

    public ParticleData(float angle, float radius, float beforeRadius, float shringRadius) {
    this.angle = angle;
    this.radius = radius;
    this.beforeRadius = beforeRadius;
    this.shringRadius = shringRadius;
    }
    }
  2. 粒子系统
    我们对于粒子系统,需要对粒子的初始状态,收缩状态和收缩钱状态负责。一步步来完成。

    • 数据变量
      我们分析一下各状态下粒子需要的参数,可以知道,我们需要明确粒子数目和各粒子的数据。
    • 初始化粒子位置
      粒子的位置,需要构成一个圆环,且在圆环中,我们希望粒子尽量集中在中间。我们采用如下方法进行构造:[min, max] => lowerbound = random [min, middle] & upperbound = random [middle, max] => initialRidus = random [lowerbound, upperbound].
      这样,我们通过分级随机的方法得到的半径,会较为集中在中部,而避免太过分散的问题。
    • 粒子运动
      粒子光环应该是有两层旋转,一层顺时针,一层逆时针。我们简化一下,通过扩大粒子数,然后根据下标奇偶性让其按照不同方向旋转运动,这样就解决了两层的问题。
    • 粒子收缩
      我们通过在圆环中心位置放置一个 cube 充当按钮,并将其放入一个单独的 layer 中,然后在摄像机中,将这个 layer “无视”,这样可以做成浸入式的体验效果。
    • 粒子收缩成圆线
      我们在确定收缩半径的时候,可以通过再次随机,将粒子收缩的半径范围限制在某个较大范围内,而不是一个小区域中,这样可以避免圆线的问题。

    完整代码如下,代码注释表明了代码含义。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    public class ParticleRing : MonoBehaviour {
    public Camera camera; //摄像机
    public ParticleSystem particleRing; //粒子系统

    private int particleNum = 10000; //粒子数目
    private ParticleSystem.Particle[] particles; //粒子
    private ParticleData[] particleDatas; //粒子数据

    private float minRadius = 5.0f; //最小半径
    private float maxRadius = 10.0f; //最大半径

    private bool isShring = false; //是否收缩

    private int level = 5;
    private float speed = 0.1f;
    private float shringSpeed = 2f;

    // Use this for initialization
    void Start() {
    particleRing.maxParticles = particleNum; //设置最大粒子数
    particles = new ParticleSystem.Particle[particleNum]; //新建粒子数组
    particleDatas = new ParticleData[particleNum]; //新建粒子数据数组
    particleRing.Emit(particleNum); //粒子系统发射粒子
    particleRing.GetParticles(particles);

    //初始化粒子位置
    for (int i = 0; i < particleNum; ++i) {
    float middleRadius = (maxRadius + minRadius) / 2;
    float upperbound = Random.Range(middleRadius, maxRadius);
    float lowerbound = Random.Range(minRadius, middleRadius);
    float radius = Random.Range(lowerbound, upperbound);
    float angle = Random.Range(0.0f, 360.0f);
    particleDatas[i] = new ParticleData(angle, radius, radius, radius - 1.5f * (radius / minRadius));
    // 随机收缩半径下界,避免收缩后成为圆线
    if (particleDatas[i].shringRadius < minRadius + 0.5f) {
    float temp = Random.Range(minRadius, minRadius + 0.25f);
    particleDatas[i].shringRadius = Random.Range(temp, minRadius + 0.5f);
    }
    }
    }

    // Update is called once per frame
    void Update() {
    for (int i = 0; i < particleNum; ++i) {
    //判断是否收缩
    if (isShring) {
    if (particleDatas[i].radius > particleDatas[i].shringRadius) {
    particleDatas[i].radius -=
    shringSpeed * (particleDatas[i].radius / particleDatas[i].shringRadius) * Time.deltaTime;
    }
    } else {
    if (particleDatas[i].radius < particleDatas[i].beforeRadius) {
    particleDatas[i].radius +=
    shringSpeed * (particleDatas[i].beforeRadius / particleDatas[i].radius) * Time.deltaTime;
    } else {
    particleDatas[i].radius = particleDatas[i].beforeRadius;
    }
    }
    //根据奇偶数判定顺时针或逆时针运动
    if (i % 2 == 0) {
    particleDatas[i].angle += (i % level + 1) * speed;
    } else {
    particleDatas[i].angle -= (i % level + 1) * speed;
    }
    particleDatas[i].angle %= 360;
    float rad = particleDatas[i].angle / 180 * Mathf.PI;
    particles[i].position =
    new Vector3(particleDatas[i].radius * Mathf.Cos(rad), particleDatas[i].radius * Mathf.Sin(rad), 0);
    }
    particleRing.SetParticles(particles, particleNum);
    //收缩射线判断
    Ray ray = camera.ScreenPointToRay(Input.mousePosition);
    RaycastHit hit;
    isShring = (Physics.Raycast(ray, out hit) && hit.collider.gameObject.tag == "button") ? true : false;
    }

    }