ヒストリアさんの記事に便乗して補足記事でも。
(いずれ書こうと思っていたネタだったが先を越されてしまった・・・)
C++ での実装箇所
ヒストリアさんの記事でも書いてあるが、公開プロパティ判定は FSequencerObjectChangeListener::FindPropertySetter
で処理されている。
その関数をより具体的に見ていく(ソースコードは UE4.18.3 時点)
const FOnAnimatablePropertyChanged* FSequencerObjectChangeListener::FindPropertySetter(const UStruct& PropertyStructure, FAnimatedPropertyKey PropertyKey, const UProperty& Property) const { const FOnAnimatablePropertyChanged* DelegatePtr = PropertyChangedEventMap.Find(PropertyKey); if (DelegatePtr != nullptr) { FString PropertyVarName = Property.GetName(); // If this is a bool property, strip off the 'b' so that the "Set" functions to be // found are, for example, "SetHidden" instead of "SetbHidden" if (PropertyKey.PropertyTypeName == "BoolProperty") { PropertyVarName.RemoveFromStart("b", ESearchCase::CaseSensitive); } static const FString Set(TEXT("Set")); const FString FunctionString = Set + PropertyVarName; FName FunctionName = FName(*FunctionString); static const FName DeprecatedFunctionName(TEXT("DeprecatedFunction")); UFunction* Function = nullptr; if (const UClass* Class = Cast<const UClass>(&PropertyStructure)) { Function = Class->FindFunctionByName(FunctionName); } bool bFoundValidFunction = false; if (Function && !Function->HasMetaData(DeprecatedFunctionName)) { bFoundValidFunction = true; } bool bFoundValidInterp = false; bool bFoundEditDefaultsOnly = false; bool bFoundEdit = false; if (Property.HasAnyPropertyFlags(CPF_Interp)) { bFoundValidInterp = true; } // @TODO: should we early out of our property path iteration if we find an "edit defaults only" property? if (Property.HasAnyPropertyFlags(CPF_DisableEditOnInstance)) { bFoundEditDefaultsOnly = true; } if (Property.HasAnyPropertyFlags(CPF_Edit)) { bFoundEdit = true; } const bool bIsHiddenFunction = IsHiddenFunction(PropertyStructure, FAnimatedPropertyKey::FromProperty(&Property), Property.GetName()); // Valid if there's a setter function and the property is editable. Also valid if there's an interp keyword. if (((bFoundValidFunction && bFoundEdit && !bFoundEditDefaultsOnly) || bFoundValidInterp) && !bIsHiddenFunction) { return DelegatePtr; } } return nullptr; }
このコードから以下のことが読み取れる。
175-200 行目: FoundValidFunction
「”Set” + プロパティ名」がついた関数をそのプロパティの Setter とみなし、その対象となる UFUNCTION 関数が存在するかどうか。
- このとき bool プロパティのみ、頭文字 “b” があったらそれをカットしたプロパティ名で検索される
- (UE4 では bool プロパティは変数名に接頭語 “b” をつけることが通例)
- また、メタに “DeprecatedFunction” がついている場合、つまり非推奨関数も検索対象外とされる
206-209 行目: FoundValidInterp
プロパティに CPF_Interp フラグがついているかどうか。
- つまり、UPROPERTY 指定子 Interp が書かれていること
- ブループリントでは、Expose to Cinematics にチェックが入っていること
212-214 行目: FoundEditDefaultOnly
プロパティに CPF_DisableEditOnInstance フラグがついているかどうか。
- つまり、UPROPERTY 指定子 EditDefaultsOnly もしくは VisibleDefaultsOnly が書かれていること
また、CPF 自体はただの #define 文であり、その宣言は ObjectMacros.h に全て記載されている。
216-219 行目: FoundEdit
プロパティに CPF_Edit フラグがついているかどうか。
- つまり、UPROPERTY 指定子に以下のいずれかが書かれていること
- EditAnywhere
- EditInstanceOnly
- EditDefaultsOnly
- VisibleAnywhere
- VisibleInstanceOnly
- VisibleDefaultsOnly
221 行目: IsHiddenFunction
IsHiddenFunction
の具体的な実装はこんな感じ。
(内部で呼ばれてる GetFunctionName
がさっきと同じコード書いていてすこぶるダサい・・・)
FName GetFunctionName(FAnimatedPropertyKey PropertyKey, const FString& InPropertyVarName) { FString PropertyVarName = InPropertyVarName; // If this is a bool property, strip off the 'b' so that the "Set" functions to be // found are, for example, "SetHidden" instead of "SetbHidden" if (PropertyKey.PropertyTypeName == "BoolProperty") { PropertyVarName.RemoveFromStart("b", ESearchCase::CaseSensitive); } static const FString Set(TEXT("Set")); const FString FunctionString = Set + PropertyVarName; FName FunctionName = FName(*FunctionString); return FunctionName; } bool IsHiddenFunction(const UStruct& PropertyStructure, FAnimatedPropertyKey PropertyKey, const FString& InPropertyVarName) { FName FunctionName = GetFunctionName(PropertyKey, InPropertyVarName); static const FName HideFunctionsName(TEXT("HideFunctions")); bool bIsHiddenFunction = false; TArray<FString> HideFunctions; if (const UClass* Class = Cast<const UClass>(&PropertyStructure)) { Class->GetHideFunctions(HideFunctions); } return HideFunctions.Contains(FunctionName.ToString()); }
そのプロパティの Setter が、それを所有するクラスの UCLASS の HideFunctions に指定されているかどうかを確認している。
公開可能プロパティの条件
以上のフラグを使用した条件式
if (((bFoundValidFunction && bFoundEdit && !bFoundEditDefaultsOnly) || bFoundValidInterp) && !bIsHiddenFunction)
より、以下の公開可能プロパティの条件が導き出される。
このとき、以下の 2 つの条件を両方満たすとき、そのプロパティはシーケンサーで公開される。
【条件1】 以下の a, b のうちどちらかを満たす
a. 非推奨関数でない Setter が存在し、以下のいずれかの UPROPERTY 指定子を持つこと
EditAnywhere, EditInstanceOnly, VisibleAnywhere, VisibleInstanceOnly
b. UPROPERTY 指定子 Interp がついていること
【条件2】対象の Setter が存在する場合、そのプロパティを所有するクラスが HideFunctions に指定していないこと
具体例
UCLASS(HideFunctions=(SetEditAnywhere3, SetEditInstanceOnly3)) class ATestSequencerActor : public AActor { GENERATED_BODY() public: // CPF_Edit だが Setter がないので NG UPROPERTY(EditAnywhere) bool bEditAnywhere = false; // CPF_Edit だが Setter がないので NG UPROPERTY(EditInstanceOnly) bool bEditInstanceOnly = false; // CPF_Edit だが Setter がないので NG UPROPERTY(VisibleAnywhere) bool bVisibleAnywhere = false; // CPF_Edit だが Setter がないので NG UPROPERTY(VisibleInstanceOnly) bool bVisibleInstanceOnly = false; //// // CPF_Edit で Setter もあるので OK UPROPERTY(EditAnywhere) bool bEditAnywhere2 = false; UFUNCTION(BlueprintCallable) void SetEditAnywhere2(bool value) { bEditAnywhere2 = value; } // CPF_Edit で Setter もあるので OK UPROPERTY(EditInstanceOnly) bool bEditInstanceOnly2 = false; UFUNCTION(BlueprintCallable) void SetEditInstanceOnly2(bool value) { bEditInstanceOnly2 = value; } // CPF_Edit で Setter もあるので OK UPROPERTY(VisibleAnywhere) bool bVisibleAnywhere2 = false; UFUNCTION(BlueprintCallable) void SetVisibleAnywhere2(bool value) { bVisibleAnywhere2 = value; } // CPF_Edit で Setter もあるので OK UPROPERTY(VisibleInstanceOnly) bool bVisibleInstanceOnly2 = false; UFUNCTION(BlueprintCallable) void SetVisibleInstanceOnly2(bool value) { bVisibleInstanceOnly2 = value; } //// // CPF_Edit だが Setter が HideFunction なので NG UPROPERTY(EditAnywhere) bool bEditAnywhere3 = false; UFUNCTION(BlueprintCallable) void SetEditAnywhere3(bool value) { bEditAnywhere3 = value; } // CPF_Edit だが Setter が HideFunction なので NG UPROPERTY(EditInstanceOnly) bool bEditInstanceOnly3 = false; UFUNCTION(BlueprintCallable) void SetEditInstanceOnly3(bool value) { bEditInstanceOnly3 = value; } // CPF_Edit だが Setter が非推奨関数なので NG UPROPERTY(VisibleAnywhere) bool bVisibleAnywhere3 = false; UFUNCTION(BlueprintCallable, meta = (DeprecatedFunction)) void SetVisibleAnywhere3(bool value) { bVisibleAnywhere3 = value; } // CPF_Edit だが Setter が非推奨関数なので NG UPROPERTY(VisibleInstanceOnly) bool bVisibleInstanceOnly3 = false; UFUNCTION(BlueprintCallable, meta = (DeprecatedFunction)) void SetVisibleInstanceOnly3(bool value) { bVisibleInstanceOnly3 = value; } //// // CPF_Interp なので OK UPROPERTY(Interp) bool bInterp = false; //// // CPF_Interp でも CPF_Edit でもないので NG UPROPERTY(BlueprintReadOnly) bool bBlueprintReadOnly = false; // CPF_Interp なので OK UPROPERTY(BlueprintReadOnly, Interp) bool bBlueprintReadOnly_Interp = false; // CPF_Edit だが Setter がないので NG UPROPERTY(BlueprintReadOnly, EditAnywhere) bool bBlueprintReadOnly_EditAnywhere = false; // CPF_Edit で Setter もあるので OK UPROPERTY(BlueprintReadOnly, EditAnywhere) bool bBlueprintReadOnly_EditAnywhere2 = false; UFUNCTION(BlueprintCallable) void SetBlueprintReadOnly_EditAnywhere2(bool value) { bBlueprintReadOnly_EditAnywhere2 = value; } };
実行結果はこんな感じ。
最後の方に書いたように、 BlueprintReadOnly, Interp の組み合わせの場合、ブループリントでは Getter しか提供されていないが、シーケンサー上では上書き可能、という特殊なプロパティも設定ができる。
シーケンサーからの更新だけを保証した上で、ブループリントの内部処理を変更したい場合などではとても有用だと思う。