diff --git a/unity/Assets/2.5D Engine/Player.prefab b/unity/Assets/2.5D Engine/Player.prefab index 4525b96..c3603a6 100644 --- a/unity/Assets/2.5D Engine/Player.prefab +++ b/unity/Assets/2.5D Engine/Player.prefab @@ -360,6 +360,7 @@ MonoBehaviour: isPlayer: 1 dissolveMaterial: {fileID: 0} fadeDuration: 2 + damageInvincibleDuration: 2 --- !u!114 &4071397948156435190 MonoBehaviour: m_ObjectHideFlags: 0 @@ -436,6 +437,10 @@ MonoBehaviour: sustainTime: 4 fadeTime: 1.5 outlineColor: {r: 1, g: 1, b: 1, a: 1} + ringColor: {r: 1, g: 0.15, b: 0.1, a: 1} + ringVisualWidth: 2.5 + ringYOffset: 0.1 + ringFadeTime: 3 cooldown: 3 --- !u!1001 &1156712605891213377 PrefabInstance: diff --git a/unity/Assets/Light/shaders/EchoRing.shader b/unity/Assets/Light/shaders/EchoRing.shader new file mode 100644 index 0000000..e05696a --- /dev/null +++ b/unity/Assets/Light/shaders/EchoRing.shader @@ -0,0 +1,92 @@ +Shader "IndianOcean/EchoRing" +{ + // 回声红圈 Shader —— 配合 EchoSystem 使用 + // 在地面(XZ平面)上绘制一个红色渐变圆环,圆环位置跟随全局 _EchoRadius + // 边缘使用 smoothstep 渐变,从圆环中心满透明度向两侧柔和消散 + Properties + { + _RingColor ("Ring Color", Color) = (1, 0.15, 0.1, 1) + _RingWidth ("Ring Width (world units)", Float) = 2.5 + } + SubShader + { + Tags + { + "RenderType" = "Transparent" + "Queue" = "Transparent+250" + "RenderPipeline" = "UniversalPipeline" + } + Blend SrcAlpha OneMinusSrcAlpha + ZWrite Off + ZTest Always + Cull Off + + Pass + { + Name "EchoRing" + HLSLPROGRAM + #pragma vertex vert + #pragma fragment frag + #pragma multi_compile_instancing + + #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" + + struct Attributes + { + float4 positionOS : POSITION; + UNITY_VERTEX_INPUT_INSTANCE_ID + }; + + struct Varyings + { + float4 positionCS : SV_POSITION; + float3 worldPos : TEXCOORD0; + UNITY_VERTEX_INPUT_INSTANCE_ID + }; + + CBUFFER_START(UnityPerMaterial) + float4 _RingColor; + float _RingWidth; + CBUFFER_END + + // 全局回声参数(由 EchoSystem 通过 Shader.SetGlobalX 设置) + float4 _EchoCenter; // xy = 回声中心世界坐标 XZ + float _EchoRingRadius; // 红圈独立扩散半径 + float _EchoRingAlpha; // 红圈独立透明度(独立于描边强度) + + Varyings vert(Attributes input) + { + Varyings output; + UNITY_SETUP_INSTANCE_ID(input); + UNITY_TRANSFER_INSTANCE_ID(input, output); + + output.positionCS = TransformObjectToHClip(input.positionOS.xyz); + output.worldPos = TransformObjectToWorld(input.positionOS.xyz); + return output; + } + + half4 frag(Varyings input) : SV_Target + { + // 世界空间 XZ 平面上的距离 + float2 wp = input.worldPos.xz; + float dist = distance(wp, _EchoCenter.xy); + + // 距离圆环中心的偏差 + float distFromRing = abs(dist - _EchoRingRadius); + + // 渐变:圆环中心满强度,两侧柔和消散 + float halfW = max(0.001, _RingWidth * 0.5); + float alpha = 1.0 - smoothstep(0.0, halfW, distFromRing); + + // 乘以红圈独立透明度(独立于描边消散时序) + alpha *= _EchoRingAlpha; + + if (alpha < 0.001) discard; + + return half4(_RingColor.rgb, alpha * _RingColor.a); + } + ENDHLSL + } + } + FallBack Off +} diff --git a/unity/Assets/Light/shaders/EchoRing.shader.meta b/unity/Assets/Light/shaders/EchoRing.shader.meta new file mode 100644 index 0000000..29c0ad6 --- /dev/null +++ b/unity/Assets/Light/shaders/EchoRing.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: CX1JsiL7AnkPMkp0FGGR15PogOhTjb8ArRIm08D4oVWvMTimFo71lmA= +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/Assets/Scenes/res/player/Player_new.prefab b/unity/Assets/Scenes/res/player/Player_new.prefab index ae6b0fc..82ec072 100644 --- a/unity/Assets/Scenes/res/player/Player_new.prefab +++ b/unity/Assets/Scenes/res/player/Player_new.prefab @@ -273,6 +273,7 @@ MonoBehaviour: isPlayer: 1 dissolveMaterial: {fileID: 0} fadeDuration: 2 + damageInvincibleDuration: 2 --- !u!114 &4737232457870655947 MonoBehaviour: m_ObjectHideFlags: 0 @@ -346,10 +347,15 @@ MonoBehaviour: expandSpeed: 15 maxRadius: 30 ringWidth: 2.5 - sustainTime: 4 + sustainTime: 2.5 fadeTime: 1.5 outlineColor: {r: 1, g: 1, b: 1, a: 1} - cooldown: 3 + ringColor: {r: 0.9245283, g: 0.2703808, b: 0.43898734, a: 0.654902} + ringVisualWidth: 0.8 + ringYOffset: 0.1 + ringFadeTime: 2 + ringExpandSpeed: 18 + cooldown: 10 --- !u!114 &4208162987965796937 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/unity/Assets/eco/EchoSystem.cs b/unity/Assets/eco/EchoSystem.cs index fd1917a..4ac50bf 100644 --- a/unity/Assets/eco/EchoSystem.cs +++ b/unity/Assets/eco/EchoSystem.cs @@ -50,16 +50,38 @@ namespace IndianOceanAssets.Engine2_5D [Tooltip("描边颜色(默认白色,可自定义)")] [SerializeField] private Color outlineColor = Color.white; + [Header("红圈视觉")] + [Tooltip("红圈颜色")] + [SerializeField] private Color ringColor = new Color(1f, 0.15f, 0.1f, 1f); + + [Tooltip("红圈宽度(世界单位)")] + [SerializeField] private float ringVisualWidth = 2.5f; + + [Tooltip("红圈离地高度(避免Z-fighting)")] + [SerializeField] private float ringYOffset = 0.1f; + + [Tooltip("红圈渐隐总时间(秒)。从释放瞬间开始线性渐隐,到 0 后隐藏。默认 4 秒")] + [SerializeField] private float ringFadeTime = 4f; + + [Tooltip("红圈扩散速度(世界单位/秒),独立于波纹扩散速度")] + [SerializeField] private float ringExpandSpeed = 15f; + [Header("冷却")] [Tooltip("回声冷却时间(秒)")] [SerializeField] private float cooldown = 3f; + // 红圈视觉 + private GameObject _ringVisual; + private Material _ringMaterial; + // 全局 shader 属性 ID(通过 Shader.SetGlobalX 设置,所有描边精灵共享) private static readonly int EchoCenterID = Shader.PropertyToID("_EchoCenter"); private static readonly int EchoRadiusID = Shader.PropertyToID("_EchoRadius"); private static readonly int EchoWidthID = Shader.PropertyToID("_EchoWidth"); private static readonly int EchoOutlineIntensityID = Shader.PropertyToID("_EchoOutlineIntensity"); private static readonly int EchoOutlineColorID = Shader.PropertyToID("_EchoOutlineColor"); + private static readonly int EchoRingAlphaID = Shader.PropertyToID("_EchoRingAlpha"); + private static readonly int EchoRingRadiusID = Shader.PropertyToID("_EchoRingRadius"); private enum State { Idle, Expanding, Sustaining, Fading } @@ -69,6 +91,8 @@ namespace IndianOceanAssets.Engine2_5D private float _stateTimer; private Vector2 _center; private float _lastEchoTime = -999f; + private float _ringAlpha = 0f; + private float _ringRadius = 0f; private void Start() { @@ -76,12 +100,58 @@ namespace IndianOceanAssets.Engine2_5D Shader.SetGlobalFloat(EchoOutlineIntensityID, 0f); Shader.SetGlobalColor(EchoOutlineColorID, outlineColor); Shader.SetGlobalFloat(EchoWidthID, ringWidth); + Shader.SetGlobalFloat(EchoRingAlphaID, 0f); + + CreateRingVisual(); + } + + /// + /// 创建红圈视觉:一个平铺在地面上的 Quad,使用 EchoRing shader。 + /// shader 通过全局 _EchoCenter / _EchoRadius 自动计算圆环位置, + /// 无需每帧更新 Mesh —— 只需在释放时移动 Quad 到回声中心即可。 + /// + private void CreateRingVisual() + { + _ringVisual = GameObject.CreatePrimitive(PrimitiveType.Quad); + _ringVisual.name = "EchoRing_Visual"; + + // 移除不需要的碰撞体 + var col = _ringVisual.GetComponent(); + if (col != null) Destroy(col); + + // 平铺在地面:绕 X 轴旋转 90° + _ringVisual.transform.rotation = Quaternion.Euler(90f, 0f, 0f); + + // 缩放:覆盖 maxRadius * 2 的范围 + float size = maxRadius * 2f; + _ringVisual.transform.localScale = new Vector3(size, size, 1f); + // 不挂载到玩家下——回声中心固定,不跟随玩家移动 + + // 创建材质(运行时) + var shader = Shader.Find("IndianOcean/EchoRing"); + if (shader != null) + { + _ringMaterial = new Material(shader); + _ringMaterial.SetColor("_RingColor", ringColor); + _ringMaterial.SetFloat("_RingWidth", ringVisualWidth); + // 确保渲染在黑暗遮罩之上(黑暗遮罩 sortingOrder=9999, renderQueue=3100) + _ringMaterial.renderQueue = 3250; + var renderer = _ringVisual.GetComponent(); + renderer.sortingOrder = 10001; + renderer.material = _ringMaterial; + } + else + { + Debug.LogError("[EchoSystem] 找不到 IndianOcean/EchoRing shader!"); + } + + _ringVisual.SetActive(false); } private void Update() { - // 触发 - if (Input.GetKeyDown(echoKey) && _state == State.Idle && Time.time > _lastEchoTime + cooldown) + // 触发:CD 好即可释放,无需等状态机结束(StartEcho 会重置所有状态) + if (Input.GetKeyDown(echoKey) && Time.time > _lastEchoTime + cooldown) { StartEcho(); } @@ -100,12 +170,19 @@ namespace IndianOceanAssets.Engine2_5D Shader.SetGlobalFloat(EchoWidthID, ringWidth); Shader.SetGlobalFloat(EchoOutlineIntensityID, _intensity); Shader.SetGlobalColor(EchoOutlineColorID, outlineColor); + + // 红圈独立渐隐与扩散 + UpdateRingAlpha(); + UpdateRingRadius(); + Shader.SetGlobalFloat(EchoRingAlphaID, _ringAlpha); + Shader.SetGlobalFloat(EchoRingRadiusID, _ringRadius); } private void StartEcho() { _state = State.Expanding; _radius = 0f; + _ringRadius = 0f; _intensity = 1f; _stateTimer = 0f; _lastEchoTime = Time.time; @@ -118,6 +195,13 @@ namespace IndianOceanAssets.Engine2_5D Vector3 p = transform.position; _center = new Vector2(p.x, p.z); + // 显示红圈:移动到回声中心位置 + if (_ringVisual != null) + { + _ringVisual.transform.position = new Vector3(p.x, ringYOffset, p.z); + _ringVisual.SetActive(true); + } + // 通知订阅者(敌人聆听等) OnEchoReleased?.Invoke(p); } @@ -156,6 +240,42 @@ namespace IndianOceanAssets.Engine2_5D } } + /// + /// 红圈独立渐隐逻辑: + /// 从释放瞬间开始线性渐隐(1 → 0),ringFadeTime 秒后完全消失。 + /// + private void UpdateRingAlpha() + { + if (_state == State.Idle) + { + _ringAlpha = 0f; + return; + } + + float elapsed = Time.time - _lastEchoTime; + _ringAlpha = Mathf.Clamp01(1f - elapsed / Mathf.Max(0.001f, ringFadeTime)); + + // 渐隐完成后隐藏红圈物体(节省渲染开销) + if (_ringAlpha <= 0f && _ringVisual != null) + _ringVisual.SetActive(false); + } + + /// + /// 红圈独立扩散:按 ringExpandSpeed 扩散,封顶 maxRadius。 + /// + private void UpdateRingRadius() + { + if (_state == State.Idle) + { + _ringRadius = 0f; + return; + } + + _ringRadius += ringExpandSpeed * Time.deltaTime; + if (_ringRadius > maxRadius) + _ringRadius = maxRadius; + } + /// /// 外部调用:强制停止回声 /// @@ -163,7 +283,12 @@ namespace IndianOceanAssets.Engine2_5D { _state = State.Idle; _intensity = 0f; + _ringAlpha = 0f; + _ringRadius = 0f; Shader.SetGlobalFloat(EchoOutlineIntensityID, 0f); + Shader.SetGlobalFloat(EchoRingAlphaID, 0f); + if (_ringVisual != null) + _ringVisual.SetActive(false); } public bool IsActive => _state != State.Idle;