UE4 C++ 杂

TMap中的Find和FindRef

在对蓝图节点

进行C++重写时,发现UE对于TMap的Find有很多方式。

  1. 首先是基础的Find,其就是返回对象类型的指针,如果不存在于TMap中其会返回nullptr
  2. 接下来是FindChecked,其返回的是对象类型的引用,并且会在内部检测指针是否为空,如果没有会触发断言
  3. FindRef 其返回的是对象类型,即会调用构造函数,如果没有那么会通过对象的默认构造函数构造一个返回

UEC++ 中的Class类型的获取

继承于同一C++类的两个蓝图其UClass并不相同
这是由于UClass信息不仅仅是类信息,其实际上是存储的反射信息还包括了序列化等内容。
如果是同一个蓝图的场景中两个不同的Actor其UClass是相同的

GetClass()函数

GetClass()函数用于对一个UObject实例获取其的UClass。

StaticClass()函数

GetClass()只能用于对UObject对象获取其UClass,如果没有UObject实例那么就可以使用StaticClass()来获取,即对于一个类直接使用类名::StaticClass()获取UClass信息可以发现UClass信息是永远一样的。
StaticClass用于判断父类

该类的UClass会存储其父类的StaticClass,可以不断获取父类的StaticClass与要判断的类作比较

在UE中使用IsChildOf来判断某个类是不是另一个类的子类,使用IsA来判断某个实例对象是不是某一类的子类

ClassDefaultObject()

通常可以通过GetClass()->GetDefaultObject()来获取AClass中某个值的默认值,前面已经知道GetClass实际上获取的是UClass信息,所以如果在蓝图中修改了该值,那么不同蓝图得到的值是不一样的,而如果改用StaticClass则所有蓝图获得的都是C++中给定的默认值

UGameplayStatics::GetObjectClass

该方法就是UGameplayStatics封装了GetClass,即返回的也是UClass信息,但是如果不存在UClass其会返回nullptr
image

继承接口类之后对接口中函数的调用

当一个类继承了一个接口类,那么需要对接口类中的纯虚函数进行重写,显然对于重写后的函数在调用的方式上就有两种,一个是通过接口来调用,一个是通过该实现类的类对象来调用。
例如:一个接口类如下

//XAIInterface.h
UINTERFACE(MinimalAPI)
class UXAIInterface : public UInterface
{
	GENERATED_BODY()
};
class RECLIMB_CPP_API IXAIInterface
{
	GENERATED_BODY()

		// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
	UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
		class AXLineBase* GetPatrolRoute();
	UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
		float SetMovementSpeed(EAIMovement SpeedEnum);
};

一个接口类由2个类组成,分别用于声明和实现接口,继承UInterface的就是用来声明接口类,而要在子类中实现的函数则写在IInterface中
UFUNCTION()表示该函数可以被UE的反射系统接受,可以再括号中添加说明符,例如BlueprintCallable表示该函数可以在蓝图中被调用
BlueprintNativeEvent则表示,可以在C++代码中提过函数的实现,而在蓝图中可以重写。不过在使用了该说明符后,C++代码中的函数声明需要在函数名后加上_Implementation,表示默认实现
而由于接口类中的函数是通过UFUNCTION()绑定到反射系统上,所以该函数本质上是一个虚函数,但是通过反射系统来调用,而不是纯的虚函数表,所以不需要c++中的virtual声明符。
定义完成接口,就需要实现类继承接口,并实现接口类中的函数了

//AXAI_Character.h
class RECLIMB_CPP_API AXAI_Character : public ACharacter, public IXAIInterface
{
	GENERATED_BODY()
  // ... ....
//Interface
public:
	class AXLineBase* GetPatrolRoute_Implementation() override;
	float SetMovementSpeed_Implementation(EAIMovement SpeedEnum) override;
};

那么调用该函数就有两种方式了,一个是使用AXAI_Character的实例化对象AICha->GetPatrolRoute(),该方法需要一个示例化对象。
这种方法就比较直观,不会涉及对虚函数的查找和类型转换,但带来的就是紧耦合,即需要知道AXAI_Character这个类对象才能进行调用。
而是用接口类,就是利用了多态性,只要该类继承了接口类,那么就可以直接通过接口类来调用函数。

//首先要检查该类是否继承了接口
ControlledPawn->Implements<UXAIInterface>()

UE定义了Implements模板函数,用来检测对象是否实现了特定接口。该函数从调用的类开始依次向父类搜索当前类中的所有接口类,如果当前接口类是UXAIInterface或者其子类,那么就会返回true,否则返回false。
如果有接口类的实现,那么就可以通过,接口类直接调用了

IXAIInterface::Execute_GetPatrolRoute(ControlledPawn);

