使用Unity实现角色残影特效

2017年04月18日 13:57 0 点赞 0 评论 更新于 2025-11-21 21:22

在网络上,有许多关于残影特效的示例。我参考了这些示例并进行整合,最终完成了一个完整且简单易用的实现方案。只需将一个脚本挂载到对象上,无需额外设置即可使用。

本文主要针对带有蒙皮的网格(即 SkinnedMeshRenderer)实现残影特效。其核心原理是按照设定的间隔时间,持续截取当前 SkinnedMeshRenderer 的网格数据,然后使用 Graphics.DrawMesh 方法绘制这些网格。

一、定义残影类

为了能够及时销毁残影对象,我们创建一个派生自 ObjectAfterImage 类:

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 模式。

三、效果图

(此处可插入实际的效果图,由于原文未提供,可在实际使用时补充)

四、使用方法

你可以按照以下步骤使用该残影特效:

  1. 新建一个名为 AfterImageEffects 的脚本。
  2. 将上述完整的代码复制到该脚本中。
  3. 将该脚本挂载到带有 SkinnedMeshRenderer 组件的模型上。
  4. 在 Inspector 面板中勾选 _OpenAfterImage 选项。
  5. 运行游戏,即可看到角色的残影特效。

通过以上步骤,你就可以在 Unity 中轻松实现角色的残影特效了。希望本文对你有所帮助!

作者信息

孟子菇凉

孟子菇凉

共发布了 3994 篇文章