82. UE5 RPG 实现角色升级系统(下)

84 篇文章 11 订阅

书接上回,在上一篇博客里,我们实现了角色升级的基础的功能。给敌人增加的经验奖励配置,并且在敌人死亡时,能够将经验通过事件传递给击杀者,玩家定义了被动技能,在被动技能中接收传递的事件,通过SetByCaller的GE应用给自身。并在AttributeSet中打印出获得的经验。
我们还修改了PlayerState,在内部增加了对经验和等级添改查的,并创建对应的委托,在UI的Controller里面,实现了对委托的监听,并实现回调函数,通过经验获取等级和升级进度广播给UI表现出来。

创建玩家接口类

有了之前制作的内容,我们接下来,将实现玩家在AS里获取到经验后,设置的PlayerState上,这里防止耦合度太高,我们选择创建一个新的接口实现此功能。
新添加一个接口类
在这里插入图片描述
作为玩家专用的命名为PlayerInterface ,除了它我们还有CombateInterface(战斗接口)EnemyInterface(敌人接口)
在这里插入图片描述
在接口类里,我们增加一个增加经验的函数

class RPG_API IPlayerInterface
{
	GENERATED_BODY()

	// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:

	UFUNCTION(BlueprintNativeEvent)
	void AddToXP(int32 InXP); //增加经验
};

在玩家角色类继承此接口

class RPG_API ARPGHero : public ARPGCharacter, public IPlayerInterface
{
	GENERATED_BODY()

并覆写此函数

	/* IPlayerInterface战斗接口 */
	virtual void AddToXP_Implementation(int32 InXP) override;
	/* IPlayerInterface战斗接口 结束 */

在实现这里,获取到PlayerState,并调用PlayerState身上的增加经验的函数

void ARPGHero::AddToXP_Implementation(int32 InXP)
{
	ARPGPlayerState* PlayerStateBase = GetPlayerState<ARPGPlayerState>();
	check(PlayerStateBase); //检测是否有效,无限会暂停游
	PlayerStateBase->AddToXP(InXP);
}

最后在AS里,我们通过调用此函数实现经验增长

	if(Data.EvaluatedData.Attribute == GetIncomingXPAttribute())
	{
		const float LocalIncomingXP = GetIncomingXP();
		SetIncomingXP(0);
		// UE_LOG(LogRPG, Log, TEXT("获取传入经验值:%f"), LocalIncomingXP);

		//将经验应用给自身
		if(Props.SourceCharacter->Implements<UPlayerInterface>())
		{
			IPlayerInterface::Execute_AddToXP(Props.SourceCharacter, LocalIncomingXP);
		}
	}

在UI上实现对经验变动监听

现在,玩家角色可以获得经验,并通过接口设置给PlayerState,然后广播给UI的控制器了。我们要先将之前创建的升级数据设置给PlayerState
在这里插入图片描述
在WBP_Overlay里面,给经验条设置控制器
在这里插入图片描述
在经验条的事件中,设置控制器回调中,绑定对经验变动的监听,在变动时,获取经验条的百分比
在这里插入图片描述
接下来可以测试了,发现上一篇文章里面,在最后类型转换时,需要提前转换将一个数值转换为浮点数类型 ,浮点数/整型 结果就可以是浮点型,要不然,整型除以整型结果还是整型,获取的结果为0
在这里插入图片描述
接着,你就会发现经验条随着怪物死亡,增长了一丝
在这里插入图片描述
然后测试杀死两只,给的经验是否一致
在这里插入图片描述

重构战斗接口中的获取等级函数

我们想将之前书写的获取等级的函数修改成可以在蓝图定义的,达到结构统一,所以,需要额外修改一些内容

	UFUNCTION(BlueprintNativeEvent)
	int32 GetPlayerLevel(); //获取玩家等级

修改后编译会发现一些报错
在这里插入图片描述
按照之前方式,将其名称后续加上_Implementation即可

	/* ICombatInterface战斗接口 */
	virtual int32 GetPlayerLevel_Implementation() override;
	/* ICombatInterface战斗接口 结束 */

接下来编译,会发现还有一些额外报错,这是因为调用的地方没有修改
在这里插入图片描述
我们可以通过双击shift键,打开搜索,找到调用的地方依次修改。
在这里插入图片描述
找到需要修改的地方,将代码修改成以下类型。注意:Implements后面的类型是以U开头的接口,它是UE增加的,而调用的时候是I开头是c++内置的接口调用方式。

	//从战斗接口获取到角色的等级
	int32 CharacterLevel = 1;
	if(ASC->GetAvatarActor()->Implements<UCombatInterface>())
	{
		CharacterLevel = ICombatInterface::Execute_GetPlayerLevel(ASC->GetAvatarActor());
	}

增加角色接口函数

为了实现接下来角色升级的功能,我们扩展角色接口的函数

class RPG_API IPlayerInterface
{
	GENERATED_BODY()

	// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:

	UFUNCTION(BlueprintNativeEvent)
	int32 FindLevelForXP(int32 InXP) const; //根据经验获取等级

	UFUNCTION(BlueprintNativeEvent)
	int32 GetXP() const; //获取当前经验值

	UFUNCTION(BlueprintNativeEvent)
	int32 GetAttributePointsReward(int32 Level) const; //获取属性点奖励

	UFUNCTION(BlueprintNativeEvent)
	int32 GetSpellPointsReward(int32 Level) const; //获取技能点奖励

	UFUNCTION(BlueprintNativeEvent)
	void AddToXP(int32 InXP); //增加经验

	UFUNCTION(BlueprintNativeEvent)
	void AddToPlayerLevel(int32 InPlayerLevel); //增加等级

	UFUNCTION(BlueprintNativeEvent)
	void AddToAttributePoints(int32 InAttributePoints); //增加属性点

	UFUNCTION(BlueprintNativeEvent)
	void AddToSpellPoints(int32 InSpellPoints); //增加技能点

	UFUNCTION(BlueprintNativeEvent)
	void LevelUp(); //升级
};

接下来,我们在继承接口的玩家角色类里面覆写函数

	/* IPlayerInterface战斗接口 */
	virtual void AddToXP_Implementation(int32 InXP) override;
	virtual void LevelUp_Implementation() override;
	virtual int32 GetXP_Implementation() const override;
	virtual int32 FindLevelForXP_Implementation(int32 InXP) const override;
	virtual int32 GetAttributePointsReward_Implementation(int32 Level) const override;
	virtual int32 GetSpellPointsReward_Implementation(int32 Level) const override;
	virtual void AddToPlayerLevel_Implementation(int32 InPlayerLevel) override;
	virtual void AddToAttributePoints_Implementation(int32 InAttributePoints) override;
	virtual void AddToSpellPoints_Implementation(int32 InSpellPoints) override;
	/* IPlayerInterface战斗接口 结束 */

除了已经实现的添加经验的函数,我们将其它函数依次实现。

首先是升级函数,这个函数主要用于提供角色升级时,播放一下升级表现效果,因为当前类是在服务器运行的,我们要实现一个在每个客户端都可以运行的效果,就需要增加一个可以在多端运行的函数,这个在稍微讲解。

接下来是获取当前角色经验值

int32 ARPGHero::GetXP_Implementation() const
{
	const ARPGPlayerState* PlayerStateBase = GetPlayerState<ARPGPlayerState>();
	check(PlayerStateBase); //检测是否有效,无限会暂停游戏
	return PlayerStateBase->GetXP();
}

通过经验值获取当前等级

int32 ARPGHero::FindLevelForXP_Implementation(const int32 InXP) const
{
	const ARPGPlayerState* PlayerStateBase = GetPlayerState<ARPGPlayerState>();
	check(PlayerStateBase); //检测是否有效,无限会暂停游戏
	return PlayerStateBase->LevelUpInfo->FindLevelForXP(InXP);
}

获取当前等级奖级的属性点

int32 ARPGHero::GetAttributePointsReward_Implementation(const int32 Level) const
{
	const ARPGPlayerState* PlayerStateBase = GetPlayerState<ARPGPlayerState>();
	check(PlayerStateBase); //检测是否有效,无限会暂停游戏
	return PlayerStateBase->LevelUpInfo->LevelUpInformation[Level].AttributePointAward;
}

获取当前等级奖励的技能点

int32 ARPGHero::GetSpellPointsReward_Implementation(const int32 Level) const
{
	const ARPGPlayerState* PlayerStateBase = GetPlayerState<ARPGPlayerState>();
	check(PlayerStateBase); //检测是否有效,无限会暂停游戏
	return PlayerStateBase->LevelUpInfo->LevelUpInformation[Level].SpellPointAward;
}

设置当前角色升级,主要是修改角色的数值

void ARPGHero::AddToPlayerLevel_Implementation(int32 InPlayerLevel)
{
	ARPGPlayerState* PlayerStateBase = GetPlayerState<ARPGPlayerState>();
	check(PlayerStateBase); //检测是否有效,无限会暂停游戏
	PlayerStateBase->AddToLevel(InPlayerLevel);
}

还有在后续章节中添加的属性修改功能函数,为角色增加属性点和技能点

void ARPGHero::AddToAttributePoints_Implementation(int32 InAttributePoints)
{
	//TODO:实现增加属性点
}

void ARPGHero::AddToSpellPoints_Implementation(int32 InSpellPoints)
{
	//TODO:实现增加技能点
}

实现等级升级

接下来,我们在AS里面获得经验后,处理升级相关逻辑。
首先判断是否升级,逻辑是当前经验+获得的经验是否能够升级,获取到增加经验后的等级和原来的等级是否一致,如果增加了,意味着角色等级得到了提升。

//获取获得经验后的新等级
const int32 CurrentLevel = ICombatInterface::Execute_GetPlayerLevel(Props.SourceCharacter);
const int32 NewLevel = IPlayerInterface::Execute_FindLevelForXP(Props.SourceCharacter, CurrentXP + LocalIncomingXP);
const int32 NumLevelUps = NewLevel - CurrentLevel; //查看等级是否有变化
if(NumLevelUps > 0)
{
	...
}

在升级逻辑里面,我们首先获取当前等级提供的属性点和技能点的奖励

//获取升级提供的技能点和属性点
int32 AttributePointsReward = IPlayerInterface::Execute_GetAttributePointsReward(Props.SourceCharacter, CurrentLevel);
int32 SpellPointsReward = IPlayerInterface::Execute_GetSpellPointsReward(Props.SourceCharacter, CurrentLevel);

然后提升角色等级,并应用奖励

//提升等级,增加角色技能点和属性点
IPlayerInterface::Execute_AddToPlayerLevel(Props.SourceCharacter, NumLevelUps);
IPlayerInterface::Execute_AddToAttributePoints(Props.SourceCharacter, AttributePointsReward);
IPlayerInterface::Execute_AddToSpellPoints(Props.SourceCharacter, SpellPointsReward);

调用升级表现函数

IPlayerInterface::Execute_LevelUp(Props.SourceCharacter); //升级

升级时,将血量和蓝量填满

//将血量和蓝量填充满
SetHealth(GetMaxHealth());
SetMana(GetMana());

下面列出来完整代码

