記錄一些UE4文檔以外編程相關的底層基礎的概念和使用方法,如GC和Gameplay,各種宏/配置項等說明和使用
備查
文檔:http://api.unrealengine.com/INT/Programming/index.html
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 << ")\n"; } void g(char const* str) { std::cout << "g(" << str << ")\n"; } void h(char const* str) { std::cout << "h(" << str << ")\n"; } // ---------------------------------------------------------------------------- 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 << ")\n"; } void cbs(char const* str) { std::cout << "foo(" << this->d_id << ")::cbs(" << str << ")\n"; } }; // ---------------------------------------------------------------------------- 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; } };