vault backup: 2024-11-26 18:16:19

This commit is contained in:
2024-11-26 18:16:19 +08:00
parent b0061ba795
commit c7e0a80a36
36 changed files with 149 additions and 39 deletions

View File

@@ -0,0 +1,158 @@
TsScreenPlayerTextureRenderer => AMultiViewActor
# 渲染逻辑
- UMultiViewRendererComponent::DrawMultiViewCameras()
渲染函数:GetRendererModule().BeginRenderingViewFamily(&SceneCanvas, &ViewFamily);
摄像机相关函数:FSceneView* UMultiViewRendererComponent::CalcSceneView(FSceneViewFamily* InViewFamily, UCineCameraComponent* InCamera,
const uint32 InViewIndex)
# 多屏与采集卡
以Preview为例
TsDirectorCamManagerActor.ts
```c++
this.PreviewWindow = UE.MultiViewActor.Open(this.GetWorld(), UE.EMultiViewCameraLayout.Display_1920x1080_Layout_4x4, UE.EMultiViewMultiGPUMode.HalfSplit)
this.PreviewWindow.SetRenderFeaturePlanarReflection(false);
this.PreviewWindow.SetRenderFeatureNiagara(false);
// video output
let videoOutputParam = new UE.VideOutputParam()
videoOutputParam.bBlackMagicCard = false
videoOutputParam.bLazyStart = false
this.PreviewWindow.StartVideoOutput(videoOutputParam)
```
PVW&PGM使用 **BLACKMAGIC_OUTPUT_CONFIG_HORIZONTAL**  也就是/Game/ResArt/BlackmagicMedia/MO_BlackmagicVideoOutput。
```ts
export function StartVideoOutput(camManager : TsDirectorCamManagerActor, targetWindow : UE.MultiViewActor):void{
let videoOutpuParam = new UE.VideOutputParam()
videoOutpuParam.FilmbackMode = camManager.FilmbackMode
videoOutpuParam.OutputConfigPath = BLACKMAGIC_OUTPUT_CONFIG_HORIZONTAL
videoOutpuParam.bBlackMagicCard = true
videoOutpuParam.bLazyStart = false
if(camManager.FilmbackMode == UE.EFilmbackMode.EFM_1080x1920){
videoOutpuParam.MatV2H = GetV2HMaterialInstace(camManager)
}
targetWindow.StartVideoOutput(videoOutpuParam)
}
```
- DirectorMode.PreviewbBlackMagicCard = false
- PVW&PGMbBlackMagicCard = true
## c++
核心函数在于**AMultiViewActor::StartVideoOutput**
# TS传入设置位置
- TsDirectorCamManagerActor.ts SwitchToDirectMode(newTag: UE.GameplayTag)
- FilmbackHelper.ts StartVideoOutput()
SwitchToDirectMode()
```ts
case DirectorMode.Preview:
console.log('启动Splite4x4预览窗口')
if (shouldCreateWindow) {
this.PreviewWindow = UE.MultiViewActor.Open(this.GetWorld(), UE.EMultiViewCameraLayout.Display_1920x1080_Layout_4x4, UE.EMultiViewMultiGPUMode.HalfSplit)
this.PreviewWindow.SetRenderFeaturePlanarReflection(false);
this.PreviewWindow.SetRenderFeatureNiagara(false);
// video output
let videoOutputParam = new UE.VideOutputParam()
videoOutputParam.bBlackMagicCard = false
videoOutputParam.bLazyStart = false
this.PreviewWindow.StartVideoOutput(videoOutputParam)
}
```
```ts
function StartVideoOutput(camManager : TsDirectorCamManagerActor, targetWindow : UE.MultiViewActor):void{
if(!BE_USE_DECKLINK){
let videoOutpuParam = new UE.VideOutputParam()
videoOutpuParam.FilmbackMode = camManager.FilmbackMode
videoOutpuParam.OutputConfigPath = BLACKMAGIC_OUTPUT_CONFIG_HORIZONTAL
videoOutpuParam.bBlackMagicCard = true
videoOutpuParam.bLazyStart = false
if(camManager.FilmbackMode == UE.EFilmbackMode.EFM_1080x1920){
videoOutpuParam.MatV2H = GetV2HMaterialInstace(camManager)
}
targetWindow.StartVideoOutput(videoOutpuParam)
}
}
```
# UDeckLinkMediaCapture
m_DeckLinkOutputDevice = DeckLinkDiscovery->GetDeviceByName(m_DeviceName);
```c++
FDeckLinkDeviceDiscovery::DeckLinkDeviceArrived(IDeckLink *device)
{
/*TComPtr<IDeckLink> device_;
device_ = device;*/
TComPtr<FDeckLinkOutputDevice> newDeviceComPtr = new FDeckLinkOutputDevice(device);
if (!newDeviceComPtr->Init())
return S_OK;
std::lock_guard<std::recursive_mutex> lock(m_DeviceMutex);
FString deviceName = newDeviceComPtr->GetDeviceName();//看这个Com对象的设备名称是否对应
if (!m_Devices.Contains(deviceName) )
{
m_Devices.Add(deviceName,newDeviceComPtr);
}
return S_OK;
}
```
DeckLinkDeviceArrived调用逻辑位于**DeckLinkAPI_h.h**。
## ADeckLinkOutputActor
ADeckLinkOutputActor的DeviceName 默认值为"DeckLink Mini Monitor 4K";
判断错误函数
UMediaCapture::CaptureTextureRenderTarget2D()=>UMediaCapture::StartSourceCapture() => ValidateMediaOutput()
```c++
bool UMediaCapture::ValidateMediaOutput() const
{
if (MediaOutput == nullptr)
{ UE_LOG(LogMediaIOCore, Error, TEXT("Can not start the capture. The Media Output is invalid."));
return false;
}
FString FailureReason;
if (!MediaOutput->Validate(FailureReason))
{ UE_LOG(LogMediaIOCore, Error, TEXT("Can not start the capture. %s."), *FailureReason);
return false;
}
if(DesiredCaptureOptions.bAutostopOnCapture && DesiredCaptureOptions.NumberOfFramesToCapture < 1)
{ UE_LOG(LogMediaIOCore, Error, TEXT("Can not start the capture. Please set the Number Of Frames To Capture when using Autostop On Capture in the Media Capture Options"));
return false;
}
return true;
}
```
```c++
bool UDeckLinkMediaCapture::InitBlackmagic(int _Width, int _Height)
{
if (DeckLinkDiscovery == nullptr)
{
return false;
}
Width = _Width;
Height = _Height;
check(Height > 0 && Width > 0)
BMDDisplayMode displayMode = GetDisplayMode(Width, Height);
m_DeckLinkOutputDevice = DeckLinkDiscovery->GetDeviceByName(m_DeviceName);
if (m_DeckLinkOutputDevice.Get() == nullptr)
{
return false;
}
if (!m_DeckLinkOutputDevice->EnableOutput(displayMode, bmdFormat8BitYUV))
{
m_DeckLinkOutputDevice.Reset();
return false;
}
return true;
}
```
***DeckLinkDiscovery->GetDeviceByName(m_DeviceName);***

