UE4.9——分享C++寫blueprint block的方法

发表于2015-11-17
评论0 2.4k浏览

首先感谢社区的damody!以下正文:


  • 任何c++函數你要用在BP(blueprint),你一定要寫在class裡面。
  • 這個class一定要有UCLASS()宣告
  • 這個class一定要有GENERATED_BODY()巨集宣告
  • 你要在BP裡面呼叫的函數一定要在函數宣告前宣告UFUNCTION
    而這個函數一定要在Public:之下
    像是這樣 UFUNCTION(BlueprintCallable, Category = "OpenCV|ImageProcess")
    BlueprintCallable表示你可以控制他什麼時候執行
    BlueprintPure你不能確定他在什麼時候執行,但他就是會執行,而且你不能用他來修改任何狀態
  • 承上,如果你的函數是不需要 class instance 的函數你還是要宣告在class裡,然後定義成static
    純函數可以用 BlueprintCallable 也可以用 BlueprintPure
  • 在給BP呼叫的函數的參數部份,只要是class 只能用有UCLASS()宣告的class而且一定要用指標
  • 參數的部份不能用int, short, char, double 你只能用 int8, int16, int32, uint 16, float來取代他們
  • 所有的函數不能用一樣的名字,你可以使用
    UFUNCTION(BlueprintCallable, meta = (DisplayName = "SmoothingEach5 (ueLine array)"), Category = "Line")
    DisplayName 這個屬性來修正在ue4裡面顯示的名字
    Category 可以用 | 來做子集
    1. UFUNCTION(BlueprintCallable, Category = "OpenCV|CurveExtraction")
    2.         bool SetPicture(UueMat* umat);
    复制代码


  • 陣列的部份你只能用TArray<YourClass*> 來傳入,也不能用 typedef 來定義別名
    你不能用std::vector
    1. UFUNCTION(BlueprintCallable, meta = (FriendlyName = "SmoothingEach5 (ueLine array)"), Category = "Line")
    2.         static TArray<UueLine*>   SmoothingEach5_Array(const TArray<UueLine*>& cvp, float centroidRadio = 1.0, int32 repeat = 1);
    复制代码
  • 當你想做出一個大家都能存取的變數時,請在變數宣告前面加上
    "UPROPERTY(BlueprintReadWrite)" or  "UPROPERTY(BlueprintReadOnly)"
    class應該要使用指標,使用NewObject<Class>() 來new。
    1. UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mat")
    2.         UTexture2D* MyTexture2D;
    复制代码
  • 當你想要輸出多個變數時,在參數宣告使用非const 的 reference 變數來輸出。
    1. UFUNCTION(BlueprintCallable, Category = "Test")
    2.         static bool TestOut2(TArray<UTexture2D*>& t1, TArray<UTexture2D*>& t2);
    复制代码
  • 當你想要輸出多個執行分支時,參考以下的方法
     
    1. UENUM(BlueprintType)
    2. enum class EMyEnum : uint8
    3. {
    4.         BranchA,
    5.         BranchB
    6. };
    复制代码
    在ue4.8版中更新後
    TEnumAsByte<EMyEnum>& 不在需要,要改成 EMyEnum&
    1. UFUNCTION(BlueprintCallable, Category = "Stuff", Meta = (ExpandEnumAsExecs = "Branches"))
    2.         void DoSomeBranch(int32 SomeInput, EMyEnum& Branches);
    复制代码
    1. void AMyActor::DoSomeBranch(int32 SomeInput, EMyEnum& Branches)
    2. {
    3.         if (SomeInput == 1)
    4.         {
    5.                 Branches = EMyEnum::BranchA;
    6.         }
    7.         else
    8.         {
    9.                 Branches = EMyEnum::BranchB;
    10.         }
    11. }
    复制代码

  • BlueprintNativeEvent 新增內建的事件,但實作由C++來實作
    一般用在Actor裡
    1. UFUNCTION(BlueprintNativeEvent, Category = "Switch Functions")
    2.         void OnOverlapBegin(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
    复制代码
    值的注意的是,實作的函數名是 XXXX_Implementation 在執行時會自動呼叫
    1. void AMyActor::OnOverlapBegin_Implementation(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
    2. {
    3.         if (OtherActor && (OtherActor != this) && OtherComp)
    4.         {
    5.                 ToggleLight();
    6.         }
    7. }
    复制代码


  • BlueprintImplementableEvent 在c++中只寫宣告,給blueprint實作的事件
    一般用在Actor裡,你的Function 會多一個可以Overide
    1. UFUNCTION(BlueprintImplementableEvent, Category = Tracking)
    2.         void IsActorInJunction(AActor* Actor, bool& Result);
    复制代码


     

     

     

    比如我在Tick 中呼叫了這個函數
    1. // Called every frame
    2. void AMyActor::Tick( float DeltaTime )
    3. {
    4.         Super::Tick( DeltaTime );
    5.         bool res;
    6.         IsActorInJunction(this, res);
    7. }
    复制代码
    看的出來就一般的C++函數呼叫,但是我在code中沒有寫他的實作

    在UE4裡面我也只拉了一個print string hello當實作
     
    實行時就出來了
  • 變數開關的設定
    1.         UPROPERTY( Category=Location, EditAnywhere )
    2.         uint32 bUseOffset : 1;

    3.         UPROPERTY(Category = Location, EditAnywhere, meta = (EditCondition = "bUseOffset"))
    4.         FVector Offset;
    复制代码
  • meta=(MakeEditWidget)
  • 當你想要在editor改變數時不可編輯的變數也更新就實作下面的函數

    實作PostEditChangeProperty函數
    virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
    這個函數將在每次變數在editor內被修改時呼叫
    函數最後要加
    Super:: PostEditChangeProperty(PropertyChangedEvent);
    可以用下面的code來判斷是哪個成員被改動
    1. FName PropertyName = (PropertyChangedEvent.Property != nullptr) ? PropertyChangedEvent.Property->GetFName() : NAME_None;

    2.     if((PropertyName == GET_MEMBER_NAME_CHECKED(AHeroCharacter, Skill_LevelCDs)))
    3.     {
    4. }
    复制代码

    TArray改動就實作PostEditChangeChainProperty函數
    virtual void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override;
    可以用下面的code來判斷是哪個成員被改動
    1. const FName TailPropName = PropertyChangedEvent.PropertyChain.GetTail()->GetValue()->GetFName();
    2.         static FName Mobility_NAME(TEXT("CDs"));
    3.         if (TailPropName == Mobility_NAME)
    4.         {
    5. }
    复制代码
    也可以用下面方法得到改動的 index
    1. int32 index = PropertyChangedEvent.GetArrayIndex(TEXT("Meshes"));
    复制代码




    實作PostInitProperties函數
    virtual void PostInitProperties() override;
    這個函數將在每次變數在初始化時呼叫
    函數最前面要加
    Super:: PostInitProperties();
    參考:
    https://answers.unrealengine.com ... pdates-in-code.html
    1. //for regular properties:
    2. void ACustomClass::PostEditChangeProperty(struct FPropertyChangedEvent& e)
    3. {
    4.      FName PropertyName = (e.Property != NULL) ? e.Property->GetFName() : NAME_None;
    5.      if (PropertyName == GET_MEMBER_NAME_CHECKED(UCustomClass, PropertyName))
    6.      {
    7.          //various uproperty tricks, see link
    8.      }
    9.      Super::PostEditChangeProperty(e);
    10. }

    11. //for TArrays:
    12. void ACustomClass::PostEditChangeChainProperty(struct FPropertyChangedChainEvent& e)
    13. {
    14.      int32 index = e.GetArrayIndex(TEXT("Meshes")); //checks skipped
    15.      UStaticMesh *mesh = Meshes[index]; //changed mesh
    16.      Super::PostEditChangeChainProperty(e);
    17. }
    复制代码

  • 有人反應說怎麼動態呼叫BP的函數
    而且是沒在C++宣告的
    查了一下終於查了到
    就是使用 CallFunctionByNameWithArguments 這個函數
    我只知道傳基本型態 int32 float FString 之類的方法
    其它參數怎麼傳請看 Engine/Source/Runtime/CoreUObject/Private/UObject/ScriptCore.cpp
    我是從這裡學來的 https://answers.unrealengine.com ... -c.html?sort=oldest
    主要就是 你的函數要格式化成 functionname parameter1 parameter2 ...
    第一個是函數名 第二個是參數一表示成字串的方式 以此類推

    這邊我有做了一個小測試 證明是可行的

    1. void XXXXX::TestCallFunctionByName(FString str)
    2. {
    3.         FOutputDeviceNull ar;
    4.         this->CallFunctionByNameWithArguments(*str, ar, NULL, true);
    5. }
    复制代码


     
  • Component可以加事件
    ex.
    要在begin play加,在建構式加會沒反應
    Component->OnClicked.AddDynamic(this, &AYourActor::OnMouseClicked);
    傳入參數請參考標頭檔
  • 建構式可以加入各種Component在該actor底下
    首先使用 GENERATED_UCLASS_BODY()
    然後在cpp實作建構式
    這是將預設的subobject移除的建構式
    1. AFightCharacter::AFightCharacter(const FObjectInitializer& ObjectInitializer)
    2.     : Super(ObjectInitializer.DoNotCreateDefaultSubobject(ACharacter::MeshComponentName))
    3. {
    4. }
    复制代码
    這是將預設的建構式
    1. AHeroCharacter::AHeroCharacter(const FObjectInitializer& ObjectInitializer)
    2.         : Super(FObjectInitializer::Get())
    3. {
    4. }
    复制代码
    之後可以用
    以Decal 為例,宣告在class
    1. UPROPERTY(Category = Character, VisibleAnywhere, BlueprintReadOnly)
    2.         UDecalComponent* SelectionDecal;
    复制代码
    建構式可以設定像這樣
    1. AHeroCharacter::AHeroCharacter(const FObjectInitializer& ObjectInitializer)
    2.         : Super(FObjectInitializer::Get())
    3. {
    4.         PrimaryActorTick.bCanEverTick = true;
    5.         SelectionDecal = ObjectInitializer.CreateDefaultSubobject<UDecalComponent>(this, TEXT("SelectionDecal0"));
    6.         SelectionDecal->SetWorldLocation(FVector(0, 0, -90));
    7.         // FRotator = rotation Y Z X
    8.         SelectionDecal->SetWorldRotation(FQuat(FRotator(90, 0, 0)));
    9.         SelectionDecal->SetWorldScale3D(FVector(10, 50, 50));
    10.         SelectionDecal->AttachParent = GetCapsuleComponent();
    11. }
    复制代码
    初始化後設定位置、旋轉、縮放,並Attach 不然會在世界座標 (0, 0, 0) 的位置
  • 如何在任何地方拿到HUD跟Controller
    1. AMyHUD * hud = Cast<AMyHUD>(UGameplayStatics::GetPlayerController(this, 0)->GetHUD());
    复制代码
    1. GetWorld()->GetFirstPlayerController()
    复制代码

  • 旋轉常要你填FQuat
    可以用FRotator去初始化
    三個變數等於editor裡 Y Z X方向的旋轉
    1. SelectionDecal->SetWorldRotation(FQuat(FRotator(90, 0, 0)));
    复制代码

  • 網路連線
    首先是函數成員
    所有要同步的UPROPERTY要加 Replicated
    像這樣
    1. UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hero", Replicated)
    2.     FVector ThrowDestination;
    复制代码
    並加入同步代碼
    1. void AYourActor::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
    2. {
    3.             Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    4.      DOREPLIFETIME(AHeroCharacter, ThrowDestination);
    5. }
    复制代码
    範例二
    1. UPROPERTY(Transient, ReplicatedUsing = OnRep_PosChange)
    2.         FVector CurrentPosition;
    复制代码
    Transient 是不能序列化的意思。
    ReplicatedUsing 是同步請用這個函數
    1. UFUNCTION()
    2.         void OnRep_PosChange();
    复制代码
    類似這樣
    1. void  AYourActor::OnRep_PosChange()
    2. {
    3.         SetActorLocation(CurrentPosition);
    4. }
    复制代码
    再來是函數
    有分Server在Call  的,這個函數會同步到所有Client
    1. UFUNCTION(Server, Reliable, WithValidation)
    2.         void ServerSetLocation(FVector location);
    复制代码
    只有Client在Call 的,這個函數在Server上不會執行
    1. UFUNCTION(Client, Reliable, WithValidation)
    2.         void ClientSetLocation(FVector location);
    复制代码
    但你的函數就會分成兩個實作
    一個加 _Validate 回傳 bool
    一個加 _Implementation 不回傳
    1. bool AYourActor::ServerSetLocation_Validate(FVector location)
    2. {
    3.     return true;
    4. }

    5. void AYourActor::ServerSetLocation_Implementation(FVector location)
    6. {
    7.         CurrentPosition = location;
    8.         SetActorLocation(location);
    9. }
    复制代码
    在這些 Actor裡面可以用
    1. if (Role == ROLE_Authority)
    复制代码

    1. if (GEngine->GetNetMode(GetWorld()) == ENetMode::NM_Client)
    复制代码
    來判斷是不是在Server還是Client

    最重要的要在Controller裡面將大家的Owener設成自己
    這個動作在Server會做 Client不會做,這樣就可以同步了。
    1. for(TActorIterator<AActor> ActorItr(GetWorld()); ActorItr; ++ActorItr)
    2.     {
    3.             if(*ActorItr != this)
    4.      {
    5.           ActorItr->SetOwner(this);
    6.      }
    7.     }
    复制代码

  • 繼承UObject的TArray可以宣告
    TArray<UYourClass>
    繼承Actor的TArray可以宣告
        TArray<TSubclassOf<AYourClass>> Skill_HintActor;
    在需要用的時候
    AYourClass* CurrentSkillHint = GetWorld()->SpawnActor<ASkillHintActor>(Skill_HintActor[index]);
    像這樣Spawn出來
  • 剩下各種規則細節詳見官網
    可編輯           唯讀
    EditAnywhere, VisibleAnywhere  在editor
    BlueprintReadWrite, BlueprintReadOnly  在blueprint連連看

    BlueprintAssignable 看不太懂要使用的場合,好像也是要給Actor用的
    就給有用過的人分享經驗吧





2015-04-04 20 40 43.png (24.37 KB, 下载次数: 3)

2015-04-04 20 40 43.png

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引