粒子系统在射击游戏中的应用

参考教程: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 中选取:

效果如下图:

Updated: