From 9245409fc9dadbf1c09e1fe2c85558aa5fe0aa0e Mon Sep 17 00:00:00 2001 From: Valentina Date: Wed, 21 Oct 2026 13:53:17 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BA=D1=80=D1=83=D1=82=D0=BE=D0=B9=20=D1=88?= =?UTF-8?q?=D0=B5=D0=B9=D0=B4=D0=B5=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/EchoCloudMat.mat | 25 +- Assets/EchoCloudShader.shader | 255 ++++++++++++++++++ Assets/EchoCloudShader.shader.meta | 9 + Assets/EchoMat.mat | 26 +- Assets/Prefabs/Mutant.prefab | 22 +- Assets/Scenes/SampleScene.unity | Bin 660188 -> 660972 bytes Assets/Scripts/BlindMutantAI.cs | 130 +++++++-- Assets/Scripts/EchoTrigger.cs | 35 ++- .../Editor/LaboratoryLevelGenerator.cs | 3 + 9 files changed, 446 insertions(+), 59 deletions(-) create mode 100644 Assets/EchoCloudShader.shader create mode 100644 Assets/EchoCloudShader.shader.meta diff --git a/Assets/EchoCloudMat.mat b/Assets/EchoCloudMat.mat index 0448a48..40d38df 100644 --- a/Assets/EchoCloudMat.mat +++ b/Assets/EchoCloudMat.mat @@ -21,7 +21,7 @@ Material: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_Name: EchoCloudMat - m_Shader: {fileID: -6465566751694194690, guid: d55932c1661b84243acfb93f7ba5a4b2, type: 3} + m_Shader: {fileID: 4800000, guid: cc58e5b86cd79a04789774a9a4b0af75, type: 3} m_Parent: {fileID: 0} m_ModifiedSerializedProperties: 0 m_ValidKeywords: [] @@ -37,6 +37,10 @@ Material: m_SavedProperties: serializedVersion: 3 m_TexEnvs: + - _BaseMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} - unity_Lightmaps: m_Texture: {fileID: 0} m_Scale: {x: 1, y: 1} @@ -51,23 +55,34 @@ Material: m_Offset: {x: 0, y: 0} m_Ints: [] m_Floats: + - _CloudBandWidth: 3.2 - _EchoOpacity1: 0 - _EchoOpacity2: 0 - _EchoOpacity3: 0 - _EchoRadius1: 0 - _EchoRadius2: 0 - _EchoRadius3: 0 + - _FresnelPower: 2.5 + - _FresnelStrength: 0.4 + - _LeadingEdgeSoftness: 1.2 - _LineDensity: 3 - _LineThickness: 0.05 + - _NoiseDistortion: 1.3 + - _NoiseScale: 0.35 + - _NoiseSpeed: 0.2 - _QueueControl: 0 - _QueueOffset: 0 - _RingWidth: 0.08 + - _RippleFrequency: 1.5 + - _RippleStrength: 0.25 - _WaveRadius: 0 m_Colors: - - _EchoColor: {r: 4, g: 3.0263731, b: 1.0490564, a: 1} - - _EchoPosition1: {r: -24.5, g: -0.027898155, b: -4, a: 0} - - _EchoPosition2: {r: -19.5, g: -0.027898155, b: -8, a: 0} - - _EchoPosition3: {r: 21.5, g: -0.027898155, b: 22, a: 0} + - _BaseColor: {r: 0.015, g: 0.015, b: 0.02, a: 1} + - _EchoColor: {r: 4, g: 3.026, b: 1.049, a: 1} + - _EchoCoreColor: {r: 7.5, g: 5.5, b: 2.2, a: 1} + - _EchoPosition1: {r: 1.5618706, g: 1.0799999, b: -34.510273, a: 0} + - _EchoPosition2: {r: -1.1804235, g: 1.0799999, b: -35.782673, a: 0} + - _EchoPosition3: {r: -0.93470556, g: 1.0799999, b: -34.72166, a: 0} - _WaveCenter: {r: 0, g: 0, b: 0, a: 0} m_BuildTextureStacks: [] m_AllowLocking: 1 diff --git a/Assets/EchoCloudShader.shader b/Assets/EchoCloudShader.shader new file mode 100644 index 0000000..0497776 --- /dev/null +++ b/Assets/EchoCloudShader.shader @@ -0,0 +1,255 @@ +Shader "Custom/EchoCloudShader" +{ + Properties + { + [Header(Base Appearance)] + _BaseColor("Base Ambient Color", Color) = (0.015, 0.015, 0.02, 1.0) + _BaseMap("Base Map (Optional)", 2D) = "white" {} + + [Header(Echo Cloud Visuals)] + [HDR] _EchoColor("Echo Mist Color", Color) = (4.0, 3.026, 1.049, 1.0) + [HDR] _EchoCoreColor("Echo Core Bloom", Color) = (7.5, 5.5, 2.2, 1.0) + _CloudBandWidth("Cloud Thickness / Tail (m)", Range(0.5, 8.0)) = 3.2 + _LeadingEdgeSoftness("Leading Edge Softness", Range(0.1, 3.0)) = 1.2 + + [Header(Cloud Noise and Billows)] + _NoiseScale("Cloud Noise Scale", Range(0.05, 1.5)) = 0.35 + _NoiseDistortion("Cloud Billow Distortion", Range(0.0, 3.0)) = 1.3 + _NoiseSpeed("Cloud Drift Speed", Range(0.0, 2.0)) = 0.2 + + [Header(Acoustic Ripples and Edge Glow)] + _RippleFrequency("Acoustic Ripple Frequency", Range(0.1, 5.0)) = 1.5 + _RippleStrength("Acoustic Ripple Strength", Range(0.0, 1.0)) = 0.25 + _FresnelPower("Edge Rim Power", Range(0.5, 5.0)) = 2.5 + _FresnelStrength("Edge Rim Strength", Range(0.0, 2.0)) = 0.4 + + [Header(Wave Inputs from Script)] + _EchoPosition1("Echo Position 1", Vector) = (0, 0, 0, 0) + _EchoPosition2("Echo Position 2", Vector) = (0, 0, 0, 0) + _EchoPosition3("Echo Position 3", Vector) = (0, 0, 0, 0) + _EchoRadius1("Echo Radius 1", Float) = 0 + _EchoRadius2("Echo Radius 2", Float) = 0 + _EchoRadius3("Echo Radius 3", Float) = 0 + _EchoOpacity1("Echo Opacity 1", Float) = 0 + _EchoOpacity2("Echo Opacity 2", Float) = 0 + _EchoOpacity3("Echo Opacity 3", Float) = 0 + + // Legacy compatibility properties + [HideInInspector] _RingWidth("Ring Width", Float) = 0.08 + [HideInInspector] _LineDensity("Line Density", Float) = 3.0 + [HideInInspector] _LineThickness("Line Thickness", Float) = 0.05 + [HideInInspector] _WaveCenter("Wave Center", Vector) = (0, 0, 0, 0) + [HideInInspector] _WaveRadius("Wave Radius", Float) = 0 + [HideInInspector] _QueueOffset("Queue Offset", Float) = 0 + [HideInInspector] _QueueControl("Queue Control", Float) = 0 + } + + SubShader + { + Tags + { + "RenderType" = "Opaque" + "RenderPipeline" = "UniversalPipeline" + "Queue" = "Geometry" + } + LOD 200 + + Pass + { + Name "ForwardLit" + Tags { "LightMode" = "UniversalForward" } + + ZWrite On + ZTest LEqual + Cull Back + + HLSLPROGRAM + #pragma vertex vert + #pragma fragment frag + #pragma multi_compile_fog + + #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" + #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" + + struct Attributes + { + float4 positionOS : POSITION; + float3 normalOS : NORMAL; + float2 uv : TEXCOORD0; + }; + + struct Varyings + { + float4 positionCS : SV_POSITION; + float3 positionWS : TEXCOORD0; + float3 normalWS : TEXCOORD1; + float2 uv : TEXCOORD2; + float fogFactor : TEXCOORD3; + }; + + TEXTURE2D(_BaseMap); + SAMPLER(sampler_BaseMap); + + CBUFFER_START(UnityPerMaterial) + half4 _BaseColor; + half4 _EchoColor; + half4 _EchoCoreColor; + float _CloudBandWidth; + float _LeadingEdgeSoftness; + float _NoiseScale; + float _NoiseDistortion; + float _NoiseSpeed; + float _RippleFrequency; + float _RippleStrength; + float _FresnelPower; + float _FresnelStrength; + + float4 _EchoPosition1; + float4 _EchoPosition2; + float4 _EchoPosition3; + float _EchoRadius1; + float _EchoRadius2; + float _EchoRadius3; + float _EchoOpacity1; + float _EchoOpacity2; + float _EchoOpacity3; + + float4 _BaseMap_ST; + float _RingWidth; + float _LineDensity; + float _LineThickness; + float4 _WaveCenter; + float _WaveRadius; + float _QueueOffset; + float _QueueControl; + CBUFFER_END + + // 3D Hash and Value Noise + float3 Hash33(float3 p) + { + p = float3(dot(p, float3(127.1, 311.7, 74.7)), + dot(p, float3(269.5, 183.3, 246.1)), + dot(p, float3(113.5, 271.9, 124.6))); + return frac(sin(p) * 43758.5453123); + } + + float ValueNoise3D(float3 p) + { + float3 i = floor(p); + float3 f = frac(p); + float3 u = f * f * (3.0 - 2.0 * f); + + float n000 = dot(Hash33(i + float3(0,0,0)) - 0.5, f - float3(0,0,0)); + float n100 = dot(Hash33(i + float3(1,0,0)) - 0.5, f - float3(1,0,0)); + float n010 = dot(Hash33(i + float3(0,1,0)) - 0.5, f - float3(0,1,0)); + float n110 = dot(Hash33(i + float3(1,1,0)) - 0.5, f - float3(1,1,0)); + float n001 = dot(Hash33(i + float3(0,0,1)) - 0.5, f - float3(0,0,1)); + float n101 = dot(Hash33(i + float3(1,0,1)) - 0.5, f - float3(1,0,1)); + float n011 = dot(Hash33(i + float3(0,1,1)) - 0.5, f - float3(0,1,1)); + float n111 = dot(Hash33(i + float3(1,1,1)) - 0.5, f - float3(1,1,1)); + + float nx00 = lerp(n000, n100, u.x); + float nx10 = lerp(n010, n110, u.x); + float nx01 = lerp(n001, n101, u.x); + float nx11 = lerp(n011, n111, u.x); + + float nxy0 = lerp(nx00, nx10, u.y); + float nxy1 = lerp(nx01, nx11, u.y); + + return lerp(nxy0, nxy1, u.z) + 0.5; + } + + float GetCloudNoise(float3 worldPos) + { + float3 p1 = worldPos * _NoiseScale + float3(0, _Time.y * _NoiseSpeed, 0); + float n1 = ValueNoise3D(p1); + float3 p2 = worldPos * (_NoiseScale * 2.2) - float3(0, _Time.y * (_NoiseSpeed * 1.4), 0); + float n2 = ValueNoise3D(p2); + return n1 * 0.65 + n2 * 0.35; + } + + float CalculateSingleWave(float3 worldPos, float3 normalWS, float3 viewDirWS, float3 waveCenter, float waveRadius, float waveOpacity, float noiseVal) + { + if (waveOpacity <= 0.001 || waveRadius <= 0.01) + return 0.0; + + float rawDist = distance(worldPos, waveCenter); + + // Organic cloud boundary distortion via 3D noise + float distortedDist = rawDist + (noiseVal - 0.5) * _NoiseDistortion; + + // Soft leading edge (smoothly fades into darkness ahead) + float leadingEdge = smoothstep(waveRadius + 0.2, waveRadius - _LeadingEdgeSoftness, distortedDist); + + // Soft trailing cloud body (billowy tail behind the wavefront) + float trailingEdge = smoothstep(waveRadius - _CloudBandWidth, waveRadius - 0.2, distortedDist); + + // Combined cloud puff shape + float cloudDensity = leadingEdge * trailingEdge; + + // Subtle acoustic harmonic ripples inside the cloud + float ripple = sin((rawDist - waveRadius) * _RippleFrequency) * 0.5 + 0.5; + cloudDensity *= lerp(1.0, ripple, _RippleStrength); + + // Subtle edge rim glow on surfaces facing away (gives 3D depth to rooms) + float NdotV = saturate(dot(normalWS, viewDirWS)); + float rim = pow(1.0 - NdotV, _FresnelPower) * _FresnelStrength; + cloudDensity += cloudDensity * rim; + + return saturate(cloudDensity) * waveOpacity; + } + + Varyings vert(Attributes input) + { + Varyings output = (Varyings)0; + VertexPositionInputs vertexInputs = GetVertexPositionInputs(input.positionOS.xyz); + VertexNormalInputs normalInputs = GetVertexNormalInputs(input.normalOS); + + output.positionCS = vertexInputs.positionCS; + output.positionWS = vertexInputs.positionWS; + output.normalWS = normalInputs.normalWS; + output.uv = TRANSFORM_TEX(input.uv, _BaseMap); + output.fogFactor = ComputeFogFactor(vertexInputs.positionCS.z); + + return output; + } + + half4 frag(Varyings input) : SV_Target + { + float3 worldPos = input.positionWS; + float3 normalWS = normalize(input.normalWS); + float3 viewDirWS = normalize(GetCameraPositionWS() - worldPos); + + // Base ambient appearance (deep dark atmospheric tone) + half4 texColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv); + half4 baseCol = texColor * _BaseColor; + + // 3D procedural noise + float noiseVal = GetCloudNoise(worldPos); + + // Evaluate all 3 simultaneous echo waves + float w1 = CalculateSingleWave(worldPos, normalWS, viewDirWS, _EchoPosition1.xyz, _EchoRadius1, _EchoOpacity1, noiseVal); + float w2 = CalculateSingleWave(worldPos, normalWS, viewDirWS, _EchoPosition2.xyz, _EchoRadius2, _EchoOpacity2, noiseVal); + float w3 = CalculateSingleWave(worldPos, normalWS, viewDirWS, _EchoPosition3.xyz, _EchoRadius3, _EchoOpacity3, noiseVal); + + float totalWave = saturate(w1 + w2 + w3); + + // Color gradient: soft outer mist (_EchoColor) to glowing core (_EchoCoreColor) + half4 mistColor = _EchoColor * totalWave; + half4 coreBloom = _EchoCoreColor * pow(totalWave, 2.2); + + half4 finalColor = baseCol + mistColor + coreBloom; + + // Apply scene fog + finalColor.rgb = MixFog(finalColor.rgb, input.fogFactor); + + return finalColor; + } + ENDHLSL + } + + UsePass "Universal Render Pipeline/Lit/ShadowCaster" + UsePass "Universal Render Pipeline/Lit/DepthOnly" + } + FallBack "Hidden/Universal Render Pipeline/FallbackError" +} diff --git a/Assets/EchoCloudShader.shader.meta b/Assets/EchoCloudShader.shader.meta new file mode 100644 index 0000000..f89b58f --- /dev/null +++ b/Assets/EchoCloudShader.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: cc58e5b86cd79a04789774a9a4b0af75 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/EchoMat.mat b/Assets/EchoMat.mat index 656596c..95b3722 100644 --- a/Assets/EchoMat.mat +++ b/Assets/EchoMat.mat @@ -8,7 +8,7 @@ Material: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_Name: EchoMat - m_Shader: {fileID: -6465566751694194690, guid: 504d814182b14b748a5afebbe6fcdb8c, type: 3} + m_Shader: {fileID: 4800000, guid: cc58e5b86cd79a04789774a9a4b0af75, type: 3} m_Parent: {fileID: 0} m_ModifiedSerializedProperties: 0 m_ValidKeywords: [] @@ -24,6 +24,10 @@ Material: m_SavedProperties: serializedVersion: 3 m_TexEnvs: + - _BaseMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} - unity_Lightmaps: m_Texture: {fileID: 0} m_Scale: {x: 1, y: 1} @@ -38,19 +42,37 @@ Material: m_Offset: {x: 0, y: 0} m_Ints: [] m_Floats: + - _CloudBandWidth: 3.2 + - _EchoOpacity1: 0 + - _EchoOpacity2: 0 + - _EchoOpacity3: 0 - _EchoRadius: 27.577322 - _EchoRadius1: 0 - _EchoRadius2: 0 - _EchoRadius3: 0 + - _FresnelPower: 2.5 + - _FresnelStrength: 0.4 + - _LeadingEdgeSoftness: 1.2 + - _LineDensity: 3 + - _LineThickness: 0.05 + - _NoiseDistortion: 1.3 + - _NoiseScale: 0.35 + - _NoiseSpeed: 0.2 - _QueueControl: 0 - _QueueOffset: 0 - _RingWidth: 0.08 + - _RippleFrequency: 1.5 + - _RippleStrength: 0.25 + - _WaveRadius: 0 m_Colors: - - _EchoColor: {r: 19.265432, g: 10.948601, b: 3.0170376, a: 1} + - _BaseColor: {r: 0.015, g: 0.015, b: 0.02, a: 1} + - _EchoColor: {r: 4, g: 3.026, b: 1.049, a: 1} + - _EchoCoreColor: {r: 7.5, g: 5.5, b: 2.2, a: 1} - _EchoPosition: {r: -4.3227067, g: 1.08, b: -10.807251, a: 0} - _EchoPosition1: {r: 0.78633124, g: 1.08, b: -10.874559, a: 0} - _EchoPosition2: {r: 0.7576918, g: 1.08, b: -11.912178, a: 0} - _EchoPosition3: {r: 0.95730513, g: 1.08, b: -16.5998, a: 0} + - _WaveCenter: {r: 0, g: 0, b: 0, a: 0} m_BuildTextureStacks: [] m_AllowLocking: 1 --- !u!114 &3906604532312570773 diff --git a/Assets/Prefabs/Mutant.prefab b/Assets/Prefabs/Mutant.prefab index de027d1..91853ab 100644 --- a/Assets/Prefabs/Mutant.prefab +++ b/Assets/Prefabs/Mutant.prefab @@ -95,27 +95,7 @@ MonoBehaviour: m_EditorClassIdentifier: Assembly-CSharp::BlindMutantAI walkSpeed: 2 runSpeed: 5 - patrolPoints: - - {fileID: 0} - - {fileID: 0} - - {fileID: 0} - - {fileID: 0} - - {fileID: 0} - - {fileID: 0} - - {fileID: 0} - - {fileID: 0} - - {fileID: 0} - - {fileID: 0} - - {fileID: 0} - - {fileID: 0} - - {fileID: 0} - - {fileID: 0} - - {fileID: 0} - - {fileID: 0} - - {fileID: 0} - - {fileID: 0} - - {fileID: 0} - - {fileID: 0} + patrolPoints: [] detectionRadius: 3.5 losePlayerDistance: 14 attackRange: 1.8 diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity index 0b001ff2f722772809aef97ce5a475dfdd17948a..1364637d962f2a8ffa35c79d780cab5146bfd4f7 100644 GIT binary patch delta 739 zcmcb!RO8KRjR|s0qHh`%S``?#Dlo}R70TdZU|?ZjU|?b}l1o^1PS?Rl%s|Wn#H>Kf2E^<@%(1<0Drb-lqt5n=7dg9`7{#Xx zUgZ?yF3zk<1zNzsU~DwK?h2>b^w$L(oFL;3g9-(vUFFmfFpLG+1p$4M6BR*%(?49{ z%+&VK7Oc}0NoUV0s~oVB2OuQ{3pnJae*kGc6r1h> z7LT|F6gTe<_62LTMAG_pWk3J)2iG`t`3(@F#RsUgzXyVVC_@ z1W|2_P`#aRy);D71R?nM&z`jD7p?=1cynbDC&UQL8$ct1Ucyl61T1UVL$(84uXMpVmiXsK0b5ylHRIL%htm_YcmO@35T+VvP zu>>lL$$PJc)iXwUE(vy?mPy@OQ$v@_1w&GB=NHOq& vx4;M@YK-JJe@S(Js3?-(&hPtSKYinFCXwj^H#r5S-+0R@uwCaS=LSmv!*tXA delta 411 zcmaE}TI0@AjR|s0LU$S!S``?#Dlo}R6^dbJU|?ZjU|?b}l1o^1PS?P8y5CeLF{uPw zpac*!m_y|MLqPq5lJ>f(OhC*G#4JF}3dC$c%nrmH+v}!s2H7ymZ@+kvvzuwUz*SC} z>8}eoIH$W_;S`5(KV0GDneK6gQv}4-fU{(#XIw>+kYP261=>E{@d~FjMBf9L^t5Xz z+9Ix@XtO}mb^$K^0j>|olAZ2(os%14JBZ6-1k?wy4a_x$b01uXSqIi;c>_h88C)G$ qn>n0&;RcE}&6_}NTGyE*rUzW%WSOpT2k2;x_nbW2ZSHVxumk|`xsuQT diff --git a/Assets/Scripts/BlindMutantAI.cs b/Assets/Scripts/BlindMutantAI.cs index ec23b85..85f6852 100644 --- a/Assets/Scripts/BlindMutantAI.cs +++ b/Assets/Scripts/BlindMutantAI.cs @@ -12,6 +12,9 @@ public class BlindMutantAI : MonoBehaviour [Header("Патрулирование")] public Transform[] patrolPoints; + [Tooltip("Время паузы на точке патрулирования для прислушивания")] + public float waitTimeAtPoint = 1.5f; + private float waitTimer = 0f; private int currentPatrolIndex; private bool isInvestigating = false; @@ -30,7 +33,20 @@ public class BlindMutantAI : MonoBehaviour void Start() { agent = GetComponent(); - agent.speed = walkSpeed; + if (agent != null) + { + agent.speed = walkSpeed; + + // Если агент чуть выше пола или не на сетке, привязываем его к ближайшей точке NavMesh + if (!agent.isOnNavMesh) + { + if (NavMesh.SamplePosition(transform.position, out NavMeshHit hit, 5.0f, NavMesh.AllAreas)) + { + agent.Warp(hit.position); + } + } + } + animator = GetComponentInChildren(); // Автоматически находим игрока на сцене @@ -40,9 +56,36 @@ public class BlindMutantAI : MonoBehaviour targetPlayerHealth = playerObj; } - if (patrolPoints != null && patrolPoints.Length > 0) + // Если точки патрулирования не заданы или массив состоит из null, ищем PatrolWaypoints на сцене + if (IsPatrolArrayEmptyOrNull()) { - GoToNextPatrolPoint(); + AutoAssignPatrolWaypoints(); + } + + GoToNextPatrolPoint(); + } + + private bool IsPatrolArrayEmptyOrNull() + { + if (patrolPoints == null || patrolPoints.Length == 0) return true; + for (int i = 0; i < patrolPoints.Length; i++) + { + if (patrolPoints[i] != null) return false; + } + return true; + } + + private void AutoAssignPatrolWaypoints() + { + var patrolRoot = GameObject.Find("PatrolWaypoints"); + if (patrolRoot != null && patrolRoot.transform.childCount > 0) + { + patrolPoints = new Transform[patrolRoot.transform.childCount]; + for (int i = 0; i < patrolRoot.transform.childCount; i++) + { + patrolPoints[i] = patrolRoot.transform.GetChild(i); + } + Debug.Log($"[BlindMutantAI] Автоматически обнаружено {patrolPoints.Length} точек патрулирования в объекте 'PatrolWaypoints'."); } } @@ -67,12 +110,14 @@ public class BlindMutantAI : MonoBehaviour private void UpdateAnimator() { if (animator == null) return; - float currentSpeed = agent != null && agent.enabled ? agent.velocity.magnitude : 0f; + float currentSpeed = agent != null && agent.enabled && agent.isOnNavMesh ? agent.velocity.magnitude : 0f; animator.SetFloat("Speed", currentSpeed); } private void HandlePlayerChaseAndAttack() { + if (agent == null || !agent.isOnNavMesh) return; + if (targetPlayerHealth != null && targetPlayerHealth.IsDead) { LosePlayerTarget(); @@ -138,26 +183,34 @@ public class BlindMutantAI : MonoBehaviour private void HandlePatrolAndInvestigation() { - if (!agent.pathPending && agent.remainingDistance < 0.5f) + if (agent == null || !agent.isOnNavMesh) return; + + if (!agent.pathPending && agent.remainingDistance < 0.6f) { if (isInvestigating) { isInvestigating = false; agent.speed = walkSpeed; - if (patrolPoints != null && patrolPoints.Length > 0) - { - GoToNextPatrolPoint(); - } + waitTimer = waitTimeAtPoint; } - else if (patrolPoints != null && patrolPoints.Length > 0) + + if (waitTimer > 0f) { - GoToNextPatrolPoint(); + waitTimer -= Time.deltaTime; + agent.isStopped = true; + return; } + + agent.isStopped = false; + waitTimer = waitTimeAtPoint; + GoToNextPatrolPoint(); } } private void EngagePlayer(Transform playerTransform) { + if (agent == null || !agent.isOnNavMesh) return; + targetPlayer = playerTransform; if (targetPlayerHealth == null) { @@ -165,6 +218,7 @@ public class BlindMutantAI : MonoBehaviour } isInvestigating = false; + waitTimer = 0f; agent.isStopped = false; agent.speed = runSpeed; agent.SetDestination(targetPlayer.position); @@ -174,22 +228,55 @@ public class BlindMutantAI : MonoBehaviour private void LosePlayerTarget() { targetPlayer = null; - agent.isStopped = false; - agent.speed = walkSpeed; - isInvestigating = false; - - if (patrolPoints != null && patrolPoints.Length > 0) + if (agent != null && agent.isOnNavMesh) { - GoToNextPatrolPoint(); + agent.isStopped = false; + agent.speed = walkSpeed; } + isInvestigating = false; + waitTimer = 0f; + + GoToNextPatrolPoint(); Debug.Log("Мутант потерял игрока и возвращается к патрулированию."); } private void GoToNextPatrolPoint() { - if (patrolPoints == null || patrolPoints.Length == 0) return; - agent.destination = patrolPoints[currentPatrolIndex].position; - currentPatrolIndex = (currentPatrolIndex + 1) % patrolPoints.Length; + if (agent == null || !agent.isOnNavMesh) return; + + if (patrolPoints != null && patrolPoints.Length > 0) + { + for (int attempts = 0; attempts < patrolPoints.Length; attempts++) + { + var wp = patrolPoints[currentPatrolIndex]; + currentPatrolIndex = (currentPatrolIndex + 1) % patrolPoints.Length; + + if (wp != null) + { + agent.isStopped = false; + agent.speed = walkSpeed; + agent.SetDestination(wp.position); + return; + } + } + } + + // Если точек нет вообще — свободное блуждание по комплексу + WanderRandomly(); + } + + private void WanderRandomly() + { + if (agent == null || !agent.isOnNavMesh) return; + + Vector3 randomDir = Random.insideUnitSphere * 15f; + randomDir += transform.position; + if (NavMesh.SamplePosition(randomDir, out NavMeshHit hit, 15f, NavMesh.AllAreas)) + { + agent.isStopped = false; + agent.speed = walkSpeed; + agent.SetDestination(hit.position); + } } public void HearSound(Vector3 soundPosition) @@ -200,7 +287,10 @@ public class BlindMutantAI : MonoBehaviour return; } + if (agent == null || !agent.isOnNavMesh) return; + isInvestigating = true; + waitTimer = 0f; agent.isStopped = false; agent.speed = runSpeed; agent.SetDestination(soundPosition); diff --git a/Assets/Scripts/EchoTrigger.cs b/Assets/Scripts/EchoTrigger.cs index 397013e..17596c8 100644 --- a/Assets/Scripts/EchoTrigger.cs +++ b/Assets/Scripts/EchoTrigger.cs @@ -71,21 +71,31 @@ public class EchoTrigger : MonoBehaviour if (waveActive[i]) { waveRadii[i] += waveSpeed * Time.deltaTime; - echoMaterial.SetFloat("_EchoRadius" + (i + 1), waveRadii[i]); - - // ВЫЧИСЛЯЕМ ПРОЗРАЧНОСТЬ с учетом стартовой яркости - // Умножаем на waveIntensities[i], чтобы бег был ярче, а стелс тусклее - float opacity = (1f - (waveRadii[i] / waveMaxRadii[i])) * waveIntensities[i]; - - // Mathf.Max не дает уйти в минус, но РАЗРЕШАЕТ быть больше 1 (для HDR свечения) - opacity = Mathf.Max(0f, opacity); + float currentRadius = waveRadii[i]; + float maxRadius = waveMaxRadii[i]; + float progress = Mathf.Clamp01(currentRadius / maxRadius); + + // Плавное появление (Fade-in): в первые 18% пути волна мягко разгорается (без резкой вспышки) + float fadeIn = Mathf.SmoothStep(0f, 1f, progress / 0.18f); + // Плавное рассеивание (Fade-out): к концу облако плавно растворяется в темноте + float fadeOut = Mathf.Pow(1f - progress, 1.4f); + + float opacity = (fadeIn * fadeOut) * waveIntensities[i]; + opacity = Mathf.Max(0f, opacity); + + echoMaterial.SetFloat("_EchoRadius" + (i + 1), currentRadius); echoMaterial.SetFloat("_EchoOpacity" + (i + 1), opacity); - - if (waveRadii[i] > waveMaxRadii[i]) + + Shader.SetGlobalFloat("_EchoRadius" + (i + 1), currentRadius); + Shader.SetGlobalFloat("_EchoOpacity" + (i + 1), opacity); + + if (currentRadius >= maxRadius) { waveActive[i] = false; echoMaterial.SetFloat("_EchoRadius" + (i + 1), 0f); echoMaterial.SetFloat("_EchoOpacity" + (i + 1), 0f); + Shader.SetGlobalFloat("_EchoRadius" + (i + 1), 0f); + Shader.SetGlobalFloat("_EchoOpacity" + (i + 1), 0f); } } } @@ -146,7 +156,10 @@ public class EchoTrigger : MonoBehaviour waveRadii[waveIndex] = 0f; waveActive[waveIndex] = true; - echoMaterial.SetFloat("_EchoOpacity" + (waveIndex + 1), targetIntensity); + // Начинаем плавно с 0, чтобы разгореться через fadeIn + echoMaterial.SetFloat("_EchoOpacity" + (waveIndex + 1), 0f); echoMaterial.SetVector("_EchoPosition" + (waveIndex + 1), position); + Shader.SetGlobalFloat("_EchoOpacity" + (waveIndex + 1), 0f); + Shader.SetGlobalVector("_EchoPosition" + (waveIndex + 1), position); } } \ No newline at end of file diff --git a/Assets/Scripts/Editor/LaboratoryLevelGenerator.cs b/Assets/Scripts/Editor/LaboratoryLevelGenerator.cs index e2ff817..99709fd 100644 --- a/Assets/Scripts/Editor/LaboratoryLevelGenerator.cs +++ b/Assets/Scripts/Editor/LaboratoryLevelGenerator.cs @@ -908,7 +908,10 @@ public static class LaboratoryLevelGenerator var ai = enemy.GetComponent(); if (ai != null) { + Undo.RecordObject(ai, "Setup Patrol Points"); ai.patrolPoints = transforms; + EditorUtility.SetDirty(ai); + EditorUtility.SetDirty(enemy); Debug.Log($"Configured {transforms.Length} patrol points for SkinnyOrc BlindMutantAI in South Concourse (0, 0.08, -28)!"); } }