147 lines
5.3 KiB
C#
147 lines
5.3 KiB
C#
|
|
using System.Collections.Generic;
|
|||
|
|
using UnityEngine;
|
|||
|
|
using Cielonos.MainGame.Characters;
|
|||
|
|
|
|||
|
|
namespace Cielonos.MainGame.Effects
|
|||
|
|
{
|
|||
|
|
public enum AfterImageSpawnMode
|
|||
|
|
{
|
|||
|
|
TimeInterval,
|
|||
|
|
DistanceInterval
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 残影发生器,挂载在角色身上,用于在高速移动时动态生成姿态残影。
|
|||
|
|
/// </summary>
|
|||
|
|
public class AfterImageGenerator : MonoBehaviour
|
|||
|
|
{
|
|||
|
|
private CharacterBase character;
|
|||
|
|
private List<SkinnedMeshRenderer> skinnedRenderers = new List<SkinnedMeshRenderer>();
|
|||
|
|
private bool isSpawning = false;
|
|||
|
|
private float timer;
|
|||
|
|
private Vector3 lastSpawnPos;
|
|||
|
|
|
|||
|
|
[Header("AfterImage Settings")]
|
|||
|
|
[Tooltip("残影所使用的菲涅尔发光材质")]
|
|||
|
|
public Material afterImageMaterial;
|
|||
|
|
[Tooltip("单个残影的持续消失时间(秒)")]
|
|||
|
|
public float afterImageDuration = 0.3f;
|
|||
|
|
|
|||
|
|
[Tooltip("残影生成模式:时间间隔 或 距离间隔")]
|
|||
|
|
public AfterImageSpawnMode spawnMode = AfterImageSpawnMode.TimeInterval;
|
|||
|
|
|
|||
|
|
[Tooltip("生成残影的时间间隔(秒,TimeInterval 模式)")]
|
|||
|
|
public float spawnInterval = 0.03f;
|
|||
|
|
[Tooltip("生成残影的距离间隔(米,DistanceInterval 模式)")]
|
|||
|
|
public float spawnDistance = 0.8f;
|
|||
|
|
|
|||
|
|
private void Start()
|
|||
|
|
{
|
|||
|
|
character = GetComponent<CharacterBase>();
|
|||
|
|
GetComponentsInChildren(true, skinnedRenderers);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void StartSpawning()
|
|||
|
|
{
|
|||
|
|
isSpawning = true;
|
|||
|
|
timer = 0f; // 时间模式下启动时立即生成一个
|
|||
|
|
lastSpawnPos = transform.position; // 距离模式下重置起始点
|
|||
|
|
|
|||
|
|
if (spawnMode == AfterImageSpawnMode.DistanceInterval)
|
|||
|
|
{
|
|||
|
|
CreateAfterImageSnapshot(); // 距离模式启动时也立即生成第一个
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void StopSpawning()
|
|||
|
|
{
|
|||
|
|
isSpawning = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#region Dynamic Configuration APIs
|
|||
|
|
|
|||
|
|
public void SetSpawnMode(AfterImageSpawnMode mode) => spawnMode = mode;
|
|||
|
|
public void SetSpawnInterval(float interval) => spawnInterval = interval;
|
|||
|
|
public void SetSpawnDistance(float distance) => spawnDistance = distance;
|
|||
|
|
public void SetAfterImageDuration(float duration) => afterImageDuration = duration;
|
|||
|
|
public void SetMaterial(Material mat) => afterImageMaterial = mat;
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
private void Update()
|
|||
|
|
{
|
|||
|
|
if (!isSpawning || character == null) return;
|
|||
|
|
|
|||
|
|
if (spawnMode == AfterImageSpawnMode.TimeInterval)
|
|||
|
|
{
|
|||
|
|
timer -= character.selfTimeSm.DeltaTime;
|
|||
|
|
if (timer <= 0f)
|
|||
|
|
{
|
|||
|
|
CreateAfterImageSnapshot();
|
|||
|
|
timer = spawnInterval;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else if (spawnMode == AfterImageSpawnMode.DistanceInterval)
|
|||
|
|
{
|
|||
|
|
Vector3 currentPos = transform.position;
|
|||
|
|
float dist = Vector3.Distance(lastSpawnPos, currentPos);
|
|||
|
|
if (dist >= spawnDistance)
|
|||
|
|
{
|
|||
|
|
CreateAfterImageSnapshot();
|
|||
|
|
lastSpawnPos = currentPos;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 手动生成一个当前姿态的残影
|
|||
|
|
/// </summary>
|
|||
|
|
public void CreateAfterImageSnapshot()
|
|||
|
|
{
|
|||
|
|
if (afterImageMaterial == null) return;
|
|||
|
|
|
|||
|
|
// 创建残影根容器
|
|||
|
|
GameObject afterImageObj = new GameObject($"{gameObject.name}_AfterImage");
|
|||
|
|
|
|||
|
|
List<MeshFilter> filters = new List<MeshFilter>();
|
|||
|
|
List<MeshRenderer> renderers = new List<MeshRenderer>();
|
|||
|
|
|
|||
|
|
foreach (var skinnedRenderer in skinnedRenderers)
|
|||
|
|
{
|
|||
|
|
// 仅烘焙当前处于显示状态的蒙皮部件
|
|||
|
|
if (skinnedRenderer == null || !skinnedRenderer.gameObject.activeInHierarchy || !skinnedRenderer.enabled)
|
|||
|
|
continue;
|
|||
|
|
|
|||
|
|
// 创建对应蒙皮部件的静态 Mesh 副本
|
|||
|
|
GameObject subObj = new GameObject(skinnedRenderer.name);
|
|||
|
|
subObj.transform.SetParent(afterImageObj.transform);
|
|||
|
|
subObj.transform.position = skinnedRenderer.transform.position;
|
|||
|
|
subObj.transform.rotation = skinnedRenderer.transform.rotation;
|
|||
|
|
subObj.transform.localScale = skinnedRenderer.transform.lossyScale;
|
|||
|
|
|
|||
|
|
// 烘焙蒙皮网格的当前姿态
|
|||
|
|
Mesh bakedMesh = new Mesh();
|
|||
|
|
skinnedRenderer.BakeMesh(bakedMesh);
|
|||
|
|
|
|||
|
|
MeshFilter filter = subObj.AddComponent<MeshFilter>();
|
|||
|
|
filter.mesh = bakedMesh;
|
|||
|
|
filters.Add(filter);
|
|||
|
|
|
|||
|
|
MeshRenderer renderer = subObj.AddComponent<MeshRenderer>();
|
|||
|
|
renderers.Add(renderer);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果没有成功烘焙出任何网格,直接销毁根容器
|
|||
|
|
if (filters.Count == 0)
|
|||
|
|
{
|
|||
|
|
Destroy(afterImageObj);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 初始化渐隐控制
|
|||
|
|
AfterImageItem afterImageItem = afterImageObj.AddComponent<AfterImageItem>();
|
|||
|
|
afterImageItem.Initialize(filters.ToArray(), renderers.ToArray(), afterImageMaterial, afterImageDuration);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|