UE5 C++ LevelSequence

前言

最近在用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都用的一个摄像机,如果大家不需要这么做就修改一下逻辑就好了,创建相机这块很简单,创建了一个相机类,代码如下:

cpp 复制代码
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。代码如下:

cpp 复制代码
	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编辑器进行动画编辑我们也把这块加上。

cpp 复制代码
	// 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然后保存

cpp 复制代码
{
	"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中进行添加和配置

cpp 复制代码
		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文件添加相关使用的头文件即可,我直接把所有代码放进来,这样大家在用的时候前面有不清楚的地方就可以回来看这个文件了。

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中涵盖了所有内容,有兴趣的话可以翻一下。

相关推荐
狂云歌6 小时前
AI写UE的3Dgame
人工智能·3d·ue5·游戏程序
日月云棠4 天前
UE5源码分析之Editor——Blutility模块全面分析
ue5
日月云棠4 天前
UE5源码分析之Editor——BehaviorTreeEditor模块全面分析
ue5
日月云棠4 天前
UE5源码分析之Editor——AssetTagsEditor模块全面分析
ue5
远离UE45 天前
UE5 显存 虚拟内存 深入学习笔记
笔记·学习·ue5
日月云棠5 天前
UE5源码分析之Editor——AnimationSettings模块全面分析
ue5
远离UE46 天前
UE5 SF_VertexShader 如何使用
ue5
电子云与长程纠缠6 天前
UE5 Lyra PocketWorld进行3D内容UI预览 - 上
开发语言·学习·3d·ue5·游戏引擎
电子云与长程纠缠6 天前
UE5 Lyra PocketWorld进行3D内容UI预览 - 下
开发语言·学习·游戏·ui·ue5
远离UE47 天前
UE5 GPU内部异步
ue5