Разработка ремейка методами чайника. Часть 4

Вот наступил новый год, и пока все валяются в салатах за добротной выпивкой, я сражаюсь с парусной оснасткой. В прошлой статье я упомянул что реализовать адаптивные паруса далеко не так просто как могло показаться на первый взгляд. Конечно, не будь я чайником, то думаю довольно быстро собрал бы рабочее решение. Но коль я чайник, то и проблемы решаю “по-чайничьи”!

Расскажу вам небольшую историю: жил да был один разработчик который влез в то, что не понимает. И пошел он по привычному в таком случае пути - гуглению необходимой информации. Долго разработчик гуглил, да никак не мог найти ответа на свой вопрос. Сами вопросы были в наличии, а вот ответ на них - так и не был получен. Но разработчик не опускал руки, он начал изучать все что косвенно связано с темой. Прочитав множество статей и просмотрев множество видеороликов, он нашел много возможных решений. Но каждое решение обладало недостатком, которое не получалось никак исправить или скрыть. В конце-концов разработчик выбился из сил, и ушел в небытие…

Блуждая во тьме длительное время, разработчик понял, что с таким темпом он утонет в этом болоте и будет жалеть об этом. Но он также осознавал, что для того чтобы что-то сделать - нужна мотивация. И тогда, в канун рождества, он решил начать цикл статей, в которых опишет процесс разработки и с какими проблемами приходится сталкиваться. Быть может это позволит взглянуть на проблему с другой стороны или даже найдется помощь от эксперта в этом направлении.

И вот, я наконец-то нашел подходящее решение которое способен реализовать, и которое выглядит не хуже, а может и лучше, чем в оригинале! Но давайте обо всем по порядку.

Попытка с использованием Procedural mesh component

Как вы помните, система тросов была организована только средствами UE, генерация, соединение между точками… все это внутри движка. Поэтому я разумеется решил, раз я сделал тросы в рамках движка, то почему бы паруса не реализовать таким же образом? Все в одном месте так сказать. Покопавшись в интернетах, я нашел методы Add procedural mesh component и Create mesh section. Получается моей задачей будет собрать массив из вершин, треугольников и UV-развертки. Если бы мы говорили о каком-нибудь питоне, то реализовать это было бы несложно за счет большого кол-ва референсов и библиотек. Но у нас тут Blueprint, и эта задача для чайника без хорошей математической базы - мягко говоря начинает ошеломлять.

Кое-как написав логику построения вершин, я собрал это дело в простенький скрипт с захардкоженными треугольниками и UV. Запустил и убедился что технически это возможно:

Разработка ремейка методами чайника. Часть 4
Разработка ремейка методами чайника. Часть 4

Круто! Давайте еще накинем сокет на сгенерированный объект и попробуем связать парус с тросом. И тут возникает первая проблема: мы не можем добавлять сокеты в рантайме. Только через редактор мешей, а сгенерированный меш так отредактировать невозможно!

Ладно, мы не можем добавлять сокеты, так может мы сможем их перемещать рассчитывая текущее положение паруса? А вот и нет. Сокеты могут перемещаться только если они связаны с костью, а кость есть только у Skeletal Mesh, который мы разумеется тоже не можем сгенерировать.

А как нам в принципе взаимодействовать с парусом? Предположим что квадратный парус можно уменьшать/увеличивать в размере путем скалирования, но как быть с треугольными парусами? Получается нижние точки должны подниматься до верхних. Т.е. нам нужно как-то в рантайме модифицировать вершины объекта. Можно ли в UE вручную двигать вершины? - Я такой возможности не нашел.

Получается тупик… копаем дальше.

Попытка через Morph targets

В поисках информации по реализации парусов на UE, я нашел стрим Inside Unreal: Prototyping Sailing Mechanics, разделенный на 2 видео по 2.5 часа. Времени было потрачено немало, но я нашел для себя такую функциональность как Morph targets и World Position Offset для манипуляции вершинами объекта через текстуру.

С помощью морфов, было реализовано поднятие/спуск парусов. А с помощью отступа - эффект их расширения от ветра. Пробуем собрать:

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

Но опустим пока что эту проблему, может получится её как-то решить. Посмотрим получится ли у нас связать трос с парусом. В данном варианте нам важно чтобы сокет учитывал значение морфа. И знаете что? Нельзя связать морф с сокетом! Да б…! Получается придется вручную просчитывать положение сокета относительно паруса. Но парус может быть разной формы, степень деформации морфами тоже… да это просто прорва потенциальной головной боли со стыковками.

Помимо вышеописанных проблем - остается проблема с самим созданием морфов. Каким образом их создавать? Руками под каждый парус в блендере? Да я с ума сойду!

Отложим вариант в сторону и продолжим поиски…

Пара слов о использовании Cloth Simulation

Продолжая свои изыскания, я вышел на дискорд проекта Ocean Project. Это набор плагинов симуляции океана, в котором мой глаз зацепился за человека который делал симуляцию паруса на драккаре. Я задал ему вопрос по реализации, но он к сожалению куда-то пропал. Однако, судя по сообщениям, он использовал симуляцию ткани. Почему бы тоже не попробовать?

  • Первый выстрел в ногу - симуляцию ткани можно настроить ТОЛЬКО в интерфейсе UE, путем ручной прорисовки областей которым нужна симуляция. Т.е. даже если в блендере создать ткань с симуляцией, в UE она работать не будет. А значит автоматизацией тут и не пахнет. Мне нужно перенести десятки кораблей, а значит сотни парусов… Мне автоматизация нужна позарез!
  • Второй выстрел в ногу - симуляция полностью ломает морфы, даже если морфы используют незадействованные в симуляции вершины. А как без морфов поднимать паруса? У меня есть мысли как можно квадратные поднять костылями, но не треугольные.
  • Третий выстрел - скорее всего будут проблемы с производительностью на карте с большим кол-вом кораблей. Симуляция все таки не такая дешевая как простая анимация.

Так что симуляция в пролете.

Гибридное решение

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

Требования:

  • Автоматизация создания парусов разных форм
  • Плавное поднятие и спуск
  • Эффект раздуваемых парусов настраиваемый под направление ветра
  • Возможность закрепить трос за перемещаемую часть паруса

Инструменты:

  • В Blender есть возможно писать свои кастомные скрипты на питоне, тем самым автоматизировать создание парусов
  • Плавности поднятия и спуска можно достигнуть с помощью Morph Targets
  • Направление и силу ветра можно регулировать через World Position Offset, но делать это через текстуру очень неудобно. Поэтому можем просто создать еще один морф, который будет отвечать за силу ветра. Благо в Blender можно имитировать ветер.
  • Трос можно закрепить только за сокет, а сокет можно закрепить только за костью. Таким образом, нам необходимо создавать кость, которая будет в точности повторять движение паруса, и экспортировать это как анимацию. Благо это тоже можно автоматизировать.

Пример скрипта, который строит анимированный парус по Empty-точкам я выложил тут.

А вот как это выглядит вместе со скриптом сбора корабля:

Как можно увидеть: сперва на сцену добавляется ветер, выжидается время пока паруса не стабилизируются, затем делается Shape Key-снимок с текущего состояния вершин(вы же помните что мы не можем импортировать в UE физику ткани?). Затем ветер удаляется, делается симуляция свертывания ткани и делается уже снимок поднятия/спуска парусов. Как можно заметить, из-за того, что точки парусов пересекают мачты, ткань в свернутом состоянии выглядит весьма странно. Это можно поправить увеличив морф силы ветра, но в некоторых случаях, если парус записался с выпиранием вперед(можно заметить на бриге такую проблему), это может сделать только хуже. Но тут я думаю вопрос просто в подборе настроек для симуляции ткани.

В следующей статье поговорим об импорте парусов в Unreal Engine, а также соберем их вместе с кораблем и тросами.

Если вам понравилась статья, то буду благодарен за ваши комментарии, вопросы или советы!

1616
7 комментариев

Я тоже повторю вопрос выше, но чуть конкретней: почему каждый тип паруса не сделать как скелетал меш, сделать ему несколько анимаций (поднятый, спущеный с разной силой ветра), и просто не поставить каждому animation blueprint?

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

Даже не уверен, зачем тут процедурная генерация...

Спасибо за вопрос!
Ответ очень простой: потому что форма парусов в корсарах не одинаковая для всех кораблей. Форма определяется специальными точками - локаторами. Соответственно, тот же кливер может иметь немного разную форму на разных кораблях, и простым скалированием это не поправить. А мне необходимо чтобы форма точно соответствовала, иначе локаторы отвечающие за крепления с тросами не будут находиться на нужных позициях. Руками это все двигать на десятках кораблей - я кукухой поеду.

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

1

Ничего не знаю ни про блендер, ни про Unreal Engine, поэтому глупый вопрос - а нельзя в блендере сделать анимацию паруса (спуск/поднятие) а потом её забейкать и импортнуть в анреал ?

Ну, собственно об этом в статье и написано)