3D-08-制作粒子光环

本文是3D游戏编程与设计第八次作业的博客,内容为编写制作一个简单粒子光环

任务说明

参考 http://i-remember.fr/en 这类网站,使用粒子流编程控制制作一些效果, 如“粒子光环”

  • 可参考以前作业

成果展示

Gitee传送门

效果展示

在这里插入图片描述

设计说明

目标网站无法打开,但参考以往的博客发现粒子光环有如下要求:

  • 光环由外环和内环组成,两个光环逆向转动,使光环产生漩涡感
  • 光环粒子是随机游离的,但主要聚集在光环部分
  • 当鼠标移动到光环中时,光环会收缩为一个更紧密的环

光环预制

分析要求后发现两个光环的区别体现在半径与旋转方向上,所以内外光环可以使用两个粒子系统实现,挂载相同的脚本,不同处在参数设置上体现,而实现收缩功能需要添加一个检测鼠标位置的碰撞器,所以我们的光环预制如下
在这里插入图片描述
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 官方文档

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值