Как я мог ограничить движение игрока к поверхности трехмерного объекта, используя Unity?

16

Я пытаюсь создать эффект, подобный эффекту Mario Galaxy или Geometry Wars 3, где, когда игрок ходит вокруг «планеты», гравитация, кажется, корректируется, и они не падают с края объекта, как если бы гравитация был зафиксирован в одном направлении.

введите описание изображения здесь
(источник: gameskinny.com )

Геометрия Войны 3

Мне удалось реализовать нечто, близкое к тому, что я ищу, используя подход, при котором объект, который должен иметь гравитацию, притягивает к нему другие твердые тела, но используя встроенный физический движок для Unity, применяя движение с помощью AddForce и тому подобное, Я просто не мог заставить движение чувствовать себя хорошо. Я не мог заставить игрока двигаться достаточно быстро без игрока, начинающего летать с поверхности объекта, и я не мог найти хороший баланс приложенной силы и силы тяжести, чтобы приспособиться к этому. Моя текущая реализация является адаптацией того, что было найдено здесь

Я чувствую, что решение, вероятно, по-прежнему будет использовать физику для заземления игрока на объект, если он должен покинуть поверхность, но как только игрок будет заземлен, появится способ привязать игрока к поверхности и отключить физику и управлять игроком с помощью других средств, но я действительно не уверен.

Какой подход я должен использовать, чтобы привязать плеер к поверхности объектов? Обратите внимание, что решение должно работать в трехмерном пространстве (в отличие от 2D) и должно быть реализовано с использованием бесплатной версии Unity.

SpartanDonut
источник
Я даже не думал о поиске прогулок по стенам. Я посмотрю и посмотрю, помогут ли они.
SpartanDonut
1
Если этот вопрос касается именно этого в 3D в Unity, его следует прояснить с помощью правок. (Тогда это не будет точной копией существующего .)
Anko
Это мое общее чувство - я собираюсь посмотреть, смогу ли я адаптировать это решение к 3D и опубликовать его в качестве ответа (или если кто-то еще может побить меня до удара, я тоже в этом согласен). Я постараюсь обновить мой вопрос, чтобы быть более ясным по этому вопросу.
SpartanDonut
Потенциально полезно: youtube.com/watch?v=JMWnufriQx4
ssb

Ответы:

7

Мне удалось выполнить то, что мне было нужно, в первую очередь с помощью этого поста в блоге для поверхностного осколка головоломки, и я предложил свои собственные идеи для движения игрока и камеры.

Привязка игрока к поверхности объекта

Базовая установка состоит из большой сферы (мир) и меньшей сферы (игрок) с прикрепленными к ним коллайдерами.

Основная часть выполняемой работы выполнялась следующими двумя методами:

private void UpdatePlayerTransform(Vector3 movementDirection)
{                
    RaycastHit hitInfo;

    if (GetRaycastDownAtNewPosition(movementDirection, out hitInfo))
    {
        Quaternion targetRotation = Quaternion.FromToRotation(Vector3.up, hitInfo.normal);
        Quaternion finalRotation = Quaternion.RotateTowards(transform.rotation, targetRotation, float.PositiveInfinity);

        transform.rotation = finalRotation;
        transform.position = hitInfo.point + hitInfo.normal * .5f;
    }
}

private bool GetRaycastDownAtNewPosition(Vector3 movementDirection, out RaycastHit hitInfo)
{
    Vector3 newPosition = transform.position;
    Ray ray = new Ray(transform.position + movementDirection * Speed, -transform.up);        

    if (Physics.Raycast(ray, out hitInfo, float.PositiveInfinity, WorldLayerMask))
    {
        return true;
    }

    return false;
}

Vector3 movementDirectionПараметр так же , как это звучит, направление мы будем двигаться нашим игроком в этом кадре, и вычисление этого вектора, а в конечном итоге относительно простой в этом примере, было немного сложно для меня , чтобы выяснить , в первую очередь. Подробнее об этом позже, но имейте в виду, что это нормализованный вектор в направлении, в котором игрок перемещает этот кадр.

Поэтапно, первое, что мы делаем, это проверяем, попадает ли луч, исходящий из гипотетической будущей позиции, направленной к вектору игрока вниз (-transform.up), с помощью WorldLayerMask, который является общедоступным свойством LayerMask скрипта. Если вы хотите более сложные столкновения или несколько слоев, вам придется создать собственную маску слоя. Если радиопередача ударит по чему-либо, hitInfo используется для извлечения нормальной точки и точки попадания, чтобы вычислить новую позицию и вращение игрока, которое должно быть прямо на объекте. Может потребоваться смещение позиции игрока в зависимости от размера и происхождения рассматриваемого объекта игрока.

