#c# #unity3d #reflect
#c# #unity3d #отразить
Вопрос:
Я уже некоторое время пытаюсь реалистично отразить 3d сферу на стенках коробки в Unity. По какой-то причине отражение в целом правильное, но когда шар ударяется о стену в определенных направлениях, отражение получается неправильным. Чтобы проиллюстрировать, что происходит с шаром при ударе о стену: T = верхняя стена, R = правая стена, L = левая стена и B = нижняя стена. Пусть r = мяч приходит / уходит вправо, l = влево и s = мяч останавливается / значительно замедляется. Приведенные ниже инструкции имеют следующий формат: Xyz, где X = стена, в которую вот-вот ударится мяч, y = начальное направление мяча, z = отражение. В игре используется перспектива сверху вниз, а инструкции основаны на ракурсе стены. Я тоже новичок в C #, поэтому код потенциально вызывает интерес.
Инструкции: Tll, Trl; Bll, Brl; Rls или после удара о другую стену Rlr, Rrl; Lls или после удара о другую стену Llr, Lrl
Как правило, когда шар останавливается, он подпрыгивает в воздух. Интересно, связано ли это с тем, что угол отражает вдоль неправильной оси, но почему это происходит только иногда? Кроме того, когда удерживается только одна клавиша, мяч отскакивает назад и вперед, пока не покинет арену. Я знаю о дискретном и непрерывном обнаружении ударов, и настройка установлена на дискретность, но стены обычно достаточно хорошо удерживают шар, за исключением этого случая.
Что я пробовал:
- Выясняем, как использовать Vector3.Reflect. Я не понимаю, какие переменные должна содержать эта функция и как применить это к моему коду. Я просмотрел страницу документации Unity за справкой, но она не ответила на мой вопрос.
- Изменение отрицательных знаков, поскольку угол должен быть отражением по оси y, и это меняет принцип работы отражений, но не решает проблему. Текущий способ упорядочивания негативов — самый идеальный, который я нашел.
- Предоставление мячу физического материала для упругости.
- Добавление небольшого числа к знаменателю в уравнении arctan, чтобы предотвратить деление на ноль. Это совсем не помогло.
- Создание различных уравнений (в основном изменение отрицательных значений) для различных комбинаций положительных и отрицательных ускорений. Поскольку с каждым нажатием кнопки связано определенное положительное или отрицательное ускорение (см. Сценарий перемещения), и, похоже, существует проблема с указанными знаками, я подумал, может ли связать каждое ускорение с его собственным набором уравнений, чтобы решить проблему. Это не сработало.
- Проверил, были ли стены под разными углами.
- Удаление переменных xA и yA или размещение их в разных местах.
- Экспериментировал с определением скорости объекта, но понятия не имел, как это реализовать.
Код скрипта перемещения для плеера с именем Controller:
public class Movement : MonoBehaviour
{
public static float xAcceleration = 0.0f;
public static float yAcceleration = 0.0f;
// Update is called once per frame
void Update()
{
if (Input.GetKey(KeyCode.W)) //If the key W is pressed:
{
Vector3 position = this.transform.position; //Variable position is set to transform the players placement in the game.
if (yAcceleration >= -5 amp;amp; yAcceleration <= 5) //If the y vector of the acceleration is >= -5 and <= 5:
{
yAcceleration = yAcceleration 0.01f; //The y vector of the acceleration increases by 0.01 as long as the key W is pressed.
}
position.z = position.z (0.1f * yAcceleration); //The position of the object on the z-axis (pretend it is the y-axis in the game world) is transformed by its original position plus its speed times its yAcceleration.
this.transform.position = position; //The gameObject is now transformed to a position equal to the variable position by the z-axis.
}
else //If the key W is let go of:
{
Vector3 position = this.transform.position;
position.z = position.z (0.1f * yAcceleration);
this.transform.position = position; //The position of the gameObject continues to update, but its acceleration does not change. Basically, it continues to move forward.
}
//The rest of the code is very similar to the above, but I included it just in case there was something wrong.
if (Input.GetKey(KeyCode.S))
{
Vector3 position = this.transform.position;
if (yAcceleration >= -5 amp;amp; yAcceleration <= 5)
{
yAcceleration = (yAcceleration) - 0.01f;
}
position.z = position.z (0.1f * yAcceleration);
this.transform.position = position;
}
else
{
Vector3 position = this.transform.position;
position.z = position.z (0.1f * yAcceleration);
this.transform.position = position;
}
if (Input.GetKey(KeyCode.A))
{
Vector3 position = this.transform.position;
if (xAcceleration >= -5 amp;amp; xAcceleration <= 5)
{
xAcceleration = (xAcceleration) - 0.01f;
}
position.x = position.x (0.1f * xAcceleration);
this.transform.position = position;
}
else
{
Vector3 position = this.transform.position;
position.x = position.x (0.1f * xAcceleration);
this.transform.position = position;
}
if (Input.GetKey(KeyCode.D))
{
Vector3 position = this.transform.position;
if (xAcceleration >= -5 amp;amp; xAcceleration <= 5)
{
xAcceleration = (xAcceleration) 0.01f;
}
position.x = position.x (0.1f * xAcceleration);
this.transform.position = position;
}
else
{
Vector3 position = this.transform.position;
position.x = position.x (0.1f * xAcceleration);
this.transform.position = position;
}
}
}
Это код для коллайдера и отражения:
public class Collider : MonoBehaviour
{
public float xA;
public float yA;
void OnCollisionEnter(Collision collision) //If a gameObject enters the collision of another object, this immediately happens once.
{
if (gameObject.tag == "Boundary") //If the gameObject has a tag named Boundary:
{
yA = -Movement.yAcceleration; //yA stores the value of yAcceleration after being called from script Movement as a negative. Its a reflection.
Movement.xAcceleration = (Movement.xAcceleration * -Mathf.Atan(yA / Movement.xAcceleration)); //xAcceleration is changed based on this equation: A * artan(A_y / A_x). The 0.000001 was here, adding to A_x to help prevent a 0 as the denominator.
xA = Movement.xAcceleration; //This is declared now...
Movement.yAcceleration = (Movement.yAcceleration * Mathf.Atan(Movement.yAcceleration / xA)); //This uses xA because Movement.xAcceleration is changed, and the yAcceleration calculation is based on the xAcceleration prior the collision.
}
}
void OnCollisionStay(Collision collision)
{
if (gameObject.tag == "Boundary")
{
yA = Movement.yAcceleration; //The same thing happens as before.
Movement.xAcceleration = (Movement.xAcceleration * -Mathf.Atan(yA / Movement.xAcceleration));
xA = Movement.xAcceleration;
Movement.yAcceleration = (Movement.yAcceleration * Mathf.Atan(Movement.yAcceleration / xA));
Movement.xAcceleration = -Movement.xAcceleration / 2; //On collision, the ball is reflected across the x-axis at half its speed.
Movement.yAcceleration = Movement.yAcceleration / 2; //yAcceleration is half its original value.
}
}
}
На картинке ниже показаны настройки игры. Прошу прощения, что это ссылка; у меня недостаточно репутации, чтобы заслужить загруженное изображение на этой странице. Кроме того, если что-то неясно, пожалуйста, напишите мне.
https://i.stack.imgur.com/VREV4.png
Я был бы действительно признателен за помощь. Спасибо!
Ответ №1:
Здесь одно очень важное замечание: как только возникает какая-либо Rigidbody
проблема, вы не хотите устанавливать какие-либо значения через .transform
— это нарушает физику и обнаружение столкновений!
Ваше движение должно скорее изменить поведение Rigidbody
, например, просто изменив его Rigibody.velocity
Затем я бы также поместил проверку столкновения непосредственно в компонент balls и проверил, ударяетесь ли вы о стену («Границу»)
Тогда еще одно замечание: ваш код в настоящее время зависит от частоты кадров. Это означает, что если ваше целевое устройство работает со скоростью всего 30 кадров в секунду, вы увеличите ускорение 0.3
в секунду. Однако, если вы запускаете на более мощном устройстве, которому удается запускать со скоростью 200 кадров в секунду, тогда вы добавляете 2
в секунду.
Вам лучше определить уменьшение / приращение в секунду и умножить его на Time.deltaTime
Все вместе может быть что-то вроде этого
public class Movement : MonoBehaviour
{
// Adjust these settings via the Inspector
[SerializeField] private float _maxMoveSpeed = 5f;
[SerializeField] private float _speedIncreasePerSecond = 1f;
// Already reference this via the Inspector
[SerializeField] private Rigidbody _rigidbody;
private void Awake()
{
if(!_rigidbody) _rigidbody = GetComponent<Rigidbody>();
}
// Get User Input in Update
private void Update()
{
var velocity = _rigidbody.velocity;
velocity.y = 0;
if (Input.GetKey(KeyCode.W) amp;amp; velocity.z < _maxMoveSpeed)
{
velocity.z = _speedIncreasePerSecond * Time.deltaTime;
}
if (Input.GetKey(KeyCode.S) amp;amp; velocity.z > -_maxMoveSpeed)
{
velocity.z -= _speedIncreasePerSecond * Time.deltaTime;
}
if (Input.GetKey(KeyCode.A) amp;amp; velocity.x > -_maxMoveSpeed)
{
velocity.x -= _speedIncreasePerSecond * Time.deltaTime;
}
if (Input.GetKey(KeyCode.D) amp;amp; velocity.x < _maxMoveSpeed)
{
velocity.x = _speedIncreasePerSecond * Time.deltaTime;
}
// clamp to the max speed in case you move diagonal
if(velocity.magnitude > _maxMoveSpeed)
{
velocity = velocity.normalized * _maxMoveSpeed;
}
_rigidbody.velocity = velocity;
}
}
И затем, наконец, просто добавьте PhysicsMaterial
с желаемыми настройками к стенам и шару.
Я использовал Friction = 0f
и Bounciness = 0.7f
для шара и стен. Для медленных движений вы также можете захотеть / должны настроить Bounce Threshold
в физических настройках проекта, в противном случае не будет подпрыгивания, если скорость меньше, чем 2
по умолчанию.
Это немного зависит от вашего определения «реалистичного». Я отключил гравитацию, поэтому шар также не вращается и не имеет углового трения:
Комментарии:
1. Код работает отлично, за исключением последнего оператора if с ограничением максимальной скорости. Через некоторое время нажатие любой комбинации кнопок приводит к зависанию шара на максимальной скорости. В этом случае она не может ни замедляться, ни ускоряться, а может лишь слегка поворачиваться влево или вправо. Когда код удален, этого не происходит. Тогда скорость по диагонали все еще больше, чем скорость по оси x или z. Кроме того, я иногда получаю «Движение поля». _rigidbody никогда не присваивается и всегда будет иметь значение по умолчанию null» ошибка. Я все еще могу запустить программу, так что, вероятно, это не так важно.
2. @BugDoctor1 последнее предупреждение абсолютно нормально для Unity, поскольку соответствующее поле является закрытым и никогда не назначается с помощью кода. Однако она назначается вами через инспектор 😉 Я изменил условие clamp, которое было неправильным ^^ Это должно быть
sqrMagnitude / _maxMoveSpeed > _maxMoveSpeed
или простоmagnitude > _maxMoveSpeed
😉3. Это изменение работает! Я также нашел ваш код полезным для перемещения других игровых объектов без ввода игроком. Эта замена значительно упростила понимание моего нового скрипта. Спасибо!