85. UE5 RPG 创建技能面板控制器

87 篇文章 12 订阅

在上一篇里,我们编辑完成了技能的UI面板。按照MVC的思路,我们还需要制作它的控制器,用于处理UI上面的交互。
到现在为止,我们控制器已经有了三种,首先是我们创建的控制器基类RPGWidgetController.h,为所有控制器需要继承的类,里面有一些公用的函数和属性。通过基类我们实现三种控制器:

  1. 用于显示在战斗场景界面的UI的控制器OverlayWidgetController.h,主要显示角色等级血量技能等的状态
  2. 用于展现角色属性面板的UI的控制器AttributeMenuWidgetController.h,主要用于显示角色属性值和属性加点功能
  3. 还有我们在这篇文章里要创建的技能面板的UI的控制器SpellMenuWidgetController.h,用于设置玩家的技能等级,技能键位等操作。

我们接下来,会先创建技能面板控制器,然后整理代码,讲通用的部分整理到基类里,并优化一些功能。

创建技能面板控制器

我们基于控制器基类创建一个新的控制器子类
在这里插入图片描述
然后在类里继承覆写基类的两个函数,它们分别为初始化广播数据,用于在创建时,显示角色当前的信息,另一个是在属性变动时的委托广播。
并且我们要设置标识,让其可以作为蓝图中转换的类和蓝图继承的类

UCLASS(BlueprintType, Blueprintable)
class RPG_API USpellMenuWidgetController : public URPGWidgetController
{
	GENERATED_BODY()

public:

	virtual void BindCallbacksToDependencies() override;
	virtual void BroadcastInitialValues() override;
	
};

Hud类里添加设置技能面板控制器

我们是在自定义的HUD里面设置UI的蓝图类,并有配置项去配置使用的控制器,我们接着讲技能面板的控制器添加进去。
按照之前,我们首先创建两个属性,一个用于设置使用的技能面板控制器类,一个用于存储类的实例,防止多次创建

	UPROPERTY()
	TObjectPtr<USpellMenuWidgetController> SpellMenuWidgetController;

	UPROPERTY(EditAnywhere)
	TSubclassOf<USpellMenuWidgetController> SpellMenuWidgetControllerClass;

然后我们增加一个用于获取技能面板控制器的函数

USpellMenuWidgetController* GetSpellMenuWidgetController(const FWidgetControllerParams& WCParams);

在实现时,首先判断当前是否被实例过,如果实例过直接返回,没有则实例一次

USpellMenuWidgetController* ARPGHUD::GetSpellMenuWidgetController(const FWidgetControllerParams& WCParams)
{
	if(SpellMenuWidgetController == nullptr)
	{
		SpellMenuWidgetController = NewObject<USpellMenuWidgetController>(this, SpellMenuWidgetControllerClass);
		SpellMenuWidgetController->SetWidgetControllerParams(WCParams);
		SpellMenuWidgetController->BindCallbacksToDependencies(); //绑定监听数值变化
	}
	return SpellMenuWidgetController;
}

创建获取RPG类型的变量

随着子类增多,我们每次切换类型去获取自定义的ASC PS PC AS的次数增多,还影响代码阅读,所以,我们在控制器基类增加对自定义的类型的获取。
首先,我们增加四个变量,用于存储对应的类型

	UPROPERTY()
	TObjectPtr<ARPGPlayerController> RPGPlayerController;

	UPROPERTY()
	TObjectPtr<ARPGPlayerState> RPGPlayerState;

	UPROPERTY()
	TObjectPtr<URPGAbilitySystemComponent> RPGAbilitySystemComponent;

	UPROPERTY()
	TObjectPtr<URPGAttributeSet> RPGAttributeSet;

然后,我们增加四个函数,用于获取自定义的参数

	ARPGPlayerController* GetRPGPC();
	ARPGPlayerState* GetRPGPS();
	URPGAbilitySystemComponent* GetRPGASC();
	URPGAttributeSet* GetRPGAS();

然后就是函数实现,在实现函数里,我们首先判断当前的变量是否设置的值,如果没有,那么将类型转换,并返回

ARPGPlayerController* URPGWidgetController::GetRPGPC()
{
	if(RPGPlayerController == nullptr)
	{
		RPGPlayerController = Cast<ARPGPlayerController>(PlayerController);
	}
	return RPGPlayerController;
}

ARPGPlayerState* URPGWidgetController::GetRPGPS()
{
	if(RPGPlayerState == nullptr)
	{
		RPGPlayerState = Cast<ARPGPlayerState>(PlayerState);
	}
	return RPGPlayerState;
}

URPGAbilitySystemComponent* URPGWidgetController::GetRPGASC()
{
	if(RPGAbilitySystemComponent == nullptr)
	{
		RPGAbilitySystemComponent = Cast<URPGAbilitySystemComponent>(AbilitySystemComponent);
	}
	return RPGAbilitySystemComponent;
}

URPGAttributeSet* URPGWidgetController::GetRPGAS()
{
	if(RPGAttributeSet == nullptr)
	{
		RPGAttributeSet = Cast<URPGAttributeSet>(AttributeSet);
	}
	return RPGAttributeSet;
}

