【UE4C++-ActionRougelike-15】作业4+玩家重生+控制台变量


【UE4C++-ActionRougelike-15】作业4+玩家重生+控制台变量

一、第四次作业

作业要求:新的AI行为:当低血量时会藏起来回血

  • Service节点:检查AI是否低血量
  • EQS:找到距离AI近的可隐藏地点
  • BT Task:治疗自己到最大生命值
  • 该行为60s只能运行一次

1、修改行为树

按照作业要求我们知道该行为大致流程为:检查是否残血(Service Node)->找到躲藏地点(EQS生成地点)->回血(TaskNode执行任务)

躲藏回血行为树

注意IsLowHealth的Observer aborts要设置为低优先级(Lower Priority),表示中止此节点右侧所有节点,这样能够在AI执行攻击时,如果AI变为残血,会中止攻击而执行躲藏回血行为。

2、创建检查残血ServiceNode

以BTService为父类创建检查残血服务节点类MyBTService_CheckHealth

UCLASS()
class ACTIONROGUELIKE_API UMyBTService_CheckHealth : public UBTService
{
	GENERATED_BODY()
public:
	UMyBTService_CheckHealth();
protected:
	virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
	//设置“是否残血”的黑板键
	UPROPERTY(EditAnywhere, Category = "AI")
	FBlackboardKeySelector LowHealthKey;
	//残血的界限
	UPROPERTY(EditAnywhere, Category = "AI")
	float LowHealthFraction;
};

在MyBTService_CheckHealth.cpp中给出检查残血的实现

UMyBTService_CheckHealth::UMyBTService_CheckHealth()
{
    //初始化残血界限值
	LowHealthFraction = 0.3f;
}
void UMyBTService_CheckHealth::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
	Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);
	APawn* MyPawn = Cast<APawn>(OwnerComp.GetAIOwner()->GetPawn());
	if (ensure(MyPawn))
	{
		UMyAttributeComponent* AIAttributeComp = UMyAttributeComponent::GetAttributes(MyPawn);
		if (AIAttributeComp)
		{
			UBlackboardComponent* BlackboardComp = OwnerComp.GetBlackboardComponent();
             //检查是否残血(要在MyAttributeComponent中新增GetHealth方法,代码很简单return Health即可)
			bool bLowHealth = AIAttributeComp->GetHealth() / AIAttributeComp->GetHealthMax() < LowHealthFraction;
			BlackboardComp->SetValueAsBool(LowHealthKey.SelectedKeyName, bLowHealth);
		}
	}
}

3、创建查询躲藏点EQS

这里可以采用适合的生成节点和参数来进行生成,此处与课程中选择一样为Donut生成器节点

寻找躲藏点EQS
3.1 Distance节点设置
  • Test Purpose设置为Filter and score,表示根据距离进行过滤和计分。
  • Filter设置为Mininum和0,表示小于0的点全部过滤(在我们的生成器情况下等于保留全部点)
  • Scoring Factor设置为-5.0,表示离目标(自己)越近的点得分越高(这里只要为负数,数值可以自行设置)
Distance节点设置
3.2 Trace节点设置
  • Test Purpose设置为Filter Only,表示仅过滤
  • Context设置为TargetActor,表示用于检测查询生成的位置与TargetActor之间是否存在遮挡物
Trace节点设置
3.3 PathFinding节点设置
  • TestMode设置为Path Length,表示计算从上下文参考点(TargetActor)到查询生成的点之间的路径长度
  • Filter 设置同Distance一样
PathFinding节点设置

4、创建回血TaskNode

以BTTaskNode为父类创建回血类任务节点MyBTTask_HealSelf

UCLASS()
class ACTIONROGUELIKE_API UMyBTTask_HealSelf : public UBTTaskNode
{
	GENERATED_BODY()
protected:
	virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;

};

在MyBTTask_HealSelf.cpp中重写UBTTaskNode 的 ExecuteTask 函数,用于在行为树中执行回血

EBTNodeResult::Type UMyBTTask_HealSelf::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
	APawn* MyPawn = Cast<APawn>(OwnerComp.GetAIOwner()->GetPawn());
	if (MyPawn == nullptr)
	{
		return EBTNodeResult::Failed;
	}
	UMyAttributeComponent* AttributeComp = UMyAttributeComponent::GetAttributes(MyPawn);
	if (ensure(AttributeComp))
	{
		AttributeComp->ApplyHealthChange(MyPawn, AttributeComp->GetHealthMax());
	}
	return EBTNodeResult::Succeeded;
}

5、效果演示

残血躲藏回血效果演示

二、玩家重生

1、游戏模式中添加玩家重生逻辑

在MyGameMode.h中添加重生玩家函数,重写OnActorKilled函数。

class ACTIONROGUELIKE_API AMyGameModeBase : public AGameModeBase
{
protected:	
    //用于在指定时间后重生玩家角色
	UFUNCTION()
	void RespawnPlayerElapsed(AController* Controller);
public:
    //OnActorKilled是一个虚函数,可以在继承自AActor的类中重载。会在该Actor被杀死时被调用,参数VictimActor为被杀死的Actor,Killer为杀死该Actor的Actor
	virtual void OnActorKilled(AActor* VictimActor, AActor* Killer);
}

