[Разработка игр, Unreal Engine] Как и зачем мы добавили новый тип треков для Sequencer UE4
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
В ходе разработки нашей игры мы столкнулись с необходимостью добавить возможность показывать элементы игрового интерфейса (виджеты) во время проигрывания катсцен. При этом требовалось обеспечить:
- возможность настройки содержимого виджетов;
- время их демонстрации;
- сделать все это простым в использовании для геймдизайнеров.
В нашем проекте практически все катсцены созданы с использованием одного из инструментов UE4 — Sequencer. Основной задачей Sequencer является предоставление удобного функционала для создания различных кинематографичных вставок на игровом движке. Наиболее популярным его применением является создание заскриптованных игровых событий — катсцен, пролетов камер и любых других событий, где мы хотим показать игроку заранее срежиссированное событие.Кроме того, Sequencer позволяет не только показывать подготовленную последовательность прямо в игре, но и отрендерить предоставленную последовательность в видео-файл, что сделало его популярным инструментом для создания трейлеров и подготовки промо материалов.В ходе изучения способов добавления новой логики, было принято решение расширить инструментарий Sequencer-а. Это позволяло продолжить создание катсцен с новыми событиями по единому пайплайну. Несмотря на то, что есть достаточно много подробной документации по работе с Sequencer-ом, способы его расширения и внутренние механизмы работы оставались покрыты тайной. В этой статье я расскажу о том, как мы добавили нужную нам логику, в качестве новых типов треков в Sequencer, и настроили их поведение. Все модификации осуществляются на версии движка UE4 4.24.3, а сам способ расширения потребует знания C++.Основные элементы Sequencer-аЕсли вы следили за развитием движка, либо работали с его ранними версиями, то можете помнить предшественника Sequencer — Matinee. Со временем от Matinee отказались в пользу нового и более развитого Sequencer-а, но общее предназначение данной тулзы сохранилось. Можно также провести аналогию с одним из инструментов другого игрового движка Unity — Timeline. В качестве одного из многочисленных примеров можно привести вступительную катсцену из одного демо проекта UE4:
А так, данная катсцена выглядит при настройке в редакторе:
Как видно, интерфейс для работы с Sequencer-ом во многом напоминает интерфейс видеоредакторов и в его использовании применяются аналогичные принципы.Из основных элементов нас интересуют раздел со списком треков (1) и окно таймлайна с секциями треков (2), где мы выстраиваем изменение секций треков в зависимости от текущего кадра последовательности.
В нашем случае в роли треков выступают разнообразные игровые события: проигрывание анимаций, звуков, эффектов, манипуляции с игровыми объектами и по сути любые возможные изменения на игровой сцене. Отмечу, что хранение всех файлов Sequencer-a в UE4 осуществляется, так же как и всех остальных файлов проекта UE4 — в виде ассетов:
И по сути работа с такими ассетами осуществляется точно так же, как и с любыми другим файлами проекта. Sequencer уже содержит множество готовых треков с разнообразными параметрами для настройки:
Так, например, “Audio Track” позволяет добавить проигрывание звука в нужные нам моменты времени, а также изменять параметры этого звука:
Трек “Actor To Sequence” позволяет добавить ссылку на одного из акторов с игровой сцены и также изменять его параметры — трансформ, анимации и другие доступные параметры:
Со всем списком доступных треков и их настроек лучше всего ознакомиться в официальной документации по UE4, а мы перейдем к возможным способам добавить необходимый нам функционал по созданию и отображению виджетов во время проигрывания катсцен.Одним из подходящих кандидатов является “Event Track”:
“Event Track” позволяет добавить скрипт в таймлайн и настроить его логику с помощью блюпринтов:
- Добавляем трек:
- Добавляем секцию трека в таймлайн:
- Далее двойным кликом мыши по секции (“SequenceEvent_0”) можно перейти в редактор логики, где можно создать свое событие:
Такой способ позволяет покрыть собой все недостающие возможности готовых треков, но у такого подхода был ключевой для нас недостаток — это отсутствие простой возможности использовать один и тот же эвент с разными настройками в разных катсценах. Нам бы пришлось каждый раз копировать содержимое эвентов между катсценами, либо каждый раз заново писать одинаковую логику поведения трека. При этом возможность каким-то образом облегчить этот процесс с помощью правок в функционале Sequencer-a выглядела достаточно трудоемкой и неочевидной.Тем не менее, “Event Track” хорошо подходит для создания дополнительных заскриптованных событий в катсценах.Другим способом оказалась идея добавить добавить свой собственный тип треков в Sequencer, который сразу бы содержал нужную логику и предоставлял аналогичный всем текущим трекам интерфейс для своей настройки. О том, как этого можно достичь и пойдет речь дальше.Создание нового трекаФункциональность Sequencer-а разнесена на несколько модулей:
- Модуль Sequencer-a (Engine\Source\Editor\Sequencer\) который отвечает за всю логику работы в редакторе (это весь UI инструмента, а следовательно и вся логика по созданию новых треков).
Данный модуль доступен только из редактора и отсутствует в игре.
- Модули MovieScene (Engine\Source\Runtime): MovieSceneTracks, MovieSceneTools, MovieSceneCapture, MovieSceneCaptureDialog. Данные модули отвечают за непосредственную логику работы разных элементов Sequencer-а.
Нас в основном будет интересовать модуль MovieSceneTracks, который реализует работу треков Sequencer-a. Если вам до этого не приходилось детально вникать в модульную архитектуру UE4, то поясню, что весь код движка разбит на множество модулей, каждый из которых имеет свое предназначение. Любой модуль, по сути, представляет собой отдельную dll-библиотеку. Изучив исходники доступных треков, можно понять какие интерфейсы и классы отвечают за элементы Sequencer. Например, Audio Track состоит из следующих частей:
Внутренние связи между классами выглядят следующим образом:
Таким образом для создания нового типа трека нам потребуется совершить следующие шаги:
- Создать наследника класса FMovieSceneTrackEditor.
- Реализовать интерфейс ISequencerSection.
- «Зарегистрировать» новый класс трека в модуле Sequencer.
- Создать наследника класса UMovieSceneTrack.
- Создать наследника класса UMovieSceneSection.
- Создать наследника FMovieSceneEventTemplate.
- Реализовать интерфейс IMovieSceneExecutionToken & IPersistentEvaluationData.
Из представленных классов часть относится к модулю редактора и реализует логику настройки треков и секций в редакторе — FMovieSceneTrackEditor, ISequencerSection. Класс-наследник FMovieSceneTrackEditor необходимо зарегистрировать в модуле Sequencer. Таким образом редактор узнает о новом типе треков.Остальные классы отвечают за логику работы трека и в игре, и в редакторе — UMovieSceneTrack, UMovieSceneSection, FMovieSceneEventTemplate;Перейдем к первому шагу создания нового трека — это создание класса, отвечающего за добавление нового трека в Sequencer — класс FMovieSceneTrackEditor.Создадим наш класс. Я заранее приведу все необходимые методы, а дальше опишу их назначение и реализацию:Код
class FMovieSceneSubtitlesTrackEditor : public FMovieSceneTrackEditor
{
public:
FMovieSceneSubtitlesTrackEditor(TSharedRef<ISequencer> InSequencer);
static TSharedRef<ISequencerTrackEditor> CreateTrackEditor(TSharedRef<ISequencer> OwningSequencer);
// ISequencerTrackEditor interface
virtual void BuildAddTrackMenu(FMenuBuilder& MenuBuilder) override;
virtual TSharedPtr<SWidget> BuildOutlinerEditWidget(const FGuid& ObjectBinding, UMovieSceneTrack* Track, const FBuildEditWidgetParams& Params);
virtual const FSlateBrush* GetIconBrush() const override;
virtual TSharedRef<ISequencerSection> MakeSectionInterface(UMovieSceneSection& SectionObject, UMovieSceneTrack& Track, FGuid ObjectBinding) override;
virtual bool SupportsType(TSubclassOf<UMovieSceneTrack> Type) const override;
virtual bool SupportsSequence(UMovieSceneSequence* InSequence) const override;
private:
FReply AddNewTrack(UMovieSceneTrack* Track);
};
Условно содержимое класса можно разбить на две части:
- Логика создания нового трека.
- Логика создания новых секций трека.
Кроме класса реализующего «трек», нам потребуется сразу создать класс FMovieSceneSubtitlesEventSection, отвечающий за секцию данного трека. Сам класс секции трека является реализацией интерфейса ISequencerSection.Код
class FMovieSceneSubtitlesEventSection : public ISequencerSection
{
public:
FMovieSceneSubtitlesEventSection(UMovieSceneSection& InSection, TWeakPtr<ISequencer> InSequencer);
virtual int32 OnPaintSection(FSequencerSectionPainter& InPainter) const override;
virtual UMovieSceneSection* GetSectionObject() override;
virtual FText GetSectionTitle() const override;
virtual float GetSectionHeight() const override;
private:
UMovieSceneSubtitleSection* Section;
};
Также сразу потребуется создать отдельный класс — UMovieSceneSubtitleSection. Новый класс будет хранить текст наших субтитров и использоваться секциями треков для запуска логики работы секции трека в игре:Код
UCLASS()
class UE4MAGICHIGH_API UMovieSceneSubtitleSection : public UMovieSceneSection
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, meta = (MultiLine = true))
FText SubtitleText;
virtual FMovieSceneEvalTemplatePtr GenerateTemplate() const override;
};
В первую очередь нам нужно зарегистрировать новый класс в модуле Sequencer. Это делается достаточно просто:Код
ISequencerModule& SequencerModule = FModuleManager::LoadModuleChecked<ISequencerModule>(TEXT("Sequencer"));
MovieSceneSubtitlesTrackEditorHandle = SequencerModule.RegisterTrackEditor(FOnCreateTrackEditor::CreateStatic(&FMovieSceneSubtitlesTrackEditor::CreateTrackEditor));
Регистрация по сути привязывает указатель на наш метод для создания объекта:Код
TSharedRef<ISequencerTrackEditor> FMovieSceneSubtitlesTrackEditor::CreateTrackEditor(TSharedRef<ISequencer> InSequencer)
{
return MakeShareable(new FMovieSceneSubtitlesTrackEditor(InSequencer));
}
Кроме этого, переопределим несколько методов отвечающих за проверки на возможность добавления нового типа трека:Код
bool FMovieSceneSubtitlesTrackEditor::SupportsType(TSubclassOf<UMovieSceneTrack> Type) const
{
return Type == UMovieSceneSubtitleTrack::StaticClass();
}
bool FMovieSceneSubtitlesTrackEditor::SupportsSequence(UMovieSceneSequence* InSequence) const
{
static UClass* LevelSequenceClass = FindObject<UClass>(ANY_PACKAGE, TEXT("LevelSequence"), true);
return InSequence != nullptr && LevelSequenceClass != nullptr && InSequence->GetClass()->IsChildOf(LevelSequenceClass);
}
Для корректной работы создания трека нам остается переопределить следующий метод:Код
void FMovieSceneSubtitlesTrackEditor::BuildAddTrackMenu(FMenuBuilder& MenuBuilder)
{
// добавляем новую кнопку(опцию) в меню
MenuBuilder.AddMenuEntry(
// прописываем название опции
LOCTEXT("AddSubtitlesEventTrack", "Subtitles Track"),
LOCTEXT("AddSubtitlesTrackTooltip", "Adds a subtitles track."),
// создаем стиль кнопки
FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.TextRenderComponent"),
// добавляем лямбду, которая реализует логику нажатия на кнопку
FUIAction(FExecuteAction::CreateLambda([=] {
auto FocusedMovieScene = GetFocusedMovieScene();
if (FocusedMovieScene == nullptr)
{
return;
}
const FScopedTransaction Transaction(LOCTEXT("MovieSceneSubtitlesTrackEditor_Transaction", "Add Subtitle Track"));
FocusedMovieScene->Modify();
// непосредственно создаем и добавляем наш трек в Sequencer
auto NewTrack = FocusedMovieScene->AddMasterTrack<UMovieSceneSubtitleTrack>();
ensure(NewTrack);
NewTrack->SetDisplayName(FText::FromString("Subtitles"));
GetSequencer()->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded);
})));
В результате мы сможем увидеть и добавить новый трек и секцию трека.
Для создания кнопки по добавлению секции, которая появляется при выделении курсором мыши нашего нового трека, потребуется реализовать оставшиеся методы:Код
TSharedPtr<SWidget> FMovieSceneSubtitlesTrackEditor::BuildOutlinerEditWidget(const FGuid& ObjectBinding, UMovieSceneTrack* Track, const FBuildEditWidgetParams& Params)
{
FSlateFontInfo SmallLayoutFont = FCoreStyle::GetDefaultFontStyle("Regular", 8);
// подготовим лямбду на отображение кнопки только при выделении ноды
auto OnGetVisibilityLambda = [this, Params]() -> EVisibility {
if (Params.NodeIsHovered.Get())
{
return EVisibility::SelfHitTestInvisible;
}
return EVisibility::Collapsed;
};
// текст кнопки
TSharedRef<STextBlock> ComboButtonText = SNew(STextBlock)
.Text(LOCTEXT("SubtitlesTextSequencer", "Subtitles")) .Font(SmallLayoutFont) .ColorAndOpacity(FSlateColor::UseForeground()) .Visibility_Lambda(OnGetVisibilityLambda);
// сама кнопка
TSharedRef<SButton> Button = SNew(SButton)
.ButtonStyle(FEditorStyle::Get(), "HoverHintOnly")
.ForegroundColor(FSlateColor::UseForeground())
.IsEnabled_Lambda([=]()
{ return GetSequencer().IsValid() ? !GetSequencer()->IsReadOnly() : false;
.ContentPadding(FMargin(5, 2))
// добавим лямбду для создания нового трека при нажатии на кнопку
.OnClicked(this, &FMovieSceneSubtitlesTrackEditor::AddNewTrack, Track)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(FMargin(0, 0, 2, 0))
[SNew(SImage)
// стиль нашей кнопки
.ColorAndOpacity(FSlateColor::UseForeground())
.Image(FEditorStyle::GetBrush("Plus"))]
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.AutoWidth()
[ComboButtonText]];
return Button;
}
// метод, вызываемый при нажатии на кнопку “добавить новую секцию”
FReply FMovieSceneSubtitlesTrackEditor::AddNewTrack(UMovieSceneTrack* Track)
{
UMovieScene* FocusedMovieScene = GetFocusedMovieScene();
Track->Modify();
// задаем начальные параметры секции
FFrameNumber KeyTime = GetSequencer()->GetGlobalTime().Time.FrameNumber;
auto SubtitleTrack = Cast<UMovieSceneSubtitleTrack>(Track);
TRange<FFrameNumber> SectionRange = FocusedMovieScene->GetPlaybackRange();
// добавляем секцию
SubtitleTrack->AddSection(KeyTime, SectionRange);
GetSequencer()->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded);
return FReply::Handled();
}
TSharedRef<ISequencerSection> FMovieSceneSubtitlesTrackEditor::MakeSectionInterface(UMovieSceneSection& SectionObject, UMovieSceneTrack& Track, FGuid ObjectBinding)
{
return MakeShareable(new FMovieSceneSubtitlesEventSection(SectionObject, GetSequencer()));
}
Таким образом мы сможем добавить новую секцию.
Возвращаясь к реализации класса FMovieSceneSubtitlesEventSection, который отвечает за отображение секций трека в Sequencer — в нашем случае ограничимся демонстрацией текста субтитров в поле секции.Код
virtual FText GetSectionTitle() const override
{
return Section->SubtitleText;
}
Для корректного отображения размера секции будем использовать простой алгоритм подсчета новых строк в субтитрах.Код
virtual float GetSectionHeight() const override
{
const FString StrSubtitles = Section->SubtitleText.ToString();
int NewLineSymbols = Algo::CountIf(StrSubtitles, [](const auto& symbol) {
return symbol == '\n';
});
NewLineSymbols++; // доп. место под первую строку
return SequencerSectionConstants::DefaultSectionHeight * NewLineSymbols;
}
Реализации методов в совокупности приводит к следующему виду секции трека:
Из остальных методов наибольший интерес представляет OnPaintSection.Код
virtual int32 OnPaintSection(FSequencerSectionPainter& InPainter) const override
{
return InPainter.PaintSectionBackground();
}
В нашем случае мы его никак не используем, но именно этот метод позволяет «рисовать» различные данные поверх окна секции. Например, именно этот метод отображает форму звука в AudioTrack.
Пример реализации такой логики лучше всего изучить самостоятельно в AudioTrackEditor.cpp.Остальные методы являются сугубо техническими.Код
FMovieSceneSubtitlesEventSection(UMovieSceneSection& InSection, TWeakPtr<ISequencer> InSequencer)
{
Section = Cast<UMovieSceneSubtitleSection>(&InSection);
}
virtual UMovieSceneSection* GetSectionObject() override
{
return Section;
}
Нам остается реализовать оставшиеся классы, которые содержат непосредственную логику работы наших субтитров:
- UMovieSceneSubtitleTrack;
- UMovieSceneSubtitleSection;
- FMovieSceneSubtitlesEventTemplate.
Класс UMovieSceneSubtitleSection является самым простым и содержит текст субтитров, которые мы хотим отображать. UPROPERTY этого класса будут доступны для настройки из самого Sequencer.Код
UCLASS()
class UE4MAGICHIGH_API UMovieSceneSubtitleSection : public UMovieSceneSection
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, meta = (MultiLine = true))
FText SubtitleText;
virtual FMovieSceneEvalTemplatePtr GenerateTemplate() const override
{
return FMovieSceneSubtitlesEventTemplate(*this);
}
};
Класс UMovieSceneNameableTrack содержит логику по созданию объекта, который уже отвечает за:
- создание виджета субтитров;
- передачу параметров;
- завершение работы виджета субтитров.
Код
void FMovieSceneSubtitlesEventTemplate::Evaluate(const FMovieSceneEvaluationOperand& Operand, const FMovieSceneContext& Context, const FPersistentEvaluationData& PersistentData, FMovieSceneExecutionTokens& ExecutionTokens) const
{
ExecutionTokens.Add(FSubtitlesSectionExecutionToken(Section));
}
void FMovieSceneSubtitlesEventTemplate::TearDown(FPersistentEvaluationData& PersistentData, IMovieScenePlayer& Player) const
{
FCachedSubtitlesTrackData& TrackData = PersistentData.GetOrAddTrackData<FCachedSubtitlesTrackData>();
TrackData.RemoveSubtitleWidget();
}
Сам класс, по сути, просто создает объект, реализующий интерфейс IMovieSceneExecutionToken. В IMovieSceneExecutionToken нас интересует метод Evaluate, который и будет создавать виджет на экране пользователя. Сам виджет будет просто отображать текст на экране пользователя.Код
struct FSubtitlesSectionExecutionToken : IMovieSceneExecutionToken
{
FSubtitlesSectionExecutionToken(const UMovieSceneSubtitleSection* InSubtitleSection)
: SubtitleSection(InSubtitleSection), SectionKey(InSubtitleSection)
{
}
virtual void Execute(const FMovieSceneContext& Context, const FMovieSceneEvaluationOperand& Operand, FPersistentEvaluationData& PersistentData, IMovieScenePlayer& Player)
{
FCachedSubtitlesTrackData& TrackData = PersistentData.GetOrAddTrackData<FCachedSubtitlesTrackData>();
UObject* PlaybackContext = Player.GetPlaybackContext();
if (!PlaybackContext)
{
return;
}
UWorld* World = PlaybackContext->GetWorld();
if (!World)
{
return;
}
UMHSubtitlesWindow* SubtitlesWindow = Cast<UMHSubtitlesWindow>(UUserWidget::CreateWidgetInstance(*World, HUD->SubtitlesWidgetTemplate, FName(TEXT("Subtitles"))));
SubtitlesWindow->AddToViewport();
SubtitlesWindow->SetSubtitltesText(SubtitleSection->SubtitleText);
TrackData.AddSubtitleWidget(SubtitlesWindow);
}
}
const UMovieSceneSubtitleSection* SubtitleSection;
FObjectKey SectionKey;
};
Для хранения данных секции используется объект, реализующий интерфейс IPersistentEvaluationData.Код
struct FCachedSubtitlesTrackData : IPersistentEvaluationData
{
void AddSubtitleWidget(UMHWindowWidget* Widget)
{
SubttitleWidget = Widget;
}
void RemoveSubtitleWidget()
{
if (SubttitleWidget &&
SubttitleWidget->IsValidLowLevel())
{
SubttitleWidget->RemoveFromViewport();
}
SubttitleWidget = nullptr;
}
UMHWindowWidget* GetSubtitleWidget() const
{
return SubttitleWidget;
}
private:
UMHWindowWidget* SubttitleWidget = nullptr;
};
В итоге мы сможем увидеть субтитры при проигрывании катсцены.
На этом наша цель достигнута!
===========
Источник:
habr.com
===========
Похожие новости:
- [Разработка под iOS, Разработка мобильных приложений, Разработка игр, Unity] Запуск игры на Unity из приложения SwiftUI для iOS (перевод)
- [Разработка игр, Тестирование игр] World of Tanks Blitz: Автоматизированное тестирование производительности
- [Разработка игр, Игры и игровые приставки, Интервью] Подкаст «Хочу в геймдев» #1
- [Разработка игр, DIY или Сделай сам, Игры и игровые приставки] Восстание игроков: игра как миф
- [Управление проектами, Монетизация игр, Бизнес-модели, Дизайн игр, Дизайн] Через тернии к Нуубам
- [Open source, Разработка игр, Графический дизайн, Дизайн игр, DIY или Сделай сам] О ходе создания русской народной игры «Колобок» в феврале
- [Python, Разработка игр, Тестирование игр] Как убедить гейм-дизайнера запустить тесты?
- [Разработка игр, Компьютерное железо, Процессоры] NVIDIA Ampere против AMD RDNA 2: битва архитектур (перевод)
- [Разработка игр, Игры и игровые приставки, Интервью] Подкаст «Хочу в геймдев» #21
- [Open source, Разработка игр, ООП, Развитие стартапа] Idewavecore. Ретроспектива
Теги для поиска: #_razrabotka_igr (Разработка игр), #_unreal_engine, #_social_quantum, #_unreal_engine_4, #_razrabotka_igr (разработка игр), #_sequencer, #_blog_kompanii_social_quantum (
Блог компании Social Quantum
), #_razrabotka_igr (
Разработка игр
), #_unreal_engine
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 16:22
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
В ходе разработки нашей игры мы столкнулись с необходимостью добавить возможность показывать элементы игрового интерфейса (виджеты) во время проигрывания катсцен. При этом требовалось обеспечить:
А так, данная катсцена выглядит при настройке в редакторе: Как видно, интерфейс для работы с Sequencer-ом во многом напоминает интерфейс видеоредакторов и в его использовании применяются аналогичные принципы.Из основных элементов нас интересуют раздел со списком треков (1) и окно таймлайна с секциями треков (2), где мы выстраиваем изменение секций треков в зависимости от текущего кадра последовательности. В нашем случае в роли треков выступают разнообразные игровые события: проигрывание анимаций, звуков, эффектов, манипуляции с игровыми объектами и по сути любые возможные изменения на игровой сцене. Отмечу, что хранение всех файлов Sequencer-a в UE4 осуществляется, так же как и всех остальных файлов проекта UE4 — в виде ассетов: И по сути работа с такими ассетами осуществляется точно так же, как и с любыми другим файлами проекта. Sequencer уже содержит множество готовых треков с разнообразными параметрами для настройки: Так, например, “Audio Track” позволяет добавить проигрывание звука в нужные нам моменты времени, а также изменять параметры этого звука: Трек “Actor To Sequence” позволяет добавить ссылку на одного из акторов с игровой сцены и также изменять его параметры — трансформ, анимации и другие доступные параметры: Со всем списком доступных треков и их настроек лучше всего ознакомиться в официальной документации по UE4, а мы перейдем к возможным способам добавить необходимый нам функционал по созданию и отображению виджетов во время проигрывания катсцен.Одним из подходящих кандидатов является “Event Track”: “Event Track” позволяет добавить скрипт в таймлайн и настроить его логику с помощью блюпринтов:
Такой способ позволяет покрыть собой все недостающие возможности готовых треков, но у такого подхода был ключевой для нас недостаток — это отсутствие простой возможности использовать один и тот же эвент с разными настройками в разных катсценах. Нам бы пришлось каждый раз копировать содержимое эвентов между катсценами, либо каждый раз заново писать одинаковую логику поведения трека. При этом возможность каким-то образом облегчить этот процесс с помощью правок в функционале Sequencer-a выглядела достаточно трудоемкой и неочевидной.Тем не менее, “Event Track” хорошо подходит для создания дополнительных заскриптованных событий в катсценах.Другим способом оказалась идея добавить добавить свой собственный тип треков в Sequencer, который сразу бы содержал нужную логику и предоставлял аналогичный всем текущим трекам интерфейс для своей настройки. О том, как этого можно достичь и пойдет речь дальше.Создание нового трекаФункциональность Sequencer-а разнесена на несколько модулей:
Внутренние связи между классами выглядят следующим образом: Таким образом для создания нового типа трека нам потребуется совершить следующие шаги:
class FMovieSceneSubtitlesTrackEditor : public FMovieSceneTrackEditor
{ public: FMovieSceneSubtitlesTrackEditor(TSharedRef<ISequencer> InSequencer); static TSharedRef<ISequencerTrackEditor> CreateTrackEditor(TSharedRef<ISequencer> OwningSequencer); // ISequencerTrackEditor interface virtual void BuildAddTrackMenu(FMenuBuilder& MenuBuilder) override; virtual TSharedPtr<SWidget> BuildOutlinerEditWidget(const FGuid& ObjectBinding, UMovieSceneTrack* Track, const FBuildEditWidgetParams& Params); virtual const FSlateBrush* GetIconBrush() const override; virtual TSharedRef<ISequencerSection> MakeSectionInterface(UMovieSceneSection& SectionObject, UMovieSceneTrack& Track, FGuid ObjectBinding) override; virtual bool SupportsType(TSubclassOf<UMovieSceneTrack> Type) const override; virtual bool SupportsSequence(UMovieSceneSequence* InSequence) const override; private: FReply AddNewTrack(UMovieSceneTrack* Track); };
class FMovieSceneSubtitlesEventSection : public ISequencerSection
{ public: FMovieSceneSubtitlesEventSection(UMovieSceneSection& InSection, TWeakPtr<ISequencer> InSequencer); virtual int32 OnPaintSection(FSequencerSectionPainter& InPainter) const override; virtual UMovieSceneSection* GetSectionObject() override; virtual FText GetSectionTitle() const override; virtual float GetSectionHeight() const override; private: UMovieSceneSubtitleSection* Section; }; UCLASS()
class UE4MAGICHIGH_API UMovieSceneSubtitleSection : public UMovieSceneSection { GENERATED_BODY() public: UPROPERTY(EditAnywhere, meta = (MultiLine = true)) FText SubtitleText; virtual FMovieSceneEvalTemplatePtr GenerateTemplate() const override; }; ISequencerModule& SequencerModule = FModuleManager::LoadModuleChecked<ISequencerModule>(TEXT("Sequencer"));
MovieSceneSubtitlesTrackEditorHandle = SequencerModule.RegisterTrackEditor(FOnCreateTrackEditor::CreateStatic(&FMovieSceneSubtitlesTrackEditor::CreateTrackEditor)); TSharedRef<ISequencerTrackEditor> FMovieSceneSubtitlesTrackEditor::CreateTrackEditor(TSharedRef<ISequencer> InSequencer)
{ return MakeShareable(new FMovieSceneSubtitlesTrackEditor(InSequencer)); } bool FMovieSceneSubtitlesTrackEditor::SupportsType(TSubclassOf<UMovieSceneTrack> Type) const
{ return Type == UMovieSceneSubtitleTrack::StaticClass(); } bool FMovieSceneSubtitlesTrackEditor::SupportsSequence(UMovieSceneSequence* InSequence) const { static UClass* LevelSequenceClass = FindObject<UClass>(ANY_PACKAGE, TEXT("LevelSequence"), true); return InSequence != nullptr && LevelSequenceClass != nullptr && InSequence->GetClass()->IsChildOf(LevelSequenceClass); } void FMovieSceneSubtitlesTrackEditor::BuildAddTrackMenu(FMenuBuilder& MenuBuilder)
{ // добавляем новую кнопку(опцию) в меню MenuBuilder.AddMenuEntry( // прописываем название опции LOCTEXT("AddSubtitlesEventTrack", "Subtitles Track"), LOCTEXT("AddSubtitlesTrackTooltip", "Adds a subtitles track."), // создаем стиль кнопки FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.TextRenderComponent"), // добавляем лямбду, которая реализует логику нажатия на кнопку FUIAction(FExecuteAction::CreateLambda([=] { auto FocusedMovieScene = GetFocusedMovieScene(); if (FocusedMovieScene == nullptr) { return; } const FScopedTransaction Transaction(LOCTEXT("MovieSceneSubtitlesTrackEditor_Transaction", "Add Subtitle Track")); FocusedMovieScene->Modify(); // непосредственно создаем и добавляем наш трек в Sequencer auto NewTrack = FocusedMovieScene->AddMasterTrack<UMovieSceneSubtitleTrack>(); ensure(NewTrack); NewTrack->SetDisplayName(FText::FromString("Subtitles")); GetSequencer()->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded); }))); Для создания кнопки по добавлению секции, которая появляется при выделении курсором мыши нашего нового трека, потребуется реализовать оставшиеся методы:Код TSharedPtr<SWidget> FMovieSceneSubtitlesTrackEditor::BuildOutlinerEditWidget(const FGuid& ObjectBinding, UMovieSceneTrack* Track, const FBuildEditWidgetParams& Params)
{ FSlateFontInfo SmallLayoutFont = FCoreStyle::GetDefaultFontStyle("Regular", 8); // подготовим лямбду на отображение кнопки только при выделении ноды auto OnGetVisibilityLambda = [this, Params]() -> EVisibility { if (Params.NodeIsHovered.Get()) { return EVisibility::SelfHitTestInvisible; } return EVisibility::Collapsed; }; // текст кнопки TSharedRef<STextBlock> ComboButtonText = SNew(STextBlock) .Text(LOCTEXT("SubtitlesTextSequencer", "Subtitles")) .Font(SmallLayoutFont) .ColorAndOpacity(FSlateColor::UseForeground()) .Visibility_Lambda(OnGetVisibilityLambda); // сама кнопка TSharedRef<SButton> Button = SNew(SButton) .ButtonStyle(FEditorStyle::Get(), "HoverHintOnly") .ForegroundColor(FSlateColor::UseForeground()) .IsEnabled_Lambda([=]() { return GetSequencer().IsValid() ? !GetSequencer()->IsReadOnly() : false; .ContentPadding(FMargin(5, 2)) // добавим лямбду для создания нового трека при нажатии на кнопку .OnClicked(this, &FMovieSceneSubtitlesTrackEditor::AddNewTrack, Track) .HAlign(HAlign_Center) .VAlign(VAlign_Center) [SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(FMargin(0, 0, 2, 0)) [SNew(SImage) // стиль нашей кнопки .ColorAndOpacity(FSlateColor::UseForeground()) .Image(FEditorStyle::GetBrush("Plus"))] + SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() [ComboButtonText]]; return Button; } // метод, вызываемый при нажатии на кнопку “добавить новую секцию” FReply FMovieSceneSubtitlesTrackEditor::AddNewTrack(UMovieSceneTrack* Track) { UMovieScene* FocusedMovieScene = GetFocusedMovieScene(); Track->Modify(); // задаем начальные параметры секции FFrameNumber KeyTime = GetSequencer()->GetGlobalTime().Time.FrameNumber; auto SubtitleTrack = Cast<UMovieSceneSubtitleTrack>(Track); TRange<FFrameNumber> SectionRange = FocusedMovieScene->GetPlaybackRange(); // добавляем секцию SubtitleTrack->AddSection(KeyTime, SectionRange); GetSequencer()->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded); return FReply::Handled(); } TSharedRef<ISequencerSection> FMovieSceneSubtitlesTrackEditor::MakeSectionInterface(UMovieSceneSection& SectionObject, UMovieSceneTrack& Track, FGuid ObjectBinding) { return MakeShareable(new FMovieSceneSubtitlesEventSection(SectionObject, GetSequencer())); } Возвращаясь к реализации класса FMovieSceneSubtitlesEventSection, который отвечает за отображение секций трека в Sequencer — в нашем случае ограничимся демонстрацией текста субтитров в поле секции.Код virtual FText GetSectionTitle() const override
{ return Section->SubtitleText; } virtual float GetSectionHeight() const override
{ const FString StrSubtitles = Section->SubtitleText.ToString(); int NewLineSymbols = Algo::CountIf(StrSubtitles, [](const auto& symbol) { return symbol == '\n'; }); NewLineSymbols++; // доп. место под первую строку return SequencerSectionConstants::DefaultSectionHeight * NewLineSymbols; } Из остальных методов наибольший интерес представляет OnPaintSection.Код virtual int32 OnPaintSection(FSequencerSectionPainter& InPainter) const override
{ return InPainter.PaintSectionBackground(); } Пример реализации такой логики лучше всего изучить самостоятельно в AudioTrackEditor.cpp.Остальные методы являются сугубо техническими.Код FMovieSceneSubtitlesEventSection(UMovieSceneSection& InSection, TWeakPtr<ISequencer> InSequencer)
{ Section = Cast<UMovieSceneSubtitleSection>(&InSection); } virtual UMovieSceneSection* GetSectionObject() override { return Section; }
UCLASS()
class UE4MAGICHIGH_API UMovieSceneSubtitleSection : public UMovieSceneSection { GENERATED_BODY() public: UPROPERTY(EditAnywhere, meta = (MultiLine = true)) FText SubtitleText; virtual FMovieSceneEvalTemplatePtr GenerateTemplate() const override { return FMovieSceneSubtitlesEventTemplate(*this); } }; Класс UMovieSceneNameableTrack содержит логику по созданию объекта, который уже отвечает за:
void FMovieSceneSubtitlesEventTemplate::Evaluate(const FMovieSceneEvaluationOperand& Operand, const FMovieSceneContext& Context, const FPersistentEvaluationData& PersistentData, FMovieSceneExecutionTokens& ExecutionTokens) const
{ ExecutionTokens.Add(FSubtitlesSectionExecutionToken(Section)); } void FMovieSceneSubtitlesEventTemplate::TearDown(FPersistentEvaluationData& PersistentData, IMovieScenePlayer& Player) const { FCachedSubtitlesTrackData& TrackData = PersistentData.GetOrAddTrackData<FCachedSubtitlesTrackData>(); TrackData.RemoveSubtitleWidget(); } struct FSubtitlesSectionExecutionToken : IMovieSceneExecutionToken
{ FSubtitlesSectionExecutionToken(const UMovieSceneSubtitleSection* InSubtitleSection) : SubtitleSection(InSubtitleSection), SectionKey(InSubtitleSection) { } virtual void Execute(const FMovieSceneContext& Context, const FMovieSceneEvaluationOperand& Operand, FPersistentEvaluationData& PersistentData, IMovieScenePlayer& Player) { FCachedSubtitlesTrackData& TrackData = PersistentData.GetOrAddTrackData<FCachedSubtitlesTrackData>(); UObject* PlaybackContext = Player.GetPlaybackContext(); if (!PlaybackContext) { return; } UWorld* World = PlaybackContext->GetWorld(); if (!World) { return; } UMHSubtitlesWindow* SubtitlesWindow = Cast<UMHSubtitlesWindow>(UUserWidget::CreateWidgetInstance(*World, HUD->SubtitlesWidgetTemplate, FName(TEXT("Subtitles")))); SubtitlesWindow->AddToViewport(); SubtitlesWindow->SetSubtitltesText(SubtitleSection->SubtitleText); TrackData.AddSubtitleWidget(SubtitlesWindow); } } const UMovieSceneSubtitleSection* SubtitleSection; FObjectKey SectionKey; }; struct FCachedSubtitlesTrackData : IPersistentEvaluationData
{ void AddSubtitleWidget(UMHWindowWidget* Widget) { SubttitleWidget = Widget; } void RemoveSubtitleWidget() { if (SubttitleWidget && SubttitleWidget->IsValidLowLevel()) { SubttitleWidget->RemoveFromViewport(); } SubttitleWidget = nullptr; } UMHWindowWidget* GetSubtitleWidget() const { return SubttitleWidget; } private: UMHWindowWidget* SubttitleWidget = nullptr; }; На этом наша цель достигнута! =========== Источник: habr.com =========== Похожие новости:
Блог компании Social Quantum ), #_razrabotka_igr ( Разработка игр ), #_unreal_engine |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 16:22
Часовой пояс: UTC + 5