Unity: Исправление проблемы перемещения при прыжке
Устраните проблему перемещения персонажа в Unity при прыжке. Узнайте, почему скорость Rigidbody перезаписывается, и как управлять движением в воздухе шаг за шагом.
Почему я не могу двигаться во время прыжка в Unity?
У меня проблемы с скриптом перемещения персонажа. Вот мой код:
using UnityEngine;
public class PlayerLocomotion : MonoBehaviour
{
PlayerManager playerManager;
AnimatorManager animatorManager;
InputManager inputManager;
Vector3 moveDicrection;
Transform cameraObject;
Rigidbody playerRigidbody;
[Header("Falling")]
public float inAirTimer;
public float leapingVelocity;
public float fallingVelocicy;
public float rayCastHeightOffSet = 0.5f;
public LayerMask groundLayer;
[Header("Movement Flags")]
public bool isSprinting;
public bool isGrounded;
public bool isJumping;
[Header("Movement Speeds")]
public float walkingSpeed = 1.5f;
public float runningSpeed = 5;
public float sprintingSpeed = 7;
public float rotationSpeed = 15;
public float maxDistance = 1;
[Header("Jump Speeds")]
public float jumpHeight = 3;
public float gravityIntensity = -15;
public void Awake()
{
playerManager = GetComponent<PlayerManager>();
animatorManager = GetComponent<AnimatorManager>();
inputManager = GetComponent<InputManager>();
playerRigidbody = GetComponent<Rigidbody>();
cameraObject = Camera.main.transform;
}
public void HandleAllMovement()
{
HanleFallingandLAnding();
if (playerManager.isInteracting)
return;
HandleMovement();
HanleRotation();
}
private void HandleMovement()
{
moveDicrection = cameraObject.forward * inputManager.verticalInput;
moveDicrection = moveDicrection + cameraObject.right * inputManager.horizontalInput;
moveDicrection.Normalize();
moveDicrection.y = 0;
if (isSprinting)
{
moveDicrection = moveDicrection * sprintingSpeed;
}
else
{
if (inputManager.moveAmount >= 0.5f)
{
moveDicrection = moveDicrection * runningSpeed;
}
else
{
moveDicrection = moveDicrection * walkingSpeed;
}
}
Vector3 targetPosition = transform.position + moveDicrection * Time.deltaTime;
playerRigidbody.MovePosition(targetPosition);
}
private void HanleRotation()
{
if(isJumping)
return ;
Vector3 targetDirection = Vector3.zero;
targetDirection = cameraObject.forward * inputManager.verticalInput;
targetDirection = targetDirection + cameraObject.right * inputManager.horizontalInput;
targetDirection.Normalize();
targetDirection.y = 0;
if(targetDirection == Vector3.zero)
targetDirection = transform.forward;
Quaternion targetRotation = Quaternion.LookRotation(targetDirection);
Quaternion playerRotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
transform.rotation = playerRotation;
}
private void HanleFallingandLAnding()
{
RaycastHit hit;
Vector3 rayCastOrigin = transform.position;
rayCastOrigin.y = rayCastOrigin.y + rayCastHeightOffSet;
if (!isGrounded && !isJumping)
{
if(!playerManager.isInteracting)
{
animatorManager.PlayTargetAnimation("Falling", true);
}
inAirTimer = inAirTimer + Time.deltaTime;
playerRigidbody.AddForce(transform.forward * leapingVelocity);
playerRigidbody.AddForce(-Vector3.up * fallingVelocicy * inAirTimer);
}
if (Physics.SphereCast(rayCastOrigin, 0.2f, -Vector3.up, out hit, maxDistance, groundLayer))
{
if (!isGrounded && !playerManager.isInteracting)
{
animatorManager.PlayTargetAnimation("Land", true);
}
inAirTimer = 0;
isGrounded = true;
}
else
{
isGrounded = false;
}
}
public void HandleJumping()
{
if (isGrounded)
{
animatorManager.animator.SetBool("isJumping", true);
animatorManager.PlayTargetAnimation("Jump", false);
float jumpingVelocity = Mathf.Sqrt(-2 * gravityIntensity * jumpHeight);
Vector3 playerVelocity = moveDicrection;
playerVelocity.y = jumpingVelocity;
playerRigidbody.linearVelocity = playerVelocity;
}
}
}
Я только учусь Unity и не понимаю, почему я не могу двигаться во время прыжка. Я следую учебнику, но что‑то не так. Можете помочь мне выявить проблему в моем коде?
Основная проблема в вашем коде заключается в том, что вы перезаписываете вертикальную скорость прыжка в коде перемещения. Когда вы вызываете playerRigidbody.MovePosition(targetPosition) в HandleMovement(), это влияет на весь вектор скорости, включая вертикальную компоненту, установленную для прыжка. В результате персонаж теряет свой подъемный импульс и сразу же падает.
Ниже объясняется, почему это происходит, и как исправить ситуацию:
Содержание
- Понимание основной проблемы
- Ключевые проблемы в вашем коде
- Решения и лучшие практики
- Исправленная реализация кода
- Альтернативные подходы
- Распространённые ошибки, которых стоит избегать
Понимание основной проблемы
Фундаментальная причина в том, что вы смешиваете методы перемещения, которые конфликтуют друг с другом. Согласно официальной документации Unity, «В большинстве случаев вам не следует напрямую изменять скорость, так как это может привести к нереалистичному поведению – используйте AddForce вместо этого».
При прыжке вы задаёте конкретную вертикальную скорость через playerRigidbody.linearVelocity = playerVelocity;, но сразу же в HandleMovement() вы используете playerRigidbody.MovePosition(targetPosition), которое полностью перезаписывает эту скорость. Как отметил один разработчик на Stack Overflow, «Похоже, ваша функция перемещения создаёт новый вектор скорости и перезаписывает существующий».
Ключевые проблемы в вашем коде
- Перезапись скорости:
HandleMovement()не сохраняет вертикальную скорость во время прыжков. - Отсутствие логики перемещения в воздухе: нет отдельной обработки горизонтального перемещения во время полёта.
- Несогласованное применение физики: смешивание прямого присваивания скорости с вызовами
MovePosition. - Управление состоянием прыжка: флаг
isJumpingтолько блокирует вращение, но не контролирует перемещение.
Как объясняет Game Dev Beginner, «Некоторые игры позволяют игроку полностью управлять горизонтальным перемещением во время прыжка, оставляя вертикальное движение под контролем физики».
Решения и лучшие практики
Решение 1: Разделить горизонтальное и вертикальное перемещение
Самый надёжный подход – разделить горизонтальное перемещение от вертикальной физики. Вместо перезаписи всей скорости сохраняйте компоненту y:
private void HandleMovement()
{
// Вычисляем горизонтальное направление
moveDicrection = cameraObject.forward * inputManager.verticalInput;
moveDicrection = moveDicrection + cameraObject.right * inputManager.horizontalInput;
moveDicrection.Normalize();
moveDicrection.y = 0;
// Выбираем скорость в зависимости от состояния
float currentSpeed = walkingSpeed;
if (isSprinting)
{
currentSpeed = sprintingSpeed;
}
else if (inputManager.moveAmount >= 0.5f)
{
currentSpeed = runningSpeed;
}
// Вычисляем горизонтальную скорость
Vector3 horizontalVelocity = moveDicrection * currentSpeed;
// Сохраняем вертикальную скорость при прыжке
if (isJumping || !isGrounded)
{
Vector3 currentVelocity = playerRigidbody.linearVelocity;
horizontalVelocity.y = currentVelocity.y;
playerRigidbody.linearVelocity = horizontalVelocity;
}
else
{
// При приземлении используем MovePosition
Vector3 targetPosition = transform.position + horizontalVelocity * Time.deltaTime;
playerRigidbody.MovePosition(targetPosition);
}
}
Решение 2: Использовать AddForce для физического перемещения
Согласно исследованиям из Game Development Stack Exchange, «Если вы хотите, чтобы объект действительно реагировал на физику, всегда перемещайте его с помощью AddForce».
private void HandleMovement()
{
moveDicrection = cameraObject.forward * inputManager.verticalInput;
moveDicrection = moveDicrection + cameraObject.right * inputManager.horizontalInput;
moveDicrection.Normalize();
moveDicrection.y = 0;
float currentSpeed = isSprinting ? sprintingSpeed :
(inputManager.moveAmount >= 0.5f ? runningSpeed : walkingSpeed);
// Применяем силу для горизонтального перемещения
Vector3 horizontalForce = moveDicrection * currentSpeed * 10f; // При необходимости скорректируйте множитель
if (isGrounded)
{
playerRigidbody.AddForce(horizontalForce);
}
else
{
// Уменьшаем управление в воздухе для более реалистичного ощущения
playerRigidbody.AddForce(horizontalForce * 0.5f);
}
}
Исправленная реализация кода
Ниже приведены обновлённые функции HandleMovement() и HandleJumping():
private void HandleMovement()
{
moveDicrection = cameraObject.forward * inputManager.verticalInput;
moveDicrection = moveDicrection + cameraObject.right * inputManager.horizontalInput;
moveDicrection.Normalize();
moveDicrection.y = 0;
float currentSpeed = walkingSpeed;
if (isSprinting)
{
currentSpeed = sprintingSpeed;
}
else if (inputManager.moveAmount >= 0.5f)
{
currentSpeed = runningSpeed;
}
Vector3 horizontalVelocity = moveDicrection * currentSpeed;
// Сохраняем вертикальную скорость во время прыжков
if (isJumping || !isGrounded)
{
Vector3 currentVelocity = playerRigidbody.linearVelocity;
horizontalVelocity.y = currentVelocity.y;
playerRigidbody.linearVelocity = horizontalVelocity;
}
else
{
// Используем MovePosition при приземлении
Vector3 targetPosition = transform.position + horizontalVelocity * Time.deltaTime;
playerRigidbody.MovePosition(targetPosition);
}
}
public void HandleJumping()
{
if (isGrounded)
{
animatorManager.animator.SetBool("isJumping", true);
animatorManager.PlayTargetAnimation("Jump", false);
float jumpingVelocity = Mathf.Sqrt(-2 * gravityIntensity * jumpHeight);
Vector3 currentVelocity = playerRigidbody.linearVelocity;
currentVelocity.y = jumpingVelocity;
playerRigidbody.linearVelocity = currentVelocity;
isJumping = true;
isGrounded = false;
}
}
Альтернативные подходы
Использование CharacterController
Если вы предпочитаете CharacterController вместо Rigidbody, вы можете добиться лучшего контроля над прыжками и перемещением:
private CharacterController characterController;
private Vector3 velocity;
void Awake()
{
characterController = GetComponent<CharacterController>();
// ... другие инициализации
}
private void HandleMovement()
{
moveDicrection = cameraObject.forward * inputManager.verticalInput;
moveDicrection = moveDicrection + cameraObject.right * inputManager.horizontalInput;
moveDicrection.Normalize();
moveDicrection.y = 0;
float currentSpeed = walkingSpeed;
if (isSprinting)
{
currentSpeed = sprintingSpeed;
}
else if (inputManager.moveAmount >= 0.5f)
{
currentSpeed = runningSpeed;
}
velocity = moveDicrection * currentSpeed;
velocity.y = playerRigidbody.linearVelocity.y; // Сохраняем вертикальную скорость
characterController.Move(velocity * Time.deltaTime);
}
public void HandleJumping()
{
if (isGrounded && characterController.isGrounded)
{
animatorManager.animator.SetBool("isJumping", true);
animatorManager.PlayTargetAnimation("Jump", false);
float jumpingVelocity = Mathf.Sqrt(-2 * gravityIntensity * jumpHeight);
velocity.y = jumpingVelocity;
characterController.Move(velocity * Time.deltaTime);
isJumping = true;
}
}
Использование AddForce с ForceMode.Impulse
Для более реалистичной физики прыжка рассмотрите применение AddForce с ForceMode.Impulse:
public void HandleJumping()
{
if (isGrounded)
{
animatorManager.animator.SetBool("isJumping", true);
animatorManager.PlayTargetAnimation("Jump", false);
Vector3 jumpForce = Vector3.up * Mathf.Sqrt(-2 * gravityIntensity * jumpHeight) * playerRigidbody.mass;
playerRigidbody.AddForce(jumpForce, ForceMode.Impulse);
isJumping = true;
isGrounded = false;
}
}
Распространённые ошибки, которых стоит избегать
- Перезапись скорости: Никогда не задавайте всю скорость, если нужно сохранить вертикальное движение.
- Несогласованное применение методов физики: Не смешивайте прямое присваивание скорости с
MovePositionилиAddForceбез чёткого понимания. - Отсутствие управления в воздухе: Игрок должен иметь возможность управлять горизонтальным перемещением во время прыжка.
- Неправильная детекция земли: Убедитесь, что ваш лучевой отскок (raycast) точен и вызывается в нужный момент.
Как отмечено в обсуждении на Reddit, «Документация Unity говорит, что прямое изменение скорости может привести к нереалистичному поведению физики».
Источники
- Unity 3d C# - Can’t move through the air while jumping - Game Development Stack Exchange
- How to jump in Unity (with or without physics) - Game Dev Beginner
- Unity C# - Move character while jumping - Stack Overflow
- Unity - Scripting API: Rigidbody.velocity
- Unity - When should I use velocity versus addForce - Game Development Stack Exchange
- r/Unity3D on Reddit: Unity’s example for a Character Controller jump
- Unity C# - How to Move and Jump in Unity - Stack Overflow
Заключение
Главная причина, по которой вы не можете перемещаться во время прыжка в Unity, заключается в том, что ваш код перемещения перезаписывает скорость прыжка. Разделив логику горизонтального и вертикального перемещения, вы сможете сохранять контроль над персонажем во время полёта, при этом сохраняя реалистичную физику.
Ключевые выводы:
- Сохраняйте вертикальную скорость при применении горизонтального перемещения во время прыжков.
- Рассмотрите использование
AddForceдля более реалистичного поведения. - Тестируйте различные подходы, чтобы подобрать ощущение, которое лучше всего подходит для вашего проекта.
- Помните, что некоторые игры намеренно ограничивают управление в воздухе ради геймплейных целей.
Начните с предложенной исправленной реализации и экспериментируйте с альтернативными подходами, чтобы достичь желаемого ощущения перемещения в вашем игровом проекте.