目标效果
I-remember的主页有一款效果很棒的粒子光环效果,这是通过JS实现的。传送门
效果如下图所展,最近学习了Unity的粒子系统Particile System,下面将尝试使用粒子系统实现这一功能。
效果分析
仔细观察硬格可以发现,这个粒子效果有两层。一层是范围比较广、粒子比较稀疏、运动较慢的外环。一层是范围比较窄、粒子比较集中、运动较快的内环。且各个粒子之间的运动比较独立并不是简单的绕圈。基本实现的思路就是写一个脚本,挂载在粒子系统上,设置不同的参数实现内外环。
实现
步骤一
新建工程和空场景,新建一个空的游戏对象放在原点。作为粒子光环的基准点。接下来创建一个C#脚本并命名为ParticleCircle 。在空对象下新建两个粒子系统作为子对象,分别命名为outer和inner。下面的操作可以先对一个对象进行,另一个对象只需要进行拷贝再修改参数即可。
先设置了粒子系统、粒子数组、粒子数目等三个公有变量。将脚本分别挂载到子对象上。创建粒子并把他们放进粒子系统。
1 | using UnityEngine; |
2 | using System.Collections; |
3 | |
4 | public class ParticleCircle : MonoBehaviour { |
5 | |
6 | public int count = 10000; |
7 | |
8 | public ParticleSystem particleSys; |
9 | public ParticleSystem.Particle[] particlesArray; |
10 | // Use this for initialization |
11 | void Start () { |
12 | particlesArray = new ParticleSys.Particle[count]; |
13 | particleSys.maxParticles = count; |
14 | particleSys.Emit(count); |
15 | particleSys.GetParticles(particlesArray); |
16 | } |
17 | |
18 | // Update is called once per frame |
19 | void Update () { |
20 | |
21 | } |
22 | } |
并将粒子系统拖入脚本的粒子系统变量上。
步骤二
我们先为各个粒子分配位置,分配的方法就是随机生成角度和半径,生成角度的范围就是0~360度(当然需要转换成弧度制),生成半径的范围就是在中间半径的正负distance区间内,使粒子能集中在这个范围内。x轴的大小通过半径乘以cos弧度来计算,y轴的大小通过半径乘以sin弧度来计算。
1 | public class ParticleCircle : MonoBehaviour { |
2 | |
3 | public int count = 10000; |
4 | public float distance = 0.5f; // 环的大小 |
5 | public float midradius = 5; // 环中间半径 |
6 | public ParticleSystem particleSys; |
7 | public ParticleSystem.Particle[] particlesArray; |
8 | // Use this for initialization |
9 | void Start () { |
10 | particlesArray = new ParticleSystem.Particle[count]; |
11 | particleSys.maxParticles = count; |
12 | particleSys.Emit(count); |
13 | particleSys.GetParticles(particlesArray); |
14 | |
15 | // 为每个粒子初始化位置,并且将位置存放入 |
16 | for (int i = 0; i < count; i++) { |
17 | float temp_radius = Random.Range (midradius - distance, midradius + distance); |
18 | float temp_angle = Random.Range (0.0f, 360.0f); |
19 | float theta = temp_angle / 180 * Mathf.PI; |
20 | particlesArray [i].position = new Vector3 (Mathf.Cos (theta) * temp_radius, Mathf.Sin (theta) * temp_radius, 0); |
21 | } |
22 | |
23 | particleSys.SetParticles(particlesArray, particlesArray.Length); |
24 | } |
25 | |
26 | // Update is called once per frame |
27 | void Update () { |
28 | |
29 | } |
30 | } |
完成了函数的构造之后,我们需要对粒子的一些参数进行设置。主要就是调整lifetime为100,size为0.1,emission rate为0,取消looping,取消play on awake。
再对摄像机的参数进行设置,把背景调整为黑色,高度调整为0就可以了。
下面来看看效果如何。
已经有了一个初步的形状了,接下来就是要让粒子们动起来就行了。
步骤三
要让粒子动起来,需要在update函数里面不断更新它的位置。但是每一个粒子只存储了自己个position而position里面只包含了x、y、z坐标,粒子要做的是圆周运动,随着时间线性改变的应该是角度。所以我们需要设置一个结构体来存储每一个粒子的角度、半径等参数。
1 | using Com.myCircle; |
2 | |
3 | namespace Com.myCircle { |
4 | public class CirclePosition { |
5 | public float radius = 0f; |
6 | public float angle = 0f; |
7 | public CirclePosition(float radius, float angle) { // 构造函数用来实例化对象 |
8 | this.angle = angle; // 角度 |
9 | this.radius = radius; // 半径 |
10 | } |
11 | } |
12 | } |
当然我们要需要在开始的时候对这个数组进行声明和实例化,初始化粒子位置的时候,也对这个数组中的元素进行实例化。
1 | private CirclePosition[] positionArray; // 类的变量声明加入 |
2 | positionArray = new CirclePosition[count]; // start中加入 |
3 | positionArray [i] = new CirclePosition (temp_radius, temp_angle); // start的for循环中加入 |
1 | void Update () { |
2 | for (int i = 0; i < count; i++) { |
3 | positionArray [i].angle -= 0.1f; |
4 | float theta = positionArray [i].angle / 180 * Mathf.PI; |
5 | particlesArray [i].position = new Vector3 (Mathf.Cos (theta) * positionArray[i].radius, Mathf.Sin (theta) * positionArray[i].radius, 0); |
6 | } |
7 | particleSys.SetParticles(particlesArray, particlesArray.Length); |
8 | } |
在update更新每一个粒子的位置,效果如下:
有一点像单纯整张的图片在旋转,效果不是很好。应该给粒子分不同的层,不同的层上移动速度不同,而且半径也会发生改变,这样子效果可能会更好。
改进update如下:
效果稍微好一点了,但是粒子的大小和数目还是需要在后面再继续修改一下的。
步骤四
光效部分,原效果之中,环不同位置的亮度是不同的。这里就使用Gradient类来实现渐变的透明度。
透明度渐变的设置通过代码来设置会比手动调方便一些
在start中声明 public Gradient colorGradient; 后加入
1 | GradientAlphaKey[] alphaKeys = new GradientAlphaKey[5]; |
2 | alphaKeys[0].time = 0.0f; alphaKeys[0].alpha = 1.0f; |
3 | alphaKeys[1].time = 0.4f; alphaKeys[1].alpha = 0.4f; |
4 | alphaKeys[2].time = 0.6f; alphaKeys[2].alpha = 1.0f; |
5 | alphaKeys[3].time = 0.9f; alphaKeys[3].alpha = 0.4f; |
6 | alphaKeys[4].time = 1.0f; alphaKeys[4].alpha = 0.9f; |
7 | GradientColorKey[] colorKeys = new GradientColorKey[2]; |
8 | colorKeys[0].time = 0.0f; colorKeys[0].color = Color.white; |
9 | colorKeys[1].time = 1.0f; colorKeys[1].color = Color.white; |
10 | |
11 | colorGradient.SetKeys(colorKeys, alphaKeys); |
在update中更新
1 | particlesArray [i].color = colorGradient.Evaluate(positionArray [i].angle / 360.0f); |
步骤五
调整粒子参数,拷贝内层对象修改参数得到两个层即可。
最终效果
实现还是比较粗略,没有原效果来的好,太像一个环了。
代码
ParticleCircle.cs
1 | using UnityEngine; |
2 | using System.Collections; |
3 | using Com.myCircle; |
4 | |
5 | namespace Com.myCircle { |
6 | public class CirclePosition { |
7 | public float radius = 0f; |
8 | public float angle = 0f; |
9 | public CirclePosition(float radius, float angle) { // 构造函数用来实例化对象 |
10 | this.angle = angle; // 角度 |
11 | this.radius = radius; // 半径 |
12 | } |
13 | } |
14 | } |
15 | |
16 | public class ParticleCircle : MonoBehaviour { |
17 | |
18 | public int count = 10000; |
19 | public float distance = 0.5f; // 环的大小 |
20 | public float midradius = 5; // 环中间半径 |
21 | public int layer = 10; // 不同层的粒子速度不同 |
22 | public ParticleSystem particleSys; |
23 | public ParticleSystem.Particle[] particlesArray; |
24 | private CirclePosition[] positionArray; |
25 | public Gradient colorGradient; |
26 | |
27 | // Use this for initialization |
28 | void Start () { |
29 | particlesArray = new ParticleSystem.Particle[count]; |
30 | positionArray = new CirclePosition[count]; |
31 | particleSys.maxParticles = count; |
32 | particleSys.Emit(count); |
33 | particleSys.GetParticles(particlesArray); |
34 | |
35 | |
36 | GradientAlphaKey[] alphaKeys = new GradientAlphaKey[5]; |
37 | alphaKeys[0].time = 0.0f; alphaKeys[0].alpha = 1.0f; |
38 | alphaKeys[1].time = 0.4f; alphaKeys[1].alpha = 0.4f; |
39 | alphaKeys[2].time = 0.6f; alphaKeys[2].alpha = 1.0f; |
40 | alphaKeys[3].time = 0.9f; alphaKeys[3].alpha = 0.4f; |
41 | alphaKeys[4].time = 1.0f; alphaKeys[4].alpha = 0.9f; |
42 | GradientColorKey[] colorKeys = new GradientColorKey[2]; |
43 | colorKeys[0].time = 0.0f; colorKeys[0].color = Color.white; |
44 | colorKeys[1].time = 1.0f; colorKeys[1].color = Color.white; |
45 | |
46 | colorGradient.SetKeys(colorKeys, alphaKeys); |
47 | // 为每个粒子初始化位置,并且将位置存放入 |
48 | for (int i = 0; i < count; i++) { |
49 | float temp_radius = Random.Range (midradius - distance, midradius + distance); |
50 | float temp_angle = Random.Range (0.0f, 360.0f); |
51 | float theta = temp_angle / 180 * Mathf.PI; |
52 | positionArray [i] = new CirclePosition (temp_radius, temp_angle); |
53 | particlesArray [i].position = new Vector3 (Mathf.Cos (theta) * temp_radius, Mathf.Sin (theta) * temp_radius, 0); |
54 | } |
55 | |
56 | particleSys.SetParticles(particlesArray, particlesArray.Length); |
57 | } |
58 | |
59 | // Update is called once per frame |
60 | void Update () { |
61 | for (int i = 0; i < count; i++) { |
62 | positionArray [i].angle -= 0.1f * (i % layer + 1); |
63 | // 保证angle在0~360度 |
64 | positionArray[i].angle = (360.0f + positionArray [i].angle) % 360.0f; |
65 | |
66 | float theta = positionArray [i].angle / 180 * Mathf.PI; |
67 | particlesArray [i].position = new Vector3 (Mathf.Cos (theta) * positionArray[i].radius, Mathf.Sin (theta) * positionArray[i].radius, 0); |
68 | |
69 | particlesArray [i].color = colorGradient.Evaluate(positionArray [i].angle / 360.0f); |
70 | } |
71 | particleSys.SetParticles(particlesArray, particlesArray.Length); |
72 | } |
73 | } |