149 lines
4.3 KiB
C#
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(AbstractProjectile arg)
|
|
{
|
|
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;
|
|
}
|
|
} |