Skip to content

Commit c85883a

Browse files
committed
update notes
1 parent ded5166 commit c85883a

6 files changed

+413
-0
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
一些参考:
2+
[UE4 Config配置文件详解]((https://gwb.tencent.com/community/detail/121619)
3+
以及官方文档 [Configuration Files](https://dev.epicgames.com/documentation/en-us/unreal-engine/configuration-files-in-unreal-engine)
4+
[5.4 Config not saving correctly](https://forums.unrealengine.com/t/5-4-config-not-saving-correctly/1860118)
5+
6+
`EKnownIniFile` 宏定义了所有的 known file,
7+
很多 [UE4 Config配置文件详解]((https://gwb.tencent.com/community/detail/121619) 中提到的 config 类型移到了 other files 中
8+
`ConfigCacheIni.cpp``LoadRemainingConfigFiles` 函数
9+
10+
UCLASS 的 config 设置会被继承,不能被子类取消,但可以修改
11+
在 object initializer 的 `PostConstructInit` 函数中,如果初始化的 uobject 是 CDO,或者这个 uobject 的类使用了 PerObjectConfig 声明,那么会调用 uobject 的 `LoadConfig` 函数读取配置文件。是
12+
13+
PerObjectConfig
14+
15+
16+
PostConstructLink,
17+
18+
在 uobject 的 `SaveConfig` 函数中,会检查写回的值是否与父类的 CDO 中的值相同的(TODO:为什么是检查父类呢,为什么要是这个 property 属于该子类就应该存下去呢)
19+
```c++
20+
// Properties that are the same as the parent class' defaults should not be saved to ini
21+
// Before modifying any key in the section, first check to see if it is different from the parent.
22+
const bool bPropDeprecated = Property->HasAnyPropertyFlags(CPF_Deprecated);
23+
const bool bIsPropertyInherited = Property->GetOwnerClass() != GetClass();
24+
const bool bShouldCheckIfIdenticalBeforeAdding = !GetClass()->HasAnyClassFlags(CLASS_ConfigDoNotCheckDefaults) && !bPerObject && bIsPropertyInherited;
25+
26+
if (!bPropDeprecated && (!bShouldCheckIfIdenticalBeforeAdding || !Property->Identical_InContainer(this, SuperClassDefaultObject, Index)))
27+
{
28+
FString Value;
29+
Property->ExportText_InContainer( Index, Value, this, this, this, PortFlags );
30+
Config->SetString( *Section, *Key, *Value, PropFileName );
31+
}
32+
```
33+
TODO:测试 PerObjectConfig
34+
TODO:解释 NewObject 中使用了非 CDO 的构造模板时的代码路径
35+
TODO:InitProperty
36+
* UObject::GetArchetype() 蓝图中这玩意返回了个啥
37+
* 我觉得 arch type 想解决的问题是这样,我一开始以为 InitProperty 的行为:
38+
* 如果是初始化模板的 CDO,就 copy post construction link 上的 property 就行了,这个 link 上主要是一些 config 相关的,而如果它是蓝图类,因为 C++ 父类中的数据可能在编辑器中被修改过,因此 InitProperty 最后调用 Class->InitPropertiesFromCustomList 这个钩子函数来引入蓝图中的修改。而对于初始化模板不是 CDO,那么就把所有的 fproperty 都拷贝了
39+
* 这样就足够了。但发现还有问题,即使这个 Class 是 Native C++ Class,还是有可能作为一个蓝图类的 sub object,然后它的数据在编辑器中被修改
40+
* instance graph,会不会和 instanced 这个 uproperty 有关系

_posts/UE/2025-01-13 Lyra Notes.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
### Start
2+
如果看一个新项目,看看 project setting 中默认的 game mode,再看看 default level map
3+
4+
5+
6+
### Engine Loop
7+
主要参考 [The Unreal Engine Game Framework: From int main() to BeginPlay](https://www.youtube.com/watch?v=IaU2Hue-ApI)
8+
TODO:自己再根据源码梳理一下
9+
```c++
10+
FEngineLoop GEngineLoop;
11+
```
12+
`FEngineLoop::PreInit`:Load game and plugin modules,load 一个模块的过程
13+
* 模块的 CDO 初始化
14+
* `StartupModule` callback
15+
PreInitPostStartupScreen
16+
`FEngineLoop::Init`
17+
* 初始化 GEngine,根据是否包含 editor,类型为 `UGameEngine``UUnrealEdEngine` 的子类。实际的类型记录 config 文件中的(虽然我看 `FEngineLoop::PreInit` 里 call 的 `FEngineLoop::PreInitPostStartupScreen` 也会 new 一个 GEngine,但断点发现这里的逻辑没有被执行)
18+
* 然后调用 GEngine 的 `Init` 函数,对于 `UGameEngine`,该函数中会创建 `UGameInstance`,然后调用 `UGameInstance``InitializeStandalone`,这个函数中就会创建一个 world context,并调用我们常见的 `UGameInstance::Init` 回调
19+
* 然后调用 GEngine 的 `Start` 函数,对于 `UGameEngine`,它会调用 `UGameInstance::StartGameInstance`,这个函数从 config 中获取 default game map,然后调用 `UEngine::Browse` 加载地图
20+
21+
`UEngine::LoadMap` 包含下面的内容:
22+
23+
cleanup 之前的 world(如果有的话),cleanup 之后,会调用 `UEngine::TrimMemory` 做垃圾回收,因此即使 open level 当前的 map,也会重新加载一次 world
24+
25+
load umap package
26+
* world 的构造函数
27+
* PostLoad
28+
29+
`UWorld::InitWorld`
30+
* `UWorld::InitializeSubsystems`
31+
32+
`UWorld::SetGameMode` 创建 game mode
33+
34+
`UWorld::InitializeActorsForPlay`
35+
* `AGameModeBase::InitGame`,在这里 game mode 创建了自己的 game session,并设置到 `GameSession` 字段
36+
* 然后初始化所有的 actors,即调用它们的 pre, initailize component 以及 post 回调,其中 `AGameModeBase::PreInitializeComponents` 在这个时候创建了自己的 game state,将其设置在自己和 world 的 `GameState` 字段。同时调用 `AGameModeBase::InitGameState` 回调
37+
38+
`UWorld::SpawnPlayActor`
39+
* `AGameModeBase::Login` 回调,spawn 出新的 player controller
40+
* player controller 的 `PostInitializeComponents` spawn 出 `PlayerState`
41+
* `AGameModeBase::PostLogin` 回调
42+
* 调用 `AGameModeBase::RestartPlayer` 回调,默认是根据当前场景找到 player start actor,然后调用 `AGameModeBase::RestartPlayerAtPlayerStart` 来 spawn default pawn
43+
44+
`UWorld::BeginPlay`
45+
* `AGameModeBase::StartPlay`
46+
* 各个 actor 以及 component 的 begin play 调用
47+
48+
TODO:在 `InitializeActorsForPlay` 中 spawn 出来的 actor 以及它的 component 回调会执行到哪个阶段,后续的回调又由谁来执行
49+
### ULevel
50+
```c++
51+
UPROPERTY()
52+
TObjectPtr<AWorldSettings> WorldSettings;
53+
54+
/** The level scripting actor, created by instantiating the class from LevelScriptBlueprint. This handles all level scripting */
55+
UPROPERTY(NonTransactional)
56+
TObjectPtr<class ALevelScriptActor> LevelScriptActor;
57+
```
58+
TODO:world settings 是逐关卡一个吗,谁创建和保存的呢?
59+
```
60+
UWorld::InitializeNewWorld()
61+
```
62+
看起来在 `UWorld::InitializeNewWorld` 中创建的 world settings
63+
### UWorld
64+
AWordSettings 是什么,
65+
66+
AInfo 是怎么用的
67+
68+
ALevelScriptActor:ULevel 中的一个字段,用来执行关卡蓝图
69+
70+
game mode 的 init game 回调是什么时候调用的
71+
72+
73+
启用 game features 时需要设置的这个 asset manager 选项是什么
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
动画部分的 root motion 逻辑只是根骨骼移动了,但是 skeletal mesh component 的 transform 还是没有移动
2+
3+
TODO:但是如果加上 character movement component,就发现 skeletal mesh component 和 capsule 的坐标都正确更新了,解释原因
4+
5+
TODO:root motion content example 中 MM_Death_Front_03 和 MM_Death_Front_03_ExtractedRM 有啥区别,一个是 root 的 bone transform 在变,一个是 root 的 mesh relative transform 在变
6+
### Overview
7+
root motion 就是动画资产的根骨骼的变换。在动画蓝图运行时如果要提取 root motion,那么根骨骼的变换就被提取到 `UAnimInstance``ExtractedRootMotion` 字段中,而不会参与 bone transform 的计算。character movement component 组件会使用提取出的 root motion,计算胶囊体新的世界坐标(在动画编辑器中,就会看到 root bone 的 bone transform 不变,但是 mesh relative transform 在不断变换)
8+
9+
有几个因素会影响动画蓝图运行中是否要提取 root motion
10+
* `UAnimSequence``bEnableRootMotion` 字段
11+
* `UAnimInstance``RootMotionMode` 字段
12+
13+
`UAnimSequence``bEnableRootMotion` 为 false 时,不会提取 root motion,因此如果动画中包含根骨骼变换,就会表现出人物在移动,但是胶囊体没移动的状态(在动画编辑器中,就会看到 root bone 的 bone transform 在不断变换,但是 mesh relative transform 没有变化)
14+
15+
`UAnimSequence``bEnableRootMotion` 为 true 时,是否提取 root motion 依赖于`UAnimInstance``RootMotionMode` 字段
16+
* `NoRootMotionExtraction`:不提取 root motion
17+
* `IgnoreRootMotion`:提取 root motion 但是不使用它,就相当于动画没有 root motion
18+
* `RootMotionFromEverything`:从所有在播放的动画资产中提取 root motion
19+
* `RootMotionFromMontagesOnly`:只从 montage 中提取 root motion
20+
21+
如果希望禁用个别动画的 root motion,可以将 `UAnimSequence``bForceRootLock` 设置为 true
22+
### Root Motion Extraction
23+
#### Asset Player 中的 Root Motion
24+
`UAnimSequence::HandleAssetPlayerTickedInternal` 中会将根骨骼的变换提取出来,设置到 `FAnimAssetTickContext``RootMotionMovementParams` 中,随后在 `FAnimSync::TickAssetPlayerInstances` 函数中累积到 `FAnimInstanceProxy``ExtractedRootMotion` 字段中
25+
26+
后续的 `UAnimSequence::GetBonePose` 调用 `DecompressPose` 来根据当前的播放进度获取骨骼 pose,`DecompressPose` 会检查是否要提取 root motion,如果是,那么将提取出的根骨骼 transform 设置为空
27+
#### Montage 中的 Root Motion
28+
`FAnimMontageInstance::Advance` 步进时也会提取 montage 中 animation asset 的 root motion,所有 slot track 上的 root motion 都会提取,但是只有 slot track 0 上的 root motion 会作用于角色移动。并且同一时刻只能有一个 montage 的 root motion 生效(`UAnimInstance``RootMotionMontageInstance` 字段),其它 slot track 或者其它的 montage 的 root motion 信息会被提取,但是不会作用到胶囊体的移动上
29+
30+
取决于 `RootMotionMode``RootMotionFromEverything` 还是 `RootMotionFromMontagesOnly`,montage 的 root motion 信息会存放在 `UAnimInstance``RootMotionBlendQueue` 中或者 `ExtractedRootMotion`
31+
32+
最后 `UAnimInstance::PostUpdateAnimation` 中将 `FAnimInstanceProxy``ExtractedRootMotion``RootMotionBlendQueue` 中的 root motion 信息都合并到自己的 `ExtractedRootMotion` 字段中
33+
### Root Motion Consumption
34+
`UCharacterMovementComponent::TickCharacterPose` 中触发 tick pose 后,调用 `USkeletalMeshComponent::ConsumeRootMotion` 将这一帧的 root motion 信息存放到自己的 `RootMotionParams` 字段中
35+
36+
后续速度的计算不再根据当前的 `Acceleration`(对应用户输入的 `InputVector`),而是使用 `RootMotionParams` 中的位移除以这一帧的 delta time。有了速度后,具体的移动还是遵循 Movement Component in Unreal 中的讨论。移动完成后,再将 root motion 的旋转叠加到当前胶囊体的旋转上
37+
### Root Motion 的计算
38+
一开始看到 root motion 的 accumulate 有些奇怪,因为在 UE 中 transform 相乘时,乘在左边的 transform 会先对坐标进行变换。但是按道理来说,accumulate 上来的 transform 显然应该在后边进行变换
39+
```c++
40+
void Accumulate(const FTransform& InTransform)
41+
{
42+
if (!bHasRootMotion)
43+
{
44+
Set(InTransform);
45+
}
46+
else
47+
{
48+
RootMotionTransform = InTransform * RootMotionTransform;
49+
RootMotionTransform.SetScale3D(RootMotionScale);
50+
}
51+
}
52+
```
53+
后面发现原因在于 `UAnimSequence::ExtractRootMotionFromRange` 中最后返回的 root motion 为
54+
```c++
55+
// Transform to Component Space
56+
const FTransform RootToComponent = RootTransformRefPose.Inverse();
57+
StartTransform = RootToComponent * StartTransform;
58+
EndTransform = RootToComponent * EndTransform;
59+
60+
return EndTransform.GetRelativeTransform(StartTransform);
61+
```
62+
而不是 `StartTransform` 的逆变换乘以 `EndTransform`
63+
64+
究其原因,因为在 `USkeletalMeshComponent::ConvertLocalRootMotionToWorld` 中计算要将 skeletal mesh 在 model space 中变换 root motion 对应的 transform,整个 actor 在 world space 下需要如何变换时,需要将原来的 skeletal mesh 中包含的 `StartTransform` 剔除出去,替换为新的 `EndTransform`,root motion 的 transform 以 `EndTransform` 乘以 `StartTransform` 的逆变换的形式表达计算会更方便一些
65+
66+
TODO:解释 mirror axis 对 root motion 的影响
67+
68+
TODO:看看 [RootMotion详解](https://zhuanlan.zhihu.com/p/74554876),解释 root motion 的网络同步和非动画的 root motion
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
### Key Concepts
2+
#### Slot Tracks
3+
```c++
4+
class UAnimMontage : public UAnimCompositeBase
5+
{
6+
// slot data, each slot contains anim track
7+
UPROPERTY()
8+
TArray<struct FSlotAnimationTrack> SlotAnimTracks;
9+
};
10+
11+
/**
12+
* Each slot data referenced by Animation Slot
13+
* contains slot name, and animation data
14+
*/
15+
USTRUCT()
16+
struct FSlotAnimationTrack
17+
{
18+
GENERATED_USTRUCT_BODY()
19+
20+
UPROPERTY(EditAnywhere, Category=Slot)
21+
FName SlotName;
22+
23+
UPROPERTY(EditAnywhere, Category=Slot)
24+
FAnimTrack AnimTrack;
25+
26+
ENGINE_API FSlotAnimationTrack();
27+
};
28+
```
29+
一个 montage 由多个 slot track 组成,每个 slot track 的 `SlotName` 与动画蓝图中的 slot 节点的 slot name 相对应,并且这些 slot 必须属于同一个 slot group。每个 slot track(表示为 `FAnimTrack` 结构体)由多个基本的 animation asset 拼接而成,拼接的 animation asset 可以只是原本的 animation asset 的一部分(由 `FAnimSegment``AnimStartTime``AnimEndTime` 控制)
30+
```c++
31+
/** This is list of anim segments for this track
32+
* For now this is only one TArray, but in the future
33+
* we should define more transition/blending behaviors
34+
**/
35+
USTRUCT()
36+
struct FAnimTrack
37+
{
38+
UPROPERTY(EditAnywhere, Category=AnimTrack, EditFixedSize)
39+
TArray<FAnimSegment> AnimSegments;
40+
};
41+
42+
/** this is anim segment that defines what animation and how **/
43+
USTRUCT()
44+
struct FAnimSegment
45+
{
46+
UPROPERTY(EditAnywhere, Category=AnimSegment, meta=(DisplayName = "Animation Reference"))
47+
TObjectPtr<UAnimSequenceBase> AnimReference;
48+
49+
/** Start Pos within this AnimCompositeBase */
50+
UPROPERTY(VisibleAnywhere, Category=AnimSegment, meta=(DisplayName = "Starting Position"))
51+
float StartPos;
52+
53+
/** Time to start playing AnimSequence at. */
54+
UPROPERTY(EditAnywhere, Category=AnimSegment, meta=(DisplayName = "Start Time"))
55+
float AnimStartTime;
56+
57+
/** Time to end playing the AnimSequence at. */
58+
UPROPERTY(EditAnywhere, Category=AnimSegment, meta=(DisplayName = "End Time"))
59+
float AnimEndTime;
60+
61+
/** Playback speed of this animation. If you'd like to reverse, set -1*/
62+
UPROPERTY(EditAnywhere, Category=AnimSegment, meta=(DisplayName = "Play Rate"))
63+
float AnimPlayRate;
64+
65+
UPROPERTY(EditAnywhere, Category=AnimSegment, meta=(DisplayName = "Loop Count"))
66+
int32 LoopingCount;
67+
};
68+
```
69+
#### Slot and Slot Group
70+
montage 能使用的所有的 slot 和 slot group 保存在 `USkeleton``SlotGroups` 中,`SlotToGroupNameMap` 用于快速查找,在 skeleton 反序列化时根据 `SlotGroups` 构建的
71+
```c++
72+
// serialized slot groups and slot names.
73+
UPROPERTY()
74+
TArray<FAnimSlotGroup> SlotGroups;
75+
76+
/** SlotName to GroupName TMap, only at runtime, not serialized. **/
77+
TMap<FName, FName> SlotToGroupNameMap;
78+
```
79+
虽然 [Animation Slots](https://dev.epicgames.com/documentation/en-us/unreal-engine/animation-slots-in-unreal-engine) 文档中说同一个 group 中只能有一个 montage 处于播放状态,但实际上是可以有多个的
80+
```c++
81+
/** Plays an animation montage. Returns the length of the animation montage in seconds. Returns 0.f if failed to play. */
82+
UFUNCTION(BlueprintCallable, Category = "Animation|Montage")
83+
ENGINE_API float Montage_Play(UAnimMontage* MontageToPlay, float InPlayRate = 1.f, EMontagePlayReturnType ReturnValueType = EMontagePlayReturnType::MontageLength, float InTimeToStartMontageAt=0.f, bool bStopAllMontages = true);
84+
```
85+
其中参数 `bStopAllMontages` 默认值为 true,表示停止同一个 group 的其它 montage。但如果调用该函数时传入的 `bStopAllMontages` 为 false,则同一个 group,甚至同一个 slot 中,可以有多个 montage 同时播放
86+
87+
所有权重非零的 montage 都记录在 `UAnimInstance` 的 `MontageInstances` 字段,虽然它注释中也说要求 group 中至多一个 montage 在播放
88+
```c++
89+
/** AnimMontage instances that are running currently
90+
* - only one is primarily active per group, and the other ones are blending out
91+
*/
92+
TArray<struct FAnimMontageInstance*> MontageInstances;
93+
```
94+
如果希望 group 中只有一个 montage 在播放,同时又想多个激活 group 中的多个 slot,只需要给这个 montage 添加多个 slot track 即可
95+
96+
具体 graph node 的 slot 是如何进行混合的,见 Animation Blending in Unreal 中的说明
97+
#### Section
98+
整个播放进度条可以划分为若干 section,每个 section 的名称,开始时间,连接的下一个 section 等信息存放在 `CompositeSections`
99+
```c++
100+
class UAnimMontage : public UAnimCompositeBase
101+
{
102+
// composite section.
103+
UPROPERTY()
104+
TArray<FCompositeSection> CompositeSections;
105+
};
106+
107+
/**
108+
* Section data for each track. Reference of data will be stored in the child class for the way they want
109+
* AnimComposite vs AnimMontage have different requirement for the actual data reference
110+
* This only contains composite section information. (vertical sequences)
111+
*/
112+
USTRUCT()
113+
struct FCompositeSection : public FAnimLinkableElement
114+
{
115+
/** Section Name */
116+
UPROPERTY(EditAnywhere, Category=Section)
117+
FName SectionName;
118+
119+
/** Should this animation loop. */
120+
UPROPERTY(VisibleAnywhere, Category=Section)
121+
FName NextSectionName;
122+
};
123+
```

0 commit comments

Comments
 (0)