	if(Data.EvaluatedData.Attribute == GetIncomingXPAttribute())
	{
		const float LocalIncomingXP = GetIncomingXP();
		SetIncomingXP(0);
		// UE_LOG(LogRPG, Log, TEXT("获取传入经验值:%f"), LocalIncomingXP);
		
		if(Props.SourceCharacter->Implements<UPlayerInterface>() && Props.SourceCharacter->Implements<UCombatInterface>())
		{
			//获取角色当前等级和经验
			const int32 CurrentLevel = ICombatInterface::Execute_GetPlayerLevel(Props.SourceCharacter);
			const int32 CurrentXP = IPlayerInterface::Execute_GetXP(Props.SourceCharacter);

			//获取获得经验后的新等级
			const int32 NewLevel = IPlayerInterface::Execute_FindLevelForXP(Props.SourceCharacter, CurrentXP + LocalIncomingXP);
			const int32 NumLevelUps = NewLevel - CurrentLevel; //查看等级是否有变化
			if(NumLevelUps > 0)
			{
				//获取升级提供的技能点和属性点
				int32 AttributePointsReward = IPlayerInterface::Execute_GetAttributePointsReward(Props.SourceCharacter, CurrentLevel);
				int32 SpellPointsReward = IPlayerInterface::Execute_GetSpellPointsReward(Props.SourceCharacter, CurrentLevel);

				//提升等级,增加角色技能点和属性点
				IPlayerInterface::Execute_AddToPlayerLevel(Props.SourceCharacter, NumLevelUps);
				IPlayerInterface::Execute_AddToAttributePoints(Props.SourceCharacter, AttributePointsReward);
				IPlayerInterface::Execute_AddToSpellPoints(Props.SourceCharacter, SpellPointsReward);

				IPlayerInterface::Execute_LevelUp(Props.SourceCharacter); //升级

				//将血量和蓝量填充满
				SetHealth(GetMaxHealth());
				SetMana(GetMana());
			}
			
			//将经验应用给自身,通过事件传递,在玩家角色被动技能GA_ListenForEvents里接收
			IPlayerInterface::Execute_AddToXP(Props.SourceCharacter, LocalIncomingXP);
		}
	}

在ui上面显示等级

我们现在角色有了等级,需要在UI上面表现出来。
我们首先处理回调,之前实现了经验的表现,我们在PlayerState类里面设置和升级时,会调用委托广播
在这里插入图片描述
那么,我们在UI Controller里面添加一个委托,用于广播给UI监听,等级为整型参数

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPlayerStateChangedSignature, int32, NewValue); //当玩家状态该表回调类型

接着创建对应类型的变量

	UPROPERTY(BlueprintAssignable, Category="GAS|Level")
	FOnPlayerStateChangedSignature OnPlayerLevelChangeDelegate; //等级变动回调

绑定一个匿名函数,用于监听PlayerState的等级回调

	//绑定等级相关回调
	RPGPlayerState->OnLevelChangedDelegate.AddLambda([this](int32 NewLevel)
	{
		OnPlayerLevelChangeDelegate.Broadcast(NewLevel);
	});

接下来,我们基于WBP_SpellGlobe复制一份,创建一个用于显示等级的ui
在这里插入图片描述
将使用图标设置的部分删除,我们不需要在等级显示UI里面设置图标
在这里插入图片描述
在设置控制器回调里面监听等级委托,回调触发修改等级显示文字
在这里插入图片描述
为了方便设置,我们可以修改对应怪物提供的经验值
在这里插入图片描述
将其拖入到WBP_Overlay上面,并设置对应控制器
在这里插入图片描述
接下来,我们就可以运行查看效果
在这里插入图片描述

创建角色图标

接下来,我们想在左上角显示对应角色的图标,这里使用WBP_GlobeProgressbar作为模版复制一份
在这里插入图片描述
命名为WBP_PictureFrame
在这里插入图片描述
移动到对应的文件夹中
在这里插入图片描述
删除一些无用的函数
在这里插入图片描述
增加一个用于显示头像的节点
在这里插入图片描述
设置对应头像
在这里插入图片描述
在WBP_Overlay里面应用,并设置好位置
在这里插入图片描述

设置升级效果