UE4 C++ 实现对AIMoveTo和PlayMontage的处理

目的是实现这两个蓝图节点中的右边输出,在C++中实际上就是委托

AIMoveTo

在AIController中,UE给出了一个委托FAIMoveCompletedSignature ReceiveMoveCompleted;

当Move结束会进行广播,所以当要在BTTask中实现当Move结束再执行下一步动作,就需要使用这个委托进行绑定。

PlayMontage

Montage有一个委托OnMontageEnded,就是用来表示Montage播放完成后的动作,使用方法与ReceiveMoveCompleted类似

BTTask延迟返回

主要用于寻路或者播放动画后才执行行为树的下一步动作
对于C++实现的BTTask,主要重写了ExecuteTask函数,而为了延时执行就需要再改函数返回EBTNodeResult::InProgress,然后通过上面两个委托的广播,调用绑定函数执行FinishLatentTask(*OwnerComp, EBTNodeResult::Succeeded);结束这个任务。
其中用到了FSimpleDelegate,进行一个委托的绑定,因为需要变量。这里给出一个案例
EBTNodeResult::Type UXBTTask_SelfMoveTo::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
AXAIController* AICon = Cast(OwnerComp.GetAIOwner());
if (AICon)
{
AXAI_Character* ControlledPawn = Cast<AXAI_Character>(AICon->GetPawn());
if (ControlledPawn)
{
//清除寻路完成委托上已经绑定的函数
AICon->GetPathFollowingComponent()->OnRequestFinished.Clear();
/AICon->ReceiveMoveCompleted.Clear();/
//绑定函数到FinishDelegate,并且添加负载
FinishDelegate.BindUObject(this, &UXBTTask_SelfMoveTo::FinishEnd, &OwnerComp);
AActor* AttackActor = AICon->GetAttackTargetActor();
//绑定函数
AICon->GetPathFollowingComponent()->OnRequestFinished.AddUObject(this, &UXBTTask_SelfMoveTo::MoveEndCall);
//通过设定黑板值,限制范围
float Radius = AICon->GetBlackboardComponent()->GetValueAsFloat(IdealRange.SelectedKeyName);
AICon->MoveToActor(AttackActor, Radius, false);
/AICon->ReceiveMoveCompleted.AddDynamic(this, &UXBTTask_SelfMoveTo::MoveEndCall);/
}
}
return EBTNodeResult::InProgress;
}

//执行Delegat
void UXBTTask_SelfMoveTo::MoveEndCall(FAIRequestID RequestID, const FPathFollowingResult& Result)
{
FinishDelegate.ExecuteIfBound();
}

//用于结束该Task
void UXBTTask_SelfMoveTo::FinishEnd(UBehaviorTreeComponent* OwnerComp)
{
FinishLatentTask(*OwnerComp, EBTNodeResult::Succeeded);
}

UE C++ 使用 DataTable

ue中使用DataTable需要先创建一个USTRUCT,DataTable的数据就是这个USTRUCT组成的。比如在一个AActor类中声明了一个USTRUCT,然后在类里面声明一个UDataTable* 的成员变量,当在编辑器中创建了DataTable后,可以在这个Actor类中进行选择,或者使用Cast<UDataTable>StaticLoadObject(UDataTable::StaticClass(),nullptr,TEXT("[资源路径]"));来进行成员变量的初始化
之后可以通过以下方式来获取每行的数据

//FTest为DT所使用的结构体
//TestDT为创建的DataTable

//1. 指定rowname
FName _rowname;
FTest* _data = TestDT->FindRow<FTest>(_rowname,contextstring,false);

//2. 获取所有的Row之后可以通过遍历获得
TArray<FName> _rownames;
_rownames = TestDT->GetRowName();
for(auto& _rowname:_rownames)
{
  FTest* _data = TestDT->FindRow<FTest>(_rowname,contextstring,false);
}

UE 宏,自定义事件和函数的区别

宏可以拥有多个输入输出,在执行前就已经编译好了,但是其不能拥有局部变量且只能在一个蓝图类中使用
事件不具有输出,其只能被触发执行,一个事件可以被多线程执行,并且可以添加延迟节点
函数和宏类似拥有多个输入或者输出,但是可以跨蓝图调用,不能使用延迟节点。这是由于函数保证的是及时返回,如果在函数中使用延迟节点,那么会导致整个蓝图事件逻辑的停滞。并且如果在函数中使用延迟,而继续执行蓝图逻辑的话,会导致在之后某个事件变量的改变,导致效果的不确定性

作者:XTG111

出处:https://www.cnblogs.com/XTG111/p/18171199

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   XTG111  阅读(129)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
more_horiz
keyboard_arrow_up light_mode palette
选择主题
点击右上角即可分享
微信分享提示