【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上的合适位置。
注:愤怒值材质可直接复制生命值材质,修改一下颜色即可
4、黑洞攻击消耗愤怒值
在Action_BlackHole中重写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。
Minion_Spotted_Widget蓝图设置如下
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、效果演示
四、获取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为目标行为