Как я собирал физику колёс в Unigine

    Доброго дня.

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



    Первым делом, чтобы понять, как делается физика колёс, отправляемся в официальную документацию и находим вот этот пример.

    Кстати, обратите внимание, что это пример для wheels joint, а лежит он почему-то по адресу /car_suspension_joints/. Ещё нужно следить, чтобы документация не перескакивала на старые версии (то есть что открыта именно DOCS (2.11)) и отметить сверху статьи плашку «C#».

    Изучив пример, видим, что написан он по аналогии с ранним C++ решением. То есть всё создаётся через код. Вот вам и визуальное программирование, приехали. Ну, не всё так печально, зато можно посмотреть, как это всё делается в коде. А побегав по ссылкам, получить общее представление об устройстве wheels joint и настройке физики.
    В комплекте с самим движком демо с ездящей колёсной техникой нет (потому что пока в приоритете было скорее показать свободную версию движка артистам и научить базовым возможностям). Вернее есть, но это мини-примеры, написанные на устаревшем UnigineScript, где опять же всё создаётся кодом и не настраивается в редакторе. Зато в одном из демо присутствуют статичные модели машинок с LOD'ами.
    Я-то, конечно, ожидал готового пошагового руководства по визуальной сборке, чтобы получить нечто катающееся, обойдясь малой кровью. В итоге всё-таки пришлось погрузиться во всё это с головой, чтобы выработать какой-то приблизительный пайплайн. Частично это получилось, но разобрался далеко не во всех моментах и остаются открытыми вопросы о том, например, должны ли ноды корпуса и колёс быть встроены в некую иерархию, правильно ли сориентированы оси джоинтов и так далее.

    В целом данная инструкция поможет вам ухватить основной принцип, а чтобы разобраться в тонкостях настройки всё-таки придётся изучать документацию (или может быть в комментариях чего полезного расскажут).

    Сборка каркаса


    1. Создаём новый проект. Выбираем C# вариант. Открываем проект в редакторе.







    2. Создаём основу машинки.

    Для этого щёлкаем правой кнопкой мыши в сцене, выбирая Create > Primitive > Box. Получится объект Cuboid. Слегка приподнимаем его над «полом».



    3. Создаём колёса.

    Снова щёлкаем кнопкой мыши и выбираем Create > Primitive > Cylinder. Потребуется 4 штуки.



    Чтобы не запутаться с ориентированием можно повернуть камеру так, чтобы сверху в кубике светилась -Y (то есть сейчас мы смотрим на будущую машинку сзади) и расположить два цилиндра позади Cuboid, два других впереди него. Как-то вращать и подгонять их не нужно. Для удобства задним и передним можно назначить разные цвета.

    4. Добавляем физику основе.

    Выбираем Cuboid и слева, в окне Parameters с вкладки Node переключаемся на Physics. В графе Body выбираем тип Rigid. После этого проматываем вниз и в открывшейся графе Shapes выбираем пункт Box.





    Видим появившуюся форму ShapeBox. Зайдём в её настройки и сразу зададим нашей основе рекомендуемую массу (Mass) в 64 килограмма.



    5. Добавляем физику колёсам.

    По очереди выбираем цилиндры и также проставляем им Rigid во вкладке Physics.
    Shape им создавать не потребуется, потому что wheel джоинты работают на рейкастах.

    6. Привязываем колёса к основе.

    Время назначить wheel joints. Чтобы сделать это правильно выбираете Cuboid и во вкладке Physics проматываете вниз, до графы Joints. В списке выбираете Wheel и при нажатии кнопки Add выпадет окошко Select a Body. Там выбираете цилиндр, он подсвечивается белой сеткой в сцене, и жмёте Ok. В списке появится привязка WheelJoint. Вот здесь уже желательно их переименовывать (для этого нужно щёлкнуть по строчке JointWheel и написать новое имя в графе Name).





    Таким же образом прикрепляете к Cuboid остальные цилиндры. Чтобы в итоге в его списке джоинтов горело 4 строчки.


    Без переименования здесь было бы 4 строчки WheelJoint

    Обратите внимание, что привязывать нужно именно от Cuboid, а не из вкладки Physics самих цилиндров. Хотя после, если выбрать конкретный цилиндр и зайти в его Physics, то его созданный джоинт там тоже отображается и можно редактировать его параметры.

    7. Настраиваем конфигурацию привязок.

    Снова выбираем Cuboid, находим в Physics список его джоинтов и выбираем один из них. Видим, что в сцене внутри Cuboid появился белый уголок с квадратиками.
    В настройках джоинта находим графу вектора Anchor 0, и регулируем значения его трёх полей таким образом, чтобы уголок этот сместить по оси X, выведя за пределы Cuboid и немного опустить по оси Z.





    После чего сдвигаем уголок по оси Y, в зависимости от того привязка какого колеса сейчас выбрана.

    Повторяем процедуру с остальными джоинтами, выставляя точно такие же сдвиги по Z и одинаковые, но разнонаправленные по остальным осям.

    Если выбрать все джоинты сразу, то станет видна общая конструкция. Также можно включить отображение joints в хелперах на специальной панельке над сценой: Helpers > Physics > Joints.


    Вид на каркас сзади с включёнными хелперами

    8. Смотрим, что получилось.

    В редакторе есть кнопка расчёта физики. Если её включить, то запустится симуляция физики (скрипты при этом не действуют) и можно проверить как себя поведёт наш франкенштейн.
    Перед запуском убедитесь что в настройках «пола» выставлены флажки enabled для Collision и Physics Intersection. Иначе наше создание может вовсе провалиться под пол целиком или одними колёсами.



    Будьте внимательны, перед включением симуляции лучше сохраниться, а исправленные во время её действия параметры вернутся к своим значениям после отключения.

    Как мы видим, колёса подхватываются в нужные места, но конструкция падает торцами цилиндров на поверхность.



    Можно замораживать на месте сам Cuboid, предварительно включив на вкладке Physics в графе Body галочку Immovable.

    9. Настраиваем оси колёс.

    Отключаем симуляцию физики. Выбираем Cuboid, заходим в его вкладку Physics и правим у каждого джоинта вектора Axis 00, Axis 10 и Axis 11. Обратите внимание, что в сумме поля каждого вектора принимают значения от 0 до 1 и редактор будет пытаться автоматически подправлять значения, если вы будете сперва ставить 1 в новую ось, не обнулив вектор.

    По всей видимости я пока что развернул вектора не совсем правильно, хелперы показывают, что левая и правая части смотрят в одну сторону, но не дают чёткого понимания какая ось где, что затрудняет настройку.

    В любом случае такая раскладка более менее работает. Тут я выставил вектора осей следующим образом: Axis 00 (0,0,1), Axis 10 (1,0,0), Axis 11 (0,0,1).



    Если запустить симуляцию физики теперь, то конструкция должна упасть на правильно повёрнутые цилиндры, которые в дальнейшем будут крутятся вдоль правильной оси.

    10. Масса и поведение колёс, физические итерации.

    Скорее всего конструкция после падения на пол теперь частично проваливается.

    Для начала зайдём в общие настройки физики. Щёлкаем на верхнюю строку редактора. Windows > Settings и в открывшейся вкладке Settings находим графу Physics (Runtime/World/). Выставляем в поле Iterations хотя бы 5.



    Снова заходим в настройки каждого джоинта.
    Вверху списка у каждого есть поле Iteration, выставляем 8.
    Linear Restitution и Angular Restitution устанавливаем на 0.1.
    Linear From меняем -0.15, Linear To на 0.15.
    Внизу проставляем им массу 25 кг в поле Wheel Mass.

    При запуске симуляции физика «машинка» всё ещё должна частично проваливаться/ заваливаться.

    Выставим каждому джоинту Linear Damping в 50. И установим Linear Spring на 10.
    Теперь при симуляции конструкция должна падать и чуть подпрыгивать на полу. Если не получается, то поэкспериментируйте со значениями Linear Damping и Linear Spring.
    В документации советуют ставить damping 400 и spring 100, но лично у меня при этом «машинка» начинает крутиться как вертолёт, подпрыгивает и улетает.

    11. Финальная настройка каркаса.

    Сохранитесь и попробуйте нажать Play (кнопка сверху, рядом с включением физической симуляции) чтобы погулять по сцене от первого лица. Скорее всего «машинка» повисла немного в воздухе, но катится, если её толкнуть в центральный куб.



    Теперь можно подрегулировать Anchor 0 у джоинтов, чтобы они оказались примерно равноудалены от центра (хотя не обязательно). После чего запустить симуляцию и прямо во время неё менять Linear Spring, чтобы найти оптимальное значение при котором каждое колесо нормально касается земли.

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



    Написание управляющего скрипта



    Заготовка «машинки» у нас есть, теперь напишем ей управляющий код на C#.
    Для этого щелкаем правой кнопкой в окошке под сценой и выбираем Create > Create C# Component. Вписываем в появившееся окошко название скрипта.





    Щёлкаем по появившемуся файлу скрипта двойным кликом и запускается среда разработки.



    Выделяете строки, залитые синим на картинке, удаляете их и вместо этого добавляете следующий код к которому я пришёл редактируя предлагаемый в документации скрипт:

    Фрагмент скрипта
    	[ShowInEditor][Parameter(Tooltip = "Left Wheel")]
    	private Node targetWheelL = null;
    	[ShowInEditor][Parameter(Tooltip = "Right Wheel")]
    	private Node targetWheelR = null;
    
    	[ShowInEditor][Parameter(Tooltip = "Left Wheel F")]
    	private Node targetFWL = null;
    	[ShowInEditor][Parameter(Tooltip = "Right Wheel F")]
    	private Node targetFWR = null;
    
    	[ShowInEditor][Parameter(Tooltip = "theCar")]
    	private Node targetCar = null;
    
        private JointWheel my_jointL;
        private JointWheel my_jointR;
        private JointWheel my_jointFL;
        private JointWheel my_jointFR;
    
    	Controls controls = null;
    	float angle = 0.0f;
    	float velocity = 0.0f;
    	float torque = 0.0f;
    
    	private float ifps;
    
    	private void Init()
    	{
    	my_jointL = targetWheelL.ObjectBodyRigid.GetJoint(0) as JointWheel;
    	my_jointR = targetWheelR.ObjectBodyRigid.GetJoint(0) as JointWheel;
    	my_jointFL = targetFWL.ObjectBodyRigid.GetJoint(0) as JointWheel;
    	my_jointFR = targetFWR.ObjectBodyRigid.GetJoint(0) as JointWheel;
    
    	PlayerPersecutor player = new PlayerPersecutor();
    	controls = player.Controls;
    	}
    	
    	private void Update()
    	{
    			ifps = Game.IFps;
    
    			if ((controls.GetState(Controls.STATE_FORWARD) == 1) || (controls.GetState(Controls.STATE_TURN_UP) == 1))
    			{
    				velocity = MathLib.Max(velocity, 0.0f);
    				velocity += ifps * 50.0f;
    				torque = 5.0f;
    			}
    			else if ((controls.GetState(Controls.STATE_BACKWARD) == 1) || (controls.GetState(Controls.STATE_TURN_DOWN) == 1))
    			{
    				velocity = MathLib.Min(velocity, 0.0f);
    				velocity -= ifps * 50.0f;
    				torque = 5.0f;
    			}
    			else
    			{
    				velocity *= MathLib.Exp(-ifps);
    			}
    			velocity = MathLib.Clamp(velocity, -90.0f, 90.0f);
    
    			if ((controls.GetState(Controls.STATE_MOVE_LEFT) == 1) || (controls.GetState(Controls.STATE_TURN_LEFT) == 1))
    				angle += ifps * 100.0f;
    			else if ((controls.GetState(Controls.STATE_MOVE_RIGHT) == 1) || (controls.GetState(Controls.STATE_TURN_RIGHT) == 1))
    				angle -= ifps * 100.0f;
    			else
    			{
    				if (MathLib.Abs(angle) < 0.25f) angle = 0.0f;
    				else angle -= MathLib.Sign(angle) * ifps * 45.0f;
    			}
    	
    			angle = MathLib.Clamp(angle, -10.0f, 10.0f);
    
    			float base_a = 3.3f;
    			float width = 3.0f;
    			float angle_0 = angle;
    			float angle_1 = angle;
    			if (MathLib.Abs(angle) > MathLib.EPSILON)
    			{
    				float radius = base_a / MathLib.Tan(angle * MathLib.DEG2RAD);
    				angle_0 = MathLib.Atan(base_a / (radius + width / 2.0f)) * MathLib.RAD2DEG;
    				angle_1 = MathLib.Atan(base_a / (radius - width / 2.0f)) * MathLib.RAD2DEG;
    			}
    
    			my_jointFL.Axis10 = MathLib.RotateZ(angle_0).GetColumn3(0);
    			my_jointFR.Axis10 = MathLib.RotateZ(angle_1).GetColumn3(0);
    
    			if (controls.GetState(Controls.STATE_USE) == 1)
    			{
    				velocity = 0.0f;
    					my_jointL.AngularDamping = 20000.0f;
    					my_jointR.AngularDamping = 20000.0f;
    					my_jointFL.AngularDamping = 20000.0f;
    					my_jointFR.AngularDamping = 20000.0f;
    			}
    			else
    			{
    					my_jointL.AngularDamping = 0.0f;
    					my_jointR.AngularDamping = 0.0f;
    					my_jointFL.AngularDamping = 0.0f;
    					my_jointFR.AngularDamping = 0.0f;
    			}
    
    			if (Input.IsKeyDown(Input.KEY.Q))
    			{
    				targetCar.ObjectBodyRigid.AddLinearImpulse(vec3.UP*1000f);
    			}
    			if (Input.IsKeyDown(Input.KEY.E))
    			{
    				targetCar.ObjectBodyRigid.AddLinearImpulse(vec3.DOWN*1000f);
    			}
    
    	}
    
    		private void UpdatePhysics()
    		{
    			my_jointL.AngularVelocity = velocity;
    			my_jointR.AngularVelocity = velocity;
    			
    			my_jointL.AngularTorque = torque;
    			my_jointR.AngularTorque = torque;
    		}
    



    Также прилагаю более подробный разбор самого скрипта:

    общий разбор получившегося TheVehicle.cs
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using Unigine;
    
    [Component(PropertyGuid = "ca695c8787d5703a22a6c2516a3c177cddf38cab")]
    public class TheVehicle : Component
    {
    

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

    [ShowInEditor][Parameter(Tooltip = "Left Wheel")]
    private Node targetWheelL = null;
    [ShowInEditor][Parameter(Tooltip = "Right Wheel")]
    private Node targetWheelR = null;
    
    [ShowInEditor][Parameter(Tooltip = "Left Wheel F")]
    private Node targetFWL = null;
    [ShowInEditor][Parameter(Tooltip = "Right Wheel F")]
    private Node targetFWR = null;
    
    [ShowInEditor][Parameter(Tooltip = "theCar")]
    private Node targetCar = null;
    

    Здесь мы выводим поля для ссылок на объекты. Поля эти будут видны в редакторе и в строке tooltip можно написать подсказку о том, что за объект требуется.

    private JointWheel my_jointL;
    private JointWheel my_jointR;
    private JointWheel my_jointFL;
    private JointWheel my_jointFR;
    

    Заводим переменные для джоинтов.

    Controls controls = null;
    
    float angle = 0.0f;
    float velocity = 0.0f;
    float torque = 0.0f;
    
    private float ifps;
    

    И для прочих параметров.

    private void Init()
    {
    my_jointL = targetWheelL.ObjectBodyRigid.GetJoint(0) as JointWheel;
    my_jointR = targetWheelR.ObjectBodyRigid.GetJoint(0) as JointWheel;
    my_jointFL = targetFWL.ObjectBodyRigid.GetJoint(0) as JointWheel;
    my_jointFR = targetFWR.ObjectBodyRigid.GetJoint(0) as JointWheel;
    

    Начинаем инициализацию с того, что найдём на физическом теле каждого цилиндра номер джоинта и преобразуем в ссылку на JointWheel.
    Вроде бы ссылку на джоинт можно взять её иначе, например, через получение компонента (но не нашёл форму записи).

    			
    // setting up player and controls
    PlayerPersecutor player = new PlayerPersecutor();
    controls = player.Controls;
    }
    

    Здесь я оставил часть кода, создающего PlayerPresecutor, чтобы просто взять его Controls для управления и не выискивать другого player в сцене.

    private void Update()
    {
    ifps = Game.IFps;
    
    // forward and backward movement by setting joint motor's velocity and torque
    			if ((controls.GetState(Controls.STATE_FORWARD) == 1) || (controls.GetState(Controls.STATE_TURN_UP) == 1))
    			{
    				velocity = MathLib.Max(velocity, 0.0f);
    				velocity += ifps * 50.0f;
    				torque = 5.0f;
    			}//Input.IsKeyDown(Input.KEY.DOWN)
    			else if ((controls.GetState(Controls.STATE_BACKWARD) == 1) || (controls.GetState(Controls.STATE_TURN_DOWN) == 1))
    			{
    				velocity = MathLib.Min(velocity, 0.0f);
    				velocity -= ifps * 50.0f;
    				torque = 5.0f;
    			}
    			else
    			{
    				velocity *= MathLib.Exp(-ifps);
    			}
    			velocity = MathLib.Clamp(velocity, -90.0f, 90.0f);
    
    			// steering left and right by changing Axis01 for front wheel joints
    			if ((controls.GetState(Controls.STATE_MOVE_LEFT) == 1) || (controls.GetState(Controls.STATE_TURN_LEFT) == 1))
    				angle += ifps * 100.0f;
    			else if ((controls.GetState(Controls.STATE_MOVE_RIGHT) == 1) || (controls.GetState(Controls.STATE_TURN_RIGHT) == 1))
    				angle -= ifps * 100.0f;
    			else
    			{
    				if (MathLib.Abs(angle) < 0.25f) angle = 0.0f;
    				else angle -= MathLib.Sign(angle) * ifps * 45.0f;
    			}
    
    			angle = MathLib.Clamp(angle, -10.0f, 10.0f);// по умолчанию был поворот на 30 градусов (angle, -30.0f, 30.0f)
    
    			// calculating steering angles for front joints (angle_0 and angle_1)
    			float base_a = 3.3f;
    			float width = 3.0f;
    			float angle_0 = angle;
    			float angle_1 = angle;
    			if (MathLib.Abs(angle) > MathLib.EPSILON)
    			{
    				float radius = base_a / MathLib.Tan(angle * MathLib.DEG2RAD);
    				angle_0 = MathLib.Atan(base_a / (radius + width / 2.0f)) * MathLib.RAD2DEG;
    				angle_1 = MathLib.Atan(base_a / (radius - width / 2.0f)) * MathLib.RAD2DEG;
    			}
    

    Начало цикла Update, где творится основная магия и где я особо ничего не трогал, кроме угла поворота передних колёс.

    my_jointFL.Axis10 = MathLib.RotateZ(angle_0).GetColumn3(0);
    my_jointFR.Axis10 = MathLib.RotateZ(angle_1).GetColumn3(0);
    
    // enabling or disabling a brake
    if (controls.GetState(Controls.STATE_USE) == 1)
    {
    velocity = 0.0f;
    my_jointL.AngularDamping = 20000.0f;
    my_jointR.AngularDamping = 20000.0f;
    my_jointFL.AngularDamping = 20000.0f;
    my_jointFR.AngularDamping = 20000.0f;
    }
    else
    {
    my_jointL.AngularDamping = 0.0f;
    my_jointR.AngularDamping = 0.0f;
    my_jointFL.AngularDamping = 0.0f;
    my_jointFR.AngularDamping = 0.0f;
    }
    

    Здесь я подставил ссылки к своим колёсам, взамен тех, что создавались через код. И убрал обращение к каждому колесу в цикле, переписав в конкретные поимённые 4 строчки. Так делать было не принципиально, но так было удобнее настраивать в прочих местах и не запутаться в элементах массива, к тому же у нас пока и не 10 колёс.

    if (Input.IsKeyDown(Input.KEY.Q))
    {
    targetCar.ObjectBodyRigid.AddLinearImpulse(vec3.UP*1000f);
    }
    
    if (Input.IsKeyDown(Input.KEY.E))
    {
    targetCar.ObjectBodyRigid.AddLinearImpulse(vec3.DOWN*1000f);
    }
    
    }
    

    В конце Update пара дополнительных кнопок, которые придают основному кубу импульс. Правда можно было не получать ссылку на саму машинку, но я пока не проверял как писать обращаясь к самому объекту — через некий местный «this.» или просто пропуская ссылку.

    private void UpdatePhysics()
    {
    // set angular velocity for rear joints
    my_jointL.AngularVelocity = velocity;
    my_jointR.AngularVelocity = velocity;
    
    // set torque for rear joints
    my_jointL.AngularTorque = torque;
    my_jointR.AngularTorque = torque;
    
    }
    
    }
    

    Ну и Update Physics в конце, где у колёс обновляются velocity и torque.


    После этого сохраняете файл скрипта и возвращаетесь в окно редактора. Unigine проверит программу на ошибки и если всё нормально, то справа внизу должен показаться зелёный прямоугольник с уведомлением Project build successful.

    Теперь выбираем Cuboid, добавляем ему поле компонента и перетягиваем туда наш скрипт. Появляется несколько полей, которые надо заполнить. В поле Target Wheel L аккуратно перетягиваем левое заднее колесо (то есть обозначающий его Cylinder), Target Whell R — правое заднее колесо, Target FWL — переднее левое, Target FWR — переднее правое, и в поле Target Car — сам Cuboid.


    Для добавления компонента находим в свойствах Cuboid вот такое поле и щёлкаем по нему


    Появится пустая строка, куда можно перетащить файл скрипта (или найти путь к нему через значок папки)


    Заполняем поля

    Запускаем Play — теперь машинка начала двигаться. Правда одновременно с персонажем, от лица которого вы сейчас смотрите на мир.

    Управление у неё следующее: W — катится вперёд, S — назад, A и D — поворот передних колёс влево и вправо соответственно. На Q подаётся импульс вверх, машинка как бы подпрыгивает. На E, наоборот, импульс подаётся вниз.

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

    Дело в том, что вы можете самостоятельно зайти в эти настройки и поменять их. Для этого щёлкаем на верхнюю строку редактора. Windows > Settings, в открывшемся окне находим графу Physics и устанавливаем для Frozen Linear Velocity значение 0.1, для Frozen Angular Velocity тоже 0.1, а самый нижний вектор Gravity меняем с (0,0,-9,8) на (0,0, -19,6).

    То же самое можно проделать в коде, добавив следующие строки в Init:

    Physics.Gravity = new vec3(0.0f, 0.0f, -9.8f * 2.0f);
    Physics.FrozenLinearVelocity = 0.1f;
    Physics.FrozenAngularVelocity = 0.1f;
    

    Если вы запустите Play теперь, то «машинка», вероятно, опять испортится и начнёт проваливаться. Чтобы поправить это попробуйте покрутить Linear Spring у каждого колеса. Поднять в два раза, для начала. Когда настроите, то заметите что гравитация во время прыжков действует сильнее (правда сам импульс прыжка я настроил довольно высокий по умолчанию).

    Теперь, когда вы уже взмокли, настраивая эту капризную тележку, пришла пора сбросить оковы firts_person_controller и взглянуть на происходящее со стороны самой машинки, с камерой от третьего лица.
    Сачала отключаем first_person_controller (нажимаем на квадратик рядом с его именем, чтобы тот стал пустым).
    Создаём камеру. Для этого щёлкаем правой кнопкой по сцене и выбираем Create > Camera > Presecutor. Появится сама камера PlayerPresecutor и её цель-пустышка Presecutor_Target.
    Пустышку можно отключить. Щёлкаем на саму камеру, обязательно отмечаем в её свойствах галочку Main Player (чтобы не получить чёрный экран при запуске). В поле Target Node находим наш Cuboid в выпадающем списке.

    Сохраняемся и нажимаем Play.

    Теперь уже можно полноценно кататься. Правда камера может довольно далеко улететь. Чтобы это поправить, нужно отрегулировать её лимиты. Возвращаемся в редактор и выставляем в свойствах камеры, например, Min Distance 10 и Max Distance 30.

    Невангеры в движке Unigine


    Научившись собирать приблизительную физику колёс я сделал тестовую сцену с моделькой био-мехоса из своего проекта Невангеры. Вот как это выглядит на данный момент:



    Скриншоты







    Основные проблемы на текущий момент — некоторая нестабильность конструкции. Что связано опять же с тем, что я пока не досконально разобрался во всех аспектах и нюансах настройки. Иногда машинка обо что-то запинается и начинает люто вращаться. Уменьшение Linear Damping вроде не особо помогает, подкручивание прочих параметров тоже, хотя что-то я ещё не крутил или настраивал, но не комплексно.

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

    В целом, на достаточно ровной поверхности движение уже более адекватное. Не совсем ясно насколько хорошо Wheel Joints ведут себя на неровной поверхности в идеале, как я понял в видеодемках движка для машинок использовались устаревшие (вроде бы), но более физически «честные» Suspension Joints (кстати на них собирать машинку я и не пробовал, возможно, там всё несколько проще). Учитывают ли Wheel Joints параметры трения ландшафта, или их интересует только то, включен ли на на нём флажок физики.

    Общие впечатления от движка


    Что касается работы с редактором, мне больше всего происходящее напомнило процесс работы с CryEngine. Хотя местами удобнее. Но это кому как, в том же CryEngine можно было моделировать прямо в редакторе. Мне это не актуально, потому как для подобного использую Blender, где у тебя на порядки больше контроля над моделью, а кому-то может быть важно.

    На нижеследующей картинке слева виден список материалов. Меши красятся через группу mesh based материалов, от которых можно клонировать свои варианты материала. Покрасить террейн текстурой уже сложнее, зато по полной используются маски — можно смешивать текстуры, карты высот и так далее, если разобраться в устройстве. Для той же воды есть своя отдельная группа материалов, добавляется она буквально в один клик и довольно хорошо выглядит.

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



    UPD.
    Исходник с наработками по этому проекту знакомый выложил на GitLab:
    gitlab.com/axelaredz/newangers

    Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

    В какой сфере Unigine engine пригодился бы лично вам?

    • 71,4%В разработке игр20
    • 25,0%Для создания красивых картинок и визуализаций7
    • 21,4%Рендер анимации и роликов6
    • 28,6%Симуляции и исследования8
    • 10,7%Прочее3
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 14

      0

      Как вы приводите колёса в движения? Используется какая-то управляющая мощность или регуляторы скорости?

        0
        Я чуть позже прикреплю код. Там активируются моторы джоинтов.
          0

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

            0
            Физический смысл подробнее разъясняется в примере из документации. Плюс там есть ссылки на отдельные аспекты.
          0
          Тоже интересен этот момент, ждем код
            0
            Добавил. Собственно он является перекомпиляцией того кода, что описывается в статье из документации для машинки на Wheel Joint'ах. Чтобы адаптировать его к собранной модели, вместо создания её через код.
          +1
          Финальный результат выглядит очень эффектно!
          –3

          сочувствую тебе что проходится через такое проходить в 2020 году...


          все это кодописание должно быть шаблонами в движке, уже давно как, и пользователь мышкой ставит куда хочет в редакторе (без выбора миллиона параметров в каждой менюшке)


          создавать игры в 2020 году не сложнее игры в майнкрафт


          если этот движок этого не позволяет и вынуждает писать тонны кода на каждое банальное движение и вручную еще все повороты с матрицами пересчитывать-то в мусорку такой движок

            0
            В мусорку таких кодеров. Люди могут создавать игры, передвигая лапками шаблоны, но потом те же люди на говно исходят, когда у них Unity тормозит
              0

              в новом поколении ничего не будет тормозить, 32Гб оперативки и супербыстрые ССД


              больше времени на реал лайф останется, а это бессмысленное кодописание должно остаться в прошлом навсегда

                0
                Скажете это, когда в предел мощностей окончательно упремся
                  0
                  Вопрос в том, когда эти 32Гб и ССД будут у юзеров (на ПК и особенно мобилках), универсальные компоненты не бесплатны не только при разработке но и при выполнении тоже.
                  Но да, для быстрого прототипирования движки с необходимостью писать много бойлерплейт-кода не подходят.
              +1
              Здесь www.youtube.com/user/aethernova/videos
              и тут
              www.youtube.com/He6oru
              можно следить за ходом разработки

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое