This commit is contained in:
2025-08-02 12:09:34 +08:00
commit e70b01cdca
2785 changed files with 575579 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
# 前言
输入`run`显示所有命令说明。
- PVW: 切换PVW
- PGM: 切换PGM
- 0: 切换Operator
- 3: 切换三级
- HandCam: 切换手持相机
- 11: 切换FreeMove
- ReMidi: 刷新midi板子
- DebugFrame: debug frame
- EnterArea: 进入Area (可填uuid)
- xs: 进入异世界雪山1 Area (测试用)
- DMXAlign: 强行对齐DMX-Area
- ReDeck: 强行加载当前区域的镜头数据并刷新StreamDeck
- IdolStatus: 角色动作状态
- AllIdolStatus: 所有角色动作状态
- IdolRelativeTr: 所有角色位置信息
- MotionLog: 角色动作Log
- IdolCache: 角色缓存情况(ServerOnly)
- GetMotionOffset: 获取动作时间偏移
- MotionReceiveStatus: 角色动作数据接收情况
- ResetOffsetTime: 重置所有角色动作包时间偏移
- SetRes: 设置目标分辨率,如run SetRes 1920 1080
- HipsTranslation: 使用Hips位移
- IdolCostume: 加4个团服角色
- ShowUI: UE4中的ShowUI指令迁移
- BindPGM2: 重新绑定PGM2的固定机位
- LipSync: 设置LipSync音频-静音阈值
- UdexGlove: 使用宇叠科技新手套(部分角色适用)
- GenerateMeshConfig: 生成 mesh config(技术用)
涉及到:
- TsLiveDirectorGameInstance.ts
- TsDirectorConsoleCommandHandler.ts
# 命令执行逻辑
## Run
TsDirectorConsoleCommandHandler.ts
```ts
static HandleConsoleCommand(gameInstance: TsLiveDirectorGameInstance, consoleCommand: string): void {
if(consoleCommand == '' || consoleCommand.toLocaleLowerCase() == 'help'){
TsDirectorConsoleCommandHandler.Help()
return
}
var parts = consoleCommand.split(' ')
var funcName = parts[0]
var func = TsDirectorConsoleCommandHandler.GetFunctionByName(funcName)
if (func == null) {
console.error('Not exist cmd ' + consoleCommand)
return
}
switch (parts.length) {
case 1: func(gameInstance); break;
case 2: func(gameInstance, parts[1]); break;
case 3: func(gameInstance, parts[1], parts[2]); break;
case 4: func(gameInstance, parts[1], parts[2], parts[3], parts[4]); break;
case 5: func(gameInstance, parts[1], parts[2], parts[3], parts[4], parts[5]); break;
default: console.error('Cmd paramenter is wrong!')
}
}
```
主要的几个切换导播台的命令基本是调用`gameInstance.SetDirectorModeStr("XXX")`
进入Area函数为调用`TsDirectorConsoleCommandHandler._EnterArea(gameInstance, "0C1E0DD349EDD9860ED8BDBB55A736F3")``_EnterArea`的代码为:
```ts
static _EnterArea(gameInstance: TsLiveDirectorGameInstance, areaUUID: string): void {
let mapEnvironment = Utils.GetMapEnvironmentManager(gameInstance.GetWorld());
if (mapEnvironment && mapEnvironment.LayerManager) {
mapEnvironment.LayerManager.EnterAreaByCMD(areaUUID);
}
}
```
TsLiveDirectorGameInstance.ts
```typescript
Run(CMDStr: $Ref<string>) : void{
let consoleCommand = $unref(CMDStr)
TsDirectorConsoleCommandHandler.HandleConsoleCommand(this, consoleCommand)
}
```
## SetDirectorModeStr
位于`ULiveDirectorGameInstance`
TsLiveDirectorGameInstance.ts
## 其他有用函数
```ts
static _HipsTranslation(gameInstance: TsLiveDirectorGameInstance, value:number): void {
var actors = UE.NewArray(UE.Actor)
UE.GameplayStatics.GetAllActorsOfClass(gameInstance, TsIdolActor.StaticClass(), $ref(actors))
for (var i = 0; i < actors.Num(); i++) {
var model = actors.GetRef(i) as TsIdolActor
if (model) {
var anim = model.Mesh.GetAnimInstance() as UE.IdolAnimInstance
let fullbodyNode = Reflect.get(anim, 'AnimGraphNode_Fullbody') as UE.AnimNode_FullBody
if (fullbodyNode) {
//fullbodyNode.bUseHipsTranslation = value > 0
}
anim.SetRootMotionMode(value > 0 ? UE.ERootMotionMode.NoRootMotionExtraction : UE.ERootMotionMode.RootMotionFromEverything)
model.RootComponent.K2_SetRelativeLocationAndRotation(new UE.Vector(0, 0, model.CapsuleComponent.CapsuleHalfHeight), new UE.Rotator(0, 0, 0), false, null, false)
console.warn("use hips translation " + (value > 0))
}
}
}
```
# RuntimeEditor插件
# 三级导播台
run 3
# MotionProcess
资产位于UIAssets/Character/WBP_CharacterItem
UI逻辑位于TsCharacterItem.ts的TsCharacterItem

