Added PlayerMovement and other
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Отзывчивый аркадный FPS-контроллер на CharacterController.
|
||||
/// Move() вызывается в Update — движение плавное на частоте кадров без интерполяции.
|
||||
/// Step offset и slope limit — встроенные в CharacterController.
|
||||
/// Init() вызывается из PlayerFacade только у владельца.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(CharacterController))]
|
||||
public class PlayerMovement : MonoBehaviour
|
||||
{
|
||||
[Header("Speed")]
|
||||
[SerializeField] private float walkSpeed = 8f;
|
||||
[SerializeField] private float sprintSpeed = 12f;
|
||||
|
||||
[Header("Acceleration")]
|
||||
[SerializeField] private float groundAcceleration = 80f; // как быстро набираем скорость на земле
|
||||
[SerializeField] private float airAcceleration = 30f; // управление в воздухе
|
||||
[SerializeField] private float groundDeceleration = 60f; // трение/торможение при отпускании клавиш
|
||||
|
||||
[Header("Jump")]
|
||||
[SerializeField] private float jumpHeight = 1.6f; // желаемая высота прыжка в метрах
|
||||
[SerializeField] private float coyoteTime = 0.12f; // окно прыжка после схода с края
|
||||
[SerializeField] private float jumpBufferTime = 0.12f; // окно "запоминания" нажатия прыжка
|
||||
|
||||
[Header("Gravity")]
|
||||
[SerializeField] private float gravity = -28f; // выше реального -9.81 = более аркадно/отзывчиво
|
||||
[SerializeField] private float fallMultiplier = 1.6f; // падаем тяжелее — меньше "плавучести"
|
||||
[SerializeField] private float lowJumpMultiplier = 2.5f; // отпустил прыжок раньше = короткий прыжок
|
||||
[SerializeField] private float maxFallSpeed = -40f; // ограничение скорости падения
|
||||
|
||||
[Header("Ground / Steps / Slopes")]
|
||||
[SerializeField] private float stepOffset = 0.4f; // высота ступеньки на которую можно зайти
|
||||
[SerializeField] private float slopeLimit = 50f; // макс. угол склона
|
||||
[SerializeField] private float groundStickForce = -4f; // прижим к земле чтобы не отрывало на спусках
|
||||
|
||||
[Header("Look")]
|
||||
[SerializeField] private float mouseSensitivity = 2f;
|
||||
[SerializeField] private Transform cameraRoot;
|
||||
[SerializeField] private float minPitch = -89f;
|
||||
[SerializeField] private float maxPitch = 89f;
|
||||
|
||||
private CharacterController controller;
|
||||
private PlayerHealth playerHealth;
|
||||
|
||||
private bool isActive;
|
||||
|
||||
// Горизонтальная скорость (x, z) — храним между кадрами для инерции
|
||||
private Vector3 horizontalVelocity;
|
||||
private float verticalVelocity;
|
||||
|
||||
// Look
|
||||
private float pitch;
|
||||
|
||||
// Jump timers
|
||||
private float coyoteCounter;
|
||||
private float jumpBufferCounter;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
controller = GetComponent<CharacterController>();
|
||||
playerHealth = GetComponent<PlayerHealth>();
|
||||
|
||||
// Встроенные возможности CharacterController — задаём из инспектора
|
||||
controller.stepOffset = stepOffset;
|
||||
controller.slopeLimit = slopeLimit;
|
||||
|
||||
// // Прячем курсор — только у владельца, Init для не-владельца не вызывается
|
||||
// Cursor.lockState = CursorLockMode.Locked;
|
||||
// Cursor.visible = false;
|
||||
|
||||
isActive = true;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!isActive) return;
|
||||
|
||||
// Esc — освободить курсор (удобно в редакторе)
|
||||
// if (Input.GetKeyDown(KeyCode.Escape))
|
||||
// {
|
||||
// Cursor.lockState = CursorLockMode.None;
|
||||
// Cursor.visible = true;
|
||||
// }
|
||||
|
||||
if (IsMovementBlocked())
|
||||
{
|
||||
// Мёртвый стоит — гасим горизонталь, но гравитацию оставляем
|
||||
horizontalVelocity = Vector3.zero;
|
||||
ApplyGravityOnly();
|
||||
return;
|
||||
}
|
||||
|
||||
HandleLookYaw(); // поворот тела по горизонтали — здесь, камера-ребёнок следует за ним
|
||||
UpdateJumpTimers();
|
||||
HandleHorizontal();
|
||||
HandleJumpAndGravity();
|
||||
|
||||
// Один общий Move за кадр — и горизонталь, и вертикаль
|
||||
Vector3 velocity = horizontalVelocity + Vector3.up * verticalVelocity;
|
||||
controller.Move(velocity * Time.deltaTime);
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (!isActive) return;
|
||||
|
||||
// Наклон камеры (pitch) — после движения, чтобы не было микро-дрожи
|
||||
cameraRoot.localRotation = Quaternion.Euler(pitch, 0f, 0f);
|
||||
}
|
||||
|
||||
private bool IsMovementBlocked()
|
||||
{
|
||||
return playerHealth != null && playerHealth.isDead.Value;
|
||||
}
|
||||
|
||||
// ── LOOK ──────────────────────────────────────────────
|
||||
|
||||
private void HandleLookYaw()
|
||||
{
|
||||
float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity;
|
||||
transform.Rotate(Vector3.up * mouseX);
|
||||
|
||||
float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity;
|
||||
pitch = Mathf.Clamp(pitch - mouseY, minPitch, maxPitch);
|
||||
}
|
||||
|
||||
// ── HORIZONTAL MOVEMENT ───────────────────────────────
|
||||
|
||||
private void HandleHorizontal()
|
||||
{
|
||||
Vector3 input = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical"));
|
||||
input = Vector3.ClampMagnitude(input, 1f);
|
||||
|
||||
Vector3 wishDirection = transform.right * input.x + transform.forward * input.z;
|
||||
bool isSprinting = Input.GetKey(KeyCode.LeftShift);
|
||||
float targetSpeed = isSprinting ? sprintSpeed : walkSpeed;
|
||||
|
||||
bool grounded = controller.isGrounded;
|
||||
|
||||
if (wishDirection.sqrMagnitude > 0.01f)
|
||||
{
|
||||
// Есть ввод — разгоняемся к целевой скорости
|
||||
float acceleration = grounded ? groundAcceleration : airAcceleration;
|
||||
Vector3 targetVelocity = wishDirection * targetSpeed;
|
||||
horizontalVelocity = Vector3.MoveTowards(
|
||||
horizontalVelocity,
|
||||
targetVelocity,
|
||||
acceleration * Time.deltaTime
|
||||
);
|
||||
}
|
||||
else if (grounded)
|
||||
{
|
||||
// Нет ввода на земле — тормозим до нуля (трение)
|
||||
horizontalVelocity = Vector3.MoveTowards(
|
||||
horizontalVelocity,
|
||||
Vector3.zero,
|
||||
groundDeceleration * Time.deltaTime
|
||||
);
|
||||
}
|
||||
// В воздухе без ввода — сохраняем импульс (момент инерции)
|
||||
}
|
||||
|
||||
// ── JUMP & GRAVITY ────────────────────────────────────
|
||||
|
||||
private void UpdateJumpTimers()
|
||||
{
|
||||
if (controller.isGrounded)
|
||||
coyoteCounter = coyoteTime;
|
||||
else
|
||||
coyoteCounter -= Time.deltaTime;
|
||||
|
||||
if (Input.GetButtonDown("Jump"))
|
||||
jumpBufferCounter = jumpBufferTime;
|
||||
else
|
||||
jumpBufferCounter -= Time.deltaTime;
|
||||
}
|
||||
|
||||
private void HandleJumpAndGravity()
|
||||
{
|
||||
bool grounded = controller.isGrounded;
|
||||
|
||||
// Прижим к земле — иначе на спусках и ступеньках вниз отрывает в "полёт"
|
||||
if (grounded && verticalVelocity < 0f)
|
||||
verticalVelocity = groundStickForce;
|
||||
|
||||
// Прыжок: есть буфер нажатия И есть coyote-окно
|
||||
if (jumpBufferCounter > 0f && coyoteCounter > 0f)
|
||||
{
|
||||
jumpBufferCounter = 0f;
|
||||
coyoteCounter = 0f;
|
||||
|
||||
// Скорость из желаемой высоты: v = sqrt(2 * h * g)
|
||||
verticalVelocity = Mathf.Sqrt(jumpHeight * -2f * gravity);
|
||||
}
|
||||
|
||||
ApplyGravity(grounded);
|
||||
}
|
||||
|
||||
private void ApplyGravity(bool grounded)
|
||||
{
|
||||
if (grounded && verticalVelocity <= 0f)
|
||||
return; // на земле гравитацию не копим (кроме прыжка вверх)
|
||||
|
||||
float g = gravity;
|
||||
|
||||
if (verticalVelocity < 0f)
|
||||
{
|
||||
g *= fallMultiplier; // падаем — тяжелее
|
||||
}
|
||||
else if (verticalVelocity > 0f && !Input.GetButton("Jump"))
|
||||
{
|
||||
g *= lowJumpMultiplier; // отпустил прыжок — короткий хоп
|
||||
}
|
||||
|
||||
verticalVelocity += g * Time.deltaTime;
|
||||
verticalVelocity = Mathf.Max(verticalVelocity, maxFallSpeed);
|
||||
}
|
||||
|
||||
// Используется когда движение заблокировано (смерть) — только падение
|
||||
private void ApplyGravityOnly()
|
||||
{
|
||||
if (controller == null) return;
|
||||
|
||||
if (controller.isGrounded && verticalVelocity < 0f)
|
||||
verticalVelocity = groundStickForce;
|
||||
else
|
||||
{
|
||||
verticalVelocity += gravity * Time.deltaTime;
|
||||
verticalVelocity = Mathf.Max(verticalVelocity, maxFallSpeed);
|
||||
}
|
||||
|
||||
controller.Move(Vector3.up * verticalVelocity * Time.deltaTime);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user