# 前言 默认存储数据路径:C:\LiveDirectorSaved\Sequoia 操作方式: 1. 4级中使用`Ctrl + Shift + D`,勾选Sequoia编辑器后显示。 2. Ctrl + ? 剪切轨道。 PS. 编辑器状态下可以打开Sequoia编辑器界面,再进4级前,Ctrl+Shift+D,点击后就可以打开。 # 相关类 - TS:`LiveDirector\Script\Sequoia` - TsSequoiaManagerActor - OnPlayButtonClicked():Sequoia播放函数。主要逻辑是打开Sequoia的序列化数据,之后创建或取得播放器斌进行播放/停止。 - TsSequoiaData => USequoiaData => USequoiaObject - TsSequoiaBinding => USequoiaBinding => UNameableSequoiaObject => USequoiaObject - TsSequoiaTake => USequoiaTake => UNameableSequoiaObject => USequoiaObject - *SequoiaDirectorCamTake* - TsSequoiaTrack => USequoiaTrack => UNameableSequoiaObject => USequoiaObject - CharacterLiveLinkAnimTrack - SequoiaMotionTrack - *SequoiaCamShotTrack*(SequoiaCamShotEvalTemplate):***相机Take的CamShot轨道。*** - *SequoiaCamTargetTrack*(SequoiaCamTargetEvalTemplate):***相机Take的Target轨道。*** - SequoiaAudioTrack - TsSequoiaSection => USequoiaSection - *SequoiaCamSection*(TS) - *SequoiaCamTargetSection*(TS) - TsSequoiaSectionWithFileRef - CharacterLiveLinkAnimSection - SequoiaMotionSection - SequoiaAudioSection - ISequoiaEvalTemplate - *SequoiaCamShotEvalTemplate* - *SequoiaCamTargetEvalTemplate* - CharacterLiveLinkAnimEvalTemplate - SequoiaMotionEvalTemplate - SequoiaAudioEvalTemplate - ICamShotEvalHandle - *SingleCamShotEvalHandle* - *DoubleCamShotEvalhandle* - c++:`LiveDirector\Source\Modules\Sequoia` - SequoiaPlayer - PlayInternal():播放逻辑,主要调用`SequoiaData->Evaluate();` - USequoiaObject => UObject # 播放逻辑 ```c++ TsSequoiaManagerActor@OnPlayButtonClicked: start play : 大聲鑽石 [2024.11.26-04.21.03:648][613]Puerts: (0x00000BD7686682F0) SequoiaManager@ Composer: On start playing... [2024.11.26-04.21.03:649][613]Puerts: (0x00000BD7686682F0) DirectorCamSequoiaHandle : Enter CamTarget Section: Idol.JiaRan [2024.11.26-04.21.03:649][613]Puerts: (0x00000BD7686682F0) DirectorCamSequoiaHandle : play Cam Section: ZhuJiwei_Zheng16-24mm group:CC8F4D734664869EC8FE788E7550AC31 index:0 scrub:false [2024.11.26-04.21.03:665][614]Puerts: (0x00000BD7686682F0) request PGM: WorkShop ``` 1. Sequoia界面点击播放后,调用TsSequoiaManagerActor::OnPlayButtonClicked() 2. SequoiaPlayer::PlayInternal(),设置时间范围后。 3. USequoiaData::Evaluate()。 1. 调用所有USequoiaBinding::Evaluate()。 1. 调用所有USequoiaTrack::Evaluate()。 2. 调用所有USequoiaTake::Evaluate()。 1. 调用所有USequoiaTrack::Evaluate()。 PS. 实际上Sequoia的镜头录制数据会创建SequoiaCamShotTrack、SequoiaCamTargetTrack轨道。 ## USequoiaTrack::Evaluate() ```c++ void USequoiaTrack::Evaluate(TRange EvaluationRange, ESequoiaEvaluateType EvalType) { Super::Evaluate(EvaluationRange, EvalType); TArray EvalSections; USequoiaUtil::GetEvalSections(Sections, EvaluationRange, EvalSections);//根据播放范围取得Section OnEvaluate(EvalSections, EvaluationRange.GetLowerBoundValue(), EvaluationRange.GetUpperBoundValue(), EvalType);//调用蓝图类的BlueprintImplementableEvent事件。 } ``` 在TsSequoiaTrack中Overrider了OnEvaluate(): ```ts OnEvaluate(EvalSections: $Ref>, EvalStartTime: UE.FrameTime, EvalEndTime: UE.FrameTime, EvalType: UE.ESequoiaEvaluateType) : void{ if(!this.CanEvaluate() || !EvalSections){ return } if(!this.evalTemplate){ this.evalTemplate = this.CreateTemplate() if(!this.evalTemplate){ return } this.evalTemplate.InitTemplate(this) } let newEvalSections = new Array() let evalSectionsRef = $unref(EvalSections) for(let index = 0; index < evalSectionsRef.Num(); index ++){ let sectionRef = evalSectionsRef.GetRef(index) let tsSection = sectionRef.Section as TsSequoiaSection if(!sectionRef || !tsSection){ continue } if(sectionRef.EvalMode == UE.ESequoiaEvaluateMode.EEM_Inside || sectionRef.EvalMode == UE.ESequoiaEvaluateMode.EEM_JumpIn){ newEvalSections.push(tsSection) if(newEvalSections.length >= MAX_EVAL_COUNT){ break } } } let bTemplateSourceChanged = this.IsTemplateSourceChanged(newEvalSections) if(bTemplateSourceChanged){ this.evalTemplate.SetTemplateSource(newEvalSections, EvalType) } this.evalTemplate.Evaluate(EvalStartTime, EvalEndTime, EvalType) this.lastEvalType = EvalType } ``` 看得出主要是主要逻辑是: 1. 创建指定类型的evalTemplate之后,调用`evalTemplate.InitTemplate()`。 2. 取得ESequoiaEvaluateMode为EEM_Inside与EEM_JumpIn的所有EvalSections。 3. 判断Template是否发生改变,如果改变则调用`evalTemplate.SetTemplateSource()`。 4. 调用`evalTemplate::Evaluate()`。 ## SequoiaCamSection => SequoiaCamSection => TsSequoiaSection。 - 数据Model类使用SequoiaCamSectionModel。 - SequoiaCamShotEvalTemplate ## ISequoiaEvalTemplate(SequoiaCamShotEvalTemplate) - InitTemplate: - SetTemplateSource:用来设置对应的*ICamShotEvalHandle* - Evaluate ISequoiaEvalTemplate => ICamShotEvalHandle.Eval() 在计算Section、以及FrameOffset参数之后,调用***DirectorCamSequoiaHandle.PlayCamShotSection()***,在创建newTask(***CamTaskDataRPC***)之后,最终会调用directorCamManager.RequestPGMTaskServerUnreliable()/RequestPVWTaskServer()进入导播系统循环。 ```ts let newTask = new UE.CamTaskDataRPC() newTask.WorkShopId = DirectorCamUtil.CopyGuid(workShopId) newTask.CamGroupId = DirectorCamUtil.CopyGuid(camSection.camGroupId) newTask.CamIndex = camSection.camIndex newTask.StartFrame = camSection.GetStartFrameOffset().Value + frameOffset newTask.bPreviewOneFrame = bPreviewOneFrame ``` ## DirectorCamManager 1. this.RequestPVWTask(newPVWTaskData) 2. this.HandlePreStreamTaskDataMulticast(newPVWTaskData) 1. DirectorCamUtil.EnsureWorkShopReady():确保WorkShop有效并且已经初始化,之后就调用对应函数。 1. DirectorEventSystem.Emit(this, DirectorEvent.OnPVWTaskRequested): 2. this.HandlePreStreamTaskByNetTag() 1. PVW => this.HandlePVWTask() 1. DirectorCamUtil.SubmitNewCommandIfDataNotChanged() 3.  this.RecordLastPVWTime() ### HandlePVWTask() ```ts HandlePVWTask(): void { if(!DirectorCamUtil.SubmitNewCommandIfDataNotChanged(this.preStreamTask, this.prestreamTaskData)){ if (this.preStreamTask) { this.preStreamTask.Stop() } this.preStreamTask = DirectorCamUtil.CreateCamTask(this, this.prestreamTaskData, CamTaskType.FullStream, this.droneCamera, this.PVWWindow, this.handHeldCamera) if (this.preStreamTask) { this.preStreamTask.Start() if (this.PVWWindow) { this.PVWWindow.SetViewBorderColor(0, new UE.LinearColor(0, 1, 0, 1)) } console.log('PVW Task: ' + this.preStreamTask.workShop.BindPlacement.Title + " " + this.preStreamTask.groupName + " " + this.preStreamTask.camName) } } } ``` 通过DirectorCamUtil.SubmitNewCommandIfDataNotChanged()判断CamTaskDataRPC是否相同,如果Camera机位相同则直接提交。 如果不同,比如换机位了,就调用 `DirectorCamUtil.CreateCamTask() => this.preStreamTask.Start()` 来发送新的任务。 ```ts export function SubmitNewCommandIfDataNotChanged(task : DirectorCamTask, taskData : UE.CamTaskDataRPC):boolean{ let bSubmitSuccess = false if(task && taskData){ let bWorkShopEqual = IsGuidEqual(task.workShopId, taskData.WorkShopId) let bCamGroupEqual = IsGuidEqual(task.groupId, taskData.CamGroupId) let bCamIndexEqual = task.GetMainCamIndex() == taskData.CamIndex let bNotSpecialCam = taskData.CamIndex != HANDHELD_CAM_INDEX && taskData.CamIndex != DRONE_CAM_INDEX if(bWorkShopEqual && bCamGroupEqual && bCamIndexEqual && bNotSpecialCam){ // submit new cmd task.SubmitNewCommand(taskData) bSubmitSuccess = true } } return bSubmitSuccess } ``` ## 裁剪相关逻辑 DirectorCamSequoiaHandle.PlayCamShotSection() # 其他 ## 添加自定义轨道 往Sequoia添加一个自定义轨道,可以按照以下大体步骤进行开发 1. 大部分的拓展逻辑都写在SequoiaCustomBuilderTool.ts 2. 在SequoiaCustomBuilderTools.ts 的BindingType,TrackType,SectionType中添加组定义类型.在关系Map(BindingToTrackMap)中添加从属关系 3. 在Sequoia代码文件夹下创建拓展文件夹,创建对应的TsBinding,TsTrack,TsSection等对应的UObject以及Model类,可以参考DirectorCam. 4. Model文件用于数据序列化和存储,通常不要使用UE类型,UObject文件是真正的逻辑类 5. 创建Binding和BindingModel类,分别定义AssignModel和构造函数用来承接数据 1. 在SequoiaCustomBuildertool.CreateBindingModel 和 CreateEmptyBindingModelByBindingType中新增新类型的Model创建。 2. 在SequoiaCustomBuildertool.CreateBinding中添加新Binding类型的创建 3. Track,Take,Section也是类似于Binding的方式在CustomBuilderTool中添加创建代码。 4. 至此就完成了数据部分的定义和代码。 1. 录制逻辑,需要首先创建对应的录制逻辑,继承自ISequoiaTakeRecorder. 2. 在SequoiaHelper.BuildTakeRecorders 中根据参数创建对应的recorder.