粒子系统在射击游戏中的应用
参考教程:Controlling Particles Via Script
先来看看效果图:
这是一个第一人称射击游戏,玩家可以通过鼠标控制手中的枪发射彩色子弹,子弹碰到墙壁后会留下彩色痕迹。
准备
因为重点是粒子系统,所以其余的部件可以直接从 bit.ly/unityparticlescripting 下载。
添加一个粒子系统子对象,命名为 ParticleLauncher
:
在默认情况下,粒子是离散发射的。为了让粒子沿直线发射,可以取消粒子系统的 Shape
模块:
为粒子系统添加之前下载好的材质 GunSprayParticle,并设置合适的粒子大小:
因为该场景涉及到物理碰撞,所以此处将 Start Speed
设置为 0,并直接在 Velocity over Lifetime
模块中设置速度:
如果此时运行游戏,会发现发射出去的子弹会跟随着枪移动的方向移动,这显然与现实世界不符。为了解决这个问题,需要将 Simulation Space
属性选择为 World:
由于游戏中的玩家也可以移动,所以最好让粒子继承上层对象的速度:
粒子碰撞
勾选 Collision
模块,并将 Type
属性设置为 World:
设置 Collides With
属性为 Environment,让粒子与 Environment 层的对象碰撞(墙壁和地板的预制已经加入 Environment 层)。为了不让粒子碰到墙壁和地板后反弹,可将 Bounce
属性设置为 0。与此同时,将 Max Kill Speed
设置为 0,将所有碰撞的粒子销毁。最后,勾选 Send Collision Messages
,用于触发 OnParticleCollision 函数。
为了显示子弹与墙壁或地板碰撞的痕迹,可以再创建一个粒子系统 SplatterDecalParticles
:
因为碰撞的痕迹是片状的,所以需要修改粒子系统的 Renderer
模块:
为了更方便地存储碰撞痕迹的位置、角度、大小以及颜色,可以创建以下的数据结构:
using UnityEngine;
public class DecalData {
public Vector3 position { get; set; }
public Vector3 rotation { get; set; }
public float size { get; set; }
public Color color { get; set; }
}
为该粒子系统添加 DecalPool
脚本:
using UnityEngine;
public class DecalPool : MonoBehaviour {
public static readonly int maxDecalNum = 1000;
public static readonly float minDecalSize = 0.5f;
public static readonly float maxDecalSize = 1.5f;
private int decalDataIndex = 0;
private DecalData[] decalDatas = new DecalData[maxDecalNum];
private ParticleSystem.Particle[] decals = new ParticleSystem.Particle[maxDecalNum];
private ParticleSystem decalParticleSystem;
private void Start() {
decalParticleSystem = GetComponent<ParticleSystem>();
for (int i = 0; i < decalDatas.Length; i++) {
decalDatas[i] = new DecalData();
}
}
public void ParticleHit(ParticleCollisionEvent particleCollisionEvent, Gradient gradient) {
SetDecalData(particleCollisionEvent, gradient);
DisplayDecals();
}
private void SetDecalData(ParticleCollisionEvent particleCollisionEvent, Gradient gradient) {
if (decalDataIndex >= maxDecalNum) {
decalDataIndex = 0;
}
// set position
decalDatas[decalDataIndex].position = particleCollisionEvent.intersection;
// set rotation
Vector3 decalRotationEuler = Quaternion.LookRotation(particleCollisionEvent.normal).eulerAngles;
decalRotationEuler.z = Random.Range(0f, 360f);
decalDatas[decalDataIndex].rotation = decalRotationEuler;
// set size
decalDatas[decalDataIndex].size = Random.Range(minDecalSize, maxDecalSize);
// set color
decalDatas[decalDataIndex].color = gradient.Evaluate(Random.Range(0f, 1f));
decalDataIndex += 1;
}
private void DisplayDecals() {
for (int i = 0; i < decals.Length; i++) {
decals[i].position = decalDatas[i].position;
decals[i].rotation3D = decalDatas[i].rotation;
decals[i].startSize = decalDatas[i].size;
decals[i].startColor = decalDatas[i].color;
}
decalParticleSystem.SetParticles(decals, decals.Length);
}
}
ParticleHit 函数:当粒子与墙壁或地面碰撞时被调用。
SetDecalData 函数:设置当前碰撞痕迹的信息。由于碰撞痕迹的信息循环复用的,所以如果碰撞痕迹数量超过最大值,那么旧的信息会被新的信息覆盖。
DisplayDecals:通过粒子系统的 SetParticles 函数设置碰撞痕迹,这些碰撞痕迹最终会显示在界面上。
射击
取消 ParticleLauncher
粒子系统的 Emission
模块,以便让脚本控制射击,而不是自动射击。
当子弹与墙壁或地板碰撞后,对应的位置会出现飞溅效果,负责该效果的粒子系统为 SplatterParticles
:
为了将发射子弹的粒子系统与实现飞溅效果的粒子系统联系起来,可为 ParticleLauncher
添加一个脚本:
using UnityEngine;
using System.Collections.Generic;
public class ParticleLauncher : MonoBehaviour {
public ParticleSystem particleLauncher;
public ParticleSystem splatterParticles;
public DecalPool decalPool;
public Gradient particleColorGradient;
private List<ParticleCollisionEvent> collisionEvents = new List<ParticleCollisionEvent>();
private void Update() {
if (Input.GetButton("Fire1")) {
// set a random color for the bullet
ParticleSystem.MainModule psMain = particleLauncher.main;
psMain.startColor = particleColorGradient.Evaluate(Random.Range(0f, 1f));
// emit the bullet
particleLauncher.Emit(1);
}
}
private void OnParticleCollision(GameObject other) {
ParticlePhysicsExtensions.GetCollisionEvents(particleLauncher, other, collisionEvents);
// display the decal and emit the splatter where the bullet collides with the wall or the floor
foreach (ParticleCollisionEvent collisionEvent in collisionEvents) {
decalPool.ParticleHit(collisionEvent, particleColorGradient);
EmitSplatterAtLocation(collisionEvent);
}
}
private void EmitSplatterAtLocation(ParticleCollisionEvent particleCollisionEvent) {
// set the position of the particle system for splatter
splatterParticles.transform.position = particleCollisionEvent.intersection;
splatterParticles.transform.rotation = Quaternion.LookRotation(particleCollisionEvent.normal);
// set a random color for the splatter
ParticleSystem.MainModule psMain = splatterParticles.main;
psMain.startColor = particleColorGradient.Evaluate(Random.Range(0f, 1f));
// emit the splatter
splatterParticles.Emit(1);
}
}
Update 函数:在接收到用户点击事件后,设置子弹的颜色并发射子弹。
OnParticleCollision 函数:在发生碰撞事件时,调用 ParticleHit 函数来显示碰撞痕迹,并在碰撞点发射飞溅的粒子。
EmitSplatterAtLocation 函数:设置发射点的值和粒子的颜色,并发射粒子。
粒子的颜色会从 Gradient 中选取:
效果如下图: