这一次要完成的是http://i-remember.fr/en 网站所示的白色圆圈效果。
首先,我们先来看一下它的效果:
一、网站效果展示
二、创建粒子
设置相关参数
把相机背景色调黑
三、编写脚本
1、新建脚本:ParticleRotate.cs,并将其拖到Paticle System中。
2、创建粒子数组,初始化。同时我们需要为记录每个粒子的初始角度,初始半径。考虑后续可能每个粒子会有更多属性,所以写了一个class来管理粒子属性
根据下图我们知道,需要随机生成角度以及半径,从而表示出某一个粒子的特定位置。
//创建粒子系统,粒子数组,粒子数目,声明粒子环的半径
public ParticleSystem particleSystem;
private ParticleSystem.Particle[] particlesArray;
private particleClass[] particleAttr; //粒子属性数组
public int particleNum = 10000;
public class particleClass {
public float radiu = 0.0f;
public float angle = 0.0f;
public particleClass(float radiu_, float angle_)
{
radiu = radiu_;
angle = angle_;
}
}
void Start()
{
particlesArray = new ParticleSystem.Particle[particleNum];
particleSystem.maxParticles = particleNum;
particleSystem.Emit(particleNum);
particleSystem.GetParticles(particlesArray);
for (int i = 0; i < particleNum; i++)
{//相应初始化操作,为每个粒子设置半径,角度
}
//设置粒子
particleSystem.SetParticles(particlesArray, particleNum);
}
3、先考虑在最大半径为maxRadius 最小半径为minRadius的区间内随机为每个粒子生成半径
float randomAngle = Random.Range(0.0f, 360.0f);
float randomRadius = Random.Range(minRadius, maxRadius);
particleAttr[i] = new particleClass(randomRadius, randomAngle);
particlesArray[i].position = new Vector3(randomRadius * Mathf.Cos(randomAngle), randomRadius * Mathf.Sin(randomAngle), 0.0f);
效果如下:
4、OK接下来要让它动起来~~那么问题来了~怎么动。
我的想法是,把这一堆粒子分成两Part,第一部分顺时针转,第二部分逆时针转。只要在update函数里面更改它的角度就好了。
public int Part = 2;
void Update()
{
//设置为两部分的粒子,一部分顺时针,一部分逆时针。
for (int i = 0; i < particleNum; i++)
{
if (i % 2 == 0) particleAttr[i].angle += (i % Part + 1) * speed;
else particleAttr[i].angle -= (i % Part + 1) * speed;
particleAttr[i].angle = particleAttr[i].angle % 360;
float rad = particleAttr[i].angle / 180 * Mathf.PI;
particlesArray[i].position = new Vector3(particleAttr[i].radiu * Mathf.Cos(rad), particleAttr[i].radiu * Mathf.Sin(rad), 0f);
}
particleSystem.SetParticles(particlesArray, particleNum);
}
动起来的效果:
5、好吧不得不承认动起来的效果太奇怪了。仔细研究一下就会发现。产生的半径必须是要在[minRadius, maxRadius]区间的没错,但是同时也要更为集中在中间的某一块区域,这样才能实现网站的效果。
这也是我觉得的最有趣的地方。
首先想到的解决方式:正态分布。
不过只有python有相应的正态分布的库。利用c#写一个正态分布太麻烦了。
然后我就找到了box-muller
传送门: http://baike.baidu.com/view/1710258.htm
方法如下:
如果在 (0,1] 值域内有两个独立的随机数字 U1 和 U2,
可以使用以下两个等式中的任一个算出一个正态分布的随机数字 Z:
Z = R * cos( θ )或Z = R * sin( θ )
其中,R = sqrt(-2 * ln(U2)) && θ = 2 * π * U1
正态值 Z 有一个等于 0 的平均值和一个等于 1 的标准偏差,可使用以下等式将 Z 映射到一个平均值为 m、标准偏差为 sd 的统计量 X:X = m + (Z * sd)
然后将该方法写入for循环中。
// 一种待完善的产生随机粒子的办法 box-muller
float sita = 2 * Mathf.PI * Random.Range(0, 1);
float R = Mathf.Sqrt(-2 * Mathf.Log(Random.Range(0, 1)));
float Z = R * Mathf.Cos(sita);
float randomRadius = (minRadius + maxRadius) / 2 + Z * 2.5f;
//print(randomRadius);
Debug.Log("r = " + R);
Debug.Log(randomRadius);
什么问题呢? R求出来是无限,等着把Log里面的数值改为0.7,
运行慢不说。10000个粒子unity跑了接近一分钟才运行出来。吓得我以为炸了。
而且效果十分糟糕。
好吧~我无能为力。然后就看了大神这一块实现的代码。
// 随机产生每个粒子距离中心的半径,同时粒子要集中在平均半径附近
float midRadius = (maxRadius + minRadius) / 2;
float minRate = Random.Range(1.0f, midRadius / minRadius);
float maxRate = Random.Range(midRadius / maxRadius, 1.0f);
float randomRadius = Random.Range(minRadius * minRate, maxRadius * maxRate);
非常巧妙地利用了三个随机函数,实现粒子的集中。
相当于[ [minRadius, midRadius] , [midRadius, maxRadius] ] 这样的区间中产生随机数。自然靠中间的随机数也会更加集中。
真是神奇。
6、那把这部分代码添加到我的代码中之后。实现的效果就很不错啦。
至此实验完成:
下面是完整的代码:
using UnityEngine;
using System.Collections;
public class ParticleRotate : MonoBehaviour
{
public class particleClass
{
public float radiu = 0.0f;
public float angle = 0.0f;
public particleClass(float radiu_, float angle_)
{
radiu = radiu_;
angle = angle_;
}
}
//创建粒子系统,粒子数组,粒子数目,声明粒子环的半径
public ParticleSystem particleSystem;
private ParticleSystem.Particle[] particlesArray;
private particleClass[] particleAttr; //粒子属性数组
public int particleNum = 10000;
public float minRadius = 5.0f;
public float maxRadius = 10.0f;
public int Part = 2;
public float speed = 0.1f;
void Start()
{
particleAttr = new particleClass[particleNum];
particlesArray = new ParticleSystem.Particle[particleNum];
particleSystem.maxParticles = particleNum;
particleSystem.Emit(particleNum);
particleSystem.GetParticles(particlesArray);
for (int i = 0; i < particleNum; i++)
{ //相应初始化操作,为每个粒子设置半径,角度
//产生一个随机角度
float randomAngle = Random.Range(0.0f, 360.0f);
// 随机产生每个粒子距离中心的半径,同时粒子要集中在平均半径附近
float midRadius = (maxRadius + minRadius) / 2;
float minRate = Random.Range(1.0f, midRadius / minRadius);
float maxRate = Random.Range(midRadius / maxRadius, 1.0f);
float randomRadius = Random.Range(minRadius * minRate, maxRadius * maxRate);
////一种待完善的产生随机粒子的办法 box-muller
//float sita = 2 * Mathf.PI * Random.Range(0, 1);
//float R = Mathf.Sqrt(-2 * Mathf.Log(0.7f));
//float Z = R * Mathf.Cos(sita);
//float randomRadius = (minRadius + maxRadius) / 2 + Z * 2.5f;
//print(randomRadius);
//Debug.Log("r = " + R);
//Debug.Log(randomRadius);
//粒子属性设置
particleAttr[i] = new particleClass(randomRadius, randomAngle);
particlesArray[i].position = new Vector3(randomRadius * Mathf.Cos(randomAngle), randomRadius * Mathf.Sin(randomAngle), 0.0f);
}
//设置粒子
particleSystem.SetParticles(particlesArray, particleNum);
}
void Update()
{
//设置为两部分的粒子,一部分顺时针,一部分逆时针。
for (int i = 0; i < particleNum; i++)
{
if (i % 2 == 0) particleAttr[i].angle += (i % Part + 1) * speed;
else particleAttr[i].angle -= (i % Part + 1) * speed;
//根据新的角度重新设置位置
particleAttr[i].angle = particleAttr[i].angle % 360;
float rad = particleAttr[i].angle / 180 * Mathf.PI;
particlesArray[i].position = new Vector3(particleAttr[i].radiu * Mathf.Cos(rad), particleAttr[i].radiu * Mathf.Sin(rad), 0f);
}
particleSystem.SetParticles(particlesArray, particleNum);
}
}