149 lines
4.3 KiB
C#

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;
}
}