下面用若干张张动图展示效果:
大概就是这样,并不是很难。 实际效果要比图中的好一点(顺畅得多)。
大致可以分为如下几个步骤,然后逐个实现就可以了:
让颜色均匀旋转
下面分步骤说明。
首先我们需要一个ParticleSystem的组件来管理这个粒子系统。 再让组件附着在一个空对象上即可。结构如图:
如图中那样, 我们在这个含有particle System的对象上挂载脚本, 准备对这个粒子系统进行控制。
在写脚本之前, 先对Particle System 进行一些必要的设置, 这样可以在测试脚本的时候看起来更舒服 :), 如图:
嘛, 由于我们都是代码控制, 下面的属性几乎都可以不勾选, renderer除外。 记得给renderer设置一个好看的Material啊 : )
这些都是为粒子能够正常产生做准备。 这些做好后, 就可以开始写脚本控制了, 产生粒子的相关代码如下:(只是产生的话, 超简单!)
using UnityEngine;
using System.Collections;
public class ParticleSea : MonoBehaviour {
public ParticleSystem particleSystem;
private ParticleSystem.Particle[] particlesArray;
public int seaResolution = 25;
void Start() {
particlesArray = new ParticleSystem.Particle[seaResolution * seaResolution];
particleSystem.maxParticles = seaResolution * seaResolution;
particleSystem.Emit(seaResolution * seaResolution);
particleSystem.GetParticles(particlesArray);
}
}
这些变量的含义:
particle System: 用来得到这个Object的particle System(在Inspector中记得把当前的Particle System 拖给脚本变量)
particlesArray: 用来存放粒子系统所产生的粒子的数组。
seaResolution: 粒子海洋的长度(实际粒子数是这个数的平方)。
Ps : 之所以不改为直接用粒子数表示有两个原因:
In all, 这样做之后创建过程就完成了。
首先考虑我们需要粒子的哪些属性:
考虑到这些, 创建一个类用于表示粒子的属性是个看起来不错的选择。代码如图:
public class particleSettings{
public float angle { get; set; }
public float radius{ get; set;}
public float speed { get; set; }
public particleSettings(float r) {
this.radius = r;
this.angle = Random.value * 2 * Mathf.PI;
this.speed = Random.value * Mathf.Sqrt(radius);
}
public Vector3 getPosition() {
return radius * new Vector3 (Mathf.Cos(angle), 0, Mathf.Sin(angle));
}
public void rotate() {
this.angle += Time.deltaTime * speed / 10;
if (this.angle > 2 * Mathf.PI)
this.angle -= 2 * Mathf.PI;
this.radius += Random.value * 0.2f - 0.1f;
if (this.radius > ParticleSea.MaxRadius)
this.radius = ParticleSea.MaxRadius;
if (this.radius < ParticleSea.MinRadius)
this.radius = ParticleSea.MinRadius;
}
}
方法getPosition就是根据radius和angle计算当前位置, 而rotate则是计算经过一帧的rotate后,该粒子的新参数。这里设置了一个MaxRadius和MinRadius, 是保证粒子的活动范围在一个圆环内。
很明显这样写之后, 我们的主类代码要发生相应变化, 如下:
using UnityEngine;
using System.Collections;
public class particleSettings{
public float angle { get; set; }
public float radius{ get; set;}
public float speed { get; set; }
public particleSettings(float r) {
this.radius = r;
this.angle = Random.value * 2 * Mathf.PI;
this.speed = Random.value * Mathf.Sqrt(radius);
}
public Vector3 getPosition() {
return radius * new Vector3 (Mathf.Cos(angle), 0, Mathf.Sin(angle));
}
public void rotate() {
this.angle += Time.deltaTime * speed / 10;
if (this.angle > 2 * Mathf.PI)
this.angle -= 2 * Mathf.PI;
this.radius += Random.value * 0.2f - 0.1f;
if (this.radius > ParticleSea.MaxRadius)
this.radius = ParticleSea.MaxRadius;
if (this.radius < ParticleSea.MinRadius)
this.radius = ParticleSea.MinRadius;
}
}
public class ParticleSea : MonoBehaviour {
public ParticleSystem particleSystem;
private ParticleSystem.Particle[] particlesArray;
private particleSettings[] psetting;
public int seaResolution = 25;
public static float MaxRadius = 120f;
public static float MinRadius = 50f;
public float radius = 100.0f;
public Gradient colorGradient;
void Start() {
particlesArray = new ParticleSystem.Particle[seaResolution * seaResolution];
psetting = new particleSettings[seaResolution * seaResolution];
particleSystem.maxParticles = seaResolution * seaResolution;
particleSystem.Emit(seaResolution * seaResolution);
particleSystem.GetParticles(particlesArray);
setInitialPosition ();
}
void Update() {
RotateParticles ();
changeColor ();
particleSystem.SetParticles(particlesArray, particlesArray.Length);
}
}
这里我将整个代码结构贴出了。后续步骤就是对这个结构中功能函数实现。那么开始实现的第一步: 设置初始粒子的属性。这里对应代码中的 setInitialPosition () 函数
很明显, 我们只要把particleSetting按照写好的构造函数构建出来, 直接设置其位置就可以了。
函数实现如下:
void setInitialPosition () {
for (int i = 0; i < seaResolution; i++) {
for (int j = 0; j < seaResolution; j++) {
psetting[i * seaResolution + j] = new particleSettings(radius);
particlesArray [i * seaResolution + j].position = psetting[i * seaResolution + j].getPosition();
}
}
particleSystem.SetParticles(particlesArray, particlesArray.Length);
}
第二步也就完成了。
这一步就是补充上面代码中的 RotateParticles() 函数。
因为之前在particleSettings类中, 我们已经写好这个方法, 所以直接用就可以了。
代码如下:
void RotateParticles () {
for (int i = 0; i < seaResolution; i++) {
for (int j = 0; j < seaResolution; j++) {
psetting [i * seaResolution + j].rotate ();
particlesArray [i * seaResolution + j].position = psetting [i * seaResolution + j].getPosition ();
}
}
}
第三步也就完成了:) 做到这里差不多可以看一下效果了, 把update中的changeColor()先注释掉, 运行。
大概是这样的效果:
下面是加特效时间hh。
这要用到类里面的 public Gradient colorGradient 这个成员。
首先到Inspector里, 看看这是个什么:
中央的这根色彩轴按照从左到右,Location值从小到大设置相应的色彩。在这里, Unity帮我们自动渐变了, 我们要做的就是设置那几个关键Location的色彩(和动画的关键帧的作用很像!)
上面那排红色箭头指的是当前Location的Alpha值(透明度), 下面黑色箭头是设置该Location的颜色。
在轴的上方或下方, 右键, 可以自行添加关键位置。(Alpha和Color可以分开设置关键位置)
做好了自己的调色板之后,点下方的“New”可以保存。
然后我们要做的, 就是写代码控制Gradient的行为就可以了。
主要通过 colorGradient.Evaluate(float)来得到相应的调色板的颜色。
先写这样的代码试试看颜色效果:
void changeColor () {
for (int i = 0; i < seaResolution; i++) {
for (int j = 0; j < seaResolution; j++) {
particlesArray [i * seaResolution + j].color = colorGradient.Evaluate (Random.value);
}
}
}
如果, 之前的Gradient用的和我上面图中一样的话 大致应该是这样的效果:
黄色是红和绿混合的结果~ 这图让我想到显像管电视机…
接下来继续加特效~
其实这步就是对刚才的changeColor ()函数改造就行了, 很明显 这样直接Random效果 不会很好.
这里其实就是让填入Evaluate的值, 显然一定和angle相关, 但是又不仅和Angle相关。 如果仅仅和angle相关, 那么其实是得到一圈固定的颜色, 而不能旋转。
我的做法是, 加入一个时间变量, 和angle值共同决定value。 一行代码胜千言:
void changeColor () {
for (int i = 0; i < seaResolution; i++) {
for (int j = 0; j < seaResolution; j++) {
float value = (Time.realtimeSinceStartup - Mathf.Floor (Time.realtimeSinceStartup));
value+= psetting [i * seaResolution + j].angle / 2 / Mathf.PI;
while (value > 1)
value--;
particlesArray[i * seaResolution + j].color = colorGradient.Evaluate(value);
//particlesArray [i * seaResolution + j].color = colorGradient.Evaluate (Random.value);
}
}
}
至此效果就都有了~ 如果调色板用的是上面那个Red-Green-Red, 那么效果如下:
通过Alpha值的组合, 可以产生更多的炫酷效果~ 例如只设置Alpha不改color, 两端alpha设置为255, 中间设置为0, 可以看到明显的明暗相间效果:)
事实上 seaResolution 相当于对粒子进行分层了, 但是简单的直接设置为粒子数然后之后用求余方法好像是等效的orz… 就简单举一个例子吧:
重写这个方法如下:
void RotateParticles () {
for (int i = 0; i < seaResolution; i++) {
for (int j = 0; j < seaResolution; j++) {
if(Random.Range(0, seaResolution) < 2 * i) psetting [i * seaResolution + j].rotate ();
particlesArray [i * seaResolution + j].position = psetting [i * seaResolution + j].getPosition ();
}
}
}
比起之前加了个对i的判断,进一步操作罢了。 实用性不强。 估计还是要改成直接用粒子数好一点…
不过, 把刚才做好的这个做成prefab, 然后多弄几个到画面中来, 感觉还是挺棒的(只要修改radius和seaResolution值就好), 如下:
还是那句话: 真实效果更好看, 更流畅…