记录一些UE4文档以外编程相关的底层基础的概念和使用方法,如GC和Gameplay,各种宏/配置项等说明和使用
备查
文档:http://api.unrealengine.com/INT/Programming/index.html
Q. UE4 配置Game的默认UGameEngine类:
UGameEngine算是整个Game的入口类了(Editor模式时是UEditorEngine),
最初的Viewport的Layout都是通过GameEngine里进行生成的.
可不使用源码版本的情况下,可通过继承UGameEngine类,修改扩展初始化Game的初始化布局等功能:
step:
往DefaultEngine.ini的[/Script/Engine.Engine]下添加,如:
+GameEngine=/Script/BJShowEx.QGameEngine
Q. UE4 第三人称(SpringArm控制下)设置初始视口位置 :
Q. UE4的委托和事件(Delegate):
记一下,开发方便Copy.
吐槽:UE4 的Delegate得定义宏又臭又长又多,C#的委托和事件不知道比UE4高到哪里去了!(UE5请务必砍掉从新实现!)
快速索引:委托文档:https://docs.unrealengine.com/en-US/Programming/UnrealArchitecture/Delegates/index.html
概念:
从设计模式上来说 多播Delegate就是 观察者模式得实现,只不过C#集成到了语言特性上,刚好C#(QT single-slot)得方式 被UE4借鉴到了引擎里
Q0:C++是怎么去实现委托的?
链接:C++实现的类C# Delegate 参考: https://stackoverflow.com/questions/23973914/c-like-delegates-in-c
实现委托的代码:
/* 代码来着上述stackoverflow */ #include <algorithm> #include <iostream> #include <memory> #include <utility> #include <vector> template <typename Signature> struct delegate; template <typename... Args> struct delegate<void(Args...)> { struct base { virtual ~base() {} virtual bool do_cmp(base* other) = 0; virtual void do_call(Args... args) = 0; }; template <typename T> struct call : base { T d_callback; template <typename S> call(S&& callback) : d_callback(std::forward<S>(callback)) { } bool do_cmp(base* other) { call<T>* tmp = dynamic_cast<call<T>*>(other); return tmp && this->d_callback == tmp->d_callback; } void do_call(Args... args) { return this->d_callback(std::forward<Args>(args)...); } }; std::vector<std::unique_ptr<base>> d_callbacks; delegate(delegate const&) = delete; void operator=(delegate const&) = delete; public: delegate() { } template <typename T> delegate& operator+= (T&& callback) { this->d_callbacks.emplace_back(new call<T>(std::forward<T>(callback))); return *this; } template <typename T> delegate& operator-= (T&& callback) { call<T> tmp(std::forward<T>(callback)); auto it = std::remove_if(this->d_callbacks.begin(), this->d_callbacks.end(), [&](std::unique_ptr<base>& other) { return tmp.do_cmp(other.get()); }); this->d_callbacks.erase(it, this->d_callbacks.end()); return *this; } void operator()(Args... args) { for (auto& callback : this->d_callbacks) { callback->do_call(args...); } } }; // ---------------------------------------------------------------------------- template <typename RC, typename Class, typename... Args> class member_call { Class* d_object; RC(Class::* d_member)(Args...); public: member_call(Class* object, RC(Class::* member)(Args...)) : d_object(object) , d_member(member) { } RC operator()(Args... args) { return (d_object->*d_member)(std::forward<Args>(args)...); } bool operator== (member_call const& other) const { return (this->d_object == other.d_object) && (this->d_member == other.d_member); } bool operator!= (member_call const& other) const { return !(*this == other); } }; /** * @prarms RC 返回类型, Class **/ template <typename RC, typename Class, typename... Args> member_call<RC, Class, Args...> mem_call(Class& object, RC(Class::* member)(Args...)) { return member_call<RC, Class, Args...>(&object, member); } // ---------------------------------------------------------------------------- void f(char const* str) { std::cout << "f(" << str << ") "; } void g(char const* str) { std::cout << "g(" << str << ") "; } void h(char const* str) { std::cout << "h(" << str << ") "; } // ---------------------------------------------------------------------------- struct foo { int d_id; explicit foo(int id) : d_id(id) { } void bar(char const* str) { std::cout << "foo(" << this->d_id << ")::bar(" << str << ") "; } void cbs(char const* str) { std::cout << "foo(" << this->d_id << ")::cbs(" << str << ") "; } }; // ---------------------------------------------------------------------------- int main() { delegate<void(char const*)> d0; foo f0(0); foo f1(1); d0 += f; d0 += g; d0 += g; d0 += h; d0 += mem_call(f0, &foo::bar); d0 += mem_call(f0, &foo::cbs); d0 += mem_call(f1, &foo::bar); d0 += mem_call(f1, &foo::cbs); d0("first call"); d0 -= g; d0 -= mem_call(f0, &foo::cbs); d0 -= mem_call(f1, &foo::bar); d0("second call"); }
C++触发时调用的假设:
#include <iostream> class DemonstrateClass { public: int a = 10; //绑定函数 void Func( std::string a) { std::cout << "hi : [ "<< a << " ] " << std::endl; } }; /** * * Qt或UE4实现绑定成员函数核心调用代码Demo: * **/ int main() { // 假设 pa 为绑定对象 DemonstrateClass* pa = new DemonstrateClass(); // &DemonstrateClass::Func为绑定函数 void(DemonstrateClass::* func_ref)(std::string) = &DemonstrateClass::Func; //委托触发时,实际调用以下代码 (pa->*(func_ref))(" linqing demonstrate "); std::cout << "exam complated.... " << std::endl; }
Q1.什么是单播委托/什么是多播委托/什么是事件:
wait
Q1.什么是动态委托:
wait
Q. UE4的Input全局钩子,全局监听鼠标键盘等外设输入事件:
方式一:
可继承GameViewportClient类实现
例子(全局监听鼠标左键并打印Log):
.h:
UCLASS() class BJ_YourProject_API UGameViewportClient_Demonstrate : public UGameViewportClient { GENERATED_BODY() public: UGameViewportClient_Demonstrate(); public: virtual EMouseCaptureMode CaptureMouseOnClick() override; /* 输入设备的全局钩子函数 */ virtual bool InputKey ( const FInputKeyEventArgs & EventArgs ) override; };
.cpp
UGameViewportClient_Demonstrate::UGameViewportClient_Demonstrate() { } EMouseCaptureMode UGameViewportClient_Demonstrate::CaptureMouseOnClick() { UE_LOG(LogTemp, Log, TEXT("[GVC:] CaptureMouseOnClick")); return Super::CaptureMouseOnClick(); } bool UGameViewportClient_Demonstrate::InputKey(const FInputKeyEventArgs & EventArgs) { if (EKeys::LeftMouseButton == EventArgs.Key) { UE_LOG(LogTemp, Log, TEXT("[GVC:] InputKey is left mouse button")); } return Super::InputKey(EventArgs); }
最后在Editor中的Project Setttings里修改默认的GameViewportClient即可
Q. UE4 关于 C++中使用或设置组件的自定义 Trace Response/Object Type的问题:
UE4关于这块配置是用的枚举映射.
自定义Collision:
打开Config/DefaultEngine.ini
在[/Script/Engine.CollisionProfile]下可以找到:
类似如:
的设置项.其中ECC_GameTraceChannel1就是映射的ECollisionChannel枚举项:
C++例子:
//因为QingResponse映射到ECC_GameTraceChannel1,所以这里修改的是 QingResponse的Collision值 Dynamic_Com->SetCollisionResponseToChannel(ECollisionChannel::ECC_EngineTraceChannel1, ECollisionResponse::ECR_Block);
Q. UE4 Skeletal Mesh模型 个别视角莫名消失隐藏的问题背后原因:
问题:UE4的场景剔除优化渲染算法(可参考DX里的视锥体优化算法)引起的问题./UE4SketalMesh无法正常显示可见
解决:剔除算法在Skeletal Mesh上,用的是Physics Asset 的碰撞资源来做的运算的,把Physics Asset刷正确就OK。(也可以使用UE4 两个 Precomputed Volume处理或关闭遮挡剔除)
Q.UE4 Blueprint(蓝图) Template Function实现(蓝图中的模板函数实现):
Meta文档:https://docs.unrealengine.com/en-US/Programming/UnrealArchitecture/Reference/Metadata/index.html
其实就是加meta标签,只是官方的meta的说明文档标签不够完善o(╥﹏╥)o
具体的可以翻源码里的ObjectMacros.h 头文件查看标签意义.
这里顺便记录一下比较常用的Meta标签:
UFUNCTION:
- DefaultToSelf 指定参数默认值为调用本函数类实例的self(类似于javascript的this,谁调用指向谁)
- HidePin 指定BP里要隐藏成员参数,通常配合DefaultToSelf标签一起食用
解决:
根据蓝图中传入的类返回类的实例.
可以参考SpawnActor的代码实现.
栗子:
note:这是个 BPLibraryFunction类,所有声明的static
UFUNCTION(BlueprintCallable, meta = (DeterminesOutputType = "actorClass")) static class AActor* TestActor(class TSubclassOf<class AActor> actorClass);
Q.UE4中使用dynamic_cast,编译报错问题.
UE4 默认是关闭RTTI的,如果要使用dynamic_cast,需要在build.cs中启用rtti;
using UnrealBuildTool; public class MyAPP : ModuleRules { public MyAPP(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; bUseRTTI = true; bEnableExceptions = true; PublicDependencyModuleNames.AddRange( new string[] { "" }); PrivateDependencyModuleNames.AddRange(new string[] { "" }); } }
Q.BP 蓝图宏 (Macros)和函数(functions)的区别:
记录一下 具体描述
BP下的function不支持delay,AI Move to的等时间Latent型节点的,但是Macro支持.
总体来说编译上function是静态编译,而Macro就是c++的动态宏,是动态编译的.
Q.跨平台相关宏:
如果需要编写平台相关的代码段的话,需要使用宏来约定。
跨平台相关的宏定义都在Platform.h里。
例如:
#if PLATFORM_WINDOWS UE_LOG(LogTemp, Error, TEXT("hi ,windows")); #elif PLATFORM_MAC UE_LOG(LogTemp, Error, TEXT("hi ,MAC")); #endif
Q.关于C++使用LoadClass加载资源的路径问题:
一般平常加载Texture或mesh时,都是采用例如TEXT("/Game/Blueprints/MyMesh.MyMesh")的路径形式,
但是如果是用LoadClass加载BP类或UMG类的话需要在路径后面加上_C的后缀:例如
LoadClass<UUserWidget>(NULL,TEXT("/Game/Blueprints/BaseWgt.BaseWgt_C"));
文档里没有详细介绍,但是这问题几乎会困扰所有刚使用LoadClass这个函数小伙伴。
Q.保存游戏/应用信息到本地:
USaveGame的应用:
发现UE4已经写了相关文档,之前没有找到:
http://api.unrealengine.com/INT/Gameplay/SaveGame/index.html
Q.Struct 和Emum 类型的定义方式 :
UE4 基础,但是不经常用总是忘记,做个笔记马一下:
Note:虽然USTRUCT可以定义函数,但是不能加UFUNCTION 标签喔
结构体:
USTRUCT(BlueprintType) struct FData_PageInfo { GENERATED_USTRUCT_BODY() FData_PageInfo(); FData_PageInfo(UChildActorComponent *parent_Com); void reInit(); UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "struct_Variants") class UChildActorComponent *Parent_Com; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "struct_Variants") class ACpp_PagerActor *PageActor; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "struct_Variants") class UChildActorComponent *FrontCanvas_Com; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "struct_Variants") class UChildActorComponent *BackCanvas_Com; };
枚举:
UENUM(BlueprintType) enum class GroupActorState : uint8 { EM_Expand UMETA(DisplayName = "Expand"), EM_Merge UMETA(DisplayName = "Merge") };
Q.C++ DatatableRow的定义方式:(经常忘,留底Copy用):
#include "Runtime/Engine/Classes/Engine/DataTable.h" #include "DTOData_AssetItem.generated.h" USTRUCT(BlueprintType) struct FDTOData_AssetItem : public FTableRowBase { GENERATED_USTRUCT_BODY() public: FDTOData_AssetItem() { } public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FDTO_AssetItem") int Id = 0; };
Q.C++ Interface 的定义方式 :(经常忘,留底Copy用)
UE4的interface 分蓝图可继承和不可集成两种:
文档:http://api.unrealengine.com/CHN/Programming/UnrealArchitecture/Reference/Interfaces/
可继承:
#include "CoreMinimal.h" #include "Demonstrate.generated.h" /** * */ UINTERFACE(BlueprintType) class BJ_3DDESIGNAPP_API UDemonstrate : public UInterface { GENERATED_BODY() }; class BJ_3DDESIGNAPP_API IDemonstrate { GENERATED_BODY() public: UFUNCTION(BlueprintImplementableEvent, Category = "Trigger Reaction") void Hi(); };
不可继承:
UINTERFACE(meta = (CannotImplementInterfaceInBlueprint) ) class BJ_3DDESIGNAPP_API UDemonstrateGameMode : public UInterface { GENERATED_BODY() }; class BJ_3DDESIGNAPP_API IDemonstrateGameMode { GENERATED_BODY() public : UFUNCTION(BlueprintCallable, Category = "IDemonstrateGameMode") virtual class ACppCharacter_Demonstrate * GetCurrentDemonstrateActor() const { return nullptr; } };