using UnityEngine; [DefaultExecutionOrder(-50)] [RequireComponent(typeof(CharacterController))] [RequireComponent(typeof(SimpleFPSController))] public class PlayerStealth : MonoBehaviour { public enum NoiseState { Silent, Low, Medium, High } [Header("References")] public CharacterController characterController; public SimpleFPSController fpsController; public Camera playerCamera; [Header("Crouch")] public float standingHeight = 2f; public float crouchingHeight = 1.2f; public float crouchSpeedMultiplier = 0.55f; public float standingCameraLocalY = 0.8f; public float crouchCameraLocalY = 0.35f; public float crouchTransitionSpeed = 8f; public float crouchVisibilityMultiplier = 0.72f; [Header("Noise")] public float noiseEmitInterval = 0.35f; public float crouchNoiseIntensity = 0.45f; public float walkNoiseIntensity = 0.8f; public float runNoiseIntensity = 1.25f; public float testNoiseIntensity = 1.6f; public bool IsCrouching { get; private set; } public NoiseState CurrentNoiseState { get; private set; } = NoiseState.Silent; public float CurrentNoiseIntensity { get; private set; } public float VisibilityMultiplier => IsCrouching ? crouchVisibilityMultiplier : 1f; public string CurrentNoiseLabel => CurrentNoiseState.ToString().ToLowerInvariant(); private float currentControllerHeight; private float targetCameraLocalY; private float lastNoiseEmitTime = -999f; private void Awake() { characterController = characterController != null ? characterController : GetComponent(); fpsController = fpsController != null ? fpsController : GetComponent(); playerCamera = playerCamera != null ? playerCamera : GetComponentInChildren(); currentControllerHeight = standingHeight; targetCameraLocalY = standingCameraLocalY; } private void Start() { ApplyCharacterHeightImmediate(standingHeight); SetCameraLocalY(standingCameraLocalY); } private void Update() { UpdateCrouchState(); UpdateNoiseState(); HandleManualNoise(); } private void LateUpdate() { SmoothCrouchPresentation(); } private void UpdateCrouchState() { IsCrouching = Input.GetKey(KeyCode.LeftControl); fpsController.AllowRunning = !IsCrouching; fpsController.SpeedScale = IsCrouching ? crouchSpeedMultiplier : 1f; targetCameraLocalY = IsCrouching ? crouchCameraLocalY : standingCameraLocalY; } private void UpdateNoiseState() { if (!fpsController.HasMovementInput) { SetNoiseState(NoiseState.Silent, 0f); return; } if (IsCrouching) { SetNoiseState(NoiseState.Low, crouchNoiseIntensity); } else if (fpsController.IsRunning) { SetNoiseState(NoiseState.High, runNoiseIntensity); } else { SetNoiseState(NoiseState.Medium, walkNoiseIntensity); } if (CurrentNoiseState != NoiseState.Silent && Time.time >= lastNoiseEmitTime + noiseEmitInterval) { lastNoiseEmitTime = Time.time; // Периодические импульсы шума проще настраивать и удобнее для AI, чем шум каждый кадр. NoiseManager.EmitNoise(transform.position, CurrentNoiseIntensity); } } private void HandleManualNoise() { if (Input.GetKeyDown(KeyCode.R)) { NoiseManager.EmitNoise(transform.position, testNoiseIntensity); } } private void SmoothCrouchPresentation() { float desiredHeight = IsCrouching ? crouchingHeight : standingHeight; currentControllerHeight = Mathf.Lerp(currentControllerHeight, desiredHeight, Time.deltaTime * crouchTransitionSpeed); ApplyCharacterHeightImmediate(currentControllerHeight); if (playerCamera != null) { Vector3 localPosition = playerCamera.transform.localPosition; localPosition.y = Mathf.Lerp(localPosition.y, targetCameraLocalY, Time.deltaTime * crouchTransitionSpeed); playerCamera.transform.localPosition = localPosition; } } private void ApplyCharacterHeightImmediate(float height) { characterController.height = height; characterController.center = new Vector3(0f, height * 0.5f, 0f); } private void SetCameraLocalY(float localY) { if (playerCamera == null) { return; } Vector3 localPosition = playerCamera.transform.localPosition; localPosition.y = localY; playerCamera.transform.localPosition = localPosition; } private void SetNoiseState(NoiseState state, float intensity) { CurrentNoiseState = state; CurrentNoiseIntensity = intensity; } }