diff --git a/Assets/EchoCloudMat.mat b/Assets/EchoCloudMat.mat index 40d38df..c1c30c4 100644 --- a/Assets/EchoCloudMat.mat +++ b/Assets/EchoCloudMat.mat @@ -80,9 +80,9 @@ Material: - _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} + - _EchoPosition1: {r: 2.9648588, g: 1.0799999, b: -29.737446, a: 0} + - _EchoPosition2: {r: 3.705747, g: 1.0799999, b: -28.43126, a: 0} + - _EchoPosition3: {r: 7.0987725, g: 1.0799999, b: -9.685643, a: 0} - _WaveCenter: {r: 0, g: 0, b: 0, a: 0} m_BuildTextureStacks: [] m_AllowLocking: 1 diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity index 1364637..281d04d 100644 Binary files a/Assets/Scenes/SampleScene.unity and b/Assets/Scenes/SampleScene.unity differ diff --git a/Assets/Scripts/EchoTrigger.cs b/Assets/Scripts/EchoTrigger.cs index 17596c8..f4e5286 100644 --- a/Assets/Scripts/EchoTrigger.cs +++ b/Assets/Scripts/EchoTrigger.cs @@ -6,7 +6,28 @@ public class EchoTrigger : MonoBehaviour [Header("Связь с шейдером")] public Material echoMaterial; - [Header("Настройки шагов")] + [Header("Синхронизация с шагами персонажа")] + [Tooltip("Включить синхронизацию волн эхолокации с анимацией шагов")] + public bool syncWithAnimation = true; + public Animator playerAnimator; + public CharacterController characterController; + [Tooltip("Позиционировать волну строго на кости наступающей ноги (L/R)")] + public bool spawnAtFootBone = true; + public Transform leftFootBone; + public Transform rightFootBone; + + [Header("Фазы касания земли в анимации (0.0 - 1.0)")] + [Tooltip("Фаза шага левой ногой при обычной ходьбе")] + [Range(0f, 1f)] public float leftFootPhaseWalk = 0.08f; + [Tooltip("Фаза шага правой ногой при обычной ходьбе")] + [Range(0f, 1f)] public float rightFootPhaseWalk = 0.55f; + [Tooltip("Фаза шага левой ногой при спринте")] + [Range(0f, 1f)] public float leftFootPhaseSprint = 0.12f; + [Tooltip("Фаза шага правой ногой при спринте")] + [Range(0f, 1f)] public float rightFootPhaseSprint = 0.62f; + + [Header("Резервный режим (по дистанции)")] + [Tooltip("Используется только если аниматор не найден или выключена синхронизация")] public float stepDistance = 1.5f; public float waveSpeed = 15f; @@ -20,11 +41,19 @@ public class EchoTrigger : MonoBehaviour public float walkIntensity = 1.0f; // Обычный свет для шага public float runIntensity = 3.0f; // Очень яркая вспышка для бега + [Header("Звуки шагов (Опционально)")] + public AudioSource audioSource; + public AudioClip[] footstepSounds; + [Range(0f, 1f)] public float footstepVolume = 0.6f; + + [Header("Реакция мутантов")] + public bool alertMutants = true; + // Пул из 3 одновременных волн private const int MAX_WAVES = 3; private float[] waveRadii = new float[MAX_WAVES]; private float[] waveMaxRadii = new float[MAX_WAVES]; - private float[] waveIntensities = new float[MAX_WAVES]; // Память для яркости каждой волны + private float[] waveIntensities = new float[MAX_WAVES]; private bool[] waveActive = new bool[MAX_WAVES]; public static EchoTrigger Instance; @@ -32,6 +61,11 @@ public class EchoTrigger : MonoBehaviour private Vector3 lastPosition; private float distanceMoved = 0f; + private bool wasMoving = false; + private int lastLeftStepCycle = int.MinValue; + private int lastRightStepCycle = int.MinValue; + private PlayerNoise playerNoise; + void Awake() { if (Instance == null) Instance = this; @@ -48,12 +82,116 @@ public class EchoTrigger : MonoBehaviour } } lastPosition = transform.position; + + if (characterController == null) + characterController = GetComponent(); + + if (playerAnimator == null) + playerAnimator = GetComponentInChildren(); + + if (playerNoise == null) + playerNoise = GetComponent(); + + if (audioSource == null) + audioSource = GetComponent(); + + FindFootBones(); + } + + public void FindFootBones() + { + if (playerAnimator == null) return; + + if (playerAnimator.isHuman) + { + if (leftFootBone == null) + leftFootBone = playerAnimator.GetBoneTransform(HumanBodyBones.LeftFoot); + if (rightFootBone == null) + rightFootBone = playerAnimator.GetBoneTransform(HumanBodyBones.RightFoot); + } + + // Резервный поиск по имени, если аватар Generic или кости не замаплены + if (leftFootBone == null || rightFootBone == null) + { + Transform[] allChildren = playerAnimator.GetComponentsInChildren(); + foreach (var t in allChildren) + { + string lower = t.name.ToLower(); + if (leftFootBone == null && (lower.Contains("l_foot") || lower.Contains("foot_l") || lower.Contains("leftfoot"))) + leftFootBone = t; + if (rightFootBone == null && (lower.Contains("r_foot") || lower.Contains("foot_r") || lower.Contains("rightfoot"))) + rightFootBone = t; + } + } } void Update() { - if (echoMaterial == null) return; + HandleStepDetection(); + UpdateWaveShaderProgress(); + } + void HandleStepDetection() + { + if (syncWithAnimation && playerAnimator != null) + { + HandleAnimationSteps(); + } + else + { + HandleDistanceSteps(); + } + } + + void HandleAnimationSteps() + { + AnimatorStateInfo stateInfo = playerAnimator.GetCurrentAnimatorStateInfo(0); + bool isMoving = playerAnimator.GetBool("IsMoving"); + float animSpeed = playerAnimator.GetFloat("Speed"); + bool isGrounded = characterController == null || characterController.isGrounded; + + // Синхронизируем шаги только когда персонаж движется по земле в режиме перемещения + if (isMoving && animSpeed > 0.1f && isGrounded && stateInfo.IsName("Locomotion")) + { + float speedRatio = Mathf.InverseLerp(1.5f, 6.0f, animSpeed); + float targetLeftPhase = Mathf.Lerp(leftFootPhaseWalk, leftFootPhaseSprint, speedRatio); + float targetRightPhase = Mathf.Lerp(rightFootPhaseWalk, rightFootPhaseSprint, speedRatio); + + float currTime = stateInfo.normalizedTime; + + if (!wasMoving) + { + // При начале движения инициализируем циклы текущим положением анимации, + // чтобы не было фантомного шага из простоя + lastLeftStepCycle = Mathf.FloorToInt(currTime - targetLeftPhase); + lastRightStepCycle = Mathf.FloorToInt(currTime - targetRightPhase); + wasMoving = true; + } + else + { + int leftCycle = Mathf.FloorToInt(currTime - targetLeftPhase); + if (leftCycle > lastLeftStepCycle) + { + lastLeftStepCycle = leftCycle; + TriggerFootstep(true); + } + + int rightCycle = Mathf.FloorToInt(currTime - targetRightPhase); + if (rightCycle > lastRightStepCycle) + { + lastRightStepCycle = rightCycle; + TriggerFootstep(false); + } + } + } + else + { + wasMoving = false; + } + } + + void HandleDistanceSteps() + { Vector3 currentPos = new Vector3(transform.position.x, 0, transform.position.z); Vector3 lastPos = new Vector3(lastPosition.x, 0, lastPosition.z); @@ -63,8 +201,94 @@ public class EchoTrigger : MonoBehaviour if (distanceMoved >= stepDistance) { distanceMoved = 0f; - TriggerStepWave(); + TriggerFootstep(true); } + } + + public void TriggerStepWave() + { + TriggerFootstep(true); + } + + void TriggerFootstep(bool isLeftFoot) + { + Vector3 spawnPos = transform.position; + + if (spawnAtFootBone) + { + Transform foot = isLeftFoot ? leftFootBone : rightFootBone; + if (foot != null) + { + spawnPos = foot.position; + } + } + + // Привязываем высоту волны точно к уровню пола под персонажем + if (characterController != null) + { + float floorY = transform.position.y + characterController.center.y - (characterController.height * 0.5f); + spawnPos.y = floorY + 0.05f; + } + + // Определяем режим передвижения (Sneak / Walk / Run) + var keyboard = Keyboard.current; + bool isRunning = keyboard != null && keyboard.leftShiftKey.isPressed; + bool isSneaking = keyboard != null && (keyboard.leftCtrlKey.isPressed || keyboard.cKey.isPressed); + + if (playerAnimator != null) + { + float speedVal = playerAnimator.GetFloat("Speed"); + if (speedVal > 4.5f) isRunning = true; + else if (speedVal > 0f && speedVal < 2.0f && !isRunning) isSneaking = true; + } + + float targetMax = walkRadius; + float targetIntensity = walkIntensity; + + if (isRunning) + { + targetMax = runRadius; + targetIntensity = runIntensity; + } + else if (isSneaking) + { + targetMax = sneakRadius; + targetIntensity = sneakIntensity; + } + + // Запуск световой волны эхолокации в шейдере + TriggerExternalWave(spawnPos, targetMax, targetIntensity); + + // Воспроизведение звука шага (если задан) + if (audioSource != null && footstepSounds != null && footstepSounds.Length > 0) + { + AudioClip clip = footstepSounds[Random.Range(0, footstepSounds.Length)]; + if (clip != null) + { + float vol = isSneaking ? footstepVolume * 0.4f : (isRunning ? footstepVolume * 1.3f : footstepVolume); + audioSource.PlayOneShot(clip, vol); + } + } + + // Синхронизация с PlayerNoise или прямое оповещение мутантов + if (playerNoise != null) + { + playerNoise.EmitFootstep(spawnPos, targetMax); + } + else if (alertMutants) + { + Collider[] colliders = Physics.OverlapSphere(spawnPos, targetMax); + foreach (Collider hit in colliders) + { + BlindMutantAI mutant = hit.GetComponent(); + if (mutant != null) mutant.HearSound(spawnPos); + } + } + } + + void UpdateWaveShaderProgress() + { + if (echoMaterial == null) return; for (int i = 0; i < MAX_WAVES; i++) { @@ -101,30 +325,10 @@ public class EchoTrigger : MonoBehaviour } } - void TriggerStepWave() - { - var keyboard = Keyboard.current; - if (keyboard == null) return; - - float targetMax = walkRadius; - float targetIntensity = walkIntensity; - - if (keyboard.leftShiftKey.isPressed) - { - targetMax = runRadius; - targetIntensity = runIntensity; - } - else if (keyboard.leftCtrlKey.isPressed) - { - targetMax = sneakRadius; - targetIntensity = sneakIntensity; - } - - TriggerExternalWave(transform.position, targetMax, targetIntensity); - } - public void TriggerExternalWave(Vector3 position, float targetMax, float targetIntensity) { + if (echoMaterial == null) return; + // Ищем свободный слот int waveIndex = -1; for (int i = 0; i < MAX_WAVES; i++) diff --git a/Assets/Scripts/Editor/CharacterSetup.cs b/Assets/Scripts/Editor/CharacterSetup.cs index effaf24..963a2c4 100644 --- a/Assets/Scripts/Editor/CharacterSetup.cs +++ b/Assets/Scripts/Editor/CharacterSetup.cs @@ -149,6 +149,14 @@ public static class CharacterSetup EditorUtility.SetDirty(pg); } + var echo = player.GetComponent(); + if (echo != null) + { + echo.playerAnimator = animator; + echo.FindFootBones(); + EditorUtility.SetDirty(echo); + } + // 9. Проверяем CameraFollow на главной камере if (Camera.main != null) { diff --git a/Assets/Scripts/PlayerNoise.cs b/Assets/Scripts/PlayerNoise.cs index cf8bacd..a7e686b 100644 --- a/Assets/Scripts/PlayerNoise.cs +++ b/Assets/Scripts/PlayerNoise.cs @@ -1,4 +1,4 @@ -using UnityEngine; +using UnityEngine; using UnityEngine.InputSystem; public class PlayerNoise : MonoBehaviour @@ -14,8 +14,17 @@ public class PlayerNoise : MonoBehaviour public GameObject wavePrefab; + [Header("Синхронизация")] + [Tooltip("Если включено и на объекте есть EchoTrigger, генерация шума шагов синхронизируется с анимацией персонажа через EchoTrigger")] + public bool syncWithEchoTrigger = true; + void Update() { + if (syncWithEchoTrigger && GetComponent() != null) + { + return; + } + if (Keyboard.current == null) return; bool isMoving = Keyboard.current.wKey.isPressed || @@ -38,25 +47,33 @@ public class PlayerNoise : MonoBehaviour } } - private void EmitStepNoise() + public void EmitFootstep(Vector3 footPosition, float radius) { - float currentRadius = walkRadius; - - if (Keyboard.current.leftShiftKey.isPressed) currentRadius = runRadius; - else if (Keyboard.current.leftCtrlKey.isPressed || Keyboard.current.cKey.isPressed) currentRadius = sneakRadius; - if (wavePrefab != null) { - Vector3 footPosition = transform.position - new Vector3(0, transform.localScale.y / 2, 0); Instantiate(wavePrefab, footPosition, Quaternion.identity); } - Collider[] colliders = Physics.OverlapSphere(transform.position, currentRadius); + Collider[] colliders = Physics.OverlapSphere(footPosition, radius); foreach (Collider hit in colliders) { BlindMutantAI mutant = hit.GetComponent(); - if (mutant != null) mutant.HearSound(transform.position); + if (mutant != null) mutant.HearSound(footPosition); } } + + private void EmitStepNoise() + { + float currentRadius = walkRadius; + + if (Keyboard.current != null) + { + if (Keyboard.current.leftShiftKey.isPressed) currentRadius = runRadius; + else if (Keyboard.current.leftCtrlKey.isPressed || Keyboard.current.cKey.isPressed) currentRadius = sneakRadius; + } + + Vector3 footPosition = transform.position - new Vector3(0, transform.localScale.y / 2, 0); + EmitFootstep(footPosition, currentRadius); + } } \ No newline at end of file