Kursach_StealthAction/Assets/Scripts/EchoTrigger.cs

369 lines
14 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 UnityEngine.InputSystem;
public class EchoTrigger : MonoBehaviour
{
[Header("Связь с шейдером")]
public Material echoMaterial;
[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;
[Header("Радиусы звука (Шум)")]
public float sneakRadius = 5f;
public float walkRadius = 15f;
public float runRadius = 30f;
[Header("Яркость волны (Интенсивность)")]
public float sneakIntensity = 0.3f; // Тусклый свет для стелса
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 bool[] waveActive = new bool[MAX_WAVES];
public static EchoTrigger Instance;
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;
}
void Start()
{
if (echoMaterial != null)
{
for (int i = 0; i < MAX_WAVES; i++)
{
echoMaterial.SetFloat("_EchoRadius" + (i + 1), 0f);
echoMaterial.SetFloat("_EchoOpacity" + (i + 1), 0f);
}
}
lastPosition = transform.position;
if (characterController == null)
characterController = GetComponent<CharacterController>();
if (playerAnimator == null)
playerAnimator = GetComponentInChildren<Animator>();
if (playerNoise == null)
playerNoise = GetComponent<PlayerNoise>();
if (audioSource == null)
audioSource = GetComponent<AudioSource>();
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<Transform>();
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()
{
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);
distanceMoved += Vector3.Distance(currentPos, lastPos);
lastPosition = transform.position;
if (distanceMoved >= stepDistance)
{
distanceMoved = 0f;
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<BlindMutantAI>();
if (mutant != null) mutant.HearSound(spawnPos);
}
}
}
void UpdateWaveShaderProgress()
{
if (echoMaterial == null) return;
for (int i = 0; i < MAX_WAVES; i++)
{
if (waveActive[i])
{
waveRadii[i] += waveSpeed * Time.deltaTime;
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);
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);
}
}
}
}
public void TriggerExternalWave(Vector3 position, float targetMax, float targetIntensity)
{
if (echoMaterial == null) return;
// Ищем свободный слот
int waveIndex = -1;
for (int i = 0; i < MAX_WAVES; i++)
{
if (!waveActive[i])
{
waveIndex = i;
break;
}
}
// Если занято, берем самую старую волну
if (waveIndex == -1)
{
float maxRad = -1f;
for (int i = 0; i < MAX_WAVES; i++)
{
if (waveRadii[i] > maxRad)
{
maxRad = waveRadii[i];
waveIndex = i;
}
}
}
// Назначаем волне её радиус и её личную яркость!
waveMaxRadii[waveIndex] = targetMax;
waveIntensities[waveIndex] = targetIntensity;
waveRadii[waveIndex] = 0f;
waveActive[waveIndex] = true;
// Начинаем плавно с 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);
}
}