синхрон шгов с шейдером

This commit is contained in:
Valentina 2026-10-21 14:06:44 +03:00
parent 9245409fc9
commit 7fba61f7a0
5 changed files with 268 additions and 39 deletions

View File

@ -80,9 +80,9 @@ Material:
- _BaseColor: {r: 0.015, g: 0.015, b: 0.02, 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} - _EchoColor: {r: 4, g: 3.026, b: 1.049, a: 1}
- _EchoCoreColor: {r: 7.5, g: 5.5, b: 2.2, 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} - _EchoPosition1: {r: 2.9648588, g: 1.0799999, b: -29.737446, a: 0}
- _EchoPosition2: {r: -1.1804235, g: 1.0799999, b: -35.782673, a: 0} - _EchoPosition2: {r: 3.705747, g: 1.0799999, b: -28.43126, a: 0}
- _EchoPosition3: {r: -0.93470556, g: 1.0799999, b: -34.72166, a: 0} - _EchoPosition3: {r: 7.0987725, g: 1.0799999, b: -9.685643, a: 0}
- _WaveCenter: {r: 0, g: 0, b: 0, a: 0} - _WaveCenter: {r: 0, g: 0, b: 0, a: 0}
m_BuildTextureStacks: [] m_BuildTextureStacks: []
m_AllowLocking: 1 m_AllowLocking: 1

Binary file not shown.

View File

@ -6,7 +6,28 @@ public class EchoTrigger : MonoBehaviour
[Header("Связь с шейдером")] [Header("Связь с шейдером")]
public Material echoMaterial; 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 stepDistance = 1.5f;
public float waveSpeed = 15f; public float waveSpeed = 15f;
@ -20,11 +41,19 @@ public class EchoTrigger : MonoBehaviour
public float walkIntensity = 1.0f; // Обычный свет для шага public float walkIntensity = 1.0f; // Обычный свет для шага
public float runIntensity = 3.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 одновременных волн // Пул из 3 одновременных волн
private const int MAX_WAVES = 3; private const int MAX_WAVES = 3;
private float[] waveRadii = new float[MAX_WAVES]; private float[] waveRadii = new float[MAX_WAVES];
private float[] waveMaxRadii = 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]; private bool[] waveActive = new bool[MAX_WAVES];
public static EchoTrigger Instance; public static EchoTrigger Instance;
@ -32,6 +61,11 @@ public class EchoTrigger : MonoBehaviour
private Vector3 lastPosition; private Vector3 lastPosition;
private float distanceMoved = 0f; private float distanceMoved = 0f;
private bool wasMoving = false;
private int lastLeftStepCycle = int.MinValue;
private int lastRightStepCycle = int.MinValue;
private PlayerNoise playerNoise;
void Awake() void Awake()
{ {
if (Instance == null) Instance = this; if (Instance == null) Instance = this;
@ -48,12 +82,116 @@ public class EchoTrigger : MonoBehaviour
} }
} }
lastPosition = transform.position; 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() 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 currentPos = new Vector3(transform.position.x, 0, transform.position.z);
Vector3 lastPos = new Vector3(lastPosition.x, 0, lastPosition.z); Vector3 lastPos = new Vector3(lastPosition.x, 0, lastPosition.z);
@ -63,8 +201,94 @@ public class EchoTrigger : MonoBehaviour
if (distanceMoved >= stepDistance) if (distanceMoved >= stepDistance)
{ {
distanceMoved = 0f; 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<BlindMutantAI>();
if (mutant != null) mutant.HearSound(spawnPos);
}
}
}
void UpdateWaveShaderProgress()
{
if (echoMaterial == null) return;
for (int i = 0; i < MAX_WAVES; i++) 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) public void TriggerExternalWave(Vector3 position, float targetMax, float targetIntensity)
{ {
if (echoMaterial == null) return;
// Ищем свободный слот // Ищем свободный слот
int waveIndex = -1; int waveIndex = -1;
for (int i = 0; i < MAX_WAVES; i++) for (int i = 0; i < MAX_WAVES; i++)

View File

@ -149,6 +149,14 @@ public static class CharacterSetup
EditorUtility.SetDirty(pg); EditorUtility.SetDirty(pg);
} }
var echo = player.GetComponent<EchoTrigger>();
if (echo != null)
{
echo.playerAnimator = animator;
echo.FindFootBones();
EditorUtility.SetDirty(echo);
}
// 9. Проверяем CameraFollow на главной камере // 9. Проверяем CameraFollow на главной камере
if (Camera.main != null) if (Camera.main != null)
{ {

View File

@ -1,4 +1,4 @@
using UnityEngine; using UnityEngine;
using UnityEngine.InputSystem; using UnityEngine.InputSystem;
public class PlayerNoise : MonoBehaviour public class PlayerNoise : MonoBehaviour
@ -14,8 +14,17 @@ public class PlayerNoise : MonoBehaviour
public GameObject wavePrefab; public GameObject wavePrefab;
[Header("Синхронизация")]
[Tooltip("Если включено и на объекте есть EchoTrigger, генерация шума шагов синхронизируется с анимацией персонажа через EchoTrigger")]
public bool syncWithEchoTrigger = true;
void Update() void Update()
{ {
if (syncWithEchoTrigger && GetComponent<EchoTrigger>() != null)
{
return;
}
if (Keyboard.current == null) return; if (Keyboard.current == null) return;
bool isMoving = Keyboard.current.wKey.isPressed || 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) if (wavePrefab != null)
{ {
Vector3 footPosition = transform.position - new Vector3(0, transform.localScale.y / 2, 0);
Instantiate(wavePrefab, footPosition, Quaternion.identity); Instantiate(wavePrefab, footPosition, Quaternion.identity);
} }
Collider[] colliders = Physics.OverlapSphere(transform.position, currentRadius); Collider[] colliders = Physics.OverlapSphere(footPosition, radius);
foreach (Collider hit in colliders) foreach (Collider hit in colliders)
{ {
BlindMutantAI mutant = hit.GetComponent<BlindMutantAI>(); BlindMutantAI mutant = hit.GetComponent<BlindMutantAI>();
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);
}
} }