114. UE5 GAS RPG 实现配置怪物生成

发布于:2024-12-06 ⋅ 阅读:(27) ⋅ 点赞:(0)

之前,我们怪物生成是直接将怪物蓝图拖入到场景中,这样好处是清晰明了,坏处就是场景怪物过多会造成卡顿,并且地图加载时间过长。我们想做一个通用的生成工具,可以配置敌人使用的蓝图等级和职业,并且在角色进入到一定位置后,才会生成敌人。

实现原理

如果需要实现这个功能,我们需要一个在场景添加的Actor来定义相关内容。
我们可以打开放置Actor面板
在这里插入图片描述
找到目标点
在这里插入图片描述
它可以标识一个场景中的位置,我们想通过以此为基类创建一个派生类,可以在上面设置生成的怪物的数据来实现生成。
在这里插入图片描述

添加新类

首先,我们基于TargetPoint定义一个新类,用于设置目标生成位置
在这里插入图片描述
在这里插入图片描述
然后定义一个Actor类,用于触发激活对应位置的Actor生成。
在这里插入图片描述
在这里插入图片描述
在ARPGEnemySpawnPoint 里,我们需要添加生成敌人实现所需的数据,所以我们定义了一个敌人类设置,敌人等级,以及敌人类型的设置。

// 版权归暮志未晚所有。

#pragma once

#include "CoreMinimal.h"
#include "AbilitySystem/Data/CharacterClassInfo.h"
#include "Engine/TargetPoint.h"
#include "RPGEnemySpawnPoint.generated.h"

/**
 * 
 */
UCLASS()
class RPG_API ARPGEnemySpawnPoint : public ATargetPoint
{
	GENERATED_BODY()

public:

	//生成敌人
	UFUNCTION(BlueprintCallable)
	void SpawnEnemy();

	//需要生成的敌人蓝图类,在类前面加class就不需要额外的前向申明
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Enemy Class")
	TSubclassOf<class ARPGEnemy> EnemyClass;

	//需要生成的敌人的等级
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Enemy Class")
	int32 EnemyLevel = 1;

	//敌人的职业类型
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Enemy Class")
	ECharacterClass CharacterClass = ECharacterClass::Warrior;
	
};

在生成敌人函数这里,我们采用延迟生成的方式,并设置Actor固定生成但可以调整位置

// 版权归暮志未晚所有。


#include "Actor/RPGEnemySpawnPoint.h"

#include "Character/RPGEnemy.h"

void ARPGEnemySpawnPoint::SpawnEnemy()
{
	//延迟生成Actor,并设置其尝试调整位置但固定生成
	ARPGEnemy* Enemy = GetWorld()->SpawnActorDeferred<ARPGEnemy>(
		EnemyClass,
		GetActorTransform(),
		nullptr,
		nullptr,
		ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn);
	
	Enemy->SetLevel(EnemyLevel);
	Enemy->SetCharacterClass(CharacterClass);
	Enemy->FinishSpawning(GetActorTransform());
	Enemy->SpawnDefaultController();
}

接着在ARPGEnemySpawnVolume里,它作为ARPGEnemySpawnPoint 触发工具,在于此碰撞体积碰撞后,设置在此体积里的对应的怪物都将自动生成。
所以,我们在ARPGEnemySpawnVolume里增加一个碰撞体,并增加碰撞体对应的重叠函数,添加bReached 项,用于判断当前是否已经由玩家角色激活过,并添加SpawnPoints用于设置与此碰撞体重叠后,会生成多少敌人。

// 版权归暮志未晚所有。

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Interaction/SaveInterface.h"
#include "RPGEnemySpawnVolume.generated.h"

class ARPGEnemySpawnPoint;
class UBoxComponent;

UCLASS()
class RPG_API ARPGEnemySpawnVolume : public AActor, public ISaveInterface
{
	GENERATED_BODY()
	
public:	
	ARPGEnemySpawnVolume();

	/* Save Interface */
	virtual void LoadActor_Implementation() override;
	/* Save Interface 结束 */

	//当前怪物生成体积是否已经生成过敌人
	UPROPERTY(BlueprintReadOnly, SaveGame)
	bool bReached = false;

protected:
	virtual void BeginPlay() override;
	
	UFUNCTION()
	virtual void OnBoxOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);

	//配置当前的生成体积所需生成的敌人
	UPROPERTY(EditAnywhere)
	TArray<ARPGEnemySpawnPoint*> SpawnPoints;
private:

	UPROPERTY(VisibleAnywhere)
	TObjectPtr<UBoxComponent> Box;
};

在构造函数里,我们将创建一个碰撞盒子实例,并只会和角色触发重叠事件

