修改位置
This commit is contained in:
185
unity/Assets/eco/EchoOutlineManager.cs
Normal file
185
unity/Assets/eco/EchoOutlineManager.cs
Normal file
@@ -0,0 +1,185 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace IndianOceanAssets.Engine2_5D
|
||||
{
|
||||
/// <summary>
|
||||
/// 描边管理器 —— 自动为场景中所有 SpriteRenderer(排除挂载 LightSource 的物体)
|
||||
/// 创建一个使用 SpriteEchoOutline shader 的子物体,用于回声描边效果。
|
||||
///
|
||||
/// 为什么需要子物体:
|
||||
/// - 精灵本体用原始材质(如 SpriteShader.shadergraph)渲染,在黑暗遮罩之下 → 黑暗中不可见
|
||||
/// - 描边用 SpriteEchoOutline 材质渲染,队列在黑暗遮罩之上(Transparent+200)→ 黑暗中可见
|
||||
/// - 同一个 SpriteRenderer 只能有一个材质,所以用子物体承载描边材质
|
||||
///
|
||||
/// 使用方式:
|
||||
/// 1. 场景中创建空物体,挂上此脚本
|
||||
/// 2. 把使用 SpriteEchoOutline shader 的材质拖到 outlineMaterial 字段
|
||||
/// 3. 运行后自动为每个非光源精灵生成描边子物体
|
||||
/// 4. 场景中新增精灵后可右键组件 > Refresh Outlines 重新扫描
|
||||
/// </summary>
|
||||
public class EchoOutlineManager : MonoBehaviour
|
||||
{
|
||||
[Header("描边材质(使用 SpriteEchoOutline shader)")]
|
||||
[SerializeField] private Material outlineMaterial;
|
||||
|
||||
[Header("过滤")]
|
||||
[Tooltip("只处理这些层的精灵(默认 Everything)")]
|
||||
[SerializeField] private LayerMask includeLayers = ~0;
|
||||
|
||||
[Tooltip("勾选后,挂载 LightSource 的物体(含父级)不生成描边")]
|
||||
[SerializeField] private bool skipLightSources = true;
|
||||
|
||||
private static readonly int EchoIntensityID = Shader.PropertyToID("_EchoOutlineIntensity");
|
||||
private static readonly int OutlineColorID = Shader.PropertyToID("_OutlineColor");
|
||||
private static readonly int OutlineWidthID = Shader.PropertyToID("_OutlineWidth");
|
||||
private static readonly int MainTexID = Shader.PropertyToID("_MainTex");
|
||||
|
||||
private readonly System.Collections.Generic.List<SpriteRenderer> _parentRenderers
|
||||
= new System.Collections.Generic.List<SpriteRenderer>();
|
||||
private readonly System.Collections.Generic.List<SpriteRenderer> _outlineRenderers
|
||||
= new System.Collections.Generic.List<SpriteRenderer>();
|
||||
|
||||
private bool _renderersEnabled = false;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
RefreshList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重新扫描场景,为符合条件的精灵创建描边子物体。
|
||||
/// 场景中新增精灵后可调用(也有右键菜单)。
|
||||
/// </summary>
|
||||
[ContextMenu("Refresh Outlines")]
|
||||
public void RefreshList()
|
||||
{
|
||||
ClearOutlines();
|
||||
|
||||
var all = FindObjectsByType<SpriteRenderer>(FindObjectsSortMode.None);
|
||||
foreach (var sr in all)
|
||||
{
|
||||
if (sr == null) continue;
|
||||
if ((includeLayers.value & (1 << sr.gameObject.layer)) == 0) continue;
|
||||
if (skipLightSources && sr.GetComponentInParent<LightSource>() != null) continue;
|
||||
|
||||
// 跳过已经是描边子物体的(避免重复)。用 sharedMaterial 避免实例化父级材质
|
||||
if (sr.sharedMaterial != null && sr.sharedMaterial.shader != null
|
||||
&& sr.sharedMaterial.shader.name == "IndianOcean/SpriteEchoOutline")
|
||||
continue;
|
||||
|
||||
// 读取自定义描边覆盖(颜色/宽度/禁用)
|
||||
var colorOverride = sr.GetComponentInParent<EchoOutlineColorOverride>();
|
||||
if (colorOverride != null && colorOverride.disableOutline) continue;
|
||||
|
||||
CreateOutlineChild(sr, colorOverride);
|
||||
}
|
||||
|
||||
_renderersEnabled = false;
|
||||
UpdateRendererEnabled();
|
||||
}
|
||||
|
||||
private void CreateOutlineChild(SpriteRenderer parent, EchoOutlineColorOverride colorOverride)
|
||||
{
|
||||
var child = new GameObject($"EchoOutline_{parent.gameObject.name}");
|
||||
child.transform.SetParent(parent.transform, false);
|
||||
child.transform.localPosition = Vector3.zero;
|
||||
child.transform.localRotation = Quaternion.identity;
|
||||
child.transform.localScale = Vector3.one;
|
||||
|
||||
var cr = child.AddComponent<SpriteRenderer>();
|
||||
cr.sprite = parent.sprite;
|
||||
cr.color = Color.white;
|
||||
cr.flipX = parent.flipX;
|
||||
cr.flipY = parent.flipY;
|
||||
// 有自定义颜色/宽度时创建材质实例;否则共享材质
|
||||
if (colorOverride != null)
|
||||
{
|
||||
// 用材质实例而非 MPB:MPB 会干扰 SpriteRenderer 的 _MainTex 自动绑定,
|
||||
// 导致 shader 采样到白色贴图 → alpha=1 → 描边公式算出 0 → 描边消失
|
||||
var mat = new Material(outlineMaterial);
|
||||
mat.SetColor(OutlineColorID, colorOverride.outlineColor);
|
||||
if (colorOverride.outlineWidth >= 0f)
|
||||
mat.SetFloat(OutlineWidthID, colorOverride.outlineWidth);
|
||||
if (parent.sprite != null)
|
||||
mat.SetTexture(MainTexID, parent.sprite.texture);
|
||||
cr.sharedMaterial = mat;
|
||||
}
|
||||
else
|
||||
{
|
||||
cr.sharedMaterial = outlineMaterial;
|
||||
}
|
||||
|
||||
cr.sortingLayerID = parent.sortingLayerID;
|
||||
cr.sortingOrder = parent.sortingOrder + 1;
|
||||
cr.enabled = false; // 默认关闭,回声激活时再开
|
||||
|
||||
_parentRenderers.Add(parent);
|
||||
_outlineRenderers.Add(cr);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// 回声不活跃时关闭所有描边渲染器(省 draw call)
|
||||
UpdateRendererEnabled();
|
||||
|
||||
// 同步精灵帧(动画)、翻转、排序
|
||||
if (!_renderersEnabled) return;
|
||||
|
||||
for (int i = 0; i < _outlineRenderers.Count; i++)
|
||||
{
|
||||
var cr = _outlineRenderers[i];
|
||||
var pr = _parentRenderers[i];
|
||||
if (pr == null || cr == null) continue;
|
||||
|
||||
if (cr.sprite != pr.sprite)
|
||||
{
|
||||
cr.sprite = pr.sprite;
|
||||
// 有自定义材质实例时同步纹理(共享材质由 SpriteRenderer 自动绑定)
|
||||
var mat = cr.sharedMaterial;
|
||||
if (mat != null && mat != outlineMaterial && pr.sprite != null)
|
||||
mat.SetTexture(MainTexID, pr.sprite.texture);
|
||||
}
|
||||
if (cr.flipX != pr.flipX) cr.flipX = pr.flipX;
|
||||
if (cr.flipY != pr.flipY) cr.flipY = pr.flipY;
|
||||
if (cr.sortingLayerID != pr.sortingLayerID) cr.sortingLayerID = pr.sortingLayerID;
|
||||
if (cr.sortingOrder != pr.sortingOrder + 1) cr.sortingOrder = pr.sortingOrder + 1;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateRendererEnabled()
|
||||
{
|
||||
float intensity = Shader.GetGlobalFloat(EchoIntensityID);
|
||||
bool shouldEnable = intensity > 0.01f;
|
||||
if (shouldEnable == _renderersEnabled) return;
|
||||
|
||||
_renderersEnabled = shouldEnable;
|
||||
for (int i = 0; i < _outlineRenderers.Count; i++)
|
||||
{
|
||||
if (_outlineRenderers[i] != null)
|
||||
_outlineRenderers[i].enabled = shouldEnable;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearOutlines()
|
||||
{
|
||||
for (int i = 0; i < _outlineRenderers.Count; i++)
|
||||
{
|
||||
if (_outlineRenderers[i] != null)
|
||||
{
|
||||
// 清理自定义材质实例(共享材质 outlineMaterial 不销毁)
|
||||
var mat = _outlineRenderers[i].sharedMaterial;
|
||||
if (mat != null && mat != outlineMaterial)
|
||||
Destroy(mat);
|
||||
Destroy(_outlineRenderers[i].gameObject);
|
||||
}
|
||||
}
|
||||
_parentRenderers.Clear();
|
||||
_outlineRenderers.Clear();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
ClearOutlines();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user