Необычная ошибка столкновения в игре Unity 2d
Репозиторий Github (папка Scripts, имеет весь код в файлах .cs)
У меня есть эта странная ошибка столкновения в единстве, вот ее gif:
![GIF]()
Воссоздание: например, в gif я нажимаю как Left arrow, так и Up arrow до тех пор, пока скорость не нормализуется, и я почему-то застрял в блоке.
У меня было это раньше с моим собственным алгоритмом столкновений, когда я сделал игру в XNA, надеясь, что этого не произойдет в Unity.
Это игрок script PlayerMovement
:
using UnityEngine;
using UnityEngine.UI;
namespace Assets.Scripts
{
public enum Directions
{
Back,
Left,
Front,
Right,
Idle = -1
}
public class PlayerMovement : MonoBehaviour
{
#region Public Members
/// <summary>
/// Maximum speed of the player (Accelerated to over a period of time)
/// </summary>
public float speed;
/// <summary>
/// Debug UI.Text element
/// </summary>
public Text debugText;
#endregion
#region Constants
/// <summary>
/// Constant for decaying the velocity on updates
/// </summary>
private const float VELOCITY_DECAY_FACTOR = 0.85f;
/// <summary>
/// Constant to convert normal speed sizes to fit the scale
/// Of UnityEngine.Vector2
/// </summary>
private const float HUMAN_TO_VECTOR_SCALE_FACTOR = 850f;
/// <summary>
/// Constant to set the base speed of the player animation
/// </summary>
private const float BASE_ANIM_SPEED = 0.7f;
/// <summary>
/// Constant to slightly reduce the animation speed after
/// It is multiplied by the velocity of the player
/// </summary>
private const float POST_VELOCITY_MULTIPLICATION_ANIM_SPEED_FACTOR = 0.5f;
/// <summary>
/// Constant to set the animation speed
/// </summary>
private const float ANIM_SPEED_MODIFIER = BASE_ANIM_SPEED * POST_VELOCITY_MULTIPLICATION_ANIM_SPEED_FACTOR;
/// <summary>
/// Epsilon before velocity zerofication
/// </summary>
private const float VELOCITY_EPSILON = 0.1f;
#endregion
#region Private Members
private Rigidbody2D rb2D;
private Vector2 velocity;
private Animator animator;
private Directions dir = Directions.Idle;
#endregion
#region Game Loop Methods
private void Awake()
{
animator = GetComponent<Animator>();
rb2D = GetComponent<Rigidbody2D>();
}
private void FixedUpdate()
{
float vertical = Input.GetAxisRaw("Vertical");
float horizontal = Input.GetAxisRaw("Horizontal");
UpdateVelocity(horizontal, vertical);
UpdateAnimation(horizontal, vertical);
UpdateMovment();
}
#endregion
#region Animation Methods
private void UpdateAnimation(float horizontal, float vertical)
{
UpdateAnimation(new Vector2(horizontal, vertical));
}
private void UpdateAnimation(Vector2 input)
{
Directions direction;
if (input.y > 0)
direction = Directions.Back;
else if (input.y < 0)
direction = Directions.Front;
else if (input.x > 0)
direction = Directions.Right;
else if (input.x < 0)
direction = Directions.Left;
else
direction = Directions.Idle;
SetDirection(direction);
}
private void SetDirection(Directions value)
{
animator.SetInteger("Direction", (int)value);
dir = value;
}
#endregion
#region Movement Methods
private void UpdateMovment()
{
rb2D.MovePosition(rb2D.position + velocity * Time.fixedDeltaTime);
KinematicsDebugPrints();
ApplySpeedDecay();
}
private string GetDebugPrintDetails()
{
return string.Format("HOR : {0}\nVER : {1}\nDIR : {2}:{3}\nX : {4}\nY : {5}",
velocity.x,
velocity.y,
animator.GetInteger("Direction").ToString().PadLeft(2),
(Directions)animator.GetInteger("Direction"),
rb2D.position.x,
rb2D.position.y);
}
private void KinematicsDebugPrints()
{
var details = GetDebugPrintDetails();
debugText.text = details;
Debug.Log(details);
}
private void UpdateVelocity(float horizontal, float vertical)
{
if (vertical != 0)
velocity.y += Mathf.Sign(vertical) * speed / HUMAN_TO_VECTOR_SCALE_FACTOR;
if (horizontal != 0)
velocity.x += Mathf.Sign(horizontal) * speed / HUMAN_TO_VECTOR_SCALE_FACTOR;
animator.speed = ANIM_SPEED_MODIFIER * velocity.MaxOfXandY() ;
}
private void ApplySpeedDecay()
{
if (velocity == Vector2.zero) return;
velocity *= VELOCITY_DECAY_FACTOR;
velocity = velocity.ZerofiyTinyValues(0.1f);
}
#endregion
}
}
Это объект игрока в настоящее время:
![player]()
И это объект стены (prefab одинаково для всех стен, кроме изображения:
![WALL]()
Это gif моей другой ошибки:
![ошибка 2]()
Вот как выглядит столбец и круги столкновений:
![новые столбики конфликтов]()
И это подробности от инспектора
![новый инспектор]()
Поэтому, поговорив с Хамза Хасаном, он помог мне превратить все коллайдеры на внешней стене в четыре продолжающихся коллайдера, по одному на каждую сторону (сверху, снизу, влево, вправо).
Код для него находится в BoardManager
script в методе CreateWallsColliders
.
Так выглядит сцена в редакторе сцены:
![новый редактор сцен]()
Ответы
Ответ 1
Ну, прежде всего, переместите код ввода от FixedUpdate
до Update
, иначе он приведет приложение к лагги. Во-вторых, вы можете сделать это, создав PhysicsMaterial2D с помощью Friction = 0
и Bounciness = 0
и прикрепите его к плееру, а также коллайдеру стены в Material
. Надеюсь, это поможет вам.
EDIT:
Вот альтернативное решение для вас, вместо использования 1 box-коллайдера на блок используйте только 1 коллайдер на каждую сторону. Всего 4 коллайдера.
Вот код, вы можете добавить его в свой класс BoardManager
. И назовите его в конце метода SetUpScene
, вы можете его изменить.
void CreateWallsColliders ()
{
GameObject colliders = new GameObject ("Colliders");
colliders.transform.position = Vector3.zero;
GameObject leftCollider = new GameObject ("LeftCollider");
leftCollider.transform.position = Vector3.zero;
BoxCollider2D bcLeftCollider = leftCollider.AddComponent<BoxCollider2D> ();
leftCollider.transform.parent = colliders.transform;
GameObject rightCollider = new GameObject ("RightCollider");
rightCollider.transform.position = Vector3.zero;
BoxCollider2D bcRightCollider = rightCollider.AddComponent<BoxCollider2D> ();
rightCollider.transform.parent = colliders.transform;
GameObject topCollider = new GameObject ("TopCollider");
topCollider.transform.position = Vector3.zero;
BoxCollider2D bcTopCollider = topCollider.AddComponent<BoxCollider2D> ();
topCollider.transform.parent = colliders.transform;
GameObject bottomCollider = new GameObject ("BottomCollider");
bottomCollider.transform.position = Vector3.zero;
BoxCollider2D bcBottomCollider = bottomCollider.AddComponent<BoxCollider2D> ();
bottomCollider.transform.parent = colliders.transform;
// Assuming 15 x 15 tiles. Make it dynamic if you need.
// Assuming -1 and 15 are the limits on both sides
int rows = 15;
int cols = 15;
int lowerLimit = -1;
int upperLimit = 15;
leftCollider.transform.position = new Vector3 (lowerLimit, rows / 2);
leftCollider.transform.localScale = new Vector3 (1, cols, 1);
rightCollider.transform.position = new Vector3 (upperLimit, rows / 2);
rightCollider.transform.localScale = new Vector3 (1, cols, 1);
topCollider.transform.position = new Vector3 (cols / 2, upperLimit);
topCollider.transform.localScale = new Vector3 (rows, 1, 1);
bottomCollider.transform.position = new Vector3 (cols / 2, lowerLimit);
bottomCollider.transform.localScale = new Vector3 (rows, 1, 1);
}
Ответ 2
Явление, которое вы испытываете, вызвано прямоугольным коллайдером вашего персонажа, сталкивающимся с нижним краем следующей плитки стены. Эта "ошибка" очень распространена в физических двигателях. Это вызвано некоторыми ошибками вычислений, и их следует ожидать. Вот почему большинство игр имеют ограниченные эллипсы для персонажей, поскольку эллипсы не имеют углов или ребер.
Один из способов избавиться от этой внезапной остановки - убедиться, что все смежные плитки стены представлены как один коллайдер (прямоугольник или многоугольник). Это требует отдельной логики, которая создает коллайдеры с препятствий после загрузки уровня, и коллайдеры должны обновляться после каждого изменения уровня (открытие дверей и т.д.).
Более простой способ решить проблему - изменить коллайдер персонажа. Если прямоугольная форма вашего персонажа не является существенной, я рекомендую вам использовать коллайдер следующей формы:
![капсульный образный коллайдер]()
Или, если прямоугольная форма важна, вы можете растягивать углы кругами:
![введите описание изображения здесь]()