【UE4C++-ActionRougelike-20】作业6


【UE4C++-ActionRougelike-20】作业6

一、愤怒属性

1、添加愤怒属性

在属性组件MyAttributeComponent.h中添加愤怒值和最大愤怒值属性,声明愤怒值变换多播事件FOnRageChanged,以及愤怒值修改函数。

//愤怒值变化多播事件
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FOnRageChanged, AActor*, InstigatorActor, UMyAttributeComponent*, OwningComp, float, NewRage, float, Delta);
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class ACTIONROGUELIKE_API UMyAttributeComponent : public UActorComponent
{
protected:
    //愤怒值
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Replicated, Category = "Attributes")
	float Rage;
    //最大愤怒值
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Replicated, Category = "Attributes")
	float MaxRage;
public:	
	//多播委托FOnRageChanged
	UPROPERTY(BlueprintAssignable)
	FOnRageChanged OnRageChange;
	//对愤怒值的改变
	UFUNCTION(BlueprintCallable, Category = "Attributes")
	bool ApplyRageChange(AActor* InstigatorActor, float Delta);
	//获取愤怒值
	UFUNCTION(BlueprintCallable)
	float GetRage() const;
}

在MyAttributeComponent.cpp中实现愤怒值修改函数。

bool UMyAttributeComponent::ApplyRageChange(AActor* InstigatorActor, float Delta)
{
	float OldRage = Rage;
	//将 Rage 的值限制在 0 到 MaxRage 之间
	Rage = FMath::Clamp(Rage + Delta, 0.0f, MaxRage);
	//计算实际的Rage变化量
	float ActualDelta = Rage - OldRage;
	if (ActualDelta != 0.0f) 
	{
		OnRageChange.Broadcast(InstigatorActor,this, Rage,ActualDelta);
	}
	return ActualDelta != 0;
}

2、受到伤害增加愤怒值

在MyCharacter.cpp中的OnHealthChanged中在玩家受到伤害时,调用属性组件的ApplyRageChange方法来修改愤怒值

void AMyCharacter::OnHealthChanged(AActor* InstigatorActor, UMyAttributeComponent* OwningComp, float NewHealth, float Delta)
{
	if (Delta < 0.0f)
	{
		GetMesh()->SetScalarParameterValueOnMaterials(TimeToHitParamName, GetWorld()->TimeSeconds);
        //当玩家生命值受到伤害时,属性组件应用愤怒值修改
		AttributeComp->ApplyRageChange(InstigatorActor, -Delta);
	}
}

3、增加愤怒值UI

创建Widget蓝图PlayerRage_Widget,逻辑与PlayerHealth_Widget几乎一致,区别在Widget创建时需要先进行一次UI的更新。之后将PlayerRage_Widget添加到Main_HUD上的合适位置。

愤怒值Widget

注:愤怒值材质可直接复制生命值材质,修改一下颜色即可

4、黑洞攻击消耗愤怒值

在Action_BlackHole中重写CanStart方法,用于判断当前角色愤怒值是否足够释放黑洞攻击

重写CanStart方法

5、效果演示

愤怒值效果演示

二、反伤效果

1、创建反伤效果类

以MyActionEffect类为父类创建反伤状态类UMyActionEffect_Thorns。添加属性ReflectFraction作为反伤系数,添加OnHealthChanged函数用于MyAttributeComponent中血量变化委托OnHealthChange触发时的回调函数

UCLASS()
class ACTIONROGUELIKE_API UMyActionEffect_Thorns : public UMyActionEffect
{
	GENERATED_BODY()
protected:
	// 反弹伤害系数
	UPROPERTY(EditDefaultsOnly, Category = "Thorns")
	float ReflectFraction;
	// 生命值变化时调用的回调函数,用于监听生命值变化
	UFUNCTION()
	void OnHealthChanged(AActor* InstigatorActor, UMyAttributeComponent* OwningComp, float NewHealth, float Delta);
public:
	void StartAction_Implementation(AActor* Instigator) override;
	void StopAction_Implementation(AActor* Instigator) override;
	UMyActionEffect_Thorns();
};

在MyActionEffect_Thorns.cpp的构造函数中初始化参数,开始动作函数中绑定血量变化事件处理函数,以及处理角色受到伤害时的函数OnHealthChanged

// 构造函数,设置默认反弹伤害比例、持续时间和间隔时间
UMyActionEffect_Thorns::UMyActionEffect_Thorns()
{
	ReflectFraction = 0.2f;
	Duration = 0.0f;
	Period = 0.0f;
}
// 反伤效果开始的方法
void UMyActionEffect_Thorns::StartAction_Implementation(AActor* Instigator)
{
	Super::StartAction_Implementation(Instigator);
	UMyAttributeComponent* Attributes = UMyAttributeComponent::GetAttributes(GetOwningComponent()->GetOwner());
	if (Attributes)
	{
		// 绑定 OnHealthChange 事件处理方法,事件触发时执行OnHealthChanged方法
		Attributes->OnHealthChange.AddDynamic(this, &UMyActionEffect_Thorns::OnHealthChanged);
	}
}
// 反伤效果结束的方法
void UMyActionEffect_Thorns::StopAction_Implementation(AActor* Instigator)
{
	Super::StopAction_Implementation(Instigator);

	// Stop listening
	UMyAttributeComponent* Attributes = UMyAttributeComponent::GetAttributes(GetOwningComponent()->GetOwner());
	if (Attributes)
	{
		Attributes->OnHealthChange.RemoveDynamic(this, &UMyActionEffect_Thorns::OnHealthChanged);
	}
}
// 处理角色受到伤害函数
void UMyActionEffect_Thorns::OnHealthChanged(AActor* InstigatorActor, UMyAttributeComponent* OwningComp, float NewHealth, float Delta)
{
	// 获取持有反伤效果的角色
	AActor* OwningActor = GetOwningComponent()->GetOwner();
	// 如果伤害值小于 0,且伤害来源不是持有该效果的角色自己,进行反弹伤害
	if (Delta < 0.0f && OwningActor != InstigatorActor)
	{
		// 计算反弹伤害值
		int32 ReflectedAmount = FMath::RoundToInt(Delta * ReflectFraction);
		if (ReflectedAmount == 0)
		{
			return;
		}
		ReflectedAmount = FMath::Abs(ReflectedAmount);
		// 应用伤害
		UMyGameplayFunctionLibrary::ApplyDamage(OwningActor, InstigatorActor, ReflectedAmount);
	}
}

