116. UE5 GAS RPG 实现击杀掉落战利品功能

这一篇,我们实现敌人被击败后,掉落战利品的功能。首先,我们将创建一个新的结构体,用于定义掉落体的内容,方便我们设置掉落物。然后,我们实现敌人死亡时的掉落函数,并在蓝图里实现对应的逻辑,在场景里生成掉落物。最后,让掉落物动起来,显得掉落物需要玩家赶紧去拾取的感觉。

添加新的资产结构体

为了实现对敌人掉落战利品的配置,我们需要创建一个新的类,作为配置掉落物的新的资产类。

命名为战利品类

在类里,我们首先添加一个结构体,用于设置一种物品的掉落内容和几率。

cpp 复制代码
USTRUCT(BlueprintType)
struct FLootItem
{
	GENERATED_BODY()

	//战利品在场景中的显示效果
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="LootTiers|Spawning")
	TSubclassOf<AActor> LootClass;

	//战利品生成几率
	UPROPERTY(EditAnywhere, Category="LootTiers|Spawning")
	float ChanceToSpawn = 0.f;

	//物品生成的最大数量
	UPROPERTY(EditAnywhere, Category="LootTiers|Spawning")
	int32 MaxNumberToSpawn = 0.f;

	//修改物品生成等级,false则使用敌人等级
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="LootTiers|Spawning")
	bool bLootLevelOverride = true;
};

然后在类里,我们增加一个数组,开发者可以配置多个掉落物,然后添加一个函数,用于获取当前可以生成的战利品数组。

cpp 复制代码
UCLASS()
class RPG_API ULootTiers : public UDataAsset
{
	GENERATED_BODY()

public:

	//获取需要生成的战利品数据
	UFUNCTION(BlueprintCallable)
	TArray<FLootItem> GetLootItems();

	UPROPERTY(EditDefaultsOnly, Category="LootTiers|Spawning")
	TArray<FLootItem> LootItems;
};

在函数实现这里,我们创建了一个新的数组,用于根据概率计算每个物品的是否可掉落,物品可以设置多个,所以,我们需要两层嵌套循环,如果当前可掉落,那么我们将其添加到返回数组里。最后返回。

cpp 复制代码
TArray<FLootItem> ULootTiers::GetLootItems()
{
	TArray<FLootItem> ReturnItems;

	for(const FLootItem Item : LootItems)
	{
		for(int32 i=0; i<Item.MaxNumberToSpawn; ++i)
		{
			if(FMath::RandRange(1.f, 100.f) < Item.ChanceToSpawn)
			{
				FLootItem NewItem;
				NewItem.LootClass = Item.LootClass;
				NewItem.bLootLevelOverride = Item.bLootLevelOverride;
				ReturnItems.Add(NewItem);
			}
		}
	}

	return ReturnItems;
}

实现配置和触发掉落逻辑

然后,我们需要一个能够去获取配置好的数据资产的地方,最方便的就是放到GameMode里,我们在GameMode类里添加一个配置,可以在蓝图配置使用哪一个数据资产

cpp 复制代码
	//战利品数据配置
	UPROPERTY(EditDefaultsOnly, Category="Loot Tiers")
	TObjectPtr<ULootTiers> LootTiers;
	

为了方便获取数据资产,我们在蓝图函数库里增加一个函数,用于获取数据资产

cpp 复制代码
	/**
	 * 获取生成的战利品数据资产,此数据会配置到GameMode上
	 * @param WorldContextObject  一个世界场景的对象,用于获取当前所在的世界
	 * @return 战利品数据
	 *
	 * @note 敌人死亡后,所需生成的战利品
	 */
	UFUNCTION(BlueprintCallable, Category="RPGAbilitySystemLibrary|CharacterClassDefaults", meta=(DefaultToSelf = "WorldContextObject"))
	static ULootTiers* GetLootTiers(const UObject* WorldContextObject);

函数实现,我们将获取到GameMode,并从GameMode身上获取到数据资产

cpp 复制代码
ULootTiers* URPGAbilitySystemLibrary::GetLootTiers(const UObject* WorldContextObject)
{
	//获取到当前关卡的GameMode实例
	const ARPGGameMode* GameMode = Cast<ARPGGameMode>(UGameplayStatics::GetGameMode(WorldContextObject));
	if(GameMode == nullptr) return nullptr;

	//返回敌人战利品配置,需要设置到GameMode上
	return  GameMode->LootTiers;
}

