332 lines
14 KiB
Markdown
Raw Normal View History

2024-10-12 17:19:46 +08:00
# BlueprintProtected
- **功能描述:** 指定该函数或属性只能在本类以及子类中被调用或读写类似C++中的protected作用域限制。不可在别的蓝图类里访问。
- **使用位置:** UFUNCTION, UPROPERTY
- **引擎模块:** Blueprint
- **元数据类型:** bool
- **关联项:** [BlueprintPrivate](../BlueprintPrivate/BlueprintPrivate.md), [AllowPrivateAccess](../AllowPrivateAccess/AllowPrivateAccess.md)
- **常用程度:** ★★★
作用在函数上:
标记该函数只能在本类以及子类中被调用类似C++中的protected函数的作用域限制。不可在别的蓝图类里调用。
作用在属性上时,标明该属性只能在本类或派生类里进行读写,但不能在别的蓝图类里访问。
指定该函数或属性只能在本类以及子类中被调用或读写类似C++中的protected函数的作用域限制。不可在别的蓝图类里访问。
## 测试代码:
```cpp
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API AMyFunction_Access :public AActor
{
public:
GENERATED_BODY()
public:
//(BlueprintProtected = true, ModuleRelativePath = Function/MyFunction_Access.h)
//FUNC_Final | FUNC_Native | FUNC_Public | FUNC_BlueprintCallable
UFUNCTION(BlueprintCallable, meta = (BlueprintProtected = "true"))
void MyNative_HasProtected() {}
//(BlueprintPrivate = true, ModuleRelativePath = Function/MyFunction_Access.h)
//FUNC_Final | FUNC_Native | FUNC_Public | FUNC_BlueprintCallable
UFUNCTION(BlueprintCallable, meta = (BlueprintPrivate = "true"))
void MyNative_HasPrivate() {}
public:
//FUNC_Final | FUNC_Native | FUNC_Public | FUNC_BlueprintCallable
UFUNCTION(BlueprintCallable)
void MyNative_NativePublic() {}
protected:
//FUNC_Final | FUNC_Native | FUNC_Protected | FUNC_BlueprintCallable
UFUNCTION(BlueprintCallable)
void MyNative_NativeProtected() {}
private:
//FUNC_Final | FUNC_Native | FUNC_Private | FUNC_BlueprintCallable
UFUNCTION(BlueprintCallable)
void MyNative_NativePrivate() {}
};
```
## 测试效果:
蓝图中的子类BPA_Access_Base继承自AMyFunction_Access )效果:
可见BlueprintProtected可以被子类调用但是BlueprintPrivate只能在本类C++类中定义的只能在C++中调用蓝图中定义的只能在蓝图本类中调用。而在C++中用protected或private修饰的函数会相应的增加FUNC_Protected和FUNC_Private但是实际上并不会发生作用。因为机制的设计目的就是如此详见后文解释
而在BPA_Access_Base中直接定义的MyBPProtected和MyBPPrivate通过在函数细节面板上直接设置AccessSpecifier可以在本类都可以调用但是MyBPPrivate在更加的蓝图子类无法被调用。
![Untitled](Untitled.png)
蓝图中的子类BPA_Access_Child继承自BPA_Access_Base效果
可见MyNative函数的访问一样。而MyBPPrivate则不能被调用了这和我们预想的规则一样。
![Untitled](Untitled%201.png)
而在外部类中(BPA_Access_Other继承自Actor)通过BPA_Access_Base或BPA_Access_Child对象实例访问函数的时候发现带有BlueprintProtected和BlueprintPrivate都不能被调用。BP的函数也只有AccessSpecifier为默认Public的可以调用。这个规则也很符合预期。
![Untitled](Untitled%202.png)
## 原理:
在蓝图右键上是否可以选择该函数的过滤逻辑:
如果是static函数则总是可以。否则必须没有BlueprintProtected或BlueprintPrivate才可以是Public可以被选择出来的。
如果是Private则外部类必须是定义的类本身。
如果是Protected则外部类只需要是定义的类或子类。
```cpp
static bool BlueprintActionFilterImpl::IsFieldInaccessible(FBlueprintActionFilter const& Filter, FBlueprintActionInfo& BlueprintAction)
{
bool const bIsProtected = Field.HasMetaData(FBlueprintMetadata::MD_Protected);
bool const bIsPrivate = Field.HasMetaData(FBlueprintMetadata::MD_Private);
bool const bIsPublic = !bIsPrivate && !bIsProtected;
if( !bIsPublic )
{
UClass const* ActionOwner = BlueprintAction.GetOwnerClass();
for (UBlueprint const* Blueprint : FilterContext.Blueprints)
{
UClass const* BpClass = GetAuthoritativeBlueprintClass(Blueprint);
if (!ensureMsgf(BpClass != nullptr
, TEXT("Unable to resolve IsFieldInaccessible() - Blueprint (%s) missing an authoratative class (skel: %s, generated: %s, parent: %s)")
, *Blueprint->GetName()
, Blueprint->SkeletonGeneratedClass ? *Blueprint->SkeletonGeneratedClass->GetName() : TEXT("[NULL]")
, Blueprint->GeneratedClass ? *Blueprint->GeneratedClass->GetName() : TEXT("[NULL]")
, Blueprint->ParentClass ? *Blueprint->ParentClass->GetName() : TEXT("[NULL]")))
{
continue;
}
// private functions are only accessible from the class they belong to
if (bIsPrivate && !IsClassOfType(BpClass, ActionOwner, /*bNeedsExactMatch =*/true))
{
bIsFilteredOut = true;
break;
}
else if (bIsProtected && !IsClassOfType(BpClass, ActionOwner))
{
bIsFilteredOut = true;
break;
}
}
}
}
bool UEdGraphSchema_K2::ClassHasBlueprintAccessibleMembers(const UClass* InClass) const
{
// @TODO Don't show other blueprints yet...
UBlueprint* ClassBlueprint = UBlueprint::GetBlueprintFromClass(InClass);
if (!InClass->HasAnyClassFlags(CLASS_Deprecated | CLASS_NewerVersionExists) && (ClassBlueprint == NULL))
{
// Find functions
for (TFieldIterator<UFunction> FunctionIt(InClass, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt)
{
UFunction* Function = *FunctionIt;
const bool bIsBlueprintProtected = Function->GetBoolMetaData(FBlueprintMetadata::MD_Protected);
const bool bHidden = FObjectEditorUtils::IsFunctionHiddenFromClass(Function, InClass);
if (UEdGraphSchema_K2::CanUserKismetCallFunction(Function) && !bIsBlueprintProtected && !bHidden)
{
return true;
}
}
// Find vars
for (TFieldIterator<FProperty> PropertyIt(InClass, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt)
{
FProperty* Property = *PropertyIt;
if (CanUserKismetAccessVariable(Property, InClass, CannotBeDelegate))
{
return true;
}
}
}
return false;
}
```
在BP中定义的函数如果通过AccessSpecifier设置为Protected或Private也会相应把该函数加上FUNC_Protected或FUNC_Private。从而实际上影响该函数的作用域。但源码中很多判断会先判断是否Native函数如果是就不继续做限制。因此我们可以理解这是UE机制的有意为之故意不把C++里的protected和private作用域算进去而要求你必须自己手动显式的写上BlueprintProtected或BlueprintPrivate这样避免有时的模糊不清。
```cpp
bool UEdGraphSchema_K2::CanFunctionBeUsedInGraph(const UClass* InClass, const UFunction* InFunction, const UEdGraph* InDestGraph, uint32 InAllowedFunctionTypes, bool bInCalledForEach, FText* OutReason) const
{
const bool bIsNotNative = !FBlueprintEditorUtils::IsNativeSignature(InFunction);
if(bIsNotNative)
{
// Blueprint functions visibility flags can be enforced in blueprints - native functions
// are often using these flags to only hide functionality from other native functions:
const bool bIsProtected = (InFunction->FunctionFlags & FUNC_Protected) != 0;
}
bool UK2Node_CallFunction::IsActionFilteredOut(FBlueprintActionFilter const& Filter)
{
bool bIsFilteredOut = false;
for(UEdGraph* TargetGraph : Filter.Context.Graphs)
{
bIsFilteredOut |= !CanPasteHere(TargetGraph);
}
if(const UFunction* TargetFunction = GetTargetFunction())
{
const bool bIsProtected = (TargetFunction->FunctionFlags & FUNC_Protected) != 0;
const bool bIsPrivate = (TargetFunction->FunctionFlags & FUNC_Private) != 0;
const UClass* OwningClass = TargetFunction->GetOwnerClass();
if( (bIsProtected || bIsPrivate) && !FBlueprintEditorUtils::IsNativeSignature(TargetFunction) && OwningClass)
{
OwningClass = OwningClass->GetAuthoritativeClass();
// we can filter private and protected blueprints that are unrelated:
bool bAccessibleInAll = true;
for (const UBlueprint* Blueprint : Filter.Context.Blueprints)
{
UClass* AuthoritativeClass = Blueprint->GeneratedClass;
if(!AuthoritativeClass)
{
continue;
}
if(bIsPrivate)
{
bAccessibleInAll = bAccessibleInAll && AuthoritativeClass == OwningClass;
}
else if(bIsProtected)
{
bAccessibleInAll = bAccessibleInAll && AuthoritativeClass->IsChildOf(OwningClass);
}
}
if(!bAccessibleInAll)
{
bIsFilteredOut = true;
}
}
}
return bIsFilteredOut;
}
```
# 作用在属性上:
作用在属性上时,标明该属性只能在本类或派生类里进行读写,但不能在别的蓝图类里访问。
测试代码:
```cpp
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API AMyFunction_Access :public AActor
{
public:
GENERATED_BODY()
public:
//(BlueprintProtected = true, Category = MyFunction_Access, ModuleRelativePath = Function/MyFunction_Access.h)
//CPF_BlueprintVisible | CPF_ZeroConstructor | CPF_IsPlainOldData | CPF_NoDestructor | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPublic
UPROPERTY(BlueprintReadWrite,meta = (BlueprintProtected = "true"))
int32 MyNativeInt_HasProtected;
//(BlueprintPrivate = true, Category = MyFunction_Access, ModuleRelativePath = Function/MyFunction_Access.h)
//CPF_BlueprintVisible | CPF_ZeroConstructor | CPF_IsPlainOldData | CPF_NoDestructor | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPublic
UPROPERTY(BlueprintReadWrite,meta = (BlueprintPrivate = "true"))
int32 MyNativeInt_HasPrivate;
public:
//CPF_BlueprintVisible | CPF_ZeroConstructor | CPF_IsPlainOldData | CPF_NoDestructor | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPublic
UPROPERTY(BlueprintReadWrite)
int32 MyNativeInt_NativePublic;
protected:
//CPF_BlueprintVisible | CPF_ZeroConstructor | CPF_IsPlainOldData | CPF_NoDestructor | CPF_Protected | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierProtected
UPROPERTY(BlueprintReadOnly)
int32 MyNativeInt_NativeProtected;
private:
//CPF_Edit | CPF_ZeroConstructor | CPF_IsPlainOldData | CPF_NoDestructor | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPrivate
//error : BlueprintReadWrite should not be used on private members
UPROPERTY(EditAnywhere)
int32 MyNativeInt_NativePrivate;
};
```
蓝图效果:
在其子类BPA_Access_Base测试发现除了MyNativeInt_HasPrivate都可以访问。这符合逻辑毕竟Private的含义就是只有在本类才可以访问。
而在本蓝图类定义的MyBPIntPrivate因为勾上了Private会导致该属性增加了BlueprintPrivate = true的meta但因为是本类里定义的所以在本类里也依然可以读写访问。
![Untitled](Untitled%203.png)
继续在蓝图中的子类BPA_Access_Child继承自BPA_Access_Base效果
Protected的属性依然都可以访问但是MyBPIntPrivate属性因为是Private的因此都不能读写如果强制粘贴节点会在编译的时候报错。Private的含义是只在本类中才可以访问。
![Untitled](Untitled%204.png)
而在外部类中(BPA_Access_Other继承自Actor)通过BPA_Access_Base或BPA_Access_Child对象实例访问属性的时候带有BlueprintProtected和BlueprintPrivate都不能访问。而C++中的protected修饰并无影响。
而MyBPIntPrivate因为是Private所以不能访问。
![Untitled](Untitled%205.png)
## 原理:
在源码里搜索CPF_NativeAccessSpecifierProtected发现并无使用的地方。
而CPF_NativeAccessSpecifierPrivate只在IsPropertyPrivate中引用后者也只在蓝图编译检测线程安全的时候被检测到。因此CPF_NativeAccessSpecifierPrivate也实际上并无真正的被用来做作用域的限制。
综合二者这也是在C++中protected和private并不在蓝图中造成影响的原因。但UHT会阻止private变量上的BlueprintReadWrite或BlueprintReadOnly造成事实上的无法在蓝图中访问达成了无法在蓝图子类里访问C++基类private变量的效果。
因此实际上在蓝图中的变量作用域控制采用的元数据BlueprintProtected 和BlueprintPrivate在蓝图右键能否创建属性读写节点的逻辑在上面的BlueprintActionFilterImpl::IsFieldInaccessible函数中体现。而编译的时候判断一个属性是否可读写的逻辑在IsPropertyWritableInBlueprint和IsPropertyReadableInBlueprint这两个函数如果最终的状态结果是Private则说明不可访问。在UK2Node_VariableGet和UK2Node_VariableSet的ValidateNodeDuringCompilation会检测出来并报错。
```cpp
bool FBlueprintEditorUtils::IsPropertyPrivate(const FProperty* Property)
{
return Property->HasAnyPropertyFlags(CPF_NativeAccessSpecifierPrivate) || Property->GetBoolMetaData(FBlueprintMetadata::MD_Private);
}
FBlueprintEditorUtils::EPropertyWritableState FBlueprintEditorUtils::IsPropertyWritableInBlueprint(const UBlueprint* Blueprint, const FProperty* Property)
{
if (Property)
{
if (!Property->HasAnyPropertyFlags(CPF_BlueprintVisible))
{
return EPropertyWritableState::NotBlueprintVisible;
}
if (Property->HasAnyPropertyFlags(CPF_BlueprintReadOnly))
{
return EPropertyWritableState::BlueprintReadOnly;
}
if (Property->GetBoolMetaData(FBlueprintMetadata::MD_Private))
{
const UClass* OwningClass = Property->GetOwnerChecked<UClass>();
if (OwningClass->ClassGeneratedBy.Get() != Blueprint)
{
return EPropertyWritableState::Private;
}
}
}
return EPropertyWritableState::Writable;
}
FBlueprintEditorUtils::EPropertyReadableState FBlueprintEditorUtils::IsPropertyReadableInBlueprint(const UBlueprint* Blueprint, const FProperty* Property)
{
if (Property)
{
if (!Property->HasAnyPropertyFlags(CPF_BlueprintVisible))
{
return EPropertyReadableState::NotBlueprintVisible;
}
if (Property->GetBoolMetaData(FBlueprintMetadata::MD_Private))
{
const UClass* OwningClass = Property->GetOwnerChecked<UClass>();
if (OwningClass->ClassGeneratedBy.Get() != Blueprint)
{
return EPropertyReadableState::Private;
}
}
}
return EPropertyReadableState::Readable;
}
```