本文是3D游戏编程与设计第八次作业的博客,内容为编写制作一个简单粒子光环
任务说明
参考 http://i-remember.fr/en 这类网站,使用粒子流编程控制制作一些效果, 如“粒子光环”
- 可参考以前作业
成果展示
效果展示
设计说明
目标网站无法打开,但参考以往的博客发现粒子光环有如下要求:
- 光环由外环和内环组成,两个光环逆向转动,使光环产生漩涡感
- 光环粒子是随机游离的,但主要聚集在光环部分
- 当鼠标移动到光环中时,光环会收缩为一个更紧密的环
光环预制
分析要求后发现两个光环的区别体现在半径与旋转方向上,所以内外光环可以使用两个粒子系统实现,挂载相同的脚本,不同处在参数设置上体现,而实现收缩功能需要添加一个检测鼠标位置的碰撞器,所以我们的光环预制如下
collider 中添加了符合环状要求的球形碰撞器 Sphere Collider
outerRing/innerRing 中添加了粒子系统 Paricle System,并挂载了脚本 Halo.cs,二者在 Radius 参数与 Clockwise 参数设置的区别将其区分为外环/内环
代码说明
代码主要参考了师兄的代码,自己仅做了部分的完善工作
基本实现
相关参数
public class CirclePosition {
public float radius = 0f, angle = 0f, time = 0f;
public CirclePosition(float radius, float angle, float time) {
this.radius = radius; // 半径
this.angle = angle; // 角度
this.time = time; // 时间
}
}
public class Halo : MonoBehaviour {
private ParticleSystem particleSys; // 粒子系统
private ParticleSystem.Particle[] particleArr; // 粒子数组
private CirclePosition[] circle; // 极坐标数组
public int count = 5000; // 粒子数量
public float size = 0.15f; // 粒子大小
public float minRadius = 4.0f; // 最小半径
public float maxRadius = 11.0f; // 最大半径
public bool clockwise = true; // 旋转方向
public float speed = 1.5f; // 速度
public float pingPong = 0.03f; // 游离范围
public int tier = 10;
}
首先在 Start() 中实现初始化,包括粒子系统参数的初始化以及粒子的发射,之后将粒子位置随机初始化在圆环附近
void Start() {
particleArr = new ParticleSystem.Particle[count];
circle = new CirclePosition[count];
// 初始化粒子系统
particleSys = this.GetComponent<ParticleSystem>();
var main = particleSys.main;
main.startSpeed = 0;
main.startSize = size;
main.loop = false;
main.maxParticles = count; // 粒子数量
particleSys.Emit(count); // 发射粒子
particleSys.GetParticles(particleArr);
...
RandomlySpread();
}
void RandomlySpread() {
// 随机每个粒子距离中心的半径,同时希望粒子集中在平均半径附近
for (int i = 0; i < count; ++i) {
float midRadius = (maxRadius + minRadius) / 2;
float minRate = Random.Range(1.0f, midRadius / minRadius);
float maxRate = Random.Range(midRadius / maxRadius, 1.0f);
float radius = Random.Range(minRadius * minRate, maxRadius * maxRate);
// 随机每个粒子的角度
float angle = Random.Range(0.0f, 360.0f);
float theta = angle / 180 * Mathf.PI;
// 随机每个粒子的游离起始时间
float time = Random.Range(0.0f, 360.0f);
circle[i] = new CirclePosition(radius, angle, time);
...
particleArr[i].position = new Vector3(circle[i].radius * Mathf.Cos(theta), 0f, circle[i].radius * Mathf.Sin(theta));
}
particleSys.SetParticles(particleArr, particleArr.Length);
}
在 Update() 中,以不同速度旋转每个粒子的角度,然后利用 Mathf.PingPong 调整粒子轨迹半径,模拟出粒子游离在光环附近的效果
void Update() {
for (int i = 0; i < count; i++) {
...
if (clockwise)
circle[i].angle -= (i % tier + 1) * (speed / circle[i].radius / tier);
else
circle[i].angle += (i % tier + 1) * (speed / circle[i].radius / tier);
circle[i].angle = (360.0f + circle[i].angle) % 360.0f; // 保证angle在0~360度
float theta = circle[i].angle / 180 * Mathf.PI;
circle[i].time += Time.deltaTime;
circle[i].radius += Mathf.PingPong(circle[i].time, pingPong) - pingPong / 2.0f; // 粒子在半径方向上游离
particleArr[i].position = new Vector3(circle[i].radius * Mathf.Cos(theta), 0f, circle[i].radius * Mathf.Sin(theta));
}
particleSys.SetParticles(particleArr, particleArr.Length);
}
渐变色实现
相关参数
public Gradient colorGradient; // 颜色渐变
private GradientAlphaKey[] alphaKeys; // 透明度
private GradientColorKey[] colorKeys;
首先在 Start() 中初始化渐变色 colorGradient,包括GradientAlphaKey[] 以及 GradientColorKey[] 的初始化,分别对应颜色透明度与 RGB,我在这里设置了粉色向红色的渐变,注意 colorKeys 的设置顺逆时针是相反的,这样才能在实际效果中使内外环颜色同步
void Start() {
...
alphaKeys = new GradientAlphaKey[5];
colorKeys = new GradientColorKey[2];
// 初始化梯度颜色控制器
alphaKeys[0].time = 0.0f; alphaKeys[0].alpha = 0.2f;
alphaKeys[1].time = 0.25f; alphaKeys[1].alpha = 1f;
alphaKeys[2].time = 0.5f; alphaKeys[2].alpha = 0.2f;
alphaKeys[3].time = 0.75f; alphaKeys[3].alpha = 1f;
alphaKeys[4].time = 1.0f; alphaKeys[4].alpha = 0.2f;
if(clockwise) {
colorKeys[0].time = 0.25f; colorKeys[0].color = new Color(193f / 255, 136f / 255, 136f / 255);
colorKeys[1].time = 0.75f; colorKeys[1].color = new Color(195f / 255, 34f / 255, 34f / 255);
}
else {
colorKeys[0].time = 0.25f; colorKeys[0].color = new Color(195f / 255, 34f / 255, 34f / 255);
colorKeys[1].time = 0.75f; colorKeys[1].color = new Color(193f / 255, 136f / 255, 136f / 255);
}
colorGradient.SetKeys(colorKeys, alphaKeys);
}
在 Update() 中,依据渐变颜色值由粒子的角度和时间变量得出,前者使颜色在环上渐变,后者使渐变色在环上旋转,而不是固定的角度对应固定的颜色
void Update() {
...
ChangeColor();
}
void ChangeColor() {
//实现颜色渐变
float colorValue;
for (int i = 0; i < count; i++) {
colorValue = (Time.realtimeSinceStartup - Mathf.Floor(Time.realtimeSinceStartup))/2;
colorValue += circle[i].angle / 360;
if (colorValue > 1) colorValue -= 1;
particleArr[i].startColor = colorGradient.Evaluate(colorValue);
}
}
收缩实现
相关参数
public Camera camera; // 主摄像机
private Ray ray; // 射线
private RaycastHit hit;
private float[] shrinkBefore; // 收缩前粒子位置
private float[] shrinkAfter; // 收缩后粒子位置
public float shrinkSpeed = 5f; // 粒子缩放的速度
private bool isShrinking = false; // 是否收缩
首先在 Start() 中初始化数组,然后在将粒子初始化位置时 shrinkBefore 记录初始半径,然后通过等比缩小得到收缩后半径 shrinkAfter
void Start() {
shrinkBefore = new float[count];
shrinkAfter = new float[count];
...
RandomlySpread();
}
void RandomlySpread() {
// 随机每个粒子距离中心的半径,同时希望粒子集中在平均半径附近
for (int i = 0; i < count; ++i) {
...
circle[i] = new CirclePosition(radius, angle, time);
shrinkBefore[i] = radius;
shrinkAfter[i] = 0.6f * radius;
if (shrinkAfter[i] < minRadius * 1.1f) {
shrinkAfter[i] = Random.Range(Random.Range(minRadius, midRadius), (minRadius * 1.1f));
}
...
}
particleSys.SetParticles(particleArr, particleArr.Length);
}
在 Update() 中,若检测到鼠标移动到圆环中心,则开始收缩,即将每个粒子的运动轨迹半径依据 shrinkSpeed 和当前半径逐帧缩小,直至达到收缩后半径,还原同理。越接近目标半径变化速度越慢,起到平滑动作的效果,匀速变化则显得突兀
void Update() {
isShrinking = mouseInCircle();
for (int i = 0; i < count; i++) {
if (isShrinking) {
if (circle[i].radius > shrinkAfter[i]) {
circle[i].radius -= shrinkSpeed * (circle[i].radius / shrinkAfter[i]) * Time.deltaTime;
}
} // 收缩到记录的收缩后位置
else {
if (circle[i].radius < shrinkBefore[i]) {
circle[i].radius += shrinkSpeed * (shrinkBefore[i] / circle[i].radius) * Time.deltaTime;
}
} // 还原到记录的收缩前位置
...
}
...
}
bool mouseInCircle(){
ray = camera.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit) && hit.collider.gameObject.tag == "button")
return true;
else
return false;
}
参考资料
Simba_Scorpio—Unity3D学习笔记(9)—— 粒子光环
Tifinity—Unity3D项目八:简单粒子光环
Unity 官方文档