要求
这次作业主要是学习粒子系统的使用,在 http://i-remember.fr/en 网站上,可以看到一个很酷炫的白色粒子光带,效果如下:
当鼠标hover到图一中间的+号圆圈中间时,光环会收缩成图二的状态,具体效果可自行前往网站体验~
我们需要模仿这个效果并尝试用粒子流编程控制来实现。
实现
光环在收缩前较为分散,位于光环中间的部分粒子较周围密集,因此我想用一个正太分布来模拟粒子在光环半径范围内的分布状况。这里我采用了Box-Muller 算法来到服从正态分布的随机数,基本思想是先得到服从均匀分布的随机数再将服从均匀分布的随机数转变为服从正态分布。关于Box-Muller 算法可参考百度百科
class Ndistribution{
System.Random rand = new System.Random();
public double getNormalDistribution(double mean, double stdDev)
{
double u1 = 1.0 - rand.NextDouble(); //uniform(0,1] random doubles
double u2 = 1.0 - rand.NextDouble();
double randStdNormal = Math.Sqrt(-2.0 * Math.Log(u1)) *
Math.Sin(2.0 * Math.PI * u2); //random normal(0,1)
double randNormal = mean + stdDev * randStdNormal; //random normal(mean,stdDev^2)
return randNormal;
}
}
有了生成服从正太分布随机数的函数之后,接下来我们将用一个脚本来控制粒子的生成以及粒子坐标的变化。
首先声明一下属性:
public int particleNum = 10000; // 粒子数目
public float minRadius = 5.0f; // 光环最小半径
public float maxRadius = 10.0f; // 光环最大半径
private ParticleSystem.Particle[] particles;
private float[] particleAngle; // 粒子角度
private float[] particleR; // 粒子半径
private int speedLevel = 5; // 粒子旋转速度水平
private float particleSpeed = 0.1f; // 粒子旋转速度
private Ray ray;
private RaycastHit hit;
// 收缩前粒子位置
private float[] before;
// 收缩后粒子位置
private float[] after;
// 粒子缩放的速度
private float shrinkSpeed = 2f;
private bool ischange = false;
在Start()函数中执行初始化的工作,这里的重点是为每一个粒子利用上面提到的随机数生成函数生成一个半径,同时也为它们随机生成(0, 360)之间的一个角度,这样粒子就可以根据半径与角度,形成一个圆形光环。并且还需要令刚生成的半径作为光环收缩前粒子的半径,收缩后的半径我设定为原来的0.7倍。这样一来,效果应该是从一个大的服从正太分布的光环变成一个小的光环,而理想中的效果在收缩后在最内圈的粒子比较集中。因此,我为收缩后半径小于一定数值的粒子重新生成一个收缩半径,如下面的代码所示:
void Start () {
particleAngle = new float[particleNum];
particleR = new float[particleNum];
before = new float[particleNum];
after = new float[particleNum];
particles = new ParticleSystem.Particle[particleNum];
particleSystem.maxParticles = particleNum;
particleSystem.Emit(particleNum);
particleSystem.GetParticles(particles);
Ndistribution nd = new Ndistribution();
// 每个粒子在初始化的时候都设定好收缩前和收缩后的粒子半径
for (int i = 0; i < particleNum; i++)
{
float r = (float)nd.getNormalDistribution((minRadius+maxRadius)*0.5f, 1);
float angle = UnityEngine.Random.Range(0.0f, 360.0f);
particleAngle[i] = angle;
particleR[i] = r;
before[i] = r;
after[i] = 0.7f * r;
if (after[i] < minRadius * 1.1f)
{
float midRadius = minRadius * 1.05f;
after[i] = UnityEngine.Random.Range(UnityEngine.Random.Range(minRadius, midRadius), (minRadius * 1.1f));
}
}
}
然后在每一帧执行Update()函数时更新所有粒子的位置,实现旋转。根据遍历粒子数组时的index的奇偶性,来区分粒子的旋转方向;同时利用不同速度级别和基本旋转速度的乘积,来实现粒子以不同速度旋转。
// 通过奇偶控制粒子顺时针或逆时针旋转
if (i % 2 == 0)
{
// 逆时针
particleAngle[i] += (i % speedLevel + 1) * particleSpeed;
}
else
{
// 顺时针
particleAngle[i] -= (i % speedLevel + 1) * particleSpeed;
}
particleAngle[i] = particleAngle[i] % 360;
// 转换为弧度制
float rad = particleAngle[i] / 180 * Mathf.PI;
// 更新粒子坐标
particles[i].position = new Vector3(particleR[i] * Mathf.Cos(rad), particleR[i] * Mathf.Sin(rad), 0f);
接着还要实现鼠标悬停中间后粒子收缩的效果。unity3d不像web一样直接可以用hover,我参考了网上的博客,使用光线射击到中间物体的方法。注意这里需要为光环gameObject添加Box Collider,并设置合适的大小。
缩放的实现是根据初始化时生成的半径,逐帧更改粒子当前的半径直到与设定的大小一致。
ray = camera.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit) && hit.collider.gameObject.tag == "button") ischange = true;
else ischange = false;
...
if (ischange)
{
// 开始收缩
if(particleR[i] > after[i])
{
particleR[i] -= shrinkSpeed * (particleR[i] / after[i]) * Time.deltaTime;
}
}
else
{
// 开始还原
if (particleR[i] < before[i])
{
particleR[i] += shrinkSpeed * (before[i] / particleR[i]) * Time.deltaTime;
}
else if (particleR[i] > before[i])
{
particleR[i] = before[i];
}
}
最后附上相关参数设定:
最终效果
演示视频地址:https://www.bilibili.com/video/av24101271/
完整项目源码见GitHub:https://github.com/CarolSum/Unity3d-Learning/tree/master/hw7