View File

@@ -0,0 +1,266 @@
# 相关蓝图类
BP_Live里面可以指定MediaPlayer以及MediaTexture并且替换蓝图子StaticMesh材质中的EmissiveMap为MediaTexture。
# 导播台
之后就可以将视频放到指定的Saved文件夹里就可以在导播台播放了。
# NDI 播放逻辑
通过道具来添加NDI 设置。
## 道具
- BP_ProjectorD0
- BP_Screen011
## 相关注释掉的代码
- TsMapEnvironmentAssets.ts
- TsMapEnvironmentSingleSelectItemView.ts
- SetMediaData()
- TsScreenPlayerItemView.ts
- SetData()
- TsScreenPlayerSelectItemPopupView.ts
- ChangeMediaType()
# NDI播放模糊问题解决
- bool UNDIMediaReceiver::CaptureConnectedVideo()
```c++
bool UNDIMediaReceiver::Initialize(const FNDIConnectionInformation& InConnectionInformation, UNDIMediaReceiver::EUsage InUsage)
{
if (this->p_receive_instance == nullptr)
{
if (IsValid(this->InternalVideoTexture))
this->InternalVideoTexture->UpdateResource();
// create a non-connected receiver instance
NDIlib_recv_create_v3_t settings;
settings.allow_video_fields = false;
settings.bandwidth = NDIlib_recv_bandwidth_highest;
settings.color_format = NDIlib_recv_color_format_fastest;
p_receive_instance = NDIlib_recv_create_v3(&settings);
// check if it was successful
if (p_receive_instance != nullptr)
{
// If the incoming connection information is valid
if (InConnectionInformation.IsValid())
{
//// Alright we created a non-connected receiver. Lets actually connect
ChangeConnection(InConnectionInformation);
}
if (InUsage == UNDIMediaReceiver::EUsage::Standalone)
{
this->OnNDIReceiverVideoCaptureEvent.Remove(VideoCaptureEventHandle);
VideoCaptureEventHandle = this->OnNDIReceiverVideoCaptureEvent.AddLambda([this](UNDIMediaReceiver* receiver, const NDIlib_video_frame_v2_t& video_frame)
{
FTextureRHIRef ConversionTexture = this->DisplayFrame(video_frame);
if (ConversionTexture != nullptr)
{
if ((GetVideoTextureResource() != nullptr) && (GetVideoTextureResource()->TextureRHI != ConversionTexture))
{
GetVideoTextureResource()->TextureRHI = ConversionTexture;
RHIUpdateTextureReference(this->VideoTexture->TextureReference.TextureReferenceRHI, ConversionTexture);
}
if ((GetInternalVideoTextureResource() != nullptr) && (GetInternalVideoTextureResource()->TextureRHI != ConversionTexture))
{
GetInternalVideoTextureResource()->TextureRHI = ConversionTexture;
RHIUpdateTextureReference(this->InternalVideoTexture->TextureReference.TextureReferenceRHI, ConversionTexture);
}
}
});
// We don't want to limit the engine rendering speed to the sync rate of the connection hook
// into the core delegates render thread 'EndFrame'
FCoreDelegates::OnEndFrameRT.Remove(FrameEndRTHandle);
FrameEndRTHandle.Reset();
FrameEndRTHandle = FCoreDelegates::OnEndFrameRT.AddLambda([this]()
{
while(this->CaptureConnectedMetadata())
; // Potential improvement: limit how much metadata is processed, to avoid appearing to lock up due to a metadata flood
this->CaptureConnectedVideo();
});
#if UE_EDITOR
// We don't want to provide perceived issues with the plugin not working so
// when we get a Pre-exit message, forcefully shutdown the receiver
FCoreDelegates::OnPreExit.AddWeakLambda(this, [&]() {
this->Shutdown();
FCoreDelegates::OnPreExit.RemoveAll(this);
});
// We handle this in the 'Play In Editor' versions as well.
FEditorDelegates::PrePIEEnded.AddWeakLambda(this, [&](const bool) {
this->Shutdown();
FEditorDelegates::PrePIEEnded.RemoveAll(this);
});
#endif
}
return true;
}
}
return false;
}
```
绘制函数
```c++
/**
Attempts to immediately update the 'VideoTexture' object with the last capture video frame
from the connected source
*/
FTextureRHIRef UNDIMediaReceiver::DisplayFrame(const NDIlib_video_frame_v2_t& video_frame)
{
// we need a command list to work with
FRHICommandListImmediate& RHICmdList = FRHICommandListExecutor::GetImmediateCommandList();
// Actually draw the video frame from cpu to gpu
switch(video_frame.frame_format_type)
{
case NDIlib_frame_format_type_progressive:
if(video_frame.FourCC == NDIlib_FourCC_video_type_UYVY)
return DrawProgressiveVideoFrame(RHICmdList, video_frame);
else if(video_frame.FourCC == NDIlib_FourCC_video_type_UYVA)
return DrawProgressiveVideoFrameAlpha(RHICmdList, video_frame);
break;
case NDIlib_frame_format_type_field_0:
case NDIlib_frame_format_type_field_1:
if(video_frame.FourCC == NDIlib_FourCC_video_type_UYVY)
return DrawInterlacedVideoFrame(RHICmdList, video_frame);
else if(video_frame.FourCC == NDIlib_FourCC_video_type_UYVA)
return DrawInterlacedVideoFrameAlpha(RHICmdList, video_frame);
break;
}
return nullptr;
}
```
DrawProgressiveVideoFrame
UNDIMediaReceiver::CaptureConnectedVideo
=>
DisplayFrame NDIlib_frame_format_type_progressive NDIlib_FourCC_video_type_UYVY
=>
DrawProgressiveVideoFrame
## Shader Binding RT
设置RT
```c++
FTextureRHIRef TargetableTexture;
// check for our frame sync object and that we are actually connected to the end point
if (p_framesync_instance != nullptr)
{
// Initialize the frame size parameter
FIntPoint FrameSize = FIntPoint(Result.xres, Result.yres);
if (!RenderTarget.IsValid() || !RenderTargetDescriptor.IsValid() ||
RenderTargetDescriptor.GetSize() != FIntVector(FrameSize.X, FrameSize.Y, 0) ||
DrawMode != EDrawMode::Progressive)
{
// Create the RenderTarget descriptor
RenderTargetDescriptor = FPooledRenderTargetDesc::Create2DDesc(
FrameSize, PF_B8G8R8A8, FClearValueBinding::None, TexCreate_None, TexCreate_RenderTargetable | TexCreate_SRGB, false);
// Update the shader resource for the 'SourceTexture'
// The source texture will be given UYVY data, so make it half-width
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 1))
const FRHITextureCreateDesc CreateDesc = FRHITextureCreateDesc::Create2D(TEXT("NDIMediaReceiverProgressiveSourceTexture"))
.SetExtent(FrameSize.X / 2, FrameSize.Y)
.SetFormat(PF_B8G8R8A8)
.SetNumMips(1)
.SetFlags(ETextureCreateFlags::RenderTargetable | ETextureCreateFlags::Dynamic);
SourceTexture = RHICreateTexture(CreateDesc);
#elif (ENGINE_MAJOR_VERSION == 4) || (ENGINE_MAJOR_VERSION == 5)
FRHIResourceCreateInfo CreateInfo(TEXT("NDIMediaReceiverProgressiveSourceTexture"));
TRefCountPtr<FRHITexture2D> DummyTexture2DRHI;
RHICreateTargetableShaderResource2D(FrameSize.X / 2, FrameSize.Y, PF_B8G8R8A8, 1, TexCreate_Dynamic,
TexCreate_RenderTargetable, false, CreateInfo, SourceTexture,
DummyTexture2DRHI);
#else
#error "Unsupported engine major version"
#endif
// Find a free target-able texture from the render pool
GRenderTargetPool.FindFreeElement(RHICmdList, RenderTargetDescriptor, RenderTarget, TEXT("NDIIO"));
DrawMode = EDrawMode::Progressive;
}
#if ENGINE_MAJOR_VERSION >= 5
TargetableTexture = RenderTarget->GetRHI();
#elif ENGINE_MAJOR_VERSION == 4
TargetableTexture = RenderTarget->GetRenderTargetItem().TargetableTexture;
...
...
// Initialize the Render pass with the conversion texture
FRHITexture* ConversionTexture = TargetableTexture.GetReference();
FRHIRenderPassInfo RPInfo(ConversionTexture, ERenderTargetActions::DontLoad_Store);
// Needs to be called *before* ApplyCachedRenderTargets, since BeginRenderPass is caching the render targets.
RHICmdList.BeginRenderPass(RPInfo, TEXT("NDI Recv Color Conversion"));
```
设置NDI传入的UYVY
```c++
// set the texture parameter of the conversion shader
FNDIIOShaderUYVYtoBGRAPS::Params Params(SourceTexture, SourceTexture, FrameSize,
FVector2D(0, 0), FVector2D(1, 1),
bPerformsRGBtoLinear ? FNDIIOShaderPS::EColorCorrection::sRGBToLinear : FNDIIOShaderPS::EColorCorrection::None,
FVector2D(0.f, 1.f));
ConvertShader->SetParameters(RHICmdList, Params);
// Create the update region structure
FUpdateTextureRegion2D Region(0, 0, 0, 0, FrameSize.X/2, FrameSize.Y);
// Set the Pixel data of the NDI Frame to the SourceTexture
RHIUpdateTexture2D(SourceTexture, 0, Region, Result.line_stride_in_bytes, (uint8*&)Result.p_data);
```
## 解决方案
[NDI plugin质量问题](https://forums.unrealengine.com/t/ndi-plugin-quality-trouble/1970097)
I changed only shader “NDIIO/Shaders/Private/NDIIOShaders.usf”.
For example function **void NDIIOUYVYtoBGRAPS (// Shader from 8 bits UYVY to 8 bits RGBA (alpha set to 1)):**
_WAS:_
```c++
float4 UYVYB = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerB, InUV);
float4 UYVYT = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerT, InUV);
float PosX = 2.0f * InUV.x * NDIIOShaderUB.InputWidth;
float4 YUVA;
float FracX = PosX % 2.0f;
YUVA.x = (1 - FracX) * UYVYT.y + FracX * UYVYT.w;
YUVA.yz = UYVYB.zx;
YUVA.w = 1;
```
_I DID:_
```c++
float4 UYVYB = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerB, InUV);
float4 UYVYT0 = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerT, InUV + float2(-0.25f / NDIIOShaderUB.InputWidth, 0));
float4 UYVYT1 = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerT, InUV + float2(0.25f / NDIIOShaderUB.InputWidth, 0));
float PosX = 2.0f * InUV.x * NDIIOShaderUB.InputWidth;
float4 YUVA;
float FracX = (PosX % 2.0f) * 0.5f;
YUVA.x = (1 - FracX) * UYVYT1.y + FracX * UYVYT0.w;
YUVA.yz = UYVYB.zx;
YUVA.w = 1;
```
Small changes but result is seems much more better.
Of course, I added a bit of sharpness to the material after I changed the shader, but even without that, the result looks better than in the original version.
滤波资料:https://zhuanlan.zhihu.com/p/633122224
## UYVYYUV422
- https://zhuanlan.zhihu.com/p/695302926
- https://blog.csdn.net/gsp1004/article/details/103037312
![](https://i-blog.csdnimg.cn/blog_migrate/24b41fd36ff7902670e11a8005afb370.jpeg)

View File

@@ -0,0 +1,50 @@
# 前言
1. 角色需要`BP_LiveArea`LiveAreaActor
2. Sequence与Camera需要`CameraRoot`Actor需要与LiveArea完全重叠
1. ~~FollowMovementComponent编写各种相机跟踪物体IdolName、Socket、Bone~~
2. 摄像机挂载FollowingComponment。
# LiveDirector
## BP_LiveArea
基类为**ALiveAreaActor**位于Source/LiveDirector/DirectorFramework/LiveAreaActor.h。直播区域占位用Actor可以用于定义
- WeatherOverride进入该区域后天气系统重载。
- WeatherBlendDuration天气系统重载过渡时间。
- CareLayers该区域加载时会自动加载关联的 DataLayer 层级。
# DirectorCam
## Core
### BP_CamPlacement_LiveArea
一般挂载在**BP_LiveArea**下面。使用LiveDirector - DirectorCam - Template下的模板生成。继承关系为**BP_CamWorkShopPlacement -> ACamWorkShopPlacementActor**位于Modules/DirectorCam/Core/CamWorkShopPlacementActor.h
## Data
### UDirectorCamGroupData
镜头组数据资产,里面存储***各个镜头LevelSequence***。
### UDirectorCamSetupData
里面存储各种***UDirectorCamGroupData***。
# LevelSequences
LevelSequence的帧数为30fps。
- DirectorCamSetupData
- Example5400x4800(大动捕室)/Game/LevelSequences/Example5400x4800/CamSetup_5400x4800
- DirectorCamGroupData/Game/LevelSequences/Example5400x4800/General
- Dance
## 创建一个新镜头的步骤
***推荐直接复制之前制作的镜头,并在此基础上进行编辑。***
1. 在LevelSequences目录下新建一个LevelSequence资产。
2. 拖入角色蓝图资产到到LevelSequence中Spawnable并且Attach指定的**LiveArea**Actor下对齐直播场景的原点。拖入SoundBase资产到LevelSequence中可选
3. 角色蓝图添加CharacterMesh轨道并且给CharacterMesh轨道指定对应的AnimationSequence资产。
4. 添加摄像机
1. CinemaCamera
1. 如果需要追踪则给CinemaCamera添加FollowingMovement组件以及轨道并且指定组件的FollowingActor为对应Actor并且***填写FollowingIdolName***例如Idol.BeiLa。以及FollowingSocketName
2. DirectorCamera为场景相关的相机静态镜头
1. Sequence中添加DirectorCamera之后Attach到CameraRoot下。
2. 如果需要追踪则给DirectorCamera添加FollowingMovement组件以及轨道并且指定组件的FollowingActor为对应Actor并且***填写FollowingIdolName***例如Idol.BeiLa。以及FollowingSocketName
5. 将LevelSequence添加到对应的`UDirectorCamGroupData`资产中。
6.`UDirectorCamGroupData`放入到对应`UDirectorCamGroupData`资产中。
7. 点击ASoul工具-配置ShotGroupBroad通用按钮。配置StreamDock面部按钮以及图标。

View File

@@ -0,0 +1,11 @@
# 添加步骤
1. 在Maps文件中构建**Map_xxx**文件夹以及同名称的大世界Map。
2. 在地图中间添加LiveArea并且设置标题。
3. 添加Config/LiveDirectorAsset/LevelAreaConfig.json配置。
## 相关资产存放位置
- 在Maps文件中构建**Map_xxx**文件夹以及同名称的大世界Map。
- **Maps/Scenes/Map_xxx**存放对应大世界的资产。
- Maps/Epic存放官方商城下载的资源。
- UIAssets/MapEnvironments/ICON_/Area存放LiveArea位置的预览图标
- UIAssets/MapEnvironments/ICON_/Level存放对应Map或者大世界的预览图标

View File

@@ -0,0 +1 @@
BP_ProjectV_EnvironmentBlendable

View File

@@ -0,0 +1,149 @@
# 前言
继承关系BP_XXX_Base -> BP_Idol_Base -> TsIdolActor -> AVCharacter -> ACharacter 。
主要逻辑位于TsIdolActor中文件路径为`Script/LiveDirector/Character/TsIdolActor.ts`
# 添加新衣服流程
1. 在Content/Character/XXX中创建继承自BP_XXX_Base的蓝图。
2. 设置SkeletalMesh。并且确保勾选了**Dynamic InsetShadow**。
3. 添加PhysicalAsset并且保证胶囊体大致包裹模型否则阴影会出现被裁剪的问题。
4. 添加对应的OutlineMaterial。
5. 在DressName中输入新衣服的显示名称。
6. 添加Prop标签。
1. Idol.XXX
2. Prop.Dress(第一件衣服需要设置成Prop.Dress.Default)
3. Prop.MountPoint.Body
7. ***脚本扫描***:点击编辑器的嘉然头像->角色道具配置全量生成 or 角色道具配置增量生成。会往IdolPropAssetConfig.json添加对应衣服或者道具配置数据。
# 添加新角色流程笔记
1. 添加一个Idol.xxx标签。
2. 修改下面相关文件[[#含有角色标签的文件]]。
3. 添加对应的蓝图与文件结构。
1. Content/Character
1. 在Idol_Base中添加[[#BP_XXX_Base]]。
2. 设置衣服、头发的SkeletalMesh。并且确保勾选了**Dynamic InsetShadow**。
3. 添加角色、衣服、头发的PhysicalAsset并且保证胶囊体大致包裹模型否则阴影会出现被裁剪的问题。
2. Content/ResArt/CharacterArt放置角色与服装按照
1. 动画蓝图中的***FullBody节点需要设置角色标签***。
1. 指定用于修型的PostProcess动画蓝图。
2. 添加Prop标签。
1. Idol.XXX
2. Prop.Dress
3. Prop.MountPoint.Body
3. ***脚本扫描***:点击编辑器的嘉然头像->角色道具配置全量生成 or 角色道具配置增量生成。会往IdolPropAssetConfig.json添加对应衣服或者道具配置数据。
4. 设置道具挂载信息数据表ResArt/MountPointConfig/DT_MountPointConfig用于设置道具挂载时的相对偏移。
5. ***材质相关操作***
1. 在ResArt/CommonMaterial/Functions/CameraLightCollection中添加对应角色的属性。
2. 在ResArt/CommonMaterial/Functions/MF_CharacterMainLightIntensity中添加对应RoleID。
3. 在ResArt/CommonMaterial/Functions/MF_CharacterRimLightIntensity中添加对应RoleID。
4. 在对应角色的基础材质中设置RoleID数值。
5. 调用Python脚本制作Dissolve材质。LiveDirector/Editor/MaterialMigration/MakeDissolveMaterials.py
## 含有角色标签的文件
1. [x] TsCharacterItem.ts `Script/LiveDirector/Character/View/TsCharacterItem.ts`
1. 3级角色控制界面相关的UI操作。
2. [x] TsCharacterMocapViewTmp.ts :这个是MotionProcess的UI继承控件`/Content/UIAssets/Character/Mocap/WBP_CharacterMocapViewTmp`
1. 在MotionProcess专用地图中创建对应的Idol。
3. [x] TsPropMocapItemTmp.ts
1. 在MotionProcess专用地图中控制道具Attach到对应IdolUI逻辑
4. [x] TsDirectorConsoleCommandHandler.ts
1. 快捷命令 Motion同步相关 GetMotionOffset
2. 快捷命令 快速创建4个角色 IdolCostume
5. [x] TsSpawnPointSettingItem.ts
1. IdolItemUI继承控件`/Content/UIAssets/Character/WBP_SpawnPointSettingItem`
6. [x] TsIdolPropManagerComponent.ts
1. 没有思诺与心怡
2. 需要搞清楚。
7. [x] ~~TsSimpleLevelManager.ts~~
1. SwitchLiveArea()中调用只调用了Idol.BeiLa属于容错语句。
8. ~~CameraDebug.cpp ~~(这个不需求)
## BP_XXX_Base
1. 指定动画蓝图。
2. 指定LiveLinkName。
3. 指定OutlineMaterial。
# AVCharacter
主要实现了`virtual void OnRep_AttachmentReplication() override;`声明了若干BlueprintNativeEvent
- bool CanSyncRelativeTransform();
- void BeforeAttachToNewParent();
- void AfterAttachToNewParent();
## OnRep_AttachmentReplication()
注释:
>// 动捕模式下CanSync=false. 各端自行计算Actor Location, client无需使用Server计算结果
// 自由行走模式下, CanSync=trueclient需要同步server的transform信息。
同步Attachment行为。在AActor::OnRep_AttachmentReplication()的基础上添加:
- 判断CanSync标记以此来决定是否同步Transform
- 未Attach组件=>Attch组件前后添加BeforeAttachToNewParent()、AfterAttachToNewParent()
```c++
auto CanSync = CanSyncRelativeTransform(); //获取Sync标记具体的逻辑位于TsIdolActor.ts中
if (attachmentReplication.AttachParent)
{
if (RootComponent)
{
USceneComponent* AttachParentComponent = (attachmentReplication.AttachComponent ? attachmentReplication.AttachComponent : attachmentReplication.AttachParent->GetRootComponent());
if (AttachParentComponent)
{
if(CanSync)//增加判断Sync判断只有在自由行走模式下才会同步Transform。
{
RootComponent->SetRelativeLocation_Direct(attachmentReplication.LocationOffset);
RootComponent->SetRelativeRotation_Direct(attachmentReplication.RotationOffset);
RootComponent->SetRelativeScale3D_Direct(attachmentReplication.RelativeScale3D);
}
// If we're already attached to the correct Parent and Socket, then the update must be position only.
// AttachToComponent would early out in this case.
// Note, we ignore the special case for simulated bodies in AttachToComponent as AttachmentReplication shouldn't get updated
// if the body is simulated (see AActor::GatherMovement).
const bool bAlreadyAttached = (AttachParentComponent == RootComponent->GetAttachParent() && attachmentReplication.AttachSocket == RootComponent->GetAttachSocketName() && AttachParentComponent->GetAttachChildren().Contains(RootComponent));
if (bAlreadyAttached)
{
// Note, this doesn't match AttachToComponent, but we're assuming it's safe to skip physics (see comment above).
if(CanSync)
{
RootComponent->UpdateComponentToWorld(EUpdateTransformFlags::SkipPhysicsUpdate, ETeleportType::None);
}
}
else
{
BeforeAttachToNewParent();//增加BlueprintNativeEvent
RootComponent->AttachToComponent(AttachParentComponent, FAttachmentTransformRules::KeepRelativeTransform, attachmentReplication.AttachSocket);
AfterAttachToNewParent();//增加BlueprintNativeEvent
}
}
}
}
```
# TsIdolActor.ts
## VirtualOverrider
CanSyncRelativeTransform()
- 不需要同步Transform的情况
- AI控制的ACao角色不需要同步。
- 使用TsIdolMovementComponent并且勾选了ManulMovement的情况不需要同步。
- 动画蓝图中使用了**AnimGraphNode_Fullbody**节点并且bGetMotionData为true的情况不需要同步。
具体代码如下:
```typescript
CanSyncRelativeTransform(): boolean {
if (Utils.HasTag(this.PropTags, new UE.GameplayTag("Idol.AIACao"))) {
return false;
}
if(this.MovementComp && this.MovementComp.ManulMovement){
return false
}
var animInstance = this.Mesh.GetAnimInstance() as UE.IdolAnimInstance
let fullbodyNode = Reflect.get(animInstance, 'AnimGraphNode_Fullbody') as UE.AnimNode_FullBody
return !(fullbodyNode && fullbodyNode.bGetMotionData)
}
```
# Prop.Dress.Default
1. TsIdolPropManagerComponent.ts ServerLoadProp()
2. DoLoadProp()
3. ServerDoLoadPropPreset()
4. GetPropPreset()
5. GetDefaultDress()取得DefaultDress标签字符串。
6. GetPropAssetConfigsByTags(tags)根据标签取得对应的资产配置UPropAssetConfig
扫描所有资产TsPropAssetManager.ts CollectAllAssets()

View File

@@ -0,0 +1,18 @@
# 原始流程
1. StartListenServer_(异世界).bat
2. MotionServer.exe
3. StartClient_MotionProcessor.bat
4. Edior
5. MotionProcessor 设置IP 动作传输
6. Editor Open IP
7. 4级添加地图
8. 3级添加角色
9. 在StartListenServer中运行run pvw
# 改进流程
1. 启动Editor并且以专用服务模式启动。Play As Listen Server
2. MotionServer.exe
3. StartClient_MotionProcessor.bat
4. MotionProcessor 设置IP 动作传输
5. 4级添加地图
6. 3级添加角色

View File

@@ -0,0 +1,106 @@
# 相关类
- TsPropActor
- TsIdolPropActor
- TsScenePropActor
- TsPropEffectActor
# 相关资产
ResArt/MountPointConfig/DT_MountPointConfig用于设置道具挂载时的相对偏移。
# MountPoint
GameplayTag中有定义相关Prop的挂载位置标签
- Prop
- MountPoint
- Back
- Body
- Feet
- Head
- HeadBottom
- HeadUp
- Hips
- LeftFoot
- LeftHand
- RightFoot
- RightHand
- Root
对应逻辑TsPropAssetManager.ts中的枚举查找函数为GetMountPointIndexByTagName():
```ts
export const enum MountPointEnum {
    HeadUp,
    Head,
    HeadDown,
    LeftHand,
    RightHand,
    Back,
    Feet,
    Hips
}
```
TsIdolPropManagerComponent.ts
AttachAllProp() => AttachProp()
TsPropAssetManager.ts
```ts
static GetMountPointName(Tag: UE.GameplayTag): string {
if (Tag.TagName.startsWith('Prop.MountPoint')) {
let res = Tag.TagName.split('.')
return res[res.length - 1]
}
return ''
}
```
# 换装代码调用步骤
PrepareLoadNewModel_Multicast
ServerDoLoadPropPreset() => ServerStartSwitchPreset() => ServerLoadProp() => DoLoadProp() => LoadDressByConfig()
- TsDirectorController.ts CreateIdol()
- TsIdolControllerActor.ServerCreateIdolControllerAtLiveArea
- controller.PropComp.ServerLoadPropPreset(0)
- TsIdolPropManagerComponent
- ServerLoadPropPreset()
- ServerLoadProp()
- DoLoadProp()
- LoadDressByConfig
LocalLoadDressByConfig() 本地加载。
LoadDressByConfig() 服务器加载。
# 角色衣服套装预设切换逻辑
在WBP_CharacterItem中5个按钮BtnSuit_1、BtnSuit_2、BtnSuit_3、BtnSuit_4、BtnSuit_5会调用EventOnPresetClicked。
```ts
EventOnPresetClicked(PresetIndex: number, IsDoubleClick: boolean):void {
let curTime = UE.KismetSystemLibrary.GetGameTimeInSeconds(this)
if (curTime - this.LastLoadPresetTime < 0.5) {
console.warn('Click too fast , please try again later')
return
}
this.LastLoadPresetTime = curTime;
if (IsDoubleClick) {
this.LoadPreset(PresetIndex)
} else {
this.PreviewPreset(PresetIndex)
}
}
public LoadPreset(PresetIndex: number): void {
if (this.Idol == null) {
console.error(`TsCharacterItem@LoadPreset error: idol is null`);
return;
}
this.Idol.PropComp.ServerLoadPropPreset(PresetIndex);
}
public PreviewPreset(PresetIndex: number): void {
if (this.Idol == null) {
console.error(`TsCharacterItem@PreviewPreset error: idol is null`);
return;
}
this.Idol.PropComp.ClientLoadPropPreset(PresetIndex);
this.RefreshPresetUIStatus()
}
```