一.作业要求
本次作业基本要求是三选一
- 简单粒子制作
按参考资源要求,制作一个粒子系统,参考资源
使用 3.3 节介绍,用代码控制使之在不同场景下效果不一样 - 完善官方的“汽车尾气”模拟
使用官方资源资源 Vehicle 的 car, 使用 Smoke 粒子系统模拟启动发动、运行、故障等场景效果 - 参考 http://i-remember.fr/en 这类网站,使用粒子流编程控制制作一些效果, 如“粒子光环”
可参考以前作业
我选择任务3,制作粒子光环。
二.实现过程
首先创建空对象ParticleHalo,然后在其下面创建一个子对象,命名为Clockwise,以及另一个反向旋转的子对象,命名为Counterclockwise。
然后创建C#脚本控制两个粒子光环ParticleHalo。
接着进行实现。
首先定义新的结构ParticalInfo存储粒子的数据,数据包含每个粒子的当前半径、角度和时间,其中时间是做PingPong时根据当前时间来决定漂浮的大小。
public class ParticalInfo {
public float radius = 0f, angle = 0f, time = 0f;
public ParticalInfo(float radius, float angle, float time){this.radius = radius;this.angle = angle;this.time = time;}
}
然后定义一个粒子系统的变量,粒子的数组和对应的粒子的数据,以及一些关于粒子属性的变量
private ParticleSystem particleSys;
private ParticleSystem.Particle[] particleArr;
private ParticalInfo[] particles;
private float[] radius;
private float[] collect_radius;
private int tier = 10;
private int time = 0;
public Gradient colorGradient;
public int particleNum = 10000;
public float size = 0.03f;
public float minRadius = 7.0f;
public float maxRadius = 10.0f;
public float collect_MaxRadius = 4.0f;
public float collect_MinRadius = 1.0f;
public bool clockwise = true;
public float speed = 2f;
public float pingPong = 0.02f;
public int isCollected = 0;
然后在start函数里实例化粒子系统和初始化各粒子的属性与位置
void Start () {
particleArr = new ParticleSystem.Particle[particleNum];
particles = new ParticalInfo[particleNum];
radius = new float[particleNum];
collect_radius = new float[particleNum];
particleSys = this.GetComponent<ParticleSystem>();
particleSys.startSpeed = 0;
particleSys.startSize = size;
particleSys.loop = false;
particleSys.maxParticles = particleNum;
particleSys.Emit(particleNum);
particleSys.GetParticles(particleArr);
RandomlySpread();
}
其中RandomlySpread函数将粒子随机分布在圆圈轨道上,实现如下:
void RandomlySpread(){
for (int i = 0; i < particleNum; ++i){
float midRadius = (maxRadius + minRadius) / 2;
float minRate = UnityEngine.Random.Range(1.0f, midRadius / minRadius);
float maxRate = UnityEngine.Random.Range(midRadius / maxRadius, 1.0f);
float _radius = UnityEngine.Random.Range(minRadius * minRate, maxRadius * maxRate);
radius[i] = _radius;
float collect_MidRadius = (collect_MaxRadius + collect_MinRadius) / 2;
float collect_outRate = Random.Range (1f, collect_MidRadius / collect_MinRadius);
float collect_inRate = Random.Range (collect_MaxRadius / collect_MidRadius, 1f);
float _collect_radius = Random.Range (collect_MinRadius * collect_outRate, collect_MaxRadius * collect_inRate);
collect_radius[i] = _collect_radius;
float angle = UnityEngine.Random.Range(0.0f, 360.0f);
float theta = angle / 180 * Mathf.PI;
float time = UnityEngine.Random.Range(0.0f, 360.0f);
if(isCollected == 0) particles [i] = new ParticalInfo (_radius, angle, time);
else particles [i] = new ParticalInfo (_collect_radius, angle, time);
particleArr[i].position = new Vector3(particles[i].radius * Mathf.Cos(theta), 0f, particles[i].radius * Mathf.Sin(theta));
}
particleSys.SetParticles(particleArr, particleArr.Length);
}
这样运行后就可以显示一个粒子光环了,但是粒子既不会动,也没有一些特殊效果。
接着对粒子旋转进行实现,粒子旋转的原理为:让每个粒子的角度在每一帧都减少或增加一个值,同时需要添加一个速度差分层数让不同层的粒子转的速度不一样,这样会使得例子旋转的效果更好。其中通过clockwise判断是顺时针还是逆时针旋转。Update实现如下:
void Update (){
for (int i = 0; i < particleNum; i++) {
if (clockwise) particles [i].angle -= (i % tier + 1) * (speed / particles [i].radius / tier);
else particles [i].angle += (i % tier + 1) * (speed / particles [i].radius / tier);
particles [i].angle = (360.0f + particles [i].angle) % 360.0f;
float theta = particles [i].angle / 180 * Mathf.PI;
if (isCollected == 1){
if (particles [i].radius > collect_radius [i]) particles [i].radius -= 15f * (collect_radius [i] / collect_radius [i]) * Time.deltaTime;
else particles [i].radius = collect_radius [i];
}
else {
if (particles [i].radius < radius [i]) particles [i].radius += 15f * (collect_radius [i] / collect_radius [i]) * Time.deltaTime;
else particles [i].radius += Mathf.PingPong (particles [i].time / minRadius / maxRadius, pingPong) - pingPong / 2.0f;
}
particleArr [i].position = new Vector3 (particles [i].radius * Mathf.Cos (theta), 0f, particles [i].radius * Mathf.Sin (theta));
}
changeColor ();
particleSys.SetParticles(particleArr, particleArr.Length);
}
粒子的漂浮效果既是调用PingPong函数实现:
particles[i].time += Time.deltaTime;
particles[i].radius += Mathf.PingPong(particleData[i].time / minRadius / maxRadius, pingPong) - pingPong / 2.0f;
这样粒子光环的旋转效果就基本实现了。接下来我们添加一个颜色、光效匀速旋转的效果,通过changeColor函数实现,具体如下:
void changeColor(){
float colorValue;
for (int i = 0; i < particleNum; i++){
colorValue = (Time.realtimeSinceStartup - Mathf.Floor(Time.realtimeSinceStartup));
colorValue += particles[i].angle/360;
while (colorValue > 1) colorValue--;
particleArr[i].color = colorGradient.Evaluate(colorValue);
}
}
实现的方法是填入Evaluate的值, 要与angle和时间相关。所以可以加入一个时间变量, 和angle值共同决定value。
接着实现第二个功能即点击屏幕可以对内圈外圈进行交换,通过OnGUI控制使用函数Input.GetMouseButtonDown(0)获取鼠标点击。首先,我们需要两个数组来存储收缩和扩展后的运动半径,好让它们能恢复到原来的轨道上
private float[] radius;
private float[] collect_radius;
以及一个变量判断现在该全是在外圈还是内圈。
public int isCollected = 0;
接着在start的RandomlySpread函数中实现对两个数组的赋值,即一个是内圈一个是外圈,赋值方法和原本类似,然后根据在内圈或外圈对particle数组的半径进行赋值
float midRadius = (maxRadius + minRadius) / 2;
float minRate = UnityEngine.Random.Range(1.0f, midRadius / minRadius);
float maxRate = UnityEngine.Random.Range(midRadius / maxRadius, 1.0f);
float _radius = UnityEngine.Random.Range(minRadius * minRate, maxRadius * maxRate);
radius[i] = _radius;
float collect_MidRadius = (collect_MaxRadius + collect_MinRadius) / 2;
float collect_outRate = Random.Range (1f, collect_MidRadius / collect_MinRadius);
float collect_inRate = Random.Range (collect_MaxRadius / collect_MidRadius, 1f);
float _collect_radius = Random.Range (collect_MinRadius * collect_outRate, collect_MaxRadius * collect_inRate);
collect_radius[i] = _collect_radius;
if(isCollected == 0) particles [i] = new ParticalInfo (_radius, angle, time);
然后再Update中对内圈外圈进行分类,执行不同的操作,即外圈变换到内圈,半径大的进行收缩直到小于目标半径;内圈变换到外圈,半径小的进行扩展直到大于目标半径。
if (isCollected == 1){
if (particles [i].radius > collect_radius [i]) particles [i].radius -= 15f * (collect_radius [i] / collect_radius [i]) * Time.deltaTime;
else particles [i].radius = collect_radius [i];
} else {
if (particles [i].radius < radius [i]) particles [i].radius += 15f * (collect_radius [i] / collect_radius [i]) * Time.deltaTime;
else particles [i].radius += Mathf.PingPong (particles [i].time / minRadius / maxRadius, pingPong) - pingPong / 2.0f;
}
最后再OnGUI中获取鼠标点击屏幕,若点击则改变控制变量isCollected
void OnGUI(){
if(Input.GetMouseButtonDown(0)){
time++;
if(time==2){
isCollected = 1 - isCollected;
time = 0;
}
}
}
这样就实现了通过点击转换内圈外圈。
三.实验总结与心得
这次作业实现了粒子光环的效果,在其中主要学到了对粒子流的控制编程以及许多数学相关的计算。也查询资料学习了色彩变幻的相关内容。
Github地址:ParticleHalo
视频展示:粒子光环
最后感谢师兄的博客!