转换完成后,编译,发现子类使用的一些变量名称出现的冲突
在这里插入图片描述
我们在子类里面可以将转换类型的内容删除,并直接调用函数获取
在这里插入图片描述

将技能应用的广播修改到基类

由于技能的更新在UOverlayWidgetController里面和USpellMenuWidgetController里面都需要应用,所以我们将属性对应内容的委托函数移动到基类里
在这里插入图片描述
首先,我们将委托定义移动到基类

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FAbilityInfoSignature, const FRPGAbilityInfo, Info); //技能更新UI回调

将技能表格数据定义也设置到基类

	//技能的表格数据
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Widget Data")
	TObjectPtr<UAbilityInfo> AbilityInfo;

然后将委托变量移动到基类,并创建一个绑定委托的函数

	UPROPERTY(BlueprintAssignable, Category="GAS|Messages")
	FAbilityInfoSignature AbilityInfoDelegate; //技能数据更新委托回调

	void BroadcastAbilityInfo(); //广播技能信息

然后将实现内容移动到函数中

void URPGWidgetController::BroadcastAbilityInfo()
{
	if(!GetRPGASC()->bStartupAbilitiesGiven) return; //判断当前技能初始化是否完成,触发回调时都已经完成

	//创建单播委托
	FForEachAbility BroadcastDelegate;
	//委托绑定回调匿名函数,委托广播时将会触发函数内部逻辑
	BroadcastDelegate.BindLambda([this](const FGameplayAbilitySpec& AbilitySpec)
	{
		//通过静态函数获取到技能实例的技能标签,并通过标签获取到技能数据
		FRPGAbilityInfo Info = AbilityInfo->FindAbilityInfoForTag(URPGAbilitySystemComponent::GetAbilityTagFromSpec(AbilitySpec));
		//获取到技能的输入标签
		Info.InputTag = URPGAbilitySystemComponent::GetInputTagFromSpec(AbilitySpec);
		//广播技能数据
		AbilityInfoDelegate.Broadcast(Info); 
	});
	//遍历技能并触发委托回调
	GetRPGASC()->ForEachAbility(BroadcastDelegate);
}