View File

@@ -0,0 +1,219 @@
# 前言
默认存储数据路径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<FFrameTime> EvaluationRange, ESequoiaEvaluateType EvalType)
{
Super::Evaluate(EvaluationRange, EvalType);
TArray<FSequoiaEvalSection> EvalSections;
USequoiaUtil::GetEvalSections(Sections, EvaluationRange, EvalSections);//根据播放范围取得Section
OnEvaluate(EvalSections, EvaluationRange.GetLowerBoundValue(), EvaluationRange.GetUpperBoundValue(), EvalType);//调用蓝图类的BlueprintImplementableEvent事件。
}
```
在TsSequoiaTrack中Overrider了OnEvaluate():
```ts
OnEvaluate(EvalSections: $Ref<UE.TArray<UE.SequoiaEvalSection>>, 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<TsSequoiaSection>()
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
## ISequoiaEvalTemplateSequoiaCamShotEvalTemplate
- 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.

View File

@@ -0,0 +1,9 @@
# 启动逻辑
1. ULiveDirectorGameInstance::ParseCommandLine()解析DirectorMode、PGMMode字符串并设置。
2. SetDirectorModeStr()
3. SetDirectorMode()
4. 调用蓝图事件OnDirectorModeChanged()
TsLiveDirectorGameInstance extends UE.LiveDirectorGameInstance
TsDirectorCamManagerActor.ts

View File

@@ -0,0 +1,20 @@
# 相关类
- WBP_LevelFunctionView
- TsMapEnvironmentFunctionManager
- TsCommonVisibleLevelFunction
- TsCommonGravityLevelFunction
- TsCommonTimeLevelFunction
- TsCommonVariantComponent
- TsCommonWindLevelFunction
- TsBaseLevelFunctionActor
# 糖果工厂的移动LiveArea实现
- UITsCandyFactoryDetails.tsx
- 逻辑实现:/ResArt/Scene/Map_Stylized_Vilage/BP/BP_CandyFactoryLift
- 基类TsBaseLevelFunctionActor
2个圈转到相对原点。停下。
# Area动画
/Props/AreaSequence/天空之城_降落

View File

@@ -0,0 +1,6 @@
ALiveDirectorGameMode::GetSeamlessTravelActorList
https://www.uejoy.com/archives/1130
使用了UE5的RelicationGraphUDirectorReplicationGraphNode_AlwaysRelevant_ForConnection::GatherActorListsForConnection()

View File

@@ -0,0 +1,76 @@
Content/UIAssets/MapEnvironments/Prop/WBP_MapEnvironmentSelectPropPopupView
搜索逻辑:
```c++
private ChangeSource(source: number/**@cpp:int*/): void {
this.curSelect = null;
MapEnvironmentSetting.SelectPropType = source;
this.widget.List.ClearListItems();
this.widget.List.BP_ClearSelection();
this.widget.Effect.SetVisibility((source == UE.EScenePropType.Item || source == UE.EScenePropType.LiveLink)? UE.ESlateVisibility.Visible: UE.ESlateVisibility.Collapsed);
let colorNormal = new UE.LinearColor(0, 0, 0, 0);
let colorSelect = new UE.LinearColor(73/255, 73/255, 73/255, 1);
for (let index = 0; index < this.btnBgs.Num(); index++) {
const element = this.btnBgs.GetRef(index);
element.SetBrushColor(index == source? colorSelect: colorNormal);
}
if (this.manager == null) {
return
}
let search =this.widget.SearchBox.GetText();
let assets = this.manager.AssetManager.AllScenePropAssetList
for (let index = 0; index < assets.Num(); index++) {
const element = assets.GetRef(index);
if (element.PropType === source) {
if (search === "" || element.DisplayName.includes(search)) {
this.widget.List.AddItem(element);
}
}
}
}
```
- WBP_AreaSelectPopupView
- WBP_MapEnvironmentSingleSelectItemView
```c++
private SetSelectLevel(levelData: UE.MapEnvironmentLevelItem): void {
this.curSelectLevelName = levelData.LevelData.LevelName;
this.curSelectAreaName = "";
this.curSelectAreaGuid = new UE.Guid();
this.bAreaIsDynamic = false;
this.widget.AreaList.ClearListItems();
this.widget.AreaList.BP_ClearSelection();
let areas = levelData.LevelData.Areas;
// let selectIdx = -1;
for (let i = 0; i < areas.Num(); i++) {
const area = areas.GetRef(i);
let data = new UE.MapEnvironmentAreaItem();
data.AreaData = area;
this.widget.AreaList.AddItem(data);
// if (DirectorCamUtil.IsGuidEqual(area.UUID, this.curSelectAreaGuid)) {
// selectIdx = i;
// }
}
// if (selectIdx >= 0) {
// this.widget.AreaList.SetSelectedIndex(selectIdx);
// }
this.RefreshDynamicBtn();
}
```

View File

@@ -0,0 +1,4 @@
身份判断逻辑:
```c++
  if (Utils.IsCurDirectorModeEqual(this, DirectorMode.IdolControllerMaster))
```