接下来,我们将上面略过的等级升级效果函数实现一下,我们需要增加一个粒子特效在升级时播放特效。由于特效是平面播放,它会跟随角色的角度,有时候看到是一个竖条的,所以我们还需要将相机在代码里创建,方便后续获取相机的旋转。
我们首先在代码里创建相机和弹簧臂

	UPROPERTY(VisibleAnywhere)
	TObjectPtr<UCameraComponent> TopDownCameraComponent; //相机组件

	UPROPERTY(VisibleAnywhere)
	TObjectPtr<USpringArmComponent> CameraBoom; //弹簧臂组件

在初始化时创建它们

	//设置相机
	CameraBoom = CreateDefaultSubobject<USpringArmComponent>("CameraBoom");
	CameraBoom->SetupAttachment(GetRootComponent());
	CameraBoom->SetUsingAbsoluteRotation(true);
	CameraBoom->bDoCollisionTest = false;

	TopDownCameraComponent = CreateDefaultSubobject<UCameraComponent>("TopDownCameraComponent");
	TopDownCameraComponent->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
	TopDownCameraComponent->bUsePawnControlRotation = false;

然后创建一个NiagaraComponent,用于设置使用的粒子特效,它需要在蓝图中设置使用的粒子资源

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
	TObjectPtr<UNiagaraComponent> LevelUpNiagaraComponent; //升级特效组件

在构造函数中初始化,并将自动播放关闭,我们在角色升级的时候让其播放

	//设置升级特效组件
	LevelUpNiagaraComponent = CreateDefaultSubobject<UNiagaraComponent>("LevelUpNiagaraComponent");
	LevelUpNiagaraComponent->SetupAttachment(GetRootComponent()); //设置附加组件
	LevelUpNiagaraComponent->bAutoActivate = false; //设置不自动激活

我们还需要创建一个支持多客户端调用的函数,它将在每个客户端都实现调用,这样,在每个客户端都能看到同样的效果

	UFUNCTION(NetMulticast, Reliable)
	void MulticastLevelUpParticles() const; //在多人游戏,每个客户端上播放升级特效

在实现这里,我们将通过相机和粒子位置的朝向,注意,这个朝向是世界坐标系的,然后通过朝向获取旋转,设置给粒子

void ARPGHero::MulticastLevelUpParticles_Implementation() const
{
	if(IsValid(LevelUpNiagaraComponent))
	{
		const FVector CameraLocation = TopDownCameraComponent->GetComponentLocation();
		const FVector NiagaraSystemLocation = LevelUpNiagaraComponent->GetComponentLocation();
		const FRotator TopCameraRotation = (CameraLocation - NiagaraSystemLocation).Rotation(); //获取相机位置和离职特效的朝向
		LevelUpNiagaraComponent->SetWorldRotation(TopCameraRotation); //设置粒子的转向
		LevelUpNiagaraComponent->Activate(true); //激活特效
	}
}

并在AS调用函数时,调用此函数,因为AS只在服务器运行,再调用此函数,实现了每个客户端的运行

void ARPGHero::LevelUp_Implementation()
{
	MulticastLevelUpParticles(); //调用播放升级特效
}

接着编译打开UE,设置粒子特效
在这里插入图片描述
并将之前在蓝图中创建的相机和弹簧臂删除掉,将之前的碰撞盒移动到代码创建的弹簧臂上面
在这里插入图片描述
删除蓝图创建的相机和弹簧臂
在这里插入图片描述
运行查看效果。
在这里插入图片描述

创建升级消息提示

接下来,我们优化效果,在升级时提供消息提示UI,并播放音效。
在这里插入图片描述
父类选择我们自定义的RPGUserWidget
在这里插入图片描述

