diff --git a/Assets/_Project/Scripts/Player.cs b/Assets/_Project/Scripts/Player.cs index 851dd39..056ca79 100644 --- a/Assets/_Project/Scripts/Player.cs +++ b/Assets/_Project/Scripts/Player.cs @@ -1,24 +1,35 @@ -using _Project.Scripts.PlayerUtils; -using _Project.Scripts.Utilities; +using UnityEngine; +using _Project.Scripts.Utilities; -namespace _Project.Scripts +namespace _Project.Scripts.PlayerUtils { public class Player : MonoSingleton { - private PlayerMovement movement; - public PlayerControls controls { get; set; } + public PlayerControls Controls { get; private set; } + + public PlayerMovement Movement { get; private set; } + public PlayerCamera CameraController { get; private set; } + public PlayerCombat Combat { get; private set; } + public PlayerSound Sound { get; private set; } + public PlayerUI UI { get; private set; } // Новый модуль - void Awake() + protected override void Awake() { base.Awake(); + + Controls = new PlayerControls(); - movement = GetComponent(); - controls = GetComponent(); + Movement = GetComponent(); + CameraController = GetComponent(); + Combat = GetComponent(); + Sound = GetComponent(); + UI = GetComponent(); // Инициализация + + Cursor.lockState = CursorLockMode.Locked; + Cursor.visible = false; } - void Update() - { - - } + private void OnEnable() => Controls.Player.Enable(); + private void OnDisable() => Controls.Player.Disable(); } } \ No newline at end of file diff --git a/Assets/_Project/Scripts/PlayerUtils/PlayerCamera.cs b/Assets/_Project/Scripts/PlayerUtils/PlayerCamera.cs new file mode 100644 index 0000000..84548a8 --- /dev/null +++ b/Assets/_Project/Scripts/PlayerUtils/PlayerCamera.cs @@ -0,0 +1,118 @@ +using UnityEngine; +using UnityEngine.InputSystem; + +namespace _Project.Scripts.PlayerUtils +{ + public class PlayerCamera : MonoBehaviour + { + [Header("Камера и Настройки")] + [SerializeField] private Transform cameraPosition; + [SerializeField] private float mouseSensitivity = 0.1f; + [SerializeField] private float maxLookAngle = 80f; + + [Header("Высота камеры")] + [SerializeField] private float standingCameraY = 1.6f; + [SerializeField] private float crouchingCameraY = 0.8f; + [SerializeField] private float crouchTransitionSpeed = 15f; + [SerializeField] private float slideCameraTiltAngle = 4f; + + [Header("Тряска Камеры (Headbob)")] + public bool enableHeadBob = true; + [SerializeField] private float walkBobSpeed = 12f; + [SerializeField] private float walkBobAmount = 0.04f; + [SerializeField] private float sprintBobSpeed = 15f; + [SerializeField] private float sprintBobAmount = 0.07f; + [SerializeField] private float impactRecoverySpeed = 6f; + + private PlayerControls controls; + private PlayerMovement movement; + + private float cameraPitch = 0f; + private float yRotation = 0f; + private float currentCameraTilt = 0f; + private float baseCameraY; + + private float bobTimer = 0f; + private Vector3 bobOffset = Vector3.zero; + private float currentImpactOffset = 0f; + private bool stepTaken = false; + + private void Start() + { + controls = Player.Instance.Controls; + movement = Player.Instance.Movement; + + mouseSensitivity = PlayerPrefs.GetFloat("MouseSensitivity", mouseSensitivity); + baseCameraY = standingCameraY; + yRotation = transform.eulerAngles.y; + } + + private void Update() + { + HandleMouseLook(); + HandleCameraAndBobbing(); + } + + private void HandleMouseLook() + { + Vector2 lookInput = controls.Player.Look.ReadValue(); + + cameraPitch -= lookInput.y * mouseSensitivity; + cameraPitch = Mathf.Clamp(cameraPitch, -maxLookAngle, maxLookAngle); + + float targetTilt = movement.IsSliding ? -slideCameraTiltAngle : 0f; + currentCameraTilt = Mathf.Lerp(currentCameraTilt, targetTilt, Time.deltaTime * 10f); + + cameraPosition.localRotation = Quaternion.Euler(cameraPitch, 0f, currentCameraTilt); + + yRotation += lookInput.x * mouseSensitivity; + transform.rotation = Quaternion.Euler(0f, yRotation, 0f); + } + + private void HandleCameraAndBobbing() + { + bool isLowered = movement.IsCrouching || movement.IsSliding; + float targetCamY = isLowered ? crouchingCameraY : standingCameraY; + baseCameraY = Mathf.Lerp(baseCameraY, targetCamY, Time.deltaTime * crouchTransitionSpeed); + + bool isMoving = movement.MoveInput.sqrMagnitude > 0.01f && movement.IsGrounded && !movement.IsSliding; + + if (isMoving && enableHeadBob) + { + float speed = movement.IsSprinting ? sprintBobSpeed : walkBobSpeed; + float amount = movement.IsSprinting ? sprintBobAmount : walkBobAmount; + + bobTimer += Time.deltaTime * speed; + bobOffset.x = Mathf.Cos(bobTimer * 0.5f) * amount; + bobOffset.y = Mathf.Sin(bobTimer) * amount; + + if (Mathf.Sin(bobTimer) <= -0.9f && !stepTaken) + { + // ВЫЗОВ ЗВУКА ИЗ НОВОГО МОДУЛЯ + Player.Instance.Sound.PlayFootstep(movement.IsSprinting); + stepTaken = true; + } + else if (Mathf.Sin(bobTimer) > -0.2f) stepTaken = false; + } + else + { + bobTimer = 0f; + stepTaken = false; + bobOffset = Vector3.Lerp(bobOffset, Vector3.zero, Time.deltaTime * 10f); + } + + currentImpactOffset = Mathf.Lerp(currentImpactOffset, 0f, Time.deltaTime * impactRecoverySpeed); + cameraPosition.localPosition = new Vector3(bobOffset.x, baseCameraY + bobOffset.y - currentImpactOffset, 0f); + } + + public void ApplyRecoil(float amount) + { + cameraPitch -= amount; + } + + public void TriggerJumpImpact() + { + if (enableHeadBob) currentImpactOffset = 0.1f; + } + } +} \ No newline at end of file diff --git a/Assets/_Project/Scripts/PlayerUtils/PlayerCamera.cs.meta b/Assets/_Project/Scripts/PlayerUtils/PlayerCamera.cs.meta new file mode 100644 index 0000000..4c9abf4 --- /dev/null +++ b/Assets/_Project/Scripts/PlayerUtils/PlayerCamera.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: bbe0b1999f6642cdb1f20dadcd7c0934 +timeCreated: 1790100214 \ No newline at end of file diff --git a/Assets/_Project/Scripts/PlayerUtils/PlayerCombat.cs b/Assets/_Project/Scripts/PlayerUtils/PlayerCombat.cs new file mode 100644 index 0000000..5fb1901 --- /dev/null +++ b/Assets/_Project/Scripts/PlayerUtils/PlayerCombat.cs @@ -0,0 +1,77 @@ +using UnityEngine; +using UnityEngine.InputSystem; + +namespace _Project.Scripts.PlayerUtils +{ + public class PlayerCombat : MonoBehaviour + { + [Header("Оружие и Стрельба (Hitscan)")] + [SerializeField] private float damage = 25f; + [SerializeField] private float fireRate = 0.12f; + [SerializeField] private float weaponRange = 100f; + [SerializeField] private LayerMask hitLayers = ~0; + [SerializeField] private bool isAutomatic = true; + [SerializeField] private float recoilKick = 1.2f; + + [Header("Эффекты (без звука)")] + [SerializeField] private ParticleSystem muzzleFlash; + [SerializeField] private GameObject impactPrefab; + + [Header("Ссылки")] + [SerializeField] private Transform cameraPosition; + + private PlayerControls controls; + private float shootTimer = 0f; + private bool isShootingInput = false; + + private void Start() + { + controls = Player.Instance.Controls; + + controls.Player.Attack.started += _ => isShootingInput = true; + controls.Player.Attack.canceled += _ => isShootingInput = false; + } + + private void Update() + { + if (shootTimer > 0f) shootTimer -= Time.deltaTime; + + bool shootPressed = isShootingInput || (Mouse.current != null && (isAutomatic ? Mouse.current.leftButton.isPressed : Mouse.current.leftButton.wasPressedThisFrame)); + + if (shootPressed && shootTimer <= 0f) + { + Shoot(); + } + } + + private void Shoot() + { + shootTimer = fireRate; + + // ВЫЗОВ ЗВУКА ИЗ НОВОГО МОДУЛЯ + Player.Instance.Sound.PlayShoot(); + + if (muzzleFlash != null) muzzleFlash.Play(); + Player.Instance.CameraController.ApplyRecoil(recoilKick); + + Ray ray = new Ray(cameraPosition.position, cameraPosition.forward); + if (Physics.Raycast(ray, out RaycastHit hit, weaponRange, hitLayers, QueryTriggerInteraction.Ignore)) + { + if (hit.collider.TryGetComponent(out var damageable)) + { + damageable.TakeDamage(damage); + } + else if (hit.rigidbody != null) + { + hit.rigidbody.AddForceAtPosition(-hit.normal * (damage * 10f), hit.point, ForceMode.Impulse); + } + + if (impactPrefab != null) + { + GameObject impact = Instantiate(impactPrefab, hit.point + (hit.normal * 0.001f), Quaternion.LookRotation(hit.normal)); + Destroy(impact, 3f); + } + } + } + } +} \ No newline at end of file diff --git a/Assets/_Project/Scripts/PlayerUtils/PlayerCombat.cs.meta b/Assets/_Project/Scripts/PlayerUtils/PlayerCombat.cs.meta new file mode 100644 index 0000000..224673a --- /dev/null +++ b/Assets/_Project/Scripts/PlayerUtils/PlayerCombat.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d9d6178191654e72a147db739b22275f +timeCreated: 1790100245 \ No newline at end of file diff --git a/Assets/_Project/Scripts/PlayerUtils/PlayerMovement.cs b/Assets/_Project/Scripts/PlayerUtils/PlayerMovement.cs index 487298a..c33418c 100644 --- a/Assets/_Project/Scripts/PlayerUtils/PlayerMovement.cs +++ b/Assets/_Project/Scripts/PlayerUtils/PlayerMovement.cs @@ -1,107 +1,249 @@ -using _Project.Scripts.Utilities; using UnityEngine; +using UnityEngine.InputSystem; namespace _Project.Scripts.PlayerUtils { - public class PlayerMovement : MonoSingleton + [RequireComponent(typeof(Rigidbody), typeof(CapsuleCollider))] + public class PlayerMovement : MonoBehaviour { - [Header("Камера и Мышь")] - [SerializeField] private Transform cameraPosition; - [SerializeField] private float mouseSensitivity = 0.1f; - [SerializeField] private float maxLookAngle = 80f; + [Header("Скорости передвижения (м/с)")] + [SerializeField] private float moveSpeed = 6.5f; + [SerializeField] private float sprintSpeed = 9.5f; + [SerializeField] private float crouchSpeed = 3f; - [Header("Скорости передвижения (м/с)")] - [SerializeField] private float moveSpeed = 6.5f; // Обычный бег (~250 u/s) - [SerializeField] private float sprintSpeed = 9.5f; // Спринт (~360 u/s) - [SerializeField] private float crouchSpeed = 3f; + [Header("Параметры Подката (Slide)")] + [SerializeField] private float slideInitialSpeed = 15f; + [SerializeField] private float slideDuration = 0.85f; + [SerializeField] private float slideFriction = 2.8f; + [SerializeField] private float slideCooldown = 0.35f; - [Header("Параметры Подката (Slide)")] - [Tooltip("Начальная взрывная скорость подката")] - [SerializeField] private float slideInitialSpeed = 15f; // (~580 u/s) - [Tooltip("Длительность скольжения в секундах")] - [SerializeField] private float slideDuration = 0.85f; - [Tooltip("Трение коленей при подкате")] - [SerializeField] private float slideFriction = 2.8f; - [Tooltip("Наклон камеры набок при подкате (градусы)")] - [SerializeField] private float slideCameraTiltAngle = 4f; - [SerializeField] private float slideCooldown = 0.35f; + [Header("Физика CS / Source (Bunnyhop & Strafe)")] + [SerializeField] private float groundFriction = 5f; + [SerializeField] private float groundAcceleration = 10f; + [SerializeField] private float airAcceleration = 80f; + [SerializeField] private float airCap = 2.0f; + [SerializeField] private bool showSpeedometer = true; - [Header("Физика CS / Source (Bunnyhop & Strafe)")] - [Tooltip("Трение на земле (гасит скорость, если не успели прыгнуть вовремя)")] - [SerializeField] private float groundFriction = 5f; - [Tooltip("Ускорение на земле")] - [SerializeField] private float groundAcceleration = 10f; - [Tooltip("Сила набора скорости в воздухе при стрейфах (sv_airaccelerate)")] - [SerializeField] private float airAcceleration = 80f; - [Tooltip("Лимит добавления скорости в воздухе")] - [SerializeField] private float airCap = 2.0f; - [Tooltip("Показывать спидометр на экране")] - [SerializeField] private bool showSpeedometer = true; + [Header("Гравитация и Прыжок")] + [SerializeField] private float jumpHeight = 1.35f; + [SerializeField] private float gravity = -25f; + [SerializeField] private float fallMultiplier = 1.8f; + [SerializeField] private LayerMask groundLayer; + [SerializeField] private float groundCheckDistance = 0.12f; + [SerializeField] private float jumpBufferTime = 0.12f; - [Header("Гравитация и Прыжок")] - [SerializeField] private float jumpHeight = 1.35f; - [SerializeField] private float gravity = -25f; - [SerializeField] private float fallMultiplier = 1.8f; - [SerializeField] private LayerMask groundLayer; - [SerializeField] private float groundCheckDistance = 0.12f; - [Tooltip("Буфер прыжка (время поблажки для колесика мыши)")] - [SerializeField] private float jumpBufferTime = 0.12f; + [Header("Коллайдер (Приседание)")] + [SerializeField] private float standingHeight = 2f; + [SerializeField] private float crouchingHeight = 1.05f; + [SerializeField] private float crouchTransitionSpeed = 15f; - [Header("Параметры приседания")] - [SerializeField] private float standingHeight = 2f; - [SerializeField] private float crouchingHeight = 1.05f; - [SerializeField] private float standingCameraY = 1.6f; - [SerializeField] private float crouchingCameraY = 0.8f; - [SerializeField] private float crouchTransitionSpeed = 15f; - - private Rigidbody rb; - private CapsuleCollider col; + private Rigidbody rb; + private CapsuleCollider col; + private PlayerControls controls; - private Vector2 moveInput; - private Vector2 lookInput; - private Vector3 horizontalVelocity = Vector3.zero; - private float verticalVelocity = 0f; - - private bool isGrounded; - private bool wasGrounded; - private bool isSprinting = false; - private bool isCrouching = false; - private float jumpBufferTimer = 0f; + public bool IsGrounded { get; private set; } + public bool IsSprinting { get; private set; } + public bool IsCrouching { get; private set; } + public bool IsSliding { get; private set; } + public Vector2 MoveInput { get; private set; } + public float CurrentSpeed => horizontalVelocity.magnitude; + private bool wasGrounded; + private float jumpBufferTimer = 0f; + private Vector3 horizontalVelocity = Vector3.zero; + private float verticalVelocity = 0f; - // Подкат - private bool isSliding = false; - private float slideTimer = 0f; - private float slideCurrentSpeed = 0f; - private Vector3 slideDirection = Vector3.zero; - private float slideCooldownTimer = 0f; - - private PlayerControls controls; - - void Awake() + private float slideTimer = 0f; + private float slideCurrentSpeed = 0f; + private Vector3 slideDirection = Vector3.zero; + private float slideCooldownTimer = 0f; + + private void Start() { - base.Awake(); - rb = GetComponent(); col = GetComponent(); - mouseSensitivity = PlayerPrefs.GetFloat("MouseSensitivity", mouseSensitivity); + rb.useGravity = false; rb.freezeRotation = true; rb.interpolation = RigidbodyInterpolation.None; + + controls = Player.Instance.Controls; + + controls.Player.Sprint.started += _ => IsSprinting = true; + controls.Player.Sprint.canceled += _ => IsSprinting = false; - controls = new PlayerControls(); - - controls = Player.Instance.controls; - // Спринт - controls.Player.Sprint.started += ctx => isSprinting = true; - controls.Player.Sprint.canceled += ctx => isSprinting = false; - - /* // Присед и Подкат - controls.Player.Crouch.started += ctx => OnCrouchPressed(); - controls.Player.Crouch.canceled += ctx => OnCrouchReleased(); - - // Прыжок на Пробел (через буфер) - controls.Player.Jump.performed += ctx => QueueJump(); - */ + controls.Player.Crouch.started += _ => OnCrouchPressed(); + controls.Player.Crouch.canceled += _ => OnCrouchReleased(); + + controls.Player.Jump.performed += _ => QueueJump(); } + + private void Update() + { + MoveInput = controls.Player.Move.ReadValue(); + + if (jumpBufferTimer > 0f) jumpBufferTimer -= Time.deltaTime; + if (slideCooldownTimer > 0f) slideCooldownTimer -= Time.deltaTime; + + if (Mouse.current != null && Mathf.Abs(Mouse.current.scroll.y.ReadValue()) > 0.1f) + QueueJump(); + + HandleColliderHeight(); + } + + private void FixedUpdate() + { + CheckGrounded(); + CalculateCustomGravity(); + + if (jumpBufferTimer > 0f && IsGrounded) + ExecuteJump(); + + ApplyMovement(); + + rb.linearVelocity = new Vector3(horizontalVelocity.x, verticalVelocity, horizontalVelocity.z); + wasGrounded = IsGrounded; + } + + private void HandleColliderHeight() + { + bool isLowered = IsCrouching || IsSliding; + float targetHeight = isLowered ? crouchingHeight : standingHeight; + col.height = Mathf.Lerp(col.height, targetHeight, Time.deltaTime * crouchTransitionSpeed); + col.center = new Vector3(col.center.x, col.height * 0.5f, col.center.z); + } + + private void QueueJump() => jumpBufferTimer = jumpBufferTime; + + private void ExecuteJump() + { + if (IsSliding) + { + horizontalVelocity = slideDirection * slideCurrentSpeed; + StopSlide(); + } + + verticalVelocity = Mathf.Sqrt(jumpHeight * -2f * gravity); + jumpBufferTimer = 0f; + + Player.Instance.CameraController.TriggerJumpImpact(); + + // ВЫЗОВ ЗВУКА ИЗ НОВОГО МОДУЛЯ + Player.Instance.Sound.PlayJump(); + } + + private void ApplyMovement() + { + if (IsSliding) + { + slideCurrentSpeed = Mathf.Lerp(slideCurrentSpeed, crouchSpeed, Time.fixedDeltaTime * slideFriction); + slideTimer -= Time.fixedDeltaTime; + + Vector3 steerDir = (transform.forward * MoveInput.y + transform.right * MoveInput.x).normalized; + if (steerDir.sqrMagnitude > 0.01f) + slideDirection = Vector3.Slerp(slideDirection, steerDir, Time.fixedDeltaTime * 2.5f); + + horizontalVelocity = slideDirection * slideCurrentSpeed; + + if (slideTimer <= 0f || slideCurrentSpeed <= crouchSpeed + 0.5f) + { + StopSlide(); + IsCrouching = controls.Player.Crouch.IsPressed(); + } + return; + } + + Vector3 wishDir = (transform.forward * MoveInput.y + transform.right * MoveInput.x); + wishDir.y = 0f; + wishDir.Normalize(); + + float targetSpeed = IsCrouching ? crouchSpeed : (IsSprinting ? sprintSpeed : moveSpeed); + + if (IsGrounded) + { + if (jumpBufferTimer <= 0f) ApplyGroundFriction(); + Accelerate(wishDir, targetSpeed, groundAcceleration); + } + else + { + AirAccelerate(wishDir, targetSpeed, airAcceleration, airCap); + } + } + + private void ApplyGroundFriction() + { + float speed = horizontalVelocity.magnitude; + if (speed < 0.0001f) { horizontalVelocity = Vector3.zero; return; } + float drop = speed * groundFriction * Time.fixedDeltaTime; + horizontalVelocity *= (Mathf.Max(speed - drop, 0f) / speed); + } + + private void Accelerate(Vector3 wishDir, float wishSpeed, float accel) + { + float currentSpeed = Vector3.Dot(horizontalVelocity, wishDir); + float addSpeed = Mathf.Max(wishSpeed - currentSpeed, 0f); + horizontalVelocity += wishDir * Mathf.Min(accel * wishSpeed * Time.fixedDeltaTime, addSpeed); + } + + private void AirAccelerate(Vector3 wishDir, float wishSpeed, float accel, float cap) + { + float currentSpeed = Vector3.Dot(horizontalVelocity, wishDir); + float addSpeed = Mathf.Max(Mathf.Min(wishSpeed, cap) - currentSpeed, 0f); + horizontalVelocity += wishDir * Mathf.Min(accel * wishSpeed * Time.fixedDeltaTime, addSpeed); + } + + private void CheckGrounded() + { + float checkRadius = col.radius * 0.85f; + float bottomOffset = (col.height * 0.5f) - checkRadius; + Vector3 rayOrigin = transform.position + col.center - (Vector3.up * bottomOffset) + (Vector3.up * 0.05f); + + IsGrounded = Physics.SphereCast(rayOrigin, checkRadius, Vector3.down, out _, groundCheckDistance + 0.05f, groundLayer, QueryTriggerInteraction.Ignore); + + if (!wasGrounded && IsGrounded) + { + // ВЫЗОВ ЗВУКА ИЗ НОВОГО МОДУЛЯ + Player.Instance.Sound.PlayLand(); + } + + if (IsGrounded && jumpBufferTimer <= 0f && rb.linearVelocity.magnitude > 0.01f && horizontalVelocity == Vector3.zero) + horizontalVelocity = new Vector3(rb.linearVelocity.x, 0f, rb.linearVelocity.z); + } + + private void CalculateCustomGravity() + { + if (IsGrounded && verticalVelocity < 0f) verticalVelocity = -2f; + else verticalVelocity += ((verticalVelocity < 0f) ? (gravity * fallMultiplier) : gravity) * Time.fixedDeltaTime; + } + + private void OnCrouchPressed() + { + if (IsSprinting && IsGrounded && MoveInput.y > 0.2f && slideCooldownTimer <= 0f) StartSlide(); + else IsCrouching = true; + } + + private void OnCrouchReleased() + { + IsCrouching = false; + if (IsSliding) StopSlide(); + } + + private void StartSlide() + { + IsSliding = true; + slideTimer = slideDuration; + slideCurrentSpeed = slideInitialSpeed; + slideDirection = (transform.forward * MoveInput.y + transform.right * MoveInput.x).normalized; + if (slideDirection.sqrMagnitude < 0.01f) slideDirection = transform.forward; + + // ВЫЗОВ ЗВУКА ИЗ НОВОГО МОДУЛЯ + Player.Instance.Sound.PlaySlide(); + } + + private void StopSlide() + { + IsSliding = false; + slideCooldownTimer = slideCooldown; + } + } } \ No newline at end of file diff --git a/Assets/_Project/Scripts/PlayerUtils/PlayerSound.cs b/Assets/_Project/Scripts/PlayerUtils/PlayerSound.cs new file mode 100644 index 0000000..abfe195 --- /dev/null +++ b/Assets/_Project/Scripts/PlayerUtils/PlayerSound.cs @@ -0,0 +1,61 @@ +using UnityEngine; + +namespace _Project.Scripts.PlayerUtils +{ + [RequireComponent(typeof(AudioSource))] + public class PlayerSound : MonoBehaviour + { + [Header("Настройки AudioSource")] + [SerializeField] private AudioSource audioSource; + + [Header("Звуки Передвижения")] + [SerializeField] private AudioClip[] footstepSounds; + [SerializeField] private AudioClip jumpSound; + [SerializeField] private AudioClip landSound; + [SerializeField] private AudioClip slideSound; + + [Header("Звуки Оружия")] + [SerializeField] private AudioClip shootSound; + + private void Reset() + { + // Автоматически подцепит AudioSource при добавлении скрипта на объект + audioSource = GetComponent(); + } + + public void PlayFootstep(bool isSprinting) + { + if (footstepSounds == null || footstepSounds.Length == 0) return; + + int randomIndex = Random.Range(0, footstepSounds.Length); + + // Немного меняем высоту звука для разнообразия + audioSource.pitch = Random.Range(0.9f, 1.1f); + + float volume = isSprinting ? 0.6f : 0.4f; + audioSource.PlayOneShot(footstepSounds[randomIndex], volume); + + audioSource.pitch = 1f; // Сбрасываем питч обратно + } + + public void PlayJump() + { + if (jumpSound != null) audioSource.PlayOneShot(jumpSound, 0.7f); + } + + public void PlayLand() + { + if (landSound != null) audioSource.PlayOneShot(landSound, 0.5f); + } + + public void PlaySlide() + { + if (slideSound != null) audioSource.PlayOneShot(slideSound, 0.7f); + } + + public void PlayShoot() + { + if (shootSound != null) audioSource.PlayOneShot(shootSound, 0.8f); + } + } +} \ No newline at end of file diff --git a/Assets/_Project/Scripts/PlayerUtils/PlayerSound.cs.meta b/Assets/_Project/Scripts/PlayerUtils/PlayerSound.cs.meta new file mode 100644 index 0000000..fa797f5 --- /dev/null +++ b/Assets/_Project/Scripts/PlayerUtils/PlayerSound.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 995d210808ca417ca08ca58fab7f8624 +timeCreated: 1790100334 \ No newline at end of file diff --git a/Assets/_Project/Scripts/PlayerUtils/PlayerUI.cs b/Assets/_Project/Scripts/PlayerUtils/PlayerUI.cs new file mode 100644 index 0000000..846167f --- /dev/null +++ b/Assets/_Project/Scripts/PlayerUtils/PlayerUI.cs @@ -0,0 +1,53 @@ +using UnityEngine; + +namespace _Project.Scripts.PlayerUtils +{ + public class PlayerUI : MonoBehaviour + { + [Header("Настройки Интерфейса")] + [SerializeField] private bool showSpeedometer = true; + + private PlayerMovement movement; + + private void Start() + { + // Берем ссылку на компонент передвижения через фасад Player + movement = Player.Instance.Movement; + } + + private void OnGUI() + { + if (!showSpeedometer || movement == null) return; + + // Читаем публичное свойство скорости (мы добавим его ниже) + float speed = movement.CurrentSpeed; + float csUnits = speed * 38.5f; + + GUIStyle style = new GUIStyle + { + fontSize = 24, + fontStyle = FontStyle.Bold + }; + + string state = ""; + + // Настраиваем цвет и текст состояния + if (movement.IsSliding) + { + style.normal.textColor = Color.yellow; + state = " [SLIDE]"; + } + else if (!movement.IsGrounded) + { + style.normal.textColor = Color.green; + state = " [AIR]"; + } + else + { + style.normal.textColor = Color.white; + } + + GUI.Label(new Rect(30, 30, 500, 50), $"Speed: {speed:F1} m/s ({csUnits:F0} u/s){state}", style); + } + } +} \ No newline at end of file diff --git a/Assets/_Project/Scripts/PlayerUtils/PlayerUI.cs.meta b/Assets/_Project/Scripts/PlayerUtils/PlayerUI.cs.meta new file mode 100644 index 0000000..b09610f --- /dev/null +++ b/Assets/_Project/Scripts/PlayerUtils/PlayerUI.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8184cc49f30946f4802bbf1bbe841e2f +timeCreated: 1790100443 \ No newline at end of file diff --git a/Assets/_Project/Scripts/ColorChanger.cs b/Assets/_Project/Scripts/Utilities/ColorChanger.cs similarity index 100% rename from Assets/_Project/Scripts/ColorChanger.cs rename to Assets/_Project/Scripts/Utilities/ColorChanger.cs diff --git a/Assets/_Project/Scripts/ColorChanger.cs.meta b/Assets/_Project/Scripts/Utilities/ColorChanger.cs.meta similarity index 100% rename from Assets/_Project/Scripts/ColorChanger.cs.meta rename to Assets/_Project/Scripts/Utilities/ColorChanger.cs.meta diff --git a/Assets/_Project/Scripts/GameBootstrapper.cs b/Assets/_Project/Scripts/Utilities/GameBootstrapper.cs similarity index 100% rename from Assets/_Project/Scripts/GameBootstrapper.cs rename to Assets/_Project/Scripts/Utilities/GameBootstrapper.cs diff --git a/Assets/_Project/Scripts/GameBootstrapper.cs.meta b/Assets/_Project/Scripts/Utilities/GameBootstrapper.cs.meta similarity index 100% rename from Assets/_Project/Scripts/GameBootstrapper.cs.meta rename to Assets/_Project/Scripts/Utilities/GameBootstrapper.cs.meta