ARPGEnemySpawnVolume::ARPGEnemySpawnVolume()
{
	PrimaryActorTick.bCanEverTick = false;

	Box = CreateDefaultSubobject<UBoxComponent>("Box");
	SetRootComponent(Box);
	Box->SetCollisionEnabled(ECollisionEnabled::QueryOnly); //仅用于坚持
	Box->SetCollisionObjectType(ECC_WorldStatic); //设置物体类型
	Box->SetCollisionResponseToChannels(ECR_Ignore); //忽略所有检测通道
	Box->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap); //开启与pawn的重叠检测
}

由于此类继承了Save接口,我们将bReached属性保存到了存档,当进入此地图时,将读取此属性,并根据此属性进行处理,当前属性会在玩家角色与碰撞体接触后设置为true,在玩家再次进入场景时,我们不期望再触发一次,如果值为true我们将直接销毁此Actor。

void ARPGEnemySpawnVolume::LoadActor_Implementation()
{
	if(bReached)
	{
		Destroy();
	}
}

在事件开始时,我们绑定Box碰撞体的重叠事件

void ARPGEnemySpawnVolume::BeginPlay()
{
	Super::BeginPlay();

	Box->OnComponentBeginOverlap.AddDynamic(this, &ThisClass::OnBoxOverlap);
}

在重叠事件里,判断重叠的Actor是否继承玩家接口,玩家角色都会继承此接口(或者通过Actor标签也可以)。随后我们将bReached 设置为true,并将添加到SpawnPoints里的所有敌人调用SpawnEnemy函数生成,最后将此Box设置为无碰撞来节约性能。

void ARPGEnemySpawnVolume::OnBoxOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	if(!OtherActor->Implements<UPlayerInterface>()) return;
	
	bReached = true;

	//在设置的所有点位生成敌人
	for(ARPGEnemySpawnPoint* SpawnPoint : SpawnPoints)
	{
		if(IsValid(SpawnPoint))
		{
			SpawnPoint->SpawnEnemy();
		}
	}

	//设置不在产生物理查询,直接销毁无法保存到存档
	Box->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}

以下是cpp的全部代码。

// 版权归暮志未晚所有。


#include "Actor/RPGEnemySpawnVolume.h"

#include "Actor/RPGEnemySpawnPoint.h"
#include "Components/BoxComponent.h"
#include "Interaction/PlayerInterface.h"

// Sets default values
ARPGEnemySpawnVolume::ARPGEnemySpawnVolume()
{
	PrimaryActorTick.bCanEverTick = false;

	Box = CreateDefaultSubobject<UBoxComponent>("Box");
	SetRootComponent(Box);
	Box->SetCollisionEnabled(ECollisionEnabled::QueryOnly); //仅用于坚持
	Box->SetCollisionObjectType(ECC_WorldStatic); //设置物体类型
	Box->SetCollisionResponseToChannels(ECR_Ignore); //忽略所有检测通道
	Box->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap); //开启与pawn的重叠检测
}

void ARPGEnemySpawnVolume::LoadActor_Implementation()
{
	if(bReached)
	{
		Destroy();
	}
}

void ARPGEnemySpawnVolume::BeginPlay()
{
	Super::BeginPlay();

	Box->OnComponentBeginOverlap.AddDynamic(this, &ThisClass::OnBoxOverlap);
}

void ARPGEnemySpawnVolume::OnBoxOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	if(!OtherActor->Implements<UPlayerInterface>()) return;
	
	bReached = true;

	//在设置的所有点位生成敌人
	for(ARPGEnemySpawnPoint* SpawnPoint : SpawnPoints)
	{
		if(IsValid(SpawnPoint))
		{
			SpawnPoint->SpawnEnemy();
		}
	}

	//设置不在产生物理查询,直接销毁无法保存到存档
	Box->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}



创建蓝图

我们创建了对应的c++类,接着编译打开蓝图,我们要基于类创建对应的蓝图,并应用到关卡中。
首先创建一个敌人生成体积蓝图
在这里插入图片描述
然后创建一个敌人位置蓝图
在这里插入图片描述
定义好名称
在这里插入图片描述
在点位里,我们定义好使用的敌人类,等级和职业类型
在这里插入图片描述
在敌人生成体积里,我们可以设置碰撞体盒子线条宽度,以及在场景中显示,方便测试。
在这里插入图片描述
接着,我们在关卡中拖入一个体积和两个位置节点,拖入场景相当于生成了对应蓝图的实例。
在这里插入图片描述
接着,我们在体积上添加把所需的位置节点添加,并进行测试即可。
在这里插入图片描述
位置节点和体积分离,可以让位置节点复用,它可以添加到多个体积里。


网站公告

今日签到

点亮在社区的每一天
去签到