Files
aibis-dream/Assets/Scripts/MiniGame/HuoShan/Language/ConnectionRenderer.cs
T

399 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using UnityEngine;
using System.Collections.Generic;
using Shapes;
namespace AibisDream.MiniGame.Language
{
/// <summary>
/// 连接线渲染器:使用Shapes绘制粒子间的连接线
/// 注意:sortingOrder 设置为 0,确保低于文字(文字 sortingOrder = 10
/// </summary>
[ExecuteAlways]
public class ConnectionRenderer : ImmediateModeShapeDrawer
{
[Header("引用")]
public LanguageParticleManager manager;
[Header("连接线设置")]
public float connectionDistance = 2f;
private Color nonTargetConnectionColor = new Color(0.39f, 0.78f, 1f, 0.7f); // 非目标粒子连线颜色
private Color targetConnectionColor = new Color(1f, 0.39f, 0.39f, 1f); // 目标粒子连线颜色
private float nonTargetLineThickness = 0.02f;
private float targetLineThickness = 0.04f;
[Header("效果传播设置")]
private Color propagationColor = new Color(1f, 0.59f, 0.2f, 1f);
private float propagationLineThickness = 0.03f;
[Header("发光设置(Bloom")]
[SerializeField] private bool enableGlow = true;
[SerializeField, Range(1f, 5f)] private float targetGlowIntensity = 2f; // 目标线发光强度
[SerializeField, Range(1f, 3f)] private float nonTargetGlowIntensity = 1.2f; // 非目标线发光强度
[SerializeField, Range(1f, 5f)] private float propagationGlowIntensity = 3f; // 传播效果发光强度
private List<CandidateParticle> candidateParticles;
private List<EffectPropagation> effectPropagations;
private CompletionPhase completionPhase;
private float fadeOutAlpha;
private float nonTargetAlphaScale = 1f;
private float targetAlphaScale = 1f;
private Bounds screenBounds;
private bool hasScreenBounds;
private bool screenClippingEnabled;
private bool warnedMissingBounds;
public float NonTargetAlphaScale
{
get => nonTargetAlphaScale;
set => nonTargetAlphaScale = Mathf.Clamp01(value);
}
public float TargetAlphaScale
{
get => targetAlphaScale;
set => targetAlphaScale = Mathf.Clamp(value, 0f, 1.5f);
}
public void SetData(
List<CandidateParticle> candidates,
List<EffectPropagation> propagations,
CompletionPhase phase,
float fadeAlpha)
{
candidateParticles = candidates;
effectPropagations = propagations;
completionPhase = phase;
fadeOutAlpha = fadeAlpha;
}
public void SetScreenBounds(Bounds bounds, bool valid = true)
{
screenBounds = bounds;
hasScreenBounds = valid && bounds.size.x > 0.001f && bounds.size.y > 0.001f;
if (hasScreenBounds)
warnedMissingBounds = false;
}
public void SetScreenClippingEnabled(bool enabled)
{
screenClippingEnabled = enabled;
}
public override void DrawShapes(Camera cam)
{
if (candidateParticles == null || candidateParticles.Count == 0)
return;
if (screenClippingEnabled && !hasScreenBounds && !warnedMissingBounds)
{
warnedMissingBounds = true;
Debug.LogWarning("[ConnectionRenderer] 未提供有效的 ExpressionScreenMaskRect,连线将沿用未裁切行为。");
}
using (Draw.Command(cam))
{
Draw.BlendMode = ShapesBlendMode.Transparent;
Draw.ZTest = UnityEngine.Rendering.CompareFunction.Always;
// 注意:连接线默认在底层,文字的 sortingOrder = 10 会显示在上层
// 绘制蓝色粒子间的连线
if (completionPhase == CompletionPhase.None || completionPhase == CompletionPhase.Completed)
{
DrawBlueConnections();
}
// 绘制红色粒子间的连线
if (completionPhase == CompletionPhase.None ||
completionPhase == CompletionPhase.Completed ||
completionPhase == CompletionPhase.Focusing)
{
DrawRedConnections();
}
// 绘制效果传播线
if (effectPropagations != null && effectPropagations.Count > 0)
{
DrawPropagationEffects();
}
}
}
private void DrawBlueConnections()
{
Draw.LineGeometry = LineGeometry.Flat2D;
Draw.Thickness = nonTargetLineThickness;
for (int i = 0; i < candidateParticles.Count; i++)
{
for (int j = i + 1; j < candidateParticles.Count; j++)
{
var p1 = candidateParticles[i];
var p2 = candidateParticles[j];
// 跳过红色粒子
if (p1.isRed && p2.isRed) continue;
float distance = Vector3.Distance(p1.transform.position, p2.transform.position);
if (distance < connectionDistance)
{
float alpha = Mathf.Lerp(0.7f, 0f, distance / connectionDistance) * fadeOutAlpha;
Color lineColor = ApplyGlow(nonTargetConnectionColor, nonTargetGlowIntensity);
lineColor.a = alpha * nonTargetAlphaScale;
DrawClippedLine(p1.transform.position, p2.transform.position, lineColor);
}
}
}
}
private void DrawRedConnections()
{
Draw.LineGeometry = LineGeometry.Flat2D;
Draw.Thickness = targetLineThickness;
var redParticles = new List<CandidateParticle>();
foreach (var p in candidateParticles)
{
if (p.isRed)
redParticles.Add(p);
}
for (int i = 0; i < redParticles.Count; i++)
{
for (int j = i + 1; j < redParticles.Count; j++)
{
float distance = Vector3.Distance(
redParticles[i].transform.position,
redParticles[j].transform.position
);
if (distance < connectionDistance)
{
float alpha = Mathf.Lerp(1f, 0.4f, distance / connectionDistance);
Color lineColor = ApplyGlow(targetConnectionColor, targetGlowIntensity);
lineColor.a = alpha * targetAlphaScale;
DrawClippedLine(redParticles[i].transform.position, redParticles[j].transform.position, lineColor);
}
}
}
}
private void DrawPropagationEffects()
{
Draw.LineGeometry = LineGeometry.Flat2D;
Draw.Thickness = propagationLineThickness;
foreach (var propagation in effectPropagations)
{
if (propagation.PropagatedParticles == null) continue;
foreach (var info in propagation.PropagatedParticles)
{
if (info.source == null || info.particle == null) continue;
// 绘制连接线(应用发光)
Color lineColor = ApplyGlow(propagationColor, propagationGlowIntensity);
lineColor.a = 0.3f;
DrawClippedLine(info.source.transform.position, info.particle.transform.position, lineColor);
// 绘制脉冲光点
if (info.pulseProgress < 1f)
{
Vector3 pulsePos = Vector3.Lerp(
info.source.transform.position,
info.particle.transform.position,
info.pulseProgress
);
float pulseSize = Mathf.Lerp(0.03f, 0.08f, info.pulseProgress);
float pulseAlpha = Mathf.Lerp(1f, 0f, info.pulseProgress);
// 脉冲光点使用更强的 HDR 发光
Color pulseColor = ApplyGlow(new Color(1f, 0.78f, 0.39f, pulseAlpha), propagationGlowIntensity * 1.5f);
float visualRadius = pulseSize * 1.5f;
if (TryConstrainPoint(pulsePos, visualRadius, out Vector3 containedPulse))
{
Draw.Ring(containedPulse, pulseSize, pulseSize * 0.5f, pulseColor);
// 光晕
pulseColor.a = pulseAlpha * 0.5f;
Draw.Disc(containedPulse, visualRadius, pulseColor);
}
}
// 绘制箭头
if (info.pulseProgress > 0.5f && info.pulseProgress < 1f)
{
Vector3 arrowPos = Vector3.Lerp(
info.source.transform.position,
info.particle.transform.position,
0.5f
);
Vector3 direction = (info.particle.transform.position - info.source.transform.position).normalized;
float angle = Mathf.Atan2(direction.y, direction.x);
Color arrowColor = ApplyGlow(new Color(1f, 0.78f, 0.39f, 0.8f), propagationGlowIntensity);
const float arrowSize = 0.1f;
if (TryConstrainPoint(arrowPos, arrowSize, out Vector3 containedArrow))
DrawArrow(containedArrow, angle, arrowSize, arrowColor);
}
}
}
}
private void DrawArrow(Vector3 position, float angle, float size, Color color)
{
Vector3 tip = position + new Vector3(Mathf.Cos(angle), Mathf.Sin(angle), 0) * size;
Vector3 left = position + new Vector3(
Mathf.Cos(angle + 2.5f),
Mathf.Sin(angle + 2.5f),
0
) * size * 0.5f;
Vector3 right = position + new Vector3(
Mathf.Cos(angle - 2.5f),
Mathf.Sin(angle - 2.5f),
0
) * size * 0.5f;
Draw.Triangle(tip, left, right, color);
}
private void DrawClippedLine(Vector3 from, Vector3 to, Color color)
{
if (screenClippingEnabled && hasScreenBounds)
{
if (!TryClipLineToBounds(from, to, screenBounds, out from, out to))
return;
}
Draw.Line(from, to, color);
}
private bool TryConstrainPoint(Vector3 point, float radius, out Vector3 constrained)
{
if (!screenClippingEnabled || !hasScreenBounds)
{
constrained = point;
return true;
}
float minX = screenBounds.min.x + radius;
float maxX = screenBounds.max.x - radius;
float minY = screenBounds.min.y + radius;
float maxY = screenBounds.max.y - radius;
if (minX > maxX || minY > maxY)
{
constrained = point;
return false;
}
constrained = new Vector3(
Mathf.Clamp(point.x, minX, maxX),
Mathf.Clamp(point.y, minY, maxY),
point.z);
return true;
}
/// <summary>
/// LiangBarsky line clipping against a world-space axis-aligned rectangle.
/// Kept public for component tests and editor diagnostics.
/// </summary>
public static bool TryClipLineToBounds(
Vector3 from,
Vector3 to,
Bounds bounds,
out Vector3 clippedFrom,
out Vector3 clippedTo)
{
clippedFrom = from;
clippedTo = to;
float dx = to.x - from.x;
float dy = to.y - from.y;
float t0 = 0f;
float t1 = 1f;
if (!ClipTest(-dx, from.x - bounds.min.x, ref t0, ref t1) ||
!ClipTest(dx, bounds.max.x - from.x, ref t0, ref t1) ||
!ClipTest(-dy, from.y - bounds.min.y, ref t0, ref t1) ||
!ClipTest(dy, bounds.max.y - from.y, ref t0, ref t1))
{
return false;
}
clippedFrom = Vector3.LerpUnclamped(from, to, t0);
clippedTo = Vector3.LerpUnclamped(from, to, t1);
return true;
}
private static bool ClipTest(float p, float q, ref float t0, ref float t1)
{
if (Mathf.Approximately(p, 0f))
return q >= 0f;
float r = q / p;
if (p < 0f)
{
if (r > t1) return false;
if (r > t0) t0 = r;
}
else
{
if (r < t0) return false;
if (r < t1) t1 = r;
}
return true;
}
/// <summary>
/// 设置连接线样式
/// </summary>
public void SetConnectionStyles(
Color targetColor,
Color nonTargetColor,
Color propColor,
float targetThickness,
float nonTargetThickness,
float propThickness)
{
targetConnectionColor = targetColor;
nonTargetConnectionColor = nonTargetColor;
propagationColor = propColor;
targetLineThickness = targetThickness;
nonTargetLineThickness = nonTargetThickness;
propagationLineThickness = propThickness;
}
/// <summary>
/// 设置发光参数
/// </summary>
public void SetGlowSettings(bool enabled, float targetIntensity, float nonTargetIntensity, float propagationIntensity)
{
enableGlow = enabled;
targetGlowIntensity = Mathf.Clamp(targetIntensity, 1f, 5f);
nonTargetGlowIntensity = Mathf.Clamp(nonTargetIntensity, 1f, 3f);
propagationGlowIntensity = Mathf.Clamp(propagationIntensity, 1f, 5f);
}
/// <summary>
/// 将颜色转换为 HDR 颜色以支持 Bloom 发光
/// </summary>
private Color ApplyGlow(Color baseColor, float intensity)
{
if (!enableGlow || intensity <= 1f)
return baseColor;
// HDR 颜色:将 RGB 值乘以强度系数,保留 Alpha
return new Color(
baseColor.r * intensity,
baseColor.g * intensity,
baseColor.b * intensity,
baseColor.a
);
}
}
}