2、应用反伤效果

在PlayerCharacter_BP的ActionComp组件中将MyActionEffect_Thorns添加到DefaultActions数组中

3、效果演示

这里为了展示反伤效果,暂时将攻击类的持续伤害关闭。

反伤效果演示

三、AI发现玩家提示UI

1、创建发现玩家提示UI

因为该UI需要附着在Actor上,所以以MyWorldUseWidget为父类创建发现玩家提示UI类Minion_Spotted_Widget。

发现玩家UI控件布局

Minion_Spotted_Widget蓝图设置如下

发现玩家UI蓝图设置

2、应用提示UI

//MyAICharacter.h
//用于指定敌人发现玩家时显示的UI Widget
UPROPERTY(EditDefaultsOnly, Category = "UI")	
TSubclassOf<UUserWidget> SpottedWidgetClass;
// 声明 TargetActorKey 变量,用于设置黑板中对应黑板键的名称(TargetActor)
UPROPERTY(VisibleAnywhere, Category = "Effects")
FName TargetActorKey;
//获取当前目标对象
UFUNCTION(BlueprintCallable, Category = "AI")
AActor* GetTargetActor() const;
//设置目标对象
UFUNCTION(BlueprintCallable, Category = "AI")
void SetTargetActor(AActor* NewTarget);
//MyAICharacter.cpp
//用于处理当角色看到其他角色时的逻辑
void AMyAICharacter::OnPawnSeen(APawn* Pawn)
{
	// 如果当前目标Actor不是已发现的Pawn,则更新目标Actor并创建新的世界Widget
	if (GetTargetActor() != Pawn)
	{
		//更新目标Actor
		SetTargetActor(Pawn);
		//创建新的发现提示Widget
		UMyWorldUserWidget* NewWidget = CreateWidget<UMyWorldUserWidget>(GetWorld(), SpottedWidgetClass);
		if (NewWidget)
		{
			NewWidget->AttachedActor = this;
			NewWidget->AddToViewport(10);
		}
	}
}
void AMyAICharacter::SetTargetActor(AActor* NewTarget)
{
	AAIController* AIC = Cast<AAIController>(GetController());
	if (AIC)
	{
		AIC->GetBlackboardComponent()->SetValueAsObject(TargetActorKey, NewTarget);
	}
}
AActor* AMyAICharacter::GetTargetActor() const
{
	AAIController* AIC = Cast<AAIController>(GetController());
	if (AIC)
	{
		return Cast<AActor>(AIC->GetBlackboardComponent()->GetValueAsObject(TargetActorKey));
	}

	return nullptr;
}

3、效果演示

发现玩家UI提示

四、获取Action拾取物

1、创建获取Action拾取物类

以MyPickup类为父类创建获取Action拾取物类MyPickup_Action,添加属性ActionToGrant表示获取行为具体的类。

UCLASS()
class ACTIONROGUELIKE_API AMyPickup_Action : public AMyPickup
{
protected:
	//赋予的行为的种类
	UPROPERTY(EditAnywhere, Category = "Powerup")
	TSubclassOf<UMyAction> ActionToGrant;
public:
	//实现交互方法
	void Interact_Implementation(APawn* InstigatorPawn) override;
};

在MyPickup_Action.cpp中实现Interact_Implementation,具体实现为:

  • 如果拾取者(InstigatorPawn)和ActionToGrant都存在,则获取拾取者上的Action组件
  • 如果拾取者已拥有ActionToGrant的能力,则返回;
  • 否则添加该ActionToGrant赋予到拾取者的ActionComp上
  • 隐藏并冷却生成该拾取物
void AMyPickup_Action::Interact_Implementation(APawn* InstigatorPawn)
{
    // 确保InstigatorPawn和ActionToGrant都存在
	if (!ensure(InstigatorPawn && ActionToGrant))
	{
		return;
	}
	// 获取InstigatorPawn身上的UMyActionComponent组件
	UMyActionComponent* ActionComp = Cast<UMyActionComponent>(InstigatorPawn->GetComponentByClass(UMyActionComponent::StaticClass()));
	if (ActionComp)
	{
        // 如果该ActionToGrant已存在,则返回
		if (ActionComp->GetAction(ActionToGrant))
		{
			FString DebugMsg = FString::Printf(TEXT("Action '%s' already known."), *GetNameSafe(ActionToGrant));
			GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red, DebugMsg);
			return;
		}
		// 否则添加该ActionToGrant到ActionComp中
		ActionComp->AddAction(InstigatorPawn, ActionToGrant); 
         // 隐藏并冷却生成该物品
		HideAndCooldownPickup();
	}
}

2、创建拾取物蓝图

以MyPickup_Action为父类创建拾取物蓝图Pickup_GrantAction,设置ActionToGrant为目标行为

获取能力拾取物蓝图

3、效果演示

获取Action拾取物

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