命名为WBP_LevelUpMessage 为升级提示
在这里插入图片描述
我们在里面添加一个覆层,作为全屏显示,并在覆层下面添加一个包裹框,文字超出范围后会自动换行
在这里插入图片描述
为了防止文字在最顶部显示,我们在上面添加一个间隔区,用来填充空间,宽度设置的尽量大一些,防止文字在间隔区后面显示。
在这里插入图片描述
增加一个文本,设置好样式,让其可以占用一整行
在这里插入图片描述
下面添加一个水平框,框内设置两个文本,显示等级和实际等级数字
在这里插入图片描述
水平框设置对其和填充空间以及强制换新行,文本向左向右对其,这样,就可以居中显示
在这里插入图片描述
实际效果如下
在这里插入图片描述
我们也可以用上下设置垂直框,这样更方便设置
在这里插入图片描述
接着,我们要在WBP_Overlay里面在设置了控制器后,监听等级的变动,在监听到等级变化后,将现存的等级提示删除,然后接着创建一个新的控件,并将目标等级设置,并添加到视口。这样可以防止连续升级时,多层覆盖的问题。
在这里插入图片描述
运行查看效果。
在这里插入图片描述
接下来,我们优化一下,制作一个动画
在这里插入图片描述
在事件开始后,播放音效和动画,并延迟三秒将UI销毁
在这里插入图片描述
接下来查看最终效果
在这里插入图片描述

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Unreal Engine 5 (UE5)中实现合成系统可以通过以下步骤完成: 1. 创建一个Actor类,作为合成系统的基础。可以使用UE5自带的Actor类或者自定义一个Actor类。 2. 在Actor类中添加必要的变量和函数,例如合成的输入和输出物品、合成的材料、合成的效果等。 3. 创建一个UI界面,用于显示和控制合成系统。可以使用UE5自带的UMG或者其他UI框架来创建。 4. 在UI界面中添加按钮和其他控件,用于控制合成系统。例如,添加一个合成按钮,当用户点击该按钮时,合成系统将进行合成操作。 5. 在合成系统的函数中实现合成的逻辑。根据输入的材料和合成的效果,计算出合成的结果,并将结果输出到指定的位置。 6. 在UE5中创建一些测试场景,用于测试合成系统的功能和性能。 下面是一个简单的例子,演示如何使用UE5实现一个合成系统: 1. 创建一个Actor类,名为CraftingSystem。 2. 在CraftingSystem中添加以下变量和函数: ```c++ UPROPERTY(EditAnywhere) UStaticMeshComponent* OutputItemMesh; // 合成的输出物品 UPROPERTY(EditAnywhere) TArray<TSubclassOf<AActor>> InputItemClasses; // 合成的输入物品 UPROPERTY(EditAnywhere) TArray<int32> InputItemCounts; // 合成的输入物品数量 UPROPERTY(EditAnywhere) TSubclassOf<AActor> CraftEffectClass; // 合成的效果 UFUNCTION(BlueprintCallable) void Craft(); // 合成的函数 ``` 3. 创建一个UI界面,名为CraftingUI。 4. 在CraftingUI中添加一个合成按钮。 5. 在CraftingUI中为合成按钮添加点击事件,调用CraftingSystem的Craft函数。 6. 在CraftingSystem的Craft函数中实现合成的逻辑。计算出需要的输入物品和数量,如果输入物品足够,将其减少并生成一个输出物品和一个合成效果。 ```c++ void ACraftingSystem::Craft() { TArray<AActor*> inputItems; for (int32 i = 0; i < InputItemClasses.Num(); i++) { UClass* inputClass = InputItemClasses[i]; int32 inputCount = InputItemCounts[i]; TArray<AActor*> foundActors; UGameplayStatics::GetAllActorsOfClass(GetWorld(), inputClass, foundActors); int32 count = 0; for (AActor* actor : foundActors) { if (count >= inputCount) { break; } if (inputItems.Contains(actor)) { continue; } inputItems.Add(actor); count++; } if (count < inputCount) { UE_LOG(LogTemp, Error, TEXT("Not enough input items.")); return; } } for (AActor* actor : inputItems) { actor->Destroy(); } AActor* outputItem = GetWorld()->SpawnActor<AActor>(OutputItemClass

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值