最后,我们要在敌人类里实现掉落逻辑,我们增加一个函数,这个函数需要在蓝图里实现逻辑

cpp 复制代码
	//生成战利品
	UFUNCTION(BlueprintImplementableEvent)
	void SpawnLoot();

然后在触发死亡逻辑时,我们调用此函数来生成战利品

添加蓝图

代码方便,我们完成了,接着,我们要在编辑器里,添加一个新的数据资产

资产类型使用我们创建的战利品资产

我们将其和之前的资产放到一块

然后,在资产里,我们将之前制作的药瓶和水晶(持续回血)添加到战利品里,掉落概率设置为100,并且要使用敌人等级掉落。

接着将其设置到GameMode蓝图里

我们在敌人蓝图基类里实现统一的掉落战利品机制,首先通过蓝图库函数获取到数据资产实例,然后通过GetLootItems获取到需要生成的掉落物

然后创建一个蓝图函数,用于通过蓝图函数库,来设置掉落物的转向。

然后我们将其设置为纯函数,这样,不需要通过执行箭头调用。

然后将转向存储起来方便后续使用

然后,我们通过一个定时器,让物品掉落持续生成,这样可以防止卡顿,并且物品有一个个生成的效果,我们在设置定时器时,直接调用一次此事件。

接着就是生成战利品的自定义事件,我们通过索引,从返回的数组里获取到需要生成的对应的数据,并计算出物品掉落位置,最后生成Actor

我们创建获取变换的纯函数,通过转向对敌人位置进行一个朝向在一定范围内随机偏移,并使用旋转的朝向。

在创建Actor后,我们需要更新掉落物的等级,如果此物品需要修改等级,那么我们将通过此函数内的逻辑进行修改

函数内,我们首先判断是否需要修改,然后将其转换为场景物品的基类,然后判断当前物品是否存在,最后将敌人等级应用到掉落物身上。

最后一步,就是修改索引,每调用一次,我们将索引+1,下次再调用此事件时,将会生成下一个坐标的物品,如果索引超过或等于数组长度时,我们将结束定时器,完成掉落物的生成。

最后展示一下完成的蓝图连线

然后进入关卡打怪测试效果

关于掉落物的一些扩展

如果后续扩展的话,我考虑对于每个大关卡创建单独的一套掉落,然后在数组资产里增加几个数组,比如效果,使用小怪的一套掉落,而精英怪使用精英怪的一套掉落,最后是boss的掉落,使用boss的一套掉落逻辑。

然后获取掉落时,可以根据敌人品质,去获取不同的掉落,我们当然没必须单独为敌人去配置掉落。BOSS除外,我们当然可以对一些特殊BOSS去配置单独的掉落,比如关卡的最终BOSS掉落,以及一些特殊怪物掉落。

实现掉落物自动旋转和悬浮效果

为了实现这个效果,我们要在掉落物的基类RPGEffectActor里增加一些属性和函数,用于实现此效果

要实现这个功能,我们增加一批函数,用于实现此功能

cpp 复制代码
	// 计算后的Actor所在的位置
	UPROPERTY(BlueprintReadWrite)
	FVector CalculatedLocation;

	// 计算后的Actor的旋转
	UPROPERTY(BlueprintReadWrite)
	FRotator CalculatedRotation;

	// Actor是否帧更新旋转
	UPROPERTY(BlueprintReadWrite, Category="Pickup Movement")
	bool bRotates = false;

	// Actor每秒旋转的角度
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Pickup Movement")
	float RotationRate = 45.f;

	// Actor是否更新位置
	UPROPERTY(BlueprintReadWrite, Category="Pickup Movement")
	bool bSinusoidalMovement = false;

	// 正弦值-1到1,此值为调整更新移动范围
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Pickup Movement")
	float SineAmplitude = 1.f;

	// 此值参与正弦运算,默认值为1秒一个循环(2PI走完一个正弦的循环,乘以时间,就是一秒一个循环,可用于调整位置移动速度)
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Pickup Movement")
	float SinePeriod = 1.f; //2 * PI

	//调用此函数,Actor开始自动更新上下位置
	UFUNCTION(BlueprintCallable)
	void StartSinusoidalMovement();

	//调用此函数,Actor开始自动旋转
	UFUNCTION(BlueprintCallable)
	void StartRotation();
private:

	//当前掉落物的存在时间,可以通过此时间实现动态效果
	float RunningTime = 0.f;

	// Actor生成的默认初始位置,在Actor动态浮动时,需要默认位置作为基础位置
	FVector InitialLocation;

	// 每一帧更新Actor的位置和转向
	void ItemMovement(float DeltaSeconds);

我们还需要用到帧更新函数

cpp 复制代码
virtual void Tick(float DeltaSeconds) override;

在事件开始时,我们将掉落物的默认位置和旋转保存,并设置计算后的属性,我们需要在后续使用它更新Actor

cpp 复制代码
void ARPGEffectActor::BeginPlay()
{
	Super::BeginPlay();

	//设置初始位置
	InitialLocation = GetActorLocation();
	CalculatedLocation = InitialLocation;
	CalculatedRotation = GetActorRotation();
}

我们需要在帧更新了去保存当前效果的执行时间,并调用更新函数

cpp 复制代码
void ARPGEffectActor::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);
	//更新当前Actor的存在时间
	RunningTime += DeltaSeconds;
	ItemMovement(DeltaSeconds);
}

在更新函数里,我们根据变量判断是否需要更新,来对转向和位置更新,这里需要提到的是,位置更新用到了正弦三角函数进行更新

cpp 复制代码
void ARPGEffectActor::ItemMovement(float DeltaSeconds)
{
	//更新转向
	if(bRotates)
	{
		const FRotator DeltaRotation(0.f, DeltaSeconds * RotationRate, 0.f);
		CalculatedRotation = UKismetMathLibrary::ComposeRotators(CalculatedRotation, DeltaRotation);
	}
	//更新位置
	if(bSinusoidalMovement)
	{
		const float Sine = SineAmplitude * FMath::Sin(RunningTime * SinePeriod * 6.28318f);
		CalculatedLocation = InitialLocation + FVector(0.f, 0.f, Sine);
	}
}

你会发现上面的变量无法在蓝图面板直接设置,那么如何将其设置为true呢,我们通用函数将其设置为true

cpp 复制代码
void ARPGEffectActor::StartSinusoidalMovement()
{
	bSinusoidalMovement = true;
	InitialLocation = GetActorLocation();
	CalculatedLocation = InitialLocation;
}

void ARPGEffectActor::StartRotation()
{
	bRotates = true;
	CalculatedRotation = GetActorRotation();
}

后续效果我们需要在蓝图里实现,所以,我们创建一个拾取的基类,然后将所有可掉落物都继承此蓝图,没必要在每个蓝图里实现一遍

我们在拾取物基类里,创建一个时间轴,来实现掉落物的从无到有的效果,并通过时间轴的更新实现一些位置更新,和缩放效果。在时间轴播放完成后,我们调用开始旋转和开始移动的默认效果。

时间轴里,我们增加了两个轨道,用于分别更新位置和缩放使用

在帧更新里,我们通过计算后的位置和旋转更新Actor即可

以下是拾取物的表现效果

添加音效

最后一个功能,我们在Actor里添加一些音效,来实现一些点缀效果。

首先在Actor增加一个变量,设置音效基础类型

然后找到对应的音效

设置给变量

在更新位置和缩放后(我折叠成了一个函数),我们通过修改位置的值,判断如果大于0.3时,执行一次模拟触碰到地面的音效

然后在事件开始时增加一个生成的音效

在销毁时,播放一个拾取的音效。

相关推荐
AA陈超21 小时前
虚幻引擎UE5专用服务器游戏开发-20 添加基础能力类与连招能力
c++·游戏·ue5·游戏引擎·虚幻
平行云1 天前
如何实现UE程序大并发多集群的像素流部署
unity·ue5·图形渲染
我的巨剑能轻松搅动潮汐1 天前
UE5 C++ 下载视频到本地。//BindLambda用法解析
ue5
不爱说话的采儿3 天前
UE5详细保姆教程(第四章)
笔记·ue5·游戏引擎·课程设计
二DUAN帝3 天前
UE实现路径回放、自动驾驶功能简记
人工智能·websocket·机器学习·ue5·自动驾驶·ue4·cesiumforue
温玉琳琅3 天前
【UE5】虚幻引擎小百科
ue5·游戏引擎·虚幻
远离UE44 天前
UE 材质 变体 概念
ue5·材质
吴梓穆6 天前
UE5 重新编译插件版本
ue5
HECUgauss6 天前
UE5 使用过程遇到的问题
ue5
小白学过的代码6 天前
ue5.4和webul通信
开发语言·javascript·ue5