Наконец, это действительно только проверено и, вероятно, хорошо работает только на простых объектах, таких как сферы. Как следует из поста в блоге, на котором я основываю свое решение, вы, вероятно, захотите выполнить несколько лучевых трансляций и усреднить их для вашей позиции и поворота, чтобы получить более приятный переход при движении по более сложной местности. Могут быть и другие подводные камни, о которых я не думал в данный момент.

Камера и Движение

Как только игрок прилип к поверхности объекта, следующей задачей было движение. Первоначально я начал с движения относительно игрока, но столкнулся с проблемами на полюсах сферы, где направления внезапно менялись, заставляя моего игрока быстро менять направление снова и снова, не позволяя мне когда-либо проходить полюса. То, что я делал, заставляло моих игроков двигаться относительно камеры.

Для моих нужд было хорошо иметь камеру, которая бы точно следовала за игроком, основываясь исключительно на положении игроков. В результате, несмотря на то, что камера технически вращалась, нажатие вверх всегда перемещало плеер к верхней части экрана, вниз к нижней части и т. Д. Влево и вправо.

Для этого на камере, где целевым объектом был игрок, было выполнено следующее:

private void FixedUpdate()
{
    // Calculate and set camera position
    Vector3 desiredPosition = this.target.TransformPoint(0, this.height, -this.distance);
    this.transform.position = Vector3.Lerp(this.transform.position, desiredPosition, Time.deltaTime * this.damping);

    // Calculate and set camera rotation
    Quaternion desiredRotation = Quaternion.LookRotation(this.target.position - this.transform.position, this.target.up);
    this.transform.rotation = Quaternion.Slerp(this.transform.rotation, desiredRotation, Time.deltaTime * this.rotationDamping);
}

Наконец, чтобы переместить игрока, мы использовали преобразование основной камеры так, чтобы с нашими элементами управления вверх двигались вверх, вниз двигались вниз и т. Д. И именно здесь мы вызываем UpdatePlayerTransform, который привязывает нашу позицию к объекту мира.

void Update () 
{        
    Vector3 movementDirection = Vector3.zero;
    if (Input.GetAxisRaw("Vertical") > 0)
    {
        movementDirection += cameraTransform.up;
    }
    else if (Input.GetAxisRaw("Vertical") < 0)
    {
        movementDirection += -cameraTransform.up;
    }

    if (Input.GetAxisRaw("Horizontal") > 0)
    {
        movementDirection += cameraTransform.right;
    }
    else if (Input.GetAxisRaw("Horizontal") < 0)
    {
        movementDirection += -cameraTransform.right;
    }

    movementDirection.Normalize();

    UpdatePlayerTransform(movementDirection);
}

Чтобы реализовать более интересную камеру, но элементы управления должны быть примерно такими же, как у нас здесь, вы можете легко реализовать камеру, которая не визуализируется, или просто другой фиктивный объект, чтобы основывать движение, а затем использовать более интересную камеру для рендеринга, что Вы хотите, чтобы игра выглядела так. Это обеспечит хорошие переходы камеры при обходе объектов без нарушения управления.

SpartanDonut
источник
3

Я думаю, что эта идея будет работать:

Сохраните копию планетарной сетки на стороне процессора. Наличие вершин сетки также означает, что у вас есть нормальные векторы для каждой точки на планете. Затем полностью отключите гравитацию для всех объектов, вместо этого применяя силу в направлении, совершенно противоположном направлению вектора нормали.

Теперь, на основании какой точки должен быть рассчитан этот нормальный вектор планеты?

Самый простой ответ (который, я уверен, сработает нормально) - приблизиться аналогично методу Ньютона : когда объекты появляются впервые, вы знаете все их начальные позиции на планете. Используйте эту начальную позицию, чтобы определить upвектор каждого объекта . Очевидно, гравитация будет в противоположном направлении (в направлении down). В следующем кадре, перед применением силы тяжести, направьте луч из новой позиции объекта в направлении его старого downвектора. Используйте пересечение этого луча с планетой в качестве нового эталона для определения upвектора. Редкий случай попадания луча в ничто означает, что что-то пошло не так, и вы должны переместить свой объект туда, где он был в предыдущем кадре.