在OverlayWidgetController.cpp里面,我们修改绑定函数

	//绑定ASC相关委托的回调
	if(GetRPGASC())
	{
		if(GetRPGASC()->bStartupAbilitiesGiven)
		{
			//如果执行到此处时,技能的初始化工作已经完成,则直接调用初始化回调
			BroadcastInitialValues();
		}
		else
		{
			//如果执行到此处,技能初始化还未完成,将通过绑定委托,监听广播的形式触发初始化完成回调
			GetRPGASC()->AbilityGivenDelegate.AddUObject(this, &ThisClass::BroadcastInitialValues);
		}

由于技能绑定我们函数不需要传入ASC,所以,我们在自定义ASC里,将返回一个参数取消,并修改对应参数

DECLARE_MULTICAST_DELEGATE(FAbilityGiven) //技能初始化应用后的回调委托

在蓝图函数库增加获取控制器函数

和获取其它函数一致,我们增加一个函数用于获取技能面板控制器

	//获取属性面板的控制器
	UFUNCTION(BlueprintPure, Category="RPGAbilitySystemLibrary|WidgetController")
	static USpellMenuWidgetController* GetSpellMenuWidgetController(const UObject* WorldContextObject);

实现也和其它函数一致

USpellMenuWidgetController* URPGAbilitySystemBlueprintLibrary::GetSpellMenuWidgetController(const UObject* WorldContextObject)
{
	//获取到playerController, 需要传入一个世界空间上下文的对象,用于得到对应世界中的PC列表,0为本地使用的PC
	if(APlayerController* PC = UGameplayStatics::GetPlayerController(WorldContextObject, 0))
	{
		//从PC获取到HUD,我们就可以从HUD获得对应的Controller
		if(ARPGHUD* HUD = Cast<ARPGHUD>(PC->GetHUD()))
		{
			ARPGPlayerState* PS = PC->GetPlayerState<ARPGPlayerState>();
			UAbilitySystemComponent* ASC = PS->GetAbilitySystemComponent();
			UAttributeSet* AS = PS->GetAttributeSet();
			const FWidgetControllerParams WidgetControllerParams(PC, PS, ASC, AS);
			return HUD->GetSpellMenuWidgetController(WidgetControllerParams);
		}
	}
	return nullptr;
}

在这里,你会发现这几个获取控制器的代码的重复度很高,基本都是为了获取到对应的数据以后,通过HUD函数来获取对应的控制器,接下来,我们将简化代码,将相同的部分修改为一个通用的函数。
通用函数需要获取到两项内容,HUD对象和创建控制器所需的FWidgetControllerParams 参数,所以,我们创建一个函数用于返回FWidgetControllerParams参数,并在参数里返回HUD。
这个函数,返回一个布尔类型,用于判断参数是否获取成功,并传入了所需值的地址引用,可以修改它的地址,将外部的值修改掉。
这里注意我们设置的元数据,meta=(DefaultToSelf = "WorldContextObject"),这样即使我们不设置它,也会有默认值就是自身

	UFUNCTION(BlueprintPure, Category="RPGAbilitySystemLibrary|WidgetController", meta=(DefaultToSelf = "WorldContextObject"))
	static bool MakeWidgetControllerParams(const UObject* WorldContextObject, FWidgetControllerParams& OutWcParams, ARPGHUD*& OutRPGHUD);

在实现这里,我们还是同样的获取参数,去修改传入的参数

bool URPGAbilitySystemBlueprintLibrary::MakeWidgetControllerParams(const UObject* WorldContextObject, FWidgetControllerParams& OutWcParams, ARPGHUD*& OutRPGHUD)
{
	//获取到playerController, 需要传入一个世界空间上下文的对象,用于得到对应世界中的PC列表,0为本地使用的PC
	if(APlayerController* PC = UGameplayStatics::GetPlayerController(WorldContextObject, 0))
	{
		//从PC获取到HUD,我们就可以从HUD获得对应的Controller
		if(ARPGHUD* HUD = Cast<ARPGHUD>(PC->GetHUD()))
		{
			OutRPGHUD = HUD; //修改指针的引用
			ARPGPlayerState* PS = PC->GetPlayerState<ARPGPlayerState>();
			//设置参数
			OutWcParams.PlayerController = PC;
			OutWcParams.PlayerState = PS;
			OutWcParams.AbilitySystemComponent = PS->GetAbilitySystemComponent();
			OutWcParams.AttributeSet = PS->GetAttributeSet();
			return true;
		}
	}
	return false;
}

接着,我们将其它获取控制器的函数都修改掉

UOverlayWidgetController* URPGAbilitySystemBlueprintLibrary::GetOverlayWidgetController(const UObject* WorldContextObject)
{
	FWidgetControllerParams WCParams;
	ARPGHUD* HUD = nullptr;
	if(MakeWidgetControllerParams(WorldContextObject, WCParams, HUD))
	{
		return HUD->GetOverlayWidgetController(WCParams);
	}
	return nullptr;
}

UAttributeMenuWidgetController* URPGAbilitySystemBlueprintLibrary::GetAttributeMenuWidgetController(const UObject* WorldContextObject)
{
	FWidgetControllerParams WCParams;
	ARPGHUD* HUD = nullptr;
	if(MakeWidgetControllerParams(WorldContextObject, WCParams, HUD))
	{
		return HUD->GetAttributeMenuWidgetController(WCParams);
	}
	return nullptr;
}

USpellMenuWidgetController* URPGAbilitySystemBlueprintLibrary::GetSpellMenuWidgetController(const UObject* WorldContextObject)
{
	FWidgetControllerParams WCParams;
	ARPGHUD* HUD = nullptr;
	if(MakeWidgetControllerParams(WorldContextObject, WCParams, HUD))
	{
		return HUD->GetSpellMenuWidgetController(WCParams);
	}
	return nullptr;
}

我们修改参数
在这里插入图片描述

创建蓝图

我们编译打开UE,在UE里创建一个基于技能面板的控制器蓝图,用于设置参数
在这里插入图片描述
将资产设置上去
在这里插入图片描述
打开HUD蓝图,设置使用的技能面板控制器类
在这里插入图片描述
在技能面板UI里面,我们获取一下控制器,发现不需要再设置世界空间了
在这里插入图片描述
我们技能面板UI的事件里面设置控制器,并打印名称,查看是否能够正确获取到
在这里插入图片描述
如果能正确打印名称,证明没有问题
在这里插入图片描述

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当使用UE5创建地形时,以下是主要的步骤: 1. 打开UE5编辑器并创建一个新的关卡(Level)。 2. 在场景中选择一个适合的位置来放置地形。 3. 打开"Landscape"工具面板,在工具栏上选择"Landscape"工具。 4. 在弹出的对话框中设置地形的大小和分辨率。您可以选择自定义大小或使用默认值。 5. 点击"Create"按钮来创建地形。 6. 接下来,您可以使用"Landscape"工具面板上的各种工具来进行细节操作。例如,您可以使用"Add/Remove"工具来添加或删除地形高度,使用"Flatten"工具来平整地形,使用"Smooth"工具来平滑地形等。 7. 您还可以使用"Landscape"工具面板上的"Paint"工具来添加纹理和植被到地形上。选择合适的纹理和植被材质,并在地形上绘制它们。 8. 您可以使用"Landscape"工具面板上的"Manage Layers"选项来管理不同的纹理和植被图层。您可以添加、删除、编辑图层,并设置它们的混合方式和权重。 9. 如果需要进一步调整地形细节,您可以在"Landscape"工具面板上选择其他工具和选项进行操作。例如,您可以使用"Sculpt"工具来雕刻地形,使用"Erosion"工具来模拟侵蚀效果,使用"Smooth"工具来进一步平滑地形等。 10. 在完成地形的创建和细节调整后,您可以保存并应用地形到关卡中。 这些是基本的步骤,您还可以根据需要进一步探索UE5提供的地形编辑工具和功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值