使用Unity实现角色残影特效
在网络上,有许多关于残影特效的示例。我参考了这些示例并进行整合,最终完成了一个完整且简单易用的实现方案。只需将一个脚本挂载到对象上,无需额外设置即可使用。
本文主要针对带有蒙皮的网格(即 SkinnedMeshRenderer)实现残影特效。其核心原理是按照设定的间隔时间,持续截取当前 SkinnedMeshRenderer 的网格数据,然后使用 Graphics.DrawMesh 方法绘制这些网格。
一、定义残影类
为了能够及时销毁残影对象,我们创建一个派生自 Object 的 AfterImage 类:
class AfterImage : Object
{
// 残影网格
public Mesh _Mesh;
// 残影纹理
public Material _Material;
// 残影位置
public Matrix4x4 _Matrix;
// 残影透明度
public float _Alpha;
// 残影启动时间
public float _StartTime;
// 残影保留时间
public float _Duration;
public AfterImage(Mesh mesh, Material material, Matrix4x4 matrix4x4, float alpha, float startTime, float duration)
{
_Mesh = mesh;
_Material = material;
_Matrix = matrix4x4;
_Alpha = alpha;
_StartTime = startTime;
_Duration = duration;
}
}
属性描述
- 残影启动时间(
_StartTime):从残影创建的那一刻开始记录。当残影的生命周期达到或超过设定的_Duration(残影保留时间)时,该残影将被清除。 - 残影网格(
_Mesh):在残影创建时,从SkinnedMeshRenderer中截取,保存了当前SkinnedMeshRenderer的网格数据。 - 残影纹理(
_Material):指定残影的材质。 - 残影位置(
_Matrix):确定残影在场景中的位置。 - 残影透明度(
_Alpha):控制残影的透明程度。 - 残影保留时间(
_Duration):定义了残影从创建到销毁的持续时间。
二、创建残影特效管理类
接下来,我们定义一个 AfterImageEffects 类来管理残影特效:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
/// <summary>
/// 残影特效
/// </summary>
public class AfterImageEffects : MonoBehaviour
{
// 开启残影
public bool _OpenAfterImage;
// 残影颜色
public Color _AfterImageColor = Color.black;
// 残影的生存时间
public float _SurvivalTime = 1;
// 生成残影的间隔时间
public float _IntervalTime = 0.2f;
private float _Time = 0;
// 残影初始透明度
[Range(0.1f, 1.0f)]
public float _InitialAlpha = 1.0f;
private List<AfterImage> _AfterImageList;
private SkinnedMeshRenderer _SkinnedMeshRenderer;
void Awake()
{
_AfterImageList = new List<AfterImage>();
_SkinnedMeshRenderer = GetComponent<SkinnedMeshRenderer>();
}
void Update()
{
if (_OpenAfterImage && _AfterImageList != null)
{
if (_SkinnedMeshRenderer == null)
{
_OpenAfterImage = false;
return;
}
_Time += Time.deltaTime;
// 生成残影
CreateAfterImage();
// 刷新残影
UpdateAfterImage();
}
}
/// <summary>
/// 生成残影
/// </summary>
void CreateAfterImage()
{
// 生成残影
if (_Time >= _IntervalTime)
{
_Time = 0;
Mesh mesh = new Mesh();
_SkinnedMeshRenderer.BakeMesh(mesh);
Material material = new Material(_SkinnedMeshRenderer.material);
SetMaterialRenderingMode(material, RenderingMode.Fade);
_AfterImageList.Add(new AfterImage(
mesh,
material,
transform.localToWorldMatrix,
_InitialAlpha,
Time.realtimeSinceStartup,
_SurvivalTime));
}
}
/// <summary>
/// 刷新残影
/// </summary>
void UpdateAfterImage()
{
// 刷新残影,根据生存时间销毁已过时的残影
for (int i = 0; i < _AfterImageList.Count; i++)
{
float _PassingTime = Time.realtimeSinceStartup - _AfterImageList[i]._StartTime;
if (_PassingTime > _AfterImageList[i]._Duration)
{
_AfterImageList.Remove(_AfterImageList[i]);
Destroy(_AfterImageList[i]);
continue;
}
if (_AfterImageList[i]._Material.HasProperty("_Color"))
{
_AfterImageList[i]._Alpha *= (1 - _PassingTime / _AfterImageList[i]._Duration);
_AfterImageColor.a = _AfterImageList[i]._Alpha;
_AfterImageList[i]._Material.SetColor("_Color", _AfterImageColor);
}
Graphics.DrawMesh(_AfterImageList[i]._Mesh, _AfterImageList[i]._Matrix, _AfterImageList[i]._Material, gameObject.layer);
}
}
/// <summary>
/// 设置纹理渲染模式
/// </summary>
void SetMaterialRenderingMode(Material material, RenderingMode renderingMode)
{
switch (renderingMode)
{
case RenderingMode.Opaque:
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
material.SetInt("_ZWrite", 1);
material.DisableKeyword("_ALPHATEST_ON");
material.DisableKeyword("_ALPHABLEND_ON");
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = -1;
break;
case RenderingMode.Cutout:
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
material.SetInt("_ZWrite", 1);
material.EnableKeyword("_ALPHATEST_ON");
material.DisableKeyword("_ALPHABLEND_ON");
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = 2450;
break;
case RenderingMode.Fade:
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
material.SetInt("_ZWrite", 0);
material.DisableKeyword("_ALPHATEST_ON");
material.EnableKeyword("_ALPHABLEND_ON");
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = 3000;
break;
case RenderingMode.Transparent:
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
material.SetInt("_ZWrite", 0);
material.DisableKeyword("_ALPHATEST_ON");
material.DisableKeyword("_ALPHABLEND_ON");
material.EnableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = 3000;
break;
}
}
}
public enum RenderingMode
{
Opaque,
Cutout,
Fade,
Transparent
}
属性描述
- 开启残影(
_OpenAfterImage):用于控制是否启用残影特效。 - 残影颜色(
_AfterImageColor):指定残影的颜色。 - 残影的生存时间(
_SurvivalTime):每个残影从创建到销毁所经历的时间。 - 生成残影的间隔时间(
_IntervalTime):相邻两个残影创建的时间间隔。 - 残影初始透明度(
_InitialAlpha):残影创建时的初始透明程度,取值范围在 0.1 到 1.0 之间。
方法说明
CreateAfterImage():当时间达到_IntervalTime时,截取当前SkinnedMeshRenderer的网格数据,创建一个新的残影对象,并将其添加到_AfterImageList中。UpdateAfterImage():遍历_AfterImageList,检查每个残影的生存时间。如果残影的生存时间超过_SurvivalTime,则将其从列表中移除并销毁。同时,根据残影的生存时间更新其透明度,并使用Graphics.DrawMesh方法绘制残影。SetMaterialRenderingMode(Material material, RenderingMode renderingMode):根据传入的RenderingMode参数,设置材质的渲染模式。由于残影存在透明通道,我们需要将材质的渲染模式设置为Fade模式。
三、效果图
(此处可插入实际的效果图,由于原文未提供,可在实际使用时补充)
四、使用方法
你可以按照以下步骤使用该残影特效:
- 新建一个名为
AfterImageEffects的脚本。 - 将上述完整的代码复制到该脚本中。
- 将该脚本挂载到带有
SkinnedMeshRenderer组件的模型上。 - 在 Inspector 面板中勾选
_OpenAfterImage选项。 - 运行游戏,即可看到角色的残影特效。
通过以上步骤,你就可以在 Unity 中轻松实现角色的残影特效了。希望本文对你有所帮助!