121 lines
4.5 KiB
C#
121 lines
4.5 KiB
C#
using UnityEngine;
|
|
using DG.Tweening;
|
|
|
|
/// <summary>
|
|
/// Визуальная "живость" рук от первого лица: свэй за движением мыши, покачивание
|
|
/// при ходьбе и сочная отдача при выстреле через DOTween.
|
|
/// Висит на FPV-Hands (активен только у владельца), поэтому проверки IsOwner не нужны.
|
|
/// Всё считается как смещение поверх исходного localPosition/localRotation,
|
|
/// а отдача пишется в отдельные поля — свэй/боб и DOTween не конфликтуют за трансформ.
|
|
/// </summary>
|
|
public class WeaponView : MonoBehaviour
|
|
{
|
|
[Header("Sway (свэй за мышью)")]
|
|
[SerializeField] private float swayAmount = 2f;
|
|
[SerializeField] private float swayMax = 6f;
|
|
[SerializeField] private float swaySmooth = 8f;
|
|
|
|
[Header("Bob (покачивание при ходьбе)")]
|
|
[SerializeField] private float bobSpeed = 9f;
|
|
[SerializeField] private float bobAmount = 0.03f;
|
|
[SerializeField] private float bobSmooth = 10f;
|
|
|
|
[Header("Recoil (отдача при выстреле)")]
|
|
[SerializeField] private float recoilKickback = 0.12f; // назад по Z
|
|
[SerializeField] private float recoilKickUp = 9f; // поворот ствола вверх (X)
|
|
[SerializeField] private float recoilDuration = 0.22f;
|
|
|
|
private Vector3 originPos;
|
|
private Quaternion originRot;
|
|
|
|
private Vector3 swayRotOffset;
|
|
private Vector3 bobPosOffset;
|
|
|
|
private Vector3 recoilPos;
|
|
private Vector3 recoilRot;
|
|
private float bobTimer;
|
|
|
|
private Sequence recoilSequence;
|
|
|
|
private void Awake()
|
|
{
|
|
originPos = transform.localPosition;
|
|
originRot = transform.localRotation;
|
|
}
|
|
|
|
private void LateUpdate()
|
|
{
|
|
ApplySway();
|
|
ApplyBob();
|
|
|
|
transform.localPosition = originPos + bobPosOffset + recoilPos;
|
|
transform.localRotation = originRot * Quaternion.Euler(swayRotOffset + recoilRot);
|
|
}
|
|
|
|
private void ApplySway()
|
|
{
|
|
float _mouseX = Input.GetAxis("Mouse X");
|
|
float _mouseY = Input.GetAxis("Mouse Y");
|
|
|
|
// руки "отстают" от поворота камеры
|
|
float _targetPitch = Mathf.Clamp(-_mouseY * swayAmount, -swayMax, swayMax);
|
|
float _targetYaw = Mathf.Clamp(-_mouseX * swayAmount, -swayMax, swayMax);
|
|
|
|
Vector3 _target = new Vector3(_targetPitch, _targetYaw, 0f);
|
|
swayRotOffset = Vector3.Lerp(swayRotOffset, _target, swaySmooth * Time.deltaTime);
|
|
}
|
|
|
|
private void ApplyBob()
|
|
{
|
|
float _moveMagnitude = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")).magnitude;
|
|
|
|
Vector3 _target = Vector3.zero;
|
|
|
|
if (_moveMagnitude > 0.1f)
|
|
{
|
|
bobTimer += Time.deltaTime * bobSpeed;
|
|
float _x = Mathf.Cos(bobTimer) * bobAmount;
|
|
float _y = Mathf.Abs(Mathf.Sin(bobTimer)) * bobAmount;
|
|
_target = new Vector3(_x, _y, 0f);
|
|
}
|
|
else
|
|
{
|
|
bobTimer = 0f;
|
|
}
|
|
|
|
bobPosOffset = Vector3.Lerp(bobPosOffset, _target, bobSmooth * Time.deltaTime);
|
|
}
|
|
|
|
public void PlayShoot()
|
|
{
|
|
recoilSequence?.Kill();
|
|
|
|
recoilPos = Vector3.zero;
|
|
recoilRot = Vector3.zero;
|
|
|
|
float _kick = recoilDuration * 0.35f;
|
|
float _return = recoilDuration * 0.65f;
|
|
|
|
recoilSequence = DOTween.Sequence().SetTarget(this);
|
|
|
|
// Позиция: резкий отскок назад, затем плавный возврат
|
|
recoilSequence.Append(DOTween.To(() => recoilPos, _v => recoilPos = _v,
|
|
new Vector3(0f, 0f, -recoilKickback), _kick).SetEase(Ease.OutQuad));
|
|
recoilSequence.Append(DOTween.To(() => recoilPos, _v => recoilPos = _v,
|
|
Vector3.zero, _return).SetEase(Ease.OutBack));
|
|
|
|
// Поворот: ствол подбрасывает вверх и возвращает — параллельно позиции
|
|
recoilSequence.Insert(0f, DOTween.To(() => recoilRot, _v => recoilRot = _v,
|
|
new Vector3(-recoilKickUp, 0f, 0f), _kick).SetEase(Ease.OutQuad));
|
|
recoilSequence.Insert(_kick, DOTween.To(() => recoilRot, _v => recoilRot = _v,
|
|
Vector3.zero, _return).SetEase(Ease.OutBack));
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
recoilSequence?.Kill();
|
|
recoilPos = Vector3.zero;
|
|
recoilRot = Vector3.zero;
|
|
}
|
|
}
|