Skip to content

Commit

Permalink
update ue notes and umg
Browse files Browse the repository at this point in the history
  • Loading branch information
ckf104 committed Nov 21, 2024
1 parent b26ffec commit c1dc8c1
Show file tree
Hide file tree
Showing 2 changed files with 12 additions and 38 deletions.
5 changes: 4 additions & 1 deletion _posts/UE/2024-11-12 UMG in Unreal.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
### Slot
解释:每个子节点都插在父节点的 slot 中,有没有 anchor 依赖于 父节点 slot 的类型,
解释:每个子节点都插在父节点的 slot 中,有没有 anchor 依赖于 父节点 slot 的类型

### Component
为什么 button component 底下有个 event 栏可以直接绑定 event,普通的蓝图类怎么没看到这玩意。普通的蓝图类中会有

### Anchor
因为我们通常设计 UI 时,总需要一个参考的屏幕的屏幕分辨率来将其可视化。anchor 这个概念要解决的问题是当屏幕分辨率变化时,如何确定新的 UI 布局。首先我们定义什么是 UI 布局:我们使用一个矩形来标定 UI 元素的位置。给定长宽的画布,我们清楚了每个 UI 矩形在画布中的位置和大小,那么就清楚了 UI 布局
Expand Down
45 changes: 8 additions & 37 deletions _posts/UE/UE Notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ BuildCookRun -nop4 -utf8output -nocompileeditor -skipbuildeditor -cook \
-stage -archive -package -build -pak -iostore -compressed -prereqs \
-archivedirectory=/home/ckf104/src/TP_ThirdPerson/ -clientconfig=Development -nocompile -nocompileuat 2 >&1 | tee log.txt
```
* 如果是想生成 UE 本身的 compile_commands.json 的话,可以使用(如果是 Linux 平台的话替换 Win64 即可)
```shell
$ ./Engine/Build/BatchFiles/RunUBT.bat UnrealEditor Development Win64 -Mode=GenerateClangDatabase
```

* 在 windows visual studio 中需要设置 editor preference 为 vs2022,详见[VisualStudio 2022 Intellisense for engine files not working in UE5](https://forums.unrealengine.com/t/ue-5-1-visualstudio-2022-intellisense-for-engine-files-not-working-in-ue5/551166)

Expand Down Expand Up @@ -207,46 +211,12 @@ TODO:
* **CDO 是如何创建的,如何从 UClass 中动态创建一个新类型,UCLASS 中的 classconstructor 字段,以及 uclass::bind 是什么时候调用的**
* 如何确定一个编译单元所在的 package,例如,`CameraComponent.gen.cpp` 的 package 名为 `/Script/Engine`
* `ClassWithin` 字段是干什么的,我原本以为是不是 outer 的 uclass 之类的,但发现大部分类的 `ClassWithin` 都对应 UObject 的 uclass。只有少部分使用了 `DECLARE_WITHIN` 或者 `DECLARE_WITHIN_UPACKAGE` 宏的类是例外

## Actor and Component

* AActor 中重要的一些字段

```c++
UPROPERTY(DuplicateTransient)
TObjectPtr<class UInputComponent> InputComponent;

/** Pawn responsible for damage and other gameplay events caused by this actor. */
UPROPERTY(BlueprintReadWrite, ReplicatedUsing=OnRep_Instigator, meta=(ExposeOnSpawn=true, AllowPrivateAccess=true), Category=Actor)
TObjectPtr<class APawn> Instigator;

/** Array of all Actors whose Owner is this actor, these are not necessarily spawned by UChildActorComponent */
UPROPERTY(Transient)
TArray<TObjectPtr<AActor>> Children;

UPROPERTY(BlueprintGetter=K2_GetRootComponent, Category="Transformation")
TObjectPtr<USceneComponent> RootComponent;

/** The UChildActorComponent that owns this Actor. */
UPROPERTY()
TWeakObjectPtr<UChildActorComponent> ParentComponent;

/**
* All ActorComponents owned by this Actor. Stored as a Set as actors may have a large number of components
* @see GetComponents()
*/
TSet<TObjectPtr<UActorComponent>> OwnedComponents;
```
* `Pawn` 相比于 `Actor`,增加了处理输入的能力,例如 `SetupPlayerInputComponent` 虚函数,使得能够使用
## API

* 创建新对象,`CreateDefaultSubobject` vs `NewObject`
* 类似地,[Components](https://dev.epicgames.com/documentation/en-us/unreal-engine/components-in-unreal-engine) 中区分 `SetupAttachment``AttachToComponent`,也是一个在构造函数,一个 during play,为什么做这样的区分?另外,在 `SetupAttachment` 中我没有看到将 child 加到 parent 的 AttachChildren 里面,为什么?
* 看起来在 `SetupAttachment` 中调用了 MARK_PROPERTY_DIRTY_FROM_NAME 宏,涉及ue4.25 加入的 pushmodel,不知道 parent 的 AttachChildren 是不是这样更新的
* 看起来是在 `USceneComponent::OnRegister` 函数中调用了 `AttachToComponent`
* `USceneComponent::AddLocalOffset`,给 Component 添加 Offset 的位移,位移向量是参照 Component 的局部坐标系
* `APown::GetViewRotation` 这个函数啥意思,SpringArm 的 `GetTargetRotation` 用到了它

## Input
Expand Down Expand Up @@ -285,14 +255,14 @@ TODO:
* 我理解了,这里的旋转不是说 `Pawn` 的旋转,而是摄像机视角的旋转,可以看到 `RotationInput` 会在 `UpdateRotation` 中被使用,该函数中调用了 `PlayerCameraManager::ProcessViewRotation` 来处理摄像机视角 的旋转
## Delegates

代理的作用和 std::function 差不多,只是前者还有多播之类的额外变体。两者实现的核心技术都是 type erasure。type erasure 的关键难点在于消除对象的类型后(例如通过一块动态分配的内存存储对象),在调用时该如何把对象的类型又拿回来。[How is std::function implemented?](https://stackoverflow.com/questions/18453145/how-is-stdfunction-implemented) 中提供了两种思路,一种是通过父类虚函数+模板子类的方式,另一种是通过模板成员函数的方式。ue 的代理实现使用的是前者的方法
### 单播代理
* 单播代理相关的宏 `DECLARE_DELEGATE` 实际上 typedef 了一下 `class TDelegate<>` 模板,默认情况下,`TDelegate` 的父类是 `class TDelegateBase<>`,而它内部有一个 `DelegateAllocator`,这个 allocator 提供了核心的泛化能力。简而言之,对于不同的 BInd 情况,内部会存储不同的 `DelegateInstance` 类型(创建 `DelegateInstance` 模板类型的函数当然也是一个模板函数)。如何存储不同的类型呢,调用 allocator 分配 `DelegateIntance` 大小的堆内存,然后利用 new 运算符在这块内存上调用 `DelegateInstance` 构造函数即可(每次绑定新代理函数时,调用原来的 `DelegateInstance` 的析构函数)
* 总的说来,`TDelegate` 内部只有来自基类 `TDelegateBase` 的两个成员变量 `DelegateAllocator` 和 `DelegateSize`,前者作为一个分配器,存储了指向堆上 `DelegateInstance` 的指针,后者表示实际分配的大小
* 不同的 `DelegateInstance` 都继承自基类 `IBaseDelegateInstance<FuncType, UserPolicy>`,这个基类提供了 `Execute` 等接口。而调用 `TDelegate``Execute` 等函数时,都是获取到堆上的 `DelegateInstance`(强转为它们的共同基类 `IBaseDelegateInstance` 的指针),调用 `DelegateInstance``Execute` 函数来执行
* 不同的 `DelegateInstance` 都继承自基类 `IBaseDelegateInstance<FuncType, UserPolicy>`,这个基类提供了 `Execute` 等接口。而调用 `TDelegate` 的 `Execute` 等函数时,都是获取到堆上的 `DelegateInstance`(强转为它们的共同基类 `IBaseDelegateInstance` 的指针),调用 `DelegateInstance` 的 `Execute` 函数来执行。这里的 `IBaseDelegateInstance::Execute` 就是实现 type erasure 需要的父类虚函数,而后面提到的 BindRaw 等函数用到的它的子类就是实现 type erasure 需要的模板子类
* 能否将 `TDelegate<>` 泛化得可以绑定任何参数和返回值的函数,而不需要把参数和返回值类型写在类模板参数里面?可以,但我觉得没必要(例如目前 `TDelegate<>` 中的 `Execute` 函数不是一个模板函数,因为参数类型都给在类模板里了,如果希望 `TDelegate<>` 类能够绑定任何函数,那么就需要将 `Execute` 函数变成一个模板函数,参数类型作为模板参数)
Expand Down Expand Up @@ -335,7 +305,8 @@ TODO:
* 动态代理也分单播和多播。
* 与普通代理不同,声明单播的系列宏 `DECLARE_DYNAMIC_DELEGATE` 不是一个 typedef,而是定义了一个新的类,它继承自 `TBaseDynamicDelegate` 类。而 `TBaseDynamicDelegate` 又继承自 `TScriptDelegate`。整个类中唯一的成员变量是 `TScriptDelegate` 中的 `Object` 和 `FunctionName`。前者是一个指向 `UObject` 的弱引用,后者是绑定的函数名。每次调用 `Execute` 函数时,都会根据函数名和 `Object`,利用反射进行查找得到 `UFunction` 进行调用。因此动态代理的执行速度更慢x
* 调用 `BindDynamic` 函数会绑定 `Object` 和 `FunctionName`。实际上 `BindDynamic` 是一个宏,会根据传入的函数来获取函数名(因为我们只需要 `FunctionName`)。这里是一个极其精彩的使用模板进行编译期计算的例子,具体可以查看 `STATIC_FUNCTION_FNAME` 宏
* 类似地,声明多播的系列宏 `DECLARE_DYNAMIC_MULTICAST_DELEGATE` 定义了一个新的类,它继承自 `TBaseDynamicMulticastDelegate`,而该父类又继承自 `TMulticastScriptDelegate`,整个类仅有一个成员变量 `InvocationList`。类似于普通代理的多播,这个 `InvocationList` 是一个 `TScriptDelegate` 数组。每次调用 `AddDynamic` 函数(宏)时,就会利用 `Object``FunctionName` 创建一个 `TBaseDynamicDelegate`,然后加入到 `InvocationList` 中。唯一的区别是,普通多播是通过返回的 Handle 来区分绑定的函数,而动态多播是根据 `Object` 指针和 `FunctionName` 来区分绑定的函数
* 类似地,声明多播的系列宏 `DECLARE_DYNAMIC_MULTICAST_DELEGATE` 定义了一个新的类,它继承自 `TBaseDynamicMulticastDelegate`,而该父类又继承自 `TMulticastScriptDelegate`,整个类仅有一个成员变量 `InvocationList`。类似于普通代理的多播,这个 `InvocationList` 是一个 `TScriptDelegate` 数组。每次调用 `AddDynamic` 函数(宏)时,就会利用 `Object` 和 `FunctionName` 创建一个 `TBaseDynamicDelegate`,然后加入到 `InvocationList` 中。唯一的区别是,普通多播是通过返回的 Handle 来区分绑定的函数,而动态多播是根据 `Object` 指针和 `FunctionName` 来区分绑定的函数
* 当多播的动态代理的 uproperty 包含 BlueprintAssignable 时可以从蓝图中绑定事件,当包含 BlueprintCallable 时可以从蓝图中调用 broadcast。对于单播的动态代理我没看明白要怎么调用比较好。相关的讨论见: [How to setup Dynamic Single Delegate (with RetVal) to make it bindable from Blueprints?](https://forums.unrealengine.com/t/how-to-setup-dynamic-single-delegate-with-retval-to-make-it-bindable-from-blueprints/764150)
* TODO:区分普通代理的 `BindUFunction` 和动态代理(两者都用函数名,通过反射来查找要执行的函数)
* TODO:[UE4 Delegate 实现原理分析](https://zhuanlan.zhihu.com/p/165126317) 中谈到相比于普通代理,动态代理支持序列化,因此能够在蓝图中使用。如何理解这段话,支持序列化和在蓝图中使用有什么必然联系吗?
Expand Down

0 comments on commit c1dc8c1

Please sign in to comment.