前言
最近在用UE C++做一些功能,用到了Level Sequence功能,但是看了下UE官方论坛包括一些文章基本没有关于C++ 处理Level Sequence 这块内容,有的也是一些修改或者源码原理的一些内容分析,接下来我就把我新建Sequence包括一些库的调用写下来,基本上把源码翻了一遍了。
过程
因为我这是基本走了一遍UE官方的新建的流程,所以我这里UE的插件模式Editor Toolbar Button模式。
创建完这个模式的插件以后,需要我们在Edit -> Edit Preferences -> Miscellaneous中将Display UI extension Poins勾选,因为我们是一个编辑器按钮的插件,所以我们要知道我们的插件按钮放在哪个UI层级下面,开启这个勾选以后,它就会显示对应的层级关系。
创建完插件同步代码以后,在我们的插件Source文件中就会默认.h / Commands.h / Style.h三个文件默认就是我们用来处理常规逻辑的,Commadns是我们同步编辑器一些配置用的,Style就是我们这个插件的一些样式内容了,我们可以先打开Commans.cpp文件编辑按钮名称以及鼠标提示词
然后打开我们的默认CPP文件,因为我这里是不需要插件按钮在其他地方显示出来的,所以我就把PlayToolBar这边的显示注解掉了,如果大家有需要去开启就好了。设置完成以后,点击编译,运行我们的项目这时你就会发现在我们创建UE LevelSequence的地方就有了一个我们自己的插件
现在我们这个Level Sequence插件就基本创建完成了,在我们的编辑器中有一个自己的按钮了,接下来我们来实现逻辑,第一步如UE一样,我们也打开一个Save Asset 的窗口对我们的Level Sequence 进行命名和路径选择。创建一个函数,命名为CreateLevelSequenceAsset,这步的操作我是在Movice下面的LevelSequenceEditor里面找到的,他主要就是打开一个UE窗口创建一个Level Sequence资产
我这里创建完Level Sequence以后是创建了一个CameraCut然后又创建了一个摄像机,将这个摄像机绑定到了CameraCut上面作为了一个默认设置。我是先在场景中创建了相机,这个逻辑处理过程是如果这个场景中没有这个摄像机我就进行创建,有的话就不创建了,这样我所有的LevelSequence都用的一个摄像机,如果大家不需要这么做就修改一下逻辑就好了,创建相机这块很简单,创建了一个相机类,代码如下:
ACameraActor* DoseCineCameraExist()
{
UWorld* World = GWorld;
bool bCineCameraExists = false;
for (TActorIterator<ACameraActor> It(World); It; ++It)
{
ACameraActor* ExistingCineCameraActor = *It;
if (ExistingCineCameraActor && ExistingCineCameraActor->GetActorLabel() == "MainCamera")
{
bCineCameraExists = true;
// 如果存在指定名称的 CineCameraActor,则直接返回该 Actor
return ExistingCineCameraActor;
break;
}
}
if (!bCineCameraExists)
{
FActorSpawnParameters SpawnParams;
ACameraActor* NewCineCameraActor = GWorld->SpawnActor<ACameraActor>(ACameraActor::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator, SpawnParams);
// 设置新创建的 CineCameraActor 的位置和旋转
if (NewCineCameraActor)
{
NewCineCameraActor->SetActorLabel("MainCamera");
// 设置位置和旋转
NewCineCameraActor->SetActorLocation(FVector(0, 0, 50));
NewCineCameraActor->SetActorRotation(FRotator(0, 0, 0));
}
return NewCineCameraActor;
}
return nullptr;
}
现在,我们进行主功能,就是创建一个CameraCut然后将我们的摄像机绑定上去,接下来的主要逻辑顺序基本分层为创建一个对应类型的Track然后设置其属性或者Section。代码如下:
if (NewAsset && NewAsset->IsA<ULevelSequence>())
{
//将CameraCut添加到Seq中
ULevelSequence* levelsequence = static_cast<ULevelSequence*>(NewAsset);
UMovieSceneCameraCutTrack* CameraCutTrack = Cast<UMovieSceneCameraCutTrack>(levelsequence->MovieScene->GetCameraCutTrack());
CameraCutTrack = Cast<UMovieSceneCameraCutTrack>(levelsequence->MovieScene->AddCameraCutTrack(UMovieSceneCameraCutTrack::StaticClass()));
//定义相机以及Transform数据
AActor* cameraActor = DoseCineCameraExist();
FVector DefaultLocation = FVector::ZeroVector;
FVector DefaultRotation = FVector::ZeroVector;
FVector DefaultScale = FVector::OneVector;
DefaultLocation = cameraActor->GetActorLocation();
DefaultRotation = cameraActor->GetActorRotation().Euler();
DefaultScale = cameraActor->GetActorScale();
if (cameraActor)
{
FGuid Guid = levelsequence->FindBindingFromObject(cameraActor, cameraActor->GetWorld());
Guid = Cast<UMovieSceneSequence>(levelsequence)->CreatePossessable(cameraActor);
int32 StartFrame = 0;
int32 EndFrame = 50;
int FrameTickValue = levelsequence->MovieScene->GetTickResolution().AsDecimal();
UMovieSceneCameraCutSection* Section = CameraCutTrack->AddNewCameraCut(UE::MovieScene::FRelativeObjectBindingID(Guid), FFrameNumber(StartFrame * FrameTickValue));
Section->SetEndFrame(FFrameNumber(EndFrame * FrameTickValue));
Section->SetIsLocked(true);
levelsequence->PostEditChange();
UMovieScene3DTransformTrack* TransformTrack = levelsequence->GetMovieScene()->AddTrack<UMovieScene3DTransformTrack>(Guid);
UMovieScene3DTransformSection* TransformSection = Cast<UMovieScene3DTransformSection>(TransformTrack->CreateNewSection());
TransformTrack->AddSection(*TransformSection);
TransformSection->SetRange(TRange<FFrameNumber>(FFrameNumber(0), FFrameNumber(150)));
TArrayView<FMovieSceneDoubleChannel*> DoubleChannels = TransformSection->GetChannelProxy().GetChannels<FMovieSceneDoubleChannel>();
DoubleChannels[0]->AddCubicKey(0, DefaultLocation.X);
DoubleChannels[1]->AddCubicKey(0, DefaultLocation.Y);
DoubleChannels[2]->AddCubicKey(0, DefaultLocation.Z);
DoubleChannels[3]->AddCubicKey(0, DefaultRotation.X);
DoubleChannels[4]->AddCubicKey(0, DefaultRotation.Y);
DoubleChannels[5]->AddCubicKey(0, DefaultRotation.Z);
DoubleChannels[6]->AddCubicKey(0, DefaultScale.X);
DoubleChannels[7]->AddCubicKey(0, DefaultScale.Y);
DoubleChannels[8]->AddCubicKey(0, DefaultScale.Z);
levelsequence->PostEditChange();
}
}
具体的实现代码看下顺序其实就就能看出来的过程比较顺利,这样我们就已经在我们的工程中创建了一个LevelSequence了,但是UE中创建的LevelSequence后会自动打开这个LevelSequecne编辑器进行动画编辑我们也把这块加上。
// Spawn an actor at the origin, and either move infront of the camera or focus camera on it (depending on the viewport) and open for edit
UActorFactory* ActorFactory = GEditor->FindActorFactoryForActorClass(ALevelSequenceActor::StaticClass());
if (!ensure(ActorFactory))
{
return;
}
AActor* Actor = GEditor->UseActorFactory(ActorFactory, FAssetData(NewAsset), &FTransform::Identity);
if (Actor == nullptr)
{
return;
}
ALevelSequenceActor* NewActor = CastChecked<ALevelSequenceActor>(Actor);
if (GCurrentLevelEditingViewportClient != nullptr && GCurrentLevelEditingViewportClient->IsPerspective())
{
GEditor->MoveActorInFrontOfCamera(*NewActor, GCurrentLevelEditingViewportClient->GetViewLocation(), GCurrentLevelEditingViewportClient->GetViewRotation().Vector());
}
else
{
GEditor->MoveViewportCamerasToActor(*NewActor, false);
}
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(NewAsset);
现在我们已经完成了打开-> 创建-> 保存 ->打开编辑器工作,接下来我把需要的头文件引用和环境应用配置进行一下,这个UE C++ levelsequence工作就完成了,第一步打开我们的uplugin文件我
们将LevelSequenceEditor配置一下,在modules中添加AdditionalDependencies然后保存
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "ViewSequence",
"Description": "",
"Category": "MainSequence",
"CreatedBy": "筱星辰",
"CreatedByURL": "",
"DocsURL": "",
"MarketplaceURL": "",
"SupportURL": "",
"CanContainContent": false,
"Installed": true,
"Modules": [
{
"Name": "ViewSequence",
"Type": "Runtime",
"LoadingPhase": "Default",
"AdditionalDependencies": [
"LevelSequenceEditor"
]
}
]
}
然后打开Buil.cs配置我们的环境在PublicDependencyModuleNames中进行添加和配置
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"LevelSequence",
"CoreUObject",
"UnrealEd",
"Engine",
"MovieScene",
"MovieSceneTracks",
"LevelSequenceEditor",
"DesktopPlatform",
"MovieSceneTracks",
"Sequencer"
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"Projects",
"InputCore",
"EditorFramework",
"UnrealEd",
"ToolMenus",
"CoreUObject",
"Engine",
"Slate",
"SlateCore"
// ... add private dependencies that you statically link with here ...
}
);
最后打开我们默认的主要CPP文件添加相关使用的头文件即可,我直接把所有代码放进来,这样大家在用的时候前面有不清楚的地方就可以回来看这个文件了。
// Copyright Epic Games, Inc. All Rights Reserved.
#include "ViewSequence.h"
#include "Editor.h"
#include "FileHelpers.h"
#include "Misc/MessageDialog.h"
#include "ToolMenus.h"
#include "ViewSequenceStyle.h"
#include "ViewSequenceCommands.h"
#include "MovieScene.h"
#include "LevelSequence.h"
#include <LevelSequencePlayer.h>
#include "LevelSequenceActor.h"
#include "MovieScene/Public/Channels/MovieSceneChannelProxy.h"
#include "Sequencer/Public/ISequencerModule.h"
#include "LevelEditorViewport.h"
#include <Tracks/MovieScene3DTransformTrack.h>
#include "Tracks/MovieSceneCameraCutTrack.h"
#include "Tracks/MovieSceneTransformTrack.h"
#include "Sections/MovieSceneCameraCutSection.h"
#include "Sections/MovieScene3DTransformSection.h"
#include "Camera/CameraActor.h"
#include "Camera/CameraComponent.h"
#include "AssetData.h"
#include "AssetRegistryModule.h"
#include "AssetToolsModule.h"
#include "AssetRegistryModule.h"
#include "AssetRegistry/Public/AssetRegistryModule.h"
#include "AssetRegistry/Public/IAssetRegistry.h"
#include "Modules/ModuleManager.h"
#include "Framework/Application/SlateApplication.h"
#include "Serialization/BulkData.h"
#include "Editor/EditorEngine.h"
#include "UnrealEd/Public/UnrealEd.h"
static const FName ViewSequenceTabName("ViewSequence");
// 定义全局FString数组
TArray<FString> GlobalFStringArray;
FString FstringCon;
#define LOCTEXT_NAMESPACE "FViewSequenceModule"
void FViewSequenceModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
FViewSequenceStyle::Initialize();
FViewSequenceStyle::ReloadTextures();
FViewSequenceCommands::Register();
PluginCommands = MakeShareable(new FUICommandList);
PluginCommands->MapAction(
FViewSequenceCommands::Get().PluginAction,
FExecuteAction::CreateRaw(this, &FViewSequenceModule::PluginButtonClicked),
FCanExecuteAction());
UToolMenus::RegisterStartupCallback(FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FViewSequenceModule::RegisterMenus));
}
void FViewSequenceModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
UToolMenus::UnRegisterStartupCallback(this);
UToolMenus::UnregisterOwner(this);
FViewSequenceStyle::Shutdown();
FViewSequenceCommands::Unregister();
}
ACameraActor* DoseCineCameraExist()
{
UWorld* World = GWorld;
bool bCineCameraExists = false;
for (TActorIterator<ACameraActor> It(World); It; ++It)
{
ACameraActor* ExistingCineCameraActor = *It;
if (ExistingCineCameraActor && ExistingCineCameraActor->GetActorLabel() == "MainCamera")
{
bCineCameraExists = true;
// 如果存在指定名称的 CineCameraActor,则直接返回该 Actor
return ExistingCineCameraActor;
break;
}
}
if (!bCineCameraExists)
{
FActorSpawnParameters SpawnParams;
ACameraActor* NewCineCameraActor = GWorld->SpawnActor<ACameraActor>(ACameraActor::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator, SpawnParams);
// 设置新创建的 CineCameraActor 的位置和旋转
if (NewCineCameraActor)
{
NewCineCameraActor->SetActorLabel("MainCamera");
// 设置位置和旋转
NewCineCameraActor->SetActorLocation(FVector(0, 0, 50));
NewCineCameraActor->SetActorRotation(FRotator(0, 0, 0));
}
return NewCineCameraActor;
}
return nullptr;
}
void CreateLevelSequenceAsset()
{
//创建资产模版
IAssetTools& AssetTools = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
UObject* NewAsset = nullptr;
for (TObjectIterator<UClass> It;It; ++It)
{
UClass* CurrentClass = *It;
if (CurrentClass->IsChildOf(UFactory::StaticClass()) && !(CurrentClass->HasAnyClassFlags(CLASS_Abstract)))
{
UFactory* Factory = Cast<UFactory>(CurrentClass->GetDefaultObject());
if (Factory->CanCreateNew() && Factory->ImportPriority >= 0 && Factory->SupportedClass == ULevelSequence::StaticClass())
{
//打开UE窗口进行创建
NewAsset = AssetTools.CreateAssetWithDialog(ULevelSequence::StaticClass(), Factory);
break;
}
}
}
if (!NewAsset)
{
return;
}
if (NewAsset && NewAsset->IsA<ULevelSequence>())
{
//将CameraCut添加到Seq中
ULevelSequence* levelsequence = static_cast<ULevelSequence*>(NewAsset);
UMovieSceneCameraCutTrack* CameraCutTrack = Cast<UMovieSceneCameraCutTrack>(levelsequence->MovieScene->GetCameraCutTrack());
CameraCutTrack = Cast<UMovieSceneCameraCutTrack>(levelsequence->MovieScene->AddCameraCutTrack(UMovieSceneCameraCutTrack::StaticClass()));
AActor* cameraActor = DoseCineCameraExist();
FVector DefaultLocation = FVector::ZeroVector;
FVector DefaultRotation = FVector::ZeroVector;
FVector DefaultScale = FVector::OneVector;
DefaultLocation = cameraActor->GetActorLocation();
DefaultRotation = cameraActor->GetActorRotation().Euler();
DefaultScale = cameraActor->GetActorScale();
if (cameraActor)
{
FGuid Guid = levelsequence->FindBindingFromObject(cameraActor, cameraActor->GetWorld());
Guid = Cast<UMovieSceneSequence>(levelsequence)->CreatePossessable(cameraActor);
int32 StartFrame = 0;
int32 EndFrame = 50;
int FrameTickValue = levelsequence->MovieScene->GetTickResolution().AsDecimal();
UMovieSceneCameraCutSection* Section = CameraCutTrack->AddNewCameraCut(UE::MovieScene::FRelativeObjectBindingID(Guid), FFrameNumber(StartFrame * FrameTickValue));
Section->SetEndFrame(FFrameNumber(EndFrame * FrameTickValue));
Section->SetIsLocked(true);
levelsequence->PostEditChange();
UMovieScene3DTransformTrack* TransformTrack = levelsequence->GetMovieScene()->AddTrack<UMovieScene3DTransformTrack>(Guid);
UMovieScene3DTransformSection* TransformSection = Cast<UMovieScene3DTransformSection>(TransformTrack->CreateNewSection());
TransformTrack->AddSection(*TransformSection);
TransformSection->SetRange(TRange<FFrameNumber>(FFrameNumber(0), FFrameNumber(150)));
TArrayView<FMovieSceneDoubleChannel*> DoubleChannels = TransformSection->GetChannelProxy().GetChannels<FMovieSceneDoubleChannel>();
DoubleChannels[0]->AddCubicKey(0, DefaultLocation.X);
DoubleChannels[1]->AddCubicKey(0, DefaultLocation.Y);
DoubleChannels[2]->AddCubicKey(0, DefaultLocation.Z);
DoubleChannels[3]->AddCubicKey(0, DefaultRotation.X);
DoubleChannels[4]->AddCubicKey(0, DefaultRotation.Y);
DoubleChannels[5]->AddCubicKey(0, DefaultRotation.Z);
DoubleChannels[6]->AddCubicKey(0, DefaultScale.X);
DoubleChannels[7]->AddCubicKey(0, DefaultScale.Y);
DoubleChannels[8]->AddCubicKey(0, DefaultScale.Z);
levelsequence->PostEditChange();
}
}
// Spawn an actor at the origin, and either move infront of the camera or focus camera on it (depending on the viewport) and open for edit
UActorFactory* ActorFactory = GEditor->FindActorFactoryForActorClass(ALevelSequenceActor::StaticClass());
if (!ensure(ActorFactory))
{
return;
}
AActor* Actor = GEditor->UseActorFactory(ActorFactory, FAssetData(NewAsset), &FTransform::Identity);
if (Actor == nullptr)
{
return;
}
ALevelSequenceActor* NewActor = CastChecked<ALevelSequenceActor>(Actor);
if (GCurrentLevelEditingViewportClient != nullptr && GCurrentLevelEditingViewportClient->IsPerspective())
{
GEditor->MoveActorInFrontOfCamera(*NewActor, GCurrentLevelEditingViewportClient->GetViewLocation(), GCurrentLevelEditingViewportClient->GetViewRotation().Vector());
}
else
{
GEditor->MoveViewportCamerasToActor(*NewActor, false);
}
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(NewAsset);
//ULevelSequenceEditorBlueprintLibrary::SetLockCameraCutToViewport(true);
}
void FViewSequenceModule::PluginButtonClicked()
{
CreateLevelSequenceAsset();
}
void FViewSequenceModule::RegisterMenus()
{
//UI对照信息可以在项目偏好设置中找到Miscellaneous中的Disply UI Extension Points开启勾选
// Owner will be used for cleanup in call to UToolMenus::UnregisterOwner
FToolMenuOwnerScoped OwnerScoped(this);
{
UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar.Cinematics");//获取到对应的分类
{
FToolMenuSection& Section = Menu->FindOrAddSection("LevelEditorNewCinematics");//添加到指定地址
Section.AddMenuEntryWithCommandList(FViewSequenceCommands::Get().PluginAction, PluginCommands);
}
}
//{
// UToolMenu* ToolbarMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar.PlayToolBar");
// {
// FToolMenuSection& Section = ToolbarMenu->FindOrAddSection("PluginTools");
// {
// FToolMenuEntry& Entry = Section.AddEntry(FToolMenuEntry::InitToolBarButton(FViewSequenceCommands::Get().PluginAction));
// Entry.SetCommandList(PluginCommands);
// }
// }
//}
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FViewSequenceModule, ViewSequence)
结尾
这个功能到这里就结束了,希望对大家有帮助,关于LevelSequence这块功能使用可以到油管看下,有个大佬写了很多绑定和添加的过程,功能也很使用,应该会帮助你详细了解Level Sequecen C++的,源码的话在Movice下面的LevelSequenceEditor中涵盖了所有内容,有兴趣的话可以翻一下。