using Cysharp.Threading.Tasks; using System; using System.Threading; using UnityEngine; public class CameraShaker : MonoBehaviour { [Header("Тряска")] [SerializeField] private float shakeSpeed; [SerializeField] private float shakeAmplitude; [SerializeField] private float shakeDuration; [Header("Отдача")] [SerializeField] private float zOffset = -2; [SerializeField] private float recoilDuration = 0.6f; [SerializeField] private AnimationCurve recoilCurve; private Vector3 _originalPosition; private bool initialized = false; private CancellationTokenSource _shakeCancellation; private CancellationTokenSource _recoilCancellation; private float _currentShakeX; private float _currentShakeY; private float _currentRecoilZ; public void Init() { _originalPosition = transform.localPosition; _currentShakeX = 0; _currentShakeY = 0; _currentRecoilZ = _originalPosition.z; UpdateCameraPosition(); G.WeaponModel.Weapons[0].OnShoot += PerformRecoil; G.PlayerStateModel.PlayerHealthComponent.Damaged += PerformShake; initialized = true; } public void PerformShake(float arg) { if (!initialized) return; CameraShake(shakeSpeed, shakeAmplitude, shakeDuration).Forget(); } public void PerformRecoil() { if (!initialized) return; WeaponRecoil(zOffset, recoilDuration, recoilCurve).Forget(); } private async UniTask WeaponRecoil(float zOffset, float duration, AnimationCurve curve) { _recoilCancellation?.Cancel(); _recoilCancellation = new CancellationTokenSource(); float elapsed = 0f; float startZ = _originalPosition.z; float targetZ = startZ + zOffset; try { while (elapsed < duration) { float t = elapsed / duration; float curveValue = curve.Evaluate(t); _currentRecoilZ = Mathf.Lerp(startZ, targetZ, curveValue); UpdateCameraPosition(); elapsed += Time.deltaTime; await UniTask.Yield(_recoilCancellation.Token); } } catch (OperationCanceledException) { // Отмена } finally { _currentRecoilZ = _originalPosition.z; UpdateCameraPosition(); } } private async UniTask CameraShake(float speed, float amplitude, float duration) { _shakeCancellation?.Cancel(); _shakeCancellation = new CancellationTokenSource(); float elapsed = 0f; float randomOffsetX = UnityEngine.Random.Range(0f, 100f); float randomOffsetY = UnityEngine.Random.Range(0f, 100f); float startAmplitude = amplitude; try { while (elapsed < duration) { float currentAmplitude = Mathf.Lerp(startAmplitude, 0f, elapsed / duration); float offsetX = Mathf.PerlinNoise(randomOffsetX + elapsed * speed, 0f); float offsetY = Mathf.PerlinNoise(randomOffsetY + elapsed * speed, 1f); _currentShakeX = (offsetX * 2f - 1f) * currentAmplitude; _currentShakeY = (offsetY * 2f - 1f) * currentAmplitude; UpdateCameraPosition(); elapsed += Time.deltaTime; await UniTask.Yield(_shakeCancellation.Token); } } catch (OperationCanceledException) { // Тряска была отменена } finally { _currentShakeX = 0; _currentShakeY = 0; UpdateCameraPosition(); } } private void UpdateCameraPosition() { transform.localPosition = new Vector3( _originalPosition.x + _currentShakeX, _originalPosition.y + _currentShakeY, _currentRecoilZ ); } private void OnDestroy() { _shakeCancellation?.Cancel(); _shakeCancellation?.Dispose(); _recoilCancellation?.Cancel(); _recoilCancellation?.Dispose(); if (G.WeaponModel != null) G.WeaponModel.Weapons[0].OnShoot -= PerformRecoil; if (G.PlayerStateModel != null) G.PlayerStateModel.PlayerHealthComponent.Damaged -= PerformShake; } }