在MyGameMode.cpp中处理玩家角色的重生。当玩家角色被击败时,触发OnActorKilled函数,该函数将为玩家角色设置一个2秒的重生延迟,然后通过RespawnPlayerElapsed函数重置玩家角色。

void AMyGameModeBase::RespawnPlayerElapsed(AController* Controller)
{
	if (ensure(Controller))
	{
         // 解除角色的占用
		Controller->UnPossess();
         // 解除角色的占用
		RestartPlayer(Controller);
	}
}
void AMyGameModeBase::OnActorKilled(AActor* VictimActor, AActor* Killer)
{
    // 尝试将被击败的角色转换为 AMyCharacter 类型
	AMyCharacter* Player = Cast<AMyCharacter>(VictimActor);
     // 如果转换成功,说明被击败的角色是一个玩家角色
	if (Player)
	{
         // 创建一个定时器句柄
		FTimerHandle TimerHandle_RespawnDelay;
		// 创建一个定时器代理,并绑定 RespawnPlayerElapsed 函数
		FTimerDelegate Delegate;
		Delegate.BindUFunction(this, "RespawnPlayerElapsed", Player->GetController());
		// 设置重生延迟
		float RespawnDelay = 2.0f;
         // 设置定时器,2秒后触发 RespawnPlayerElapsed 函数
		GetWorldTimerManager().SetTimer(TimerHandle_RespawnDelay, Delegate, RespawnDelay, false);
	}

	UE_LOG(LogTemp, Log, TEXT("OnActorKilled: Victim: %s, Killer: %s"), *GetNameSafe(VictimActor), *GetNameSafe(Killer));
}

2、玩家死亡时调用Actor死亡函数

MyAttribute.cpp中的ApplyHealthChange添加死亡判断逻辑,如果玩家死亡,则触发GameMode中的OnActorKilled函数。

bool UMyAttributeComponent::ApplyHealthChange(AActor* InstigatorActor, float Delta)
{
	if (ActualDelta < 0.0f && Health == 0.0f)
	{
         // 获取游戏模式对象 AMyGameModeBase
		AMyGameModeBase* GM = GetWorld()->GetAuthGameMode<AMyGameModeBase>();
		if (GM)
		{
             // 触发游戏模式中的 OnActorKilled 函数,通知角色被击败
			GM->OnActorKilled(GetOwner(), InstigatorActor);
		}
	}

	return ActualDelta != 0;
}

3、效果演示

玩家重生效果演示

三、控制台变量和静态函数库

1、控制台变量

1.1 禁止生成机器人

在MyGameModeBase.cpp中添加是否禁止生成机器人控制台变量CVarSpawnBots

// 定义一个静态控制台变量(Console Variable),用于控制是否通过计时器生成机器人
static TAutoConsoleVariable<bool> CVarSpawnBots(
    TEXT("su.SpawnBots"), // 控制台变量名
    true,                 // 默认值,表示默认允许通过计时器生成机器人
    TEXT("Enable spawning of bots via timer."), // 描述文本
    ECVF_Cheat            // 控制台变量标志,表示这是一个作弊命令
);

在SpawnBotTimerElapsed方法最开始处添加根据静态控制台变量CVarSpawnBots来禁止机器人生成的逻辑

void AMyGameModeBase::SpawnBotTimerElapsed()
{
	// 在游戏线程上获取 CVarSpawnBots 控制台变量的值
	if (!CVarSpawnBots.GetValueOnGameThread())
	{
		// 如果 CVarSpawnBots 的值为 false(禁止生成机器人),则打印一条警告日志,直接返回,不执行后续的生成机器人操作。
		UE_LOG(LogTemp, Warning, TEXT("Bot spawning disabled via cvar 'CVarSpawnBots'."));
		return;
	}
}
1.2 伤害倍数

在MyGameModeBase.cpp中添加伤害倍数控制台变量CVarDamageMultiplier

// 定义一个静态控制台变量(Console Variable),用于控制全局伤害乘数
static TAutoConsoleVariable<float> CVarDamageMultiplier(
    TEXT("su.DamageMultiplier"),  // 控制台变量名
    1.0f,                         // 默认值,表示默认的伤害乘数为 1.0
    TEXT("Global Damage Modifier for Attribute Component."), // 描述文本
    ECVF_Cheat                    // 控制台变量标志,表示这是一个作弊命令
);

在ApplyHealthChange中添加伤害倍数的逻辑

bool UMyAttributeComponent::ApplyHealthChange(AActor* InstigatorActor, float Delta)
{
	// 如果 Delta 值小于 0,即表示要对角色造成伤害
	if (Delta < 0.0f)
	{
		// 获取 CVarDamageMultiplier 控制台变量在游戏线程上的值
		float DamageMultiplier = CVarDamageMultiplier.GetValueOnGameThread();
		// 根据全局伤害乘数调整 Delta 值
		Delta *= DamageMultiplier;
	}
}
1.3 显示调试绘图