Также обратите внимание, что при использовании этого метода дальнейшее происхождение игрока с планеты, чем хуже становится аппроксимация. Следовательно, лучше использовать где-нибудь вокруг ног каждого игрока в качестве источника. Я предполагаю, но я думаю, что использование ног в качестве источника также приведет к более легкой обработке и навигации игрока.


Последнее замечание: для достижения лучших результатов вы даже можете сделать следующее: отслеживать движение игрока в каждом кадре (например, используя current_position - last_position). Затем зафиксируйте его movement_vectorтак, чтобы его длина по отношению к объекту была upравна нулю. Давайте назовем этот новый вектор reference_movement. Переместить предыдущий reference_pointна reference_movementи использовать эту новую точку как трассировка лучей происхождения. После того, как (и если) луч достигнет планеты, перейдите reference_pointк этой точке попадания. Наконец, рассчитать новый upвектор, из этого нового reference_point.

Некоторый псевдокод, чтобы подвести итог:

update_up_vector(player:object, planet:mesh)
{
    up_vector        = normalize(planet.up);
    reference_point  = ray_trace (object.old_position, -up_vector, planet);
    movement         = object.position - object.old_position;
    movement_clamped = movement - up_vector * dot (movement, up_vector);

    reference_point  += movement_clamped;
    reference_point  = ray_trace (reference_point, -up_vector, planet);
    player.up        = normal_vector(mesh, reference_point);
}
Ali1S232
источник
2

Этот пост может быть полезным. Суть в том, что вы не используете контроллеры персонажей, а создаете свои собственные, используя физический движок. Затем вы используете нормали, обнаруженные под игроком, чтобы ориентировать их на поверхность меша.

Вот хороший обзор техники. Есть много других ресурсов с такими терминами веб-поиска, как "прогулка в единстве по трехмерным объектам".

Также есть демонстрационный проект из unity 3.x, в котором был гуляющий двигатель, с солдатом и собакой и в одной из сцен. Это продемонстрировало ходьбу по трехмерным объектам Galaxy- style. Это называется локомоции runevision.

SpidermanSpidermanDWASC
источник
1
На первый взгляд демонстрация работает не очень хорошо, а пакет Unity содержит ошибки сборки. Я посмотрю, смогу ли я сделать эту работу, но был бы признателен за более полный ответ.
SpartanDonut
2

вращение

Оформить ответ на этот вопрос на answers.unity3d.com (на самом деле задал сам). Quote:

Первая задача - получить вектор, который определяет. С вашего рисунка вы можете сделать это одним из двух способов. Вы можете рассматривать планету как сферу и использовать (object.position - planet.position). Второй способ - использовать Collider.Raycast () и использовать «hit.normal», который он возвращает.

Вот код, который он мне предложил:

var up : Vector3 = transform.position - planet.position;
transform.rotation = Quaternion.FromToRotation(transform.up, up) * transform.rotation;

Называйте это каждое обновление, и вы должны заставить его работать. (обратите внимание, что код в UnityScript ).
Тот же код в C # :

Vector3 up = transform.position - planet.position;
transform.rotation = Quaternion.FromToRotation(transform.up, up) * transform.rotation;

Сила тяжести

Для гравитации вы можете использовать мою «технику физики планет», которую я описал в своем вопросе, но она не очень оптимизирована. Это было просто то, что я подумал.
Я предлагаю вам создать собственную систему гравитации.

Вот учебник на Youtube от Себастьяна Лиги . Это очень хорошо работающее решение.

РЕДАКТИРОВАТЬ: В единстве перейдите в « Правка» > « Настройки проекта» > « Физика» и установите для всех значений силы тяжести значение 0 (или удалите твердое тело из всех объектов), чтобы предотвратить конфликт со встроенной гравитацией (которая просто тянет игрока вниз) индивидуальное решение. (выключи это)

Даниэль Квист
источник
Гравитация игрока к центру планеты (и его вращение соответственно) хорошо работает для почти сферических планет, но я могу себе представить, что это происходит неправильно для менее сферических объектов, таких как эта форма ракеты, которую Марио прыгает в первом GIF.
Анко
Вы можете создать куб, ограниченный только перемещением, например, вокруг оси X, внутри несферического объекта, и перемещать его, когда игрок движется так, чтобы он всегда находился прямо под игроком, а затем тянуть игрока до куба. Попытайся.
Даниэль Квист