在SInteractionComponent.cpp中添加是否显示调试线控制台变量CVarDebugDrawInteraction

// 定义一个静态控制台变量(Console Variable),用于控制是否显示交互组件的调试线
static TAutoConsoleVariable<bool> CVarDebugDrawInteraction(
    TEXT("su.InteractionDebugDraw"), // 控制台变量名
    false,                           // 默认值,表示默认情况下禁用调试线
    TEXT("Enable Debug Lines for Interact Component."), // 描述文本
    ECVF_Cheat                        // 控制台变量标志,表示这是一个作弊命令
);

之后的展示调试信息代码就可以加上if语句来判断是否显示调试绘图信息

bool bDebugDraw = CVarDebugDrawInteraction.GetValueOnGameThread();
if(bDebugDraw)
{
	//...调试信息
}

注:这里之前检测玩家与其他物品交互时选取的是角色视角的位置,会产生偏差。在MyCharacter中我们对GetPawnViewLocation进行重写,调用了CameraComp->GetComponentLocation()来获取摄像机组件的位置。这个位置作为角色视角的位置返回,当获取角色视角位置时,可以确保使用的是与角色关联的摄像机的位置。使得能够正确计算射线投射。

FVector AMyCharacter::GetPawnViewLocation() const
{
    // 返回摄像机组件的位置,作为角色视角的位置
	return CameraComp->GetComponentLocation();
}
1.4 效果演示
控制台变量效果演示

2、攻击击飞效果

2.1 新建全局静态函数库

UBlueprintFunctionLibrary是Unreal Engine 4中的一个基类,用于创建自定义的全局静态函数库。通过继承UBlueprintFunctionLibrary类并实现自定义函数,开发者可以在蓝图和C++代码中使用这些函数,方便地实现游戏逻辑和功能。

以BlueprintFunctionLibrary为父类新建C++类MyGameplayFunctionLibrary

UCLASS()
class ACTIONROGUELIKE_API UMyGameplayFunctionLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()
public:
	//实现对目标角色应用伤害
	UFUNCTION(BlueprintCallable, Category = "Gameplay")
	static bool ApplyDamage(AActor* DamageCauser, AActor* TargetActor, float DamageAmount);
	//实现对目标角色应用方向性伤害
	UFUNCTION(BlueprintCallable, Category = "Gameplay")
	static bool ApplyDirectionalDamage(AActor* DamageCauser, AActor* TargetActor, float DamageAmount, const FHitResult& HitResult);
};

在MyGameplayFunctionLibrary.cpp中实现两个应用伤害方法

bool UMyGameplayFunctionLibrary::ApplyDamage(AActor* DamageCauser, AActor* TargetActor, float DamageAmount)
{
    UMyAttributeComponent* AttributeComp = UMyAttributeComponent::GetAttributes(TargetActor);
    if (AttributeComp)
    {
        // 对目标角色应用伤害,调用 UMyAttributeComponent 的 ApplyHealthChange 函数
        return AttributeComp->ApplyHealthChange(DamageCauser, -DamageAmount);
    }
    return false;
}
bool UMyGameplayFunctionLibrary::ApplyDirectionalDamage(AActor* DamageCauser, AActor* TargetActor, float DamageAmount, const FHitResult& HitResult)
{
    // 如果成功应用伤害
    if (ApplyDamage(DamageCauser, TargetActor, DamageAmount))
    {
        // 获取受到伤害的组件
        UPrimitiveComponent* HitComp = HitResult.GetComponent();
        // 如果组件存在并且在命中的骨骼上模拟物理效果
        if (HitComp && HitComp->IsSimulatingPhysics(HitResult.BoneName))
        {
            // 在受到伤害的位置添加一个反方向的冲击力
            HitComp->AddImpulseAtLocation(-HitResult.ImpactNormal * 300000.f, HitResult.ImpactPoint, HitResult.BoneName);
        }
        // 返回 true,表示成功应用方向性伤害
        return true;
    }
    // 如果未成功应用伤害,则返回 false
    return false;
}
2.2 调用攻击击飞函数

在MyMagicProjectile.cpp中修改攻击命中函数OnActorOverlap,应用新的可击飞攻击伤害函数。

void AMyMagicProjectile::OnActorOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	if (OtherActor && OtherActor != GetInstigator())
	{        
		if (UMyGameplayFunctionLibrary::ApplyDirectionalDamage(GetInstigator(), OtherActor, DamageAmount, SweepResult))
		{
			Explode();
		}
	}
}

注:要在MyAICharacter中设置碰撞设置和死亡后禁止碰撞体

//构造函数中
GetCapsuleComponent()->SetCollisionResponseToChannel(ECC_WorldDynamic, ECR_Ignore);
GetMesh()->SetGenerateOverlapEvents(true);

//AI死亡逻辑中
GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
GetCharacterMovement()->DisableMovement();
2.3 效果演示
攻击击飞效果演示

文章作者: Woilin
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Woilin !
评论
  目录