UEGAS学习之路
开始GAS学习以及冲刺功能
首先我们创建两个类,一个以ActorComponent为基类,一个以Object为基类
按下Alt + O可以直接在头文件和cpp中来回切换
//SActionComponent.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "SActionComponent.generated.h"
class USAction;
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class ACTIONROGUELIKE_API USActionComponent : public UActorComponent
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable,Category="Actions")
void AddAction(TSubclassOf<USAction> ActionClass);
UFUNCTION(BlueprintCallable,Category="Actions")
bool StartActionByName(AActor* Instigator,FName ActionName);
UFUNCTION(BlueprintCallable,Category="Actions")
bool StopActionByName(AActor* Instigator,FName ActionName);
// Sets default values for this component's properties
USActionComponent();
protected:
UPROPERTY()
TArray<USAction*> Actions;
// Called when the game starts
virtual void BeginPlay() override;
public:
// Called every frame
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
};
//SActionComponent.cpp
#include "SActionComponent.h"
#include "SAction.h"
USActionComponent::USActionComponent()
{
PrimaryComponentTick.bCanEverTick = true;
}
void USActionComponent::BeginPlay()
{
Super::BeginPlay();
}
void USActionComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
}
void USActionComponent::AddAction(TSubclassOf<USAction> ActionClass)
{
if(!ensure(ActionClass))
{
return;
}
USAction* NewAction = NewObject<USAction>(this,ActionClass);
if(ensure(NewAction))
{
Actions.Add(NewAction);
}
}
bool USActionComponent::StartActionByName(AActor* Instigator, FName ActionName)
{
for(USAction* Action : Actions)
{
if(Action && Action->ActionName == ActionName)
{
Action->StartAction(Instigator);
return true;
}
}
return false;
}
bool USActionComponent::StopActionByName(AActor* Instigator, FName ActionName)
{
for(USAction* Action : Actions)
{
if(Action && Action->ActionName == ActionName)
{
Action->StopAction(Instigator);
return true;
}
}
return false;
}
//SAction.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "SAction.generated.h"
/**
*
*/
UCLASS(Blueprintable)
class ACTIONROGUELIKE_API USAction : public UObject
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintNativeEvent,Category="Action")
void StartAction(AActor* Instigator);
UFUNCTION(BlueprintNativeEvent,Category="Action")
void StopAction(AActor* Instigator);
UPROPERTY(EditDefaultsOnly,Category="Action")
FName ActionName;
};
//SAction.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "SAction.h"
void USAction::StartAction_Implementation(AActor* Instigator)
{
UE_LOG(LogTemp,Log,TEXT("Running: %s") , *GetNameSafe(this));
}
void USAction::StopAction_Implementation(AActor* Instigator)
{
UE_LOG(LogTemp,Log,TEXT("Stopped: %s") , *GetNameSafe(this));
}
//Scharacter.h
protected:
UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category="Components")
USActionComponent* ActionComp;
public:
void SprintStart();
void SprintStop();
//Scharacter.cpp
ASCharacter::ASCharacter()
{
ActionComp = CreateDefaultSubobject<USActionComponent>("ActionComp");
}
void ASCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
PlayerInputComponent->BindAction("Sprint",IE_Pressed,this,&ASCharacter::SprintStart);
PlayerInputComponent->BindAction("Sprint",IE_Released,this,&ASCharacter::SprintStop);
}
void ASCharacter::SprintStart()
{
ActionComp->StartActionByName(this,"Sprint");
}
void ASCharacter::SprintStop()
{
ActionComp->StopActionByName(this,"Sprint");
}
以SAction为基类创建蓝图
在蓝图中重载函数
在按键映射中自定义
在Sprint蓝图中添加下图的内容
同时也要注意初始量的设置
在PlayCharacter中为角色添加冲刺的类
创建投射物攻击
//SAction_ProjectileAttack.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "SAction.h"
#include "SAction_ProjectileAttack.generated.h"
/**
*
*/
UCLASS()
class ACTIONROGUELIKE_API USAction_ProjectileAttack : public USAction
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, Category = "Attack")
TSubclassOf<AActor> ProjectileClass;
UPROPERTY(VisibleAnywhere, Category = "Effects")
FName HandSocketName;
UPROPERTY(EditDefaultsOnly, Category = "Attack")
float AttackAnimDelay;
UPROPERTY(EditAnywhere, Category = "Attack")
UAnimMontage* AttackAnim;
/* Particle System played during attack animation */
UPROPERTY(EditAnywhere, Category = "Attack")
UParticleSystem* CastingEffect;
UFUNCTION()
void AttackDelay_Elapsed(ACharacter* InstigatorCharacter);
public:
virtual void StartAction_Implementation(AActor* Instigator) override;
USAction_ProjectileAttack();
};
//SAction_ProjectileAttack.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "SAction_ProjectileAttack.h"
#include "GameFramework/Character.h"
#include "Kismet/GameplayStatics.h"
USAction_ProjectileAttack::USAction_ProjectileAttack()
{
HandSocketName = "Muzzle_01";
AttackAnimDelay = 0.2f;
}
void USAction_ProjectileAttack::StartAction_Implementation(AActor* Instigator)
{
Super::StartAction_Implementation(Instigator);
ACharacter* Character = Cast<ACharacter>(Instigator);
if(Character)
{
Character->PlayAnimMontage(AttackAnim);
UGameplayStatics::SpawnEmitterAttached(CastingEffect, Character->GetMesh(), HandSocketName, FVector::ZeroVector, FRotator::ZeroRotator, EAttachLocation::SnapToTarget);
FTimerHandle TimerHandle_AttackDelay;
FTimerDelegate Delegate;
Delegate.BindUFunction(this, "AttackDelay_Elapsed", Character);
GetWorld()->GetTimerManager().SetTimer(TimerHandle_AttackDelay, Delegate, AttackAnimDelay, false);
}
}
void USAction_ProjectileAttack::AttackDelay_Elapsed(ACharacter* InstigatorCharacter)
{
if (ensureAlways(ProjectileClass))
{
FVector HandLocation = InstigatorCharacter->GetMesh()->GetSocketLocation(HandSocketName);
FActorSpawnParameters SpawnParams;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
SpawnParams.Instigator = InstigatorCharacter;
FCollisionShape Shape;
Shape.SetSphere(20.0f);
// Ignore Player
FCollisionQueryParams Params;
Params.AddIgnoredActor(InstigatorCharacter);
FCollisionObjectQueryParams ObjParams;
ObjParams.AddObjectTypesToQuery(ECC_WorldDynamic);
ObjParams.AddObjectTypesToQuery(ECC_WorldStatic);
ObjParams.AddObjectTypesToQuery(ECC_Pawn);
FVector TraceStart = InstigatorCharacter->GetPawnViewLocation();
// endpoint far into the look-at distance (not too far, still adjust somewhat towards crosshair on a miss)
FVector TraceEnd = TraceStart + (InstigatorCharacter->GetControlRotation().Vector() * 5000);
FHitResult Hit;
// returns true if we got to a blocking hit
if (GetWorld()->SweepSingleByObjectType(Hit, TraceStart, TraceEnd, FQuat::Identity, ObjParams, Shape, Params))
{
// Overwrite trace end with impact point in world
TraceEnd = Hit.ImpactPoint;
}
// find new direction/rotation from Hand pointing to impact point in world.
FRotator ProjRotation = FRotationMatrix::MakeFromX(TraceEnd - HandLocation).Rotator();
FTransform SpawnTM = FTransform(ProjRotation, HandLocation);
GetWorld()->SpawnActor<AActor>(ProjectileClass, SpawnTM, SpawnParams);
}
StopAction(InstigatorCharacter);
}
//SAction.h
public:
UWorld* GetWorld() const override;
//SAction.cpp
UWorld* USAction::GetWorld() const
{
// Outer is set when creating action via NewObject<T>
UActorComponent* Comp = Cast<UActorComponent>(GetOuter());
if (Comp)
{
return Comp->GetWorld();
}
return nullptr;
}
//SCharacter.cpp
//这个Cpp中函数的内容即是全部的内容
// Fill out your copyright notice in the Description page of Project Settings.
#include "SCharacter.h"
#include "SActionComponent.h"
#include "SAttributeComponent.h"
#include "SDashProjectile.h"
#include "SInteractionComponent.h"
#include "Chaos/ChaosPerfTest.h"
#include "GameFramework/CharacterMovementComponent.h"
// Sets default values
ASCharacter::ASCharacter()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
SpringArmComp = CreateDefaultSubobject<USpringArmComponent>("SpringarmComp");
SpringArmComp->SetupAttachment(RootComponent);
SpringArmComp->bUsePawnControlRotation = true;
CameraComp = CreateDefaultSubobject<UCameraComponent>("CameraComp");
CameraComp->SetupAttachment(SpringArmComp);
InteractionComp = CreateDefaultSubobject<USInteractionComponent>("InteractionComp");
AttributeComp = CreateDefaultSubobject<USAttributeComponent>("AttributeComp");
ActionComp = CreateDefaultSubobject<USActionComponent>("ActionComp");
GetCharacterMovement()->bOrientRotationToMovement = true;
bUseControllerRotationYaw = false;
TimeToHitParamName = "TimeToHit";
}
void ASCharacter::HealSelf(float Amount)
{
AttributeComp->ApplyHealthChange(this,Amount);
}
void ASCharacter::PostInitializeComponents()
{
Super::PostInitializeComponents();
AttributeComp->OnHealthChanged.AddDynamic(this,&ASCharacter::OnHealthChanged);
}
FVector ASCharacter::GetPawnViewLocation() const
{
return CameraComp->GetComponentLocation();
}
// Called when the game starts or when spawned
void ASCharacter::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void ASCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void ASCharacter::MoveForward(float value)
{
FRotator ControlRot = GetControlRotation();
ControlRot.Pitch = 0.0f;
ControlRot.Roll = 0.0f;
AddMovementInput(ControlRot.Vector(),value);
}
void ASCharacter::MoveRight(float value)
{
FRotator ControlRot = GetControlRotation();
ControlRot.Pitch = 0.0f;
ControlRot.Roll = 0.0f;
FVector RightVector = FRotationMatrix(ControlRot).GetScaledAxis(EAxis::Y);
AddMovementInput(RightVector,value);
}
void ASCharacter::SprintStart()
{
ActionComp->StartActionByName(this,"Sprint");
}
void ASCharacter::SprintStop()
{
ActionComp->StopActionByName(this,"Sprint");
}
void ASCharacter::PrimaryAttack()
{
ActionComp->StartActionByName(this, "PrimaryAttack");
}
void ASCharacter::BlackHoleAttack()
{
ActionComp->StartActionByName(this, "Blackhole");
}
void ASCharacter::DashAttack()
{
ActionComp->StartActionByName(this, "Dash");
}
void ASCharacter::PrimaryInteract()
{
if(InteractionComp)
{
InteractionComp->PrimaryInteract();
}
}
void ASCharacter::OnHealthChanged(AActor* InstigatorActor, USAttributeComponent* OwningComp, float NewHealth,
float Delta)
{
//Die
if(NewHealth <= 0.0f && Delta <0.0f)
{
APlayerController* PC = Cast<APlayerController>(GetController());
DisableInput(PC);
}
// Damaged
if (Delta < 0.0f)
{
GetMesh()->SetScalarParameterValueOnMaterials(TimeToHitParamName, GetWorld()->TimeSeconds);
}
}
// Called to bind functionality to input
void ASCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
PlayerInputComponent->BindAxis("MoveForward",this,&ASCharacter::MoveForward);
PlayerInputComponent->BindAxis("MoveRight",this,&ASCharacter::MoveRight);
PlayerInputComponent->BindAxis("Turn",this,&APawn::AddControllerYawInput);
PlayerInputComponent->BindAxis("LookUp",this,&APawn::AddControllerPitchInput);
PlayerInputComponent->BindAction("Jump",IE_Pressed,this,&ACharacter::Jump);
PlayerInputComponent->BindAction("PrimaryAttack",IE_Pressed,this,&ASCharacter::PrimaryAttack);
PlayerInputComponent->BindAction("PrimaryInteract",IE_Pressed,this,&ASCharacter::PrimaryInteract);
PlayerInputComponent->BindAction("BlackHoleAttack",IE_Pressed,this,&ASCharacter::BlackHoleAttack);
PlayerInputComponent->BindAction("DashAttack",IE_Pressed,this,&ASCharacter::DashAttack);
PlayerInputComponent->BindAction("Sprint",IE_Pressed,this,&ASCharacter::SprintStart);
PlayerInputComponent->BindAction("Sprint",IE_Released,this,&ASCharacter::SprintStop);
}
//SCharacter.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/Character.h"
#include "GameFramework/SpringArmComponent.h"
#include "SCharacter.generated.h"
class USActionComponent;
class USAttributeComponent;
class USInteractionComponent;
class UCameraComponent;
class USpringArmComponent;
class UAnimMontage;
UCLASS()
class ACTIONROGUELIKE_API ASCharacter : public ACharacter
{
GENERATED_BODY()
public:
ASCharacter();
UFUNCTION(Exec)
void HealSelf(float Amount = 100);
protected:
UPROPERTY(VisibleAnywhere,Category="Effects")
FName TimeToHitParamName;
UPROPERTY(VisibleAnywhere)
UCameraComponent* CameraComp;
UPROPERTY(VisibleAnywhere)
USpringArmComponent* SpringArmComp;
UPROPERTY(VisibleAnywhere)
USInteractionComponent* InteractionComp;
UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category="Components")
USAttributeComponent* AttributeComp;
UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category="Components")
USActionComponent* ActionComp;
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
void MoveForward(float value);
void MoveRight(float value);
void SprintStart();
void SprintStop();
void PrimaryAttack();
void PrimaryInteract();
void BlackHoleAttack();
void DashAttack();
UFUNCTION()
void OnHealthChanged(AActor* InstigatorActor, USAttributeComponent* OwningComp, float NewHealth,float Delta);
virtual void PostInitializeComponents() override;
virtual FVector GetPawnViewLocation() const override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
};
新建蓝图
在蓝图中可以修改我们想要的投射物类,特效,动画
在我们的角色蓝图中连线,添加我们刚刚设置的技能
这里图中的红框中调用的类有些错误,应该是Action_MagicProjectile
//SActorComponents.h
protected:
/* Granted abilities at game start */
UPROPERTY(EditAnywhere, Category = "Actions")
TArray<TSubclassOf<USAction>> DefaultActions;
//SActorComponents.cpp
void USActionComponent::BeginPlay()
{
Super::BeginPlay();
for (TSubclassOf<USAction> ActionClass : DefaultActions)
{
AddAction(ActionClass);
}
}
在优化完我们Scharacter的架构后,如之前一样,我们将我们的Dash,BlackHole添加进去,我们这一次不使用子类,我们使用Duplicate
这一次我们可以使用数组直接添加我们的技能了
上图中的蓝图内容就可以删除了
无意间解决了之前收到攻击没有反馈的情况
//SCharacter.h
UPROPERTY(VisibleAnywhere,Category="Effects")
FName TimeToHitParamName;
//SCharacter.cpp
ASCharacter::ASCharacter()
{
TimeToHitParamName = "TimeToHit";
}
void ASCharacter::OnHealthChanged(AActor* InstigatorActor, USAttributeComponent* OwningComp, float NewHealth,
float Delta)
{
// Damaged
if (Delta < 0.0f)
{
GetMesh()->SetScalarParameterValueOnMaterials(TimeToHitParamName, GetWorld()->TimeSeconds);
}
}
在SAICharacter中的bug在于字母敲错了
//SAICharacter.cpp
TimeToHitParamName = "TimeToHit";
之前是TimeOfHit
本节课的内容就结束了,但是现在仍有一个问题停留,那就是我们可以同时按下很多按键,这将会导致我们投射物十分混乱
设置游戏中的Tags
//SActionComponent.h
UPROPERTY(EditAnywhere,BlueprintReadWrite,Category="Tags")
FGameplayTagContainer ActiveGameplayTags;
//SActionComponent.cpp
void USActionComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
FString DebugMsg = GetNameSafe(GetOwner()) + " : " + ActiveGameplayTags.ToStringSimple();
GEngine->AddOnScreenDebugMessage(-1, 0.0f, FColor::White, DebugMsg);
}
//SAction.h
protected:
UFUNCTION(BlueprintCallable, Category = "Action")
USActionComponent* GetOwningComponent() const;
/* Tags added to owning actor when activated, removed when action stops */
UPROPERTY(EditDefaultsOnly, Category = "Tags")
FGameplayTagContainer GrantsTags;
/* Action can only start if OwningActor has none of these Tags applied */
UPROPERTY(EditDefaultsOnly, Category = "Tags")
FGameplayTagContainer BlockedTags;
//SAction.cpp
USActionComponent* USAction::GetOwningComponent() const
{
return Cast<USActionComponent>(GetOuter());
}
void USAction::StartAction_Implementation(AActor* Instigator)
{
UE_LOG(LogTemp,Log,TEXT("Running: %s") , *GetNameSafe(this));
USActionComponent* Comp = GetOwningComponent();
Comp->ActiveGameplayTags.AppendTags(GrantsTags);
}
void USAction::StopAction_Implementation(AActor* Instigator)
{
UE_LOG(LogTemp,Log,TEXT("Stopped: %s") , *GetNameSafe(this));
USActionComponent* Comp = GetOwningComponent();
Comp->ActiveGameplayTags.RemoveTags(GrantsTags);
}
在Gameplay Tag中新建Action.Attacking Action.Sprinting
为Sprint设置Grant Tags和Blocked Tags
为Attack设置Grant Tags和Blocked Tags
剩下的Dash和BlackHole也做同样的处理
接下来当我们Attack或者Sprint的时候,左上角就会有相关的提示,但是现在我们还没有实现,当Sprint时无法Attack,本节课到此结束。
在系统中比较Tags
//SAction.h
public:
UFUNCTION(BlueprintCallable, Category = "Action")
bool IsRunning() const;
UFUNCTION(BlueprintNativeEvent, Category = "Action")
bool CanStart(AActor* Instigator);
protected:
bool bIsRunning;
//SAction.cpp
bool USAction::CanStart_Implementation(AActor* Instigator)
{
if (IsRunning())
{
return false;
}
USActionComponent* Comp = GetOwningComponent();
if (Comp->ActiveGameplayTags.HasAny(BlockedTags))
{
return false;
}
return true;
}
bool USAction::IsRunning() const
{
return bIsRunning;
}
void USAction::StartAction_Implementation(AActor* Instigator)
{
bIsRunning = true;
}
void USAction::StopAction_Implementation(AActor* Instigator)
{
ensureAlways(bIsRunning);
bIsRunning = false;
}
//SActionComponent.cpp
bool USActionComponent::StartActionByName(AActor* Instigator, FName ActionName)
{
for(USAction* Action : Actions)
{
if(Action && Action->ActionName == ActionName)
{
if (!Action->CanStart(Instigator))
{
FString FailedMsg = FString::Printf(TEXT("Failed to run: %s"), *ActionName.ToString());
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red, FailedMsg);
continue;
}
Action->StartAction(Instigator);
return true;
}
}
return false;
}
bool USActionComponent::StopActionByName(AActor* Instigator, FName ActionName)
{
for(USAction* Action : Actions)
{
if(Action && Action->ActionName == ActionName)
{
if (Action->IsRunning())
{
Action->StopAction(Instigator);
return true;
}
}
}
return false;
}
将我们AttackAction中的BlockedTag也将AttackTag添加上,这样只能同时执行一个攻击
类DOOM风格的门和钥匙卡系统
在GamePlayTag中创建KeyCard的Tag
如图下修改LeverBP的蓝图
修改TreasureChest中的蓝图
我们将Lever的KeyCard设置为Blue,其余两个箱子一个为Blue,一个为Red,在拉下把手后,Blue的箱子能正常被玩家打开
使用Tags弹反攻击
//SMagicProjectile.h
UPROPERTY(EditDefaultsOnly, Category = "Damage")
FGameplayTag ParryTag;
//SMagicProjectile.cpp
void ASMagicProjectile::OnActorOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if(OtherActor && OtherActor != GetInstigator())
{
//FName Muzzle = "Muzzle_01";
//static FGameplayTag Tag = FGameplayTag::RequestGameplayTag("Status.Parrying");
USActionComponent* ActionComp = Cast<USActionComponent>(OtherActor->GetComponentByClass(USActionComponent::StaticClass()));
if (ActionComp && ActionComp->ActiveGameplayTags.HasTag(ParryTag))
{
MovementComp->Velocity = -MovementComp->Velocity;
SetInstigator(Cast<APawn>(OtherActor));
return;
}
if(USGameplayFunctionLibrary::ApplyDirctionalDamage(GetInstigator(),OtherActor,DamageAmount,SweepResult))
{
Destroy();
}
}
}
创建新的Tag Status.Parrying
将MagicProjectileBP的Parry Tag设置为Status.Parring
将SCharacter的Tag也设置为Status.Parring 当Bots的子弹射中玩家时,会产生反弹
//SAICharacter.h
protected:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
USActionComponent* ActionComp;
//SAICharacter.cpp
ASAICharacter::ASAICharacter()
{
ActionComp = CreateDefaultSubobject<USActionComponent>("ActionComp");
}
接下来我们也给MinionRangedBP(机器人)添加一个相同的Tag,那么子弹就会在我们和机器人之间互相弹射
接下来我们创建一个新的蓝图,Action_Parry
//SAction.h
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Action")
void StopAction(AActor* Instigator);
修改Action_Parry的蓝图如下,然后将之前我们在SCharacter和MinionRangedBP蓝图中设置的TAG删除
在之前的数组中添加Action_Parry
在设置中的Input中添加新的按键,而这一次我们将通过蓝图去实现
如下在蓝图中实现
在DamagePopup_Widget中创建获取受到伤害蓝图
在MinionRangedBP中设置蓝图,当生命受伤时,产生受到伤害的数字
燃烧效果BUFF
首先以SAction为基类创建一个新的类
//SActionEffect.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "SAction.h"
#include "SActionEffect.generated.h"
/**
*
*/
UCLASS()
class ACTIONROGUELIKE_API USActionEffect : public USAction
{
GENERATED_BODY()
public:
void StartAction_Implementation(AActor* Instigator) override;
void StopAction_Implementation(AActor* Instigator) override;
protected:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Effect")
float Duration;
/* Time between 'ticks' to apply effect */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Effect")
float Period;
FTimerHandle PeriodHandle;
FTimerHandle DurationHandle;
UFUNCTION(BlueprintNativeEvent, Category = "Effect")
void ExecutePeriodicEffect(AActor* Instigator);
public:
USActionEffect();
};
//SActionEffect.cpp
//SActionComponent.h
UFUNCTION(BlueprintCallable,Category="Actions")
void AddAction(AActor* Instigator, TSubclassOf<USAction> ActionClass);
UFUNCTION(BlueprintCallable, Category = "Actions")
void RemoveAction(USAction* ActionToRemove);
//SActionComponent.cpp
void USActionComponent::BeginPlay()
{
Super::BeginPlay();
for (TSubclassOf<USAction> ActionClass : DefaultActions)
{
AddAction(GetOwner(),ActionClass);
}
}
void USActionComponent::AddAction(AActor* Instigator,TSubclassOf<USAction> ActionClass)
{
if(!ensure(ActionClass))
{
return;
}
USAction* NewAction = NewObject<USAction>(this,ActionClass);
if(ensure(NewAction))
{
Actions.Add(NewAction);
if (NewAction->bAutoStart && ensure(NewAction->CanStart(Instigator)))
{
NewAction->StartAction(Instigator);
}
}
}
void USActionComponent::RemoveAction(USAction* ActionToRemove)
{
if (!ensure(ActionToRemove && !ActionToRemove->IsRunning()))
{
return;
}
Actions.Remove(ActionToRemove);
}
//SAction.h
public:
/* Start immediately when added to an action component */
UPROPERTY(EditDefaultsOnly, Category = "Action")
bool bAutoStart;
在 Actions文件夹中创建新的蓝图
如下图中设置蓝图内容和class的默认值
//SMagicProjectile.h
UPROPERTY(EditDefaultsOnly, Category = "Damage")
TSubclassOf<USActionEffect> BurningActionClass;
//SMagicProjectile.cpp
if(USGameplayFunctionLibrary::ApplyDirctionalDamage(GetInstigator(),OtherActor,DamageAmount,SweepResult))
{
Destroy();
if (ActionComp)
{
ActionComp->AddAction(GetInstigator(), BurningActionClass);
}
}
在MagicProjectileBP中,将BuringActionClass设置
使用UI反馈改善交互
//SInteractionComponent.h
protected:
void FindBestInteractable();
//SInteractionComponent.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "SInteractionComponent.h"
#include "SGameplayInterface.h"
#include "SWorldUserWidget.h"
#include "Blueprint/UserWidget.h"
#include "Evaluation/Blending/MovieSceneBlendType.h"
static TAutoConsoleVariable<bool> CVarDebugDrawInteraction(TEXT("su.InteractionDebugDraw"), false, TEXT("Enable Debug Lines for Interact Component."), ECVF_Cheat);
// Sets default values for this component's properties
USInteractionComponent::USInteractionComponent()
{
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
// off to improve performance if you don't need them.
PrimaryComponentTick.bCanEverTick = true;
TraceRadius = 30.0f;
TraceDistance = 500.0f;
CollisionChannel = ECC_WorldDynamic;
// ...
}
// Called when the game starts
void USInteractionComponent::BeginPlay()
{
Super::BeginPlay();
// ...
}
// Called every frame
void USInteractionComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
FindBestInteractable();
}
void USInteractionComponent::FindBestInteractable()
{
bool bDebugDraw = CVarDebugDrawInteraction.GetValueOnGameThread();
FCollisionObjectQueryParams ObjectQueryParams;
ObjectQueryParams.AddObjectTypesToQuery(CollisionChannel);
AActor* MyOwner = GetOwner();
FVector EyeLocation;
FRotator EyeRotation;
MyOwner->GetActorEyesViewPoint(EyeLocation,EyeRotation);
/*FHitResult Hit;
bool bBlockingHit = GetWorld()->LineTraceSingleByObjectType(Hit,EyeLocation,End,ObjectQueryParams);*/
FVector End = EyeLocation + (EyeRotation.Vector() * TraceDistance);
FCollisionShape Shape;
TArray<FHitResult> Hits;
Shape.SetSphere(TraceRadius);
bool bBlockingHit = GetWorld()->SweepMultiByObjectType(Hits,EyeLocation,End,FQuat::Identity,ObjectQueryParams,Shape);
FColor LineColor = bBlockingHit ? FColor::Green : FColor::Red;
FocusedActor = nullptr;
for(FHitResult Hit : Hits)
{
if(bDebugDraw)
{
DrawDebugSphere(GetWorld(),Hit.ImpactPoint,TraceRadius,32,LineColor,false,2.0f);
}
AActor* HitActor = Hit.GetActor();
if(HitActor)
{
if(HitActor->Implements<USGameplayInterface>())
{
FocusedActor = HitActor;
break;
}
}
}
if (FocusedActor)
{
if (DefaultWidgetInstance == nullptr && ensure(DefaultWidgetClass))
{
DefaultWidgetInstance = CreateWidget<USWorldUserWidget>(GetWorld(), DefaultWidgetClass);
}
if (DefaultWidgetInstance)
{
DefaultWidgetInstance->AttachedActor = FocusedActor;
if (!DefaultWidgetInstance->IsInViewport())
{
DefaultWidgetInstance->AddToViewport();
}
}
}
else
{
if (DefaultWidgetInstance)
{
DefaultWidgetInstance->RemoveFromParent();
}
}
if(bDebugDraw)
{
DrawDebugLine(GetWorld(),EyeLocation,End,LineColor,false,2.0f,0,2.0f);
}
}
void USInteractionComponent::PrimaryInteract()
{
if (FocusedActor == nullptr)
{
GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Red, "No Focus Actor to interact.");
return;
}
APawn* MyPawn = Cast<APawn>(GetOwner());
ISGameplayInterface::Execute_Interact(FocusedActor, MyPawn);
}
创建新的蓝图
在SCharacter中将默认类设置
这时候我们靠近可以互动物体时就有提示了
怒气,”警觉”组件,反伤效果
//SAttributeComponent.h
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FOnHealthChanged, AActor*, InstigatorActor, USAttributeComponent*, OwningComp, float, NewHealth, float, Delta);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FOnAttributeChanged, AActor*, InstigatorActor, USAttributeComponent*, OwningComp, float, NewValue, float, Delta);
protected:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Attributes")
float Rage;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Attributes")
float RageMax;
public:
UFUNCTION(BlueprintCallable, Category = "Attributes")
bool Kill(AActor* InstigatorActor);
UFUNCTION(BlueprintCallable, Category = "Attributes")
bool IsAlive() const;
UFUNCTION(NetMulticast, Reliable) // @FIXME: mark as unreliable once we moved the 'state' our of scharacter
void MulticastHealthChanged(AActor* InstigatorActor, float NewHealth, float Delta);
UFUNCTION(BlueprintCallable, Category = "Attributes")
bool IsFullHealth() const;
UFUNCTION(BlueprintCallable, Category = "Attributes")
float GetHealthMax() const;
UFUNCTION(BlueprintCallable, Category = "Attributes")
float GetHealth() const;
UPROPERTY(BlueprintAssignable, Category = "Attributes")
FOnHealthChanged OnHealthChanged;
UFUNCTION(BlueprintCallable,Category="Attributes")
bool ApplyHealthChange(AActor* InstigatorActor,float Delta);
UPROPERTY(BlueprintAssignable, Category = "Attributes")
FOnAttributeChanged OnRageChanged;
UFUNCTION(BlueprintCallable)
float GetRage() const;
UFUNCTION(BlueprintCallable, Category = "Attributes")
bool ApplyRage(AActor* InstigatorActor, float Delta);
//SAttributeComponent.cpp
USAttributeComponent::USAttributeComponent()
{
Rage = 0;
RageMax = 100;
}
//SCharacter.cpp
void ASCharacter::OnHealthChanged(AActor* InstigatorActor, USAttributeComponent* OwningComp, float NewHealth,
float Delta)
{
//Die
if(NewHealth <= 0.0f && Delta <0.0f)
{
APlayerController* PC = Cast<APlayerController>(GetController());
DisableInput(PC);
}
// Damaged
if (Delta < 0.0f)
{
GetMesh()->SetScalarParameterValueOnMaterials(TimeToHitParamName, GetWorld()->TimeSeconds);
// Rage added equal to damage received (Abs to turn into positive rage number)
float RageDelta = FMath::Abs(Delta);
AttributeComp->ApplyRage(InstigatorActor, RageDelta);
}
}
//SAICharacter.h
protected:
/* Widget to display when bot first sees a player. */
UPROPERTY(EditDefaultsOnly, Category = "UI")
TSubclassOf<UUserWidget> SpottedWidgetClass;
USWorldUserWidget* ActiveHealthBar;
UPROPERTY(EditDefaultsOnly,Category="UI")
TSubclassOf<UUserWidget> HealthBarWidgetClass;
/* Material parameter for Hitflashes */
UPROPERTY(VisibleAnywhere,Category="Effects")
FName TimeToHitParamName;
/* Key for AI Blackboard 'TargetActor' */
UPROPERTY(VisibleAnywhere, Category = "Effects")
FName TargetActorKey;
UFUNCTION(BlueprintCallable, Category = "AI")
void SetTargetActor(AActor* NewTarget);
UFUNCTION(BlueprintCallable, Category = "AI")
AActor* GetTargetActor() const;
//SAICharacter.cpp
ASAICharacter::ASAICharacter()
{
TargetActorKey = "TargetActor";
}
void ASAICharacter::SetTargetActor(AActor* NewTarget)
{
AAIController* AIC = Cast<AAIController>(GetController());
if(AIC)
{
AIC->GetBlackboardComponent()->SetValueAsObject("TargetActorKey",NewTarget);
}
}
AActor* ASAICharacter::GetTargetActor() const
{
AAIController* AIC = Cast<AAIController>(GetController());
if (AIC)
{
return Cast<AActor>(AIC->GetBlackboardComponent()->GetValueAsObject(TargetActorKey));
}
return nullptr;
}
void ASAICharacter::OnPawnSeen(APawn* Pawn)
{
// Ignore if target already set
if (GetTargetActor() != Pawn)
{
SetTargetActor(Pawn);
/*
DrawDebugString(GetWorld(),GetActorLocation(),"PlayerSpotted",nullptr,FColor::White,4.0f,true);
*/
USWorldUserWidget* NewWidget = CreateWidget<USWorldUserWidget>(GetWorld(), SpottedWidgetClass);
if (NewWidget)
{
NewWidget->AttachedActor = this;
// Index of 10 (or anything higher than default of 0) places this on top of any other widget.
// May end up behind the minion health bar otherwise.
NewWidget->AddToViewport(10);
}
}
}
//SActionComponent.h
public:
/* Returns first occurance of action matching the class provided */
UFUNCTION(BlueprintCallable, Category = "Actions")
USAction* GetAction(TSubclassOf<USAction> ActionClass) const;
//SActionComponent.cpp
USAction* USActionComponent::GetAction(TSubclassOf<USAction> ActionClass) const
{
for (USAction* Action : Actions)
{
if (Action && Action->IsA(ActionClass))
{
return Action;
}
}
return nullptr;
}
创建两个新类负责反伤效果和药水瓶在Action上的控制
//SActionEffect_Thorns.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "SActionEffect.h"
#include "SActionEffect_Thorns.generated.h"
/**
*
*/
UCLASS()
class ACTIONROGUELIKE_API USActionEffect_Thorns : public USActionEffect
{
GENERATED_BODY()
protected:
UPROPERTY(EditDefaultsOnly, Category = "Thorns")
float ReflectFraction;
UFUNCTION()
void OnHealthChanged(AActor* InstigatorActor, USAttributeComponent* OwningComp, float NewHealth, float Delta);
public:
void StartAction_Implementation(AActor* Instigator) override;
void StopAction_Implementation(AActor* Instigator) override;
USActionEffect_Thorns();
};
//SActionEffect_Thorns.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "SActionEffect_Thorns.h"
#include "SActionComponent.h"
#include "SAttributeComponent.h"
#include "SGameplayFunctionLibrary.h"
USActionEffect_Thorns::USActionEffect_Thorns()
{
ReflectFraction = 0.2f;
Duration = 0.0f;
Period = 0.0f;
}
void USActionEffect_Thorns::OnHealthChanged(AActor* InstigatorActor, USAttributeComponent* OwningComp, float NewHealth,
float Delta)
{
AActor* OwningActor = GetOwningComponent()->GetOwner();
// Damage Only
if (Delta < 0.0f && OwningActor != InstigatorActor)
{
// Round to nearest to avoid 'ugly' damage numbers and tiny reflections
int32 ReflectedAmount = FMath::RoundToInt(Delta * ReflectFraction);
if (ReflectedAmount == 0)
{
return;
}
// Flip to positive, so we don't end up healing ourselves when passed into damage
ReflectedAmount = FMath::Abs(ReflectedAmount);
// Return damage sender...
USGameplayFunctionLibrary::ApplyDamage(OwningActor, InstigatorActor, ReflectedAmount);
}
}
void USActionEffect_Thorns::StartAction_Implementation(AActor* Instigator)
{
Super::StartAction_Implementation(Instigator);
// Start listening
USAttributeComponent* Attributes = USAttributeComponent::GetAttributes(GetOwningComponent()->GetOwner());
if (Attributes)
{
Attributes->OnHealthChanged.AddDynamic(this, &USActionEffect_Thorns::OnHealthChanged);
}
}
void USActionEffect_Thorns::StopAction_Implementation(AActor* Instigator)
{
Super::StopAction_Implementation(Instigator);
// Stop listening
USAttributeComponent* Attributes = USAttributeComponent::GetAttributes(GetOwningComponent()->GetOwner());
if (Attributes)
{
Attributes->OnHealthChanged.RemoveDynamic(this, &USActionEffect_Thorns::OnHealthChanged);
}
}
//SPowerup_Action.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "SPowerupActor.h"
#include "SPowerup_Action.generated.h"
/**
*
*/
UCLASS()
class ACTIONROGUELIKE_API ASPowerup_Action : public ASPowerupActor
{
GENERATED_BODY()
protected:
UPROPERTY(EditAnywhere, Category = "Powerup")
TSubclassOf<USAction> ActionToGrant;
public:
void Interact_Implementation(APawn* InstigatorPawn) override;
};
//SPowerup_Action.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "SPowerup_Action.h"
#include "SAction.h"
#include "SActionComponent.h"
class USActionComponent;
void ASPowerup_Action::Interact_Implementation(APawn* InstigatorPawn)
{
Super::Interact_Implementation(InstigatorPawn);
// Make sure we have instigator & that action class was set up
if (!ensure(InstigatorPawn && ActionToGrant))
{
return;
}
USActionComponent* ActionComp = Cast<USActionComponent>(InstigatorPawn->GetComponentByClass(USActionComponent::StaticClass()));
// Check if Player already has action class
if (ActionComp)
{
if (ActionComp->GetAction(ActionToGrant))
{
//UE_LOG(LogTemp, Log, TEXT("Instigator already has action of class: %s"), *GetNameSafe(ActionToGrant));
FString DebugMsg = FString::Printf(TEXT("Action '%s' already known."), *GetNameSafe(ActionToGrant));
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red, DebugMsg);
return;
}
// Give new Ability
ActionComp->AddAction(InstigatorPawn, ActionToGrant);
HideAndCooldownPowerup();
}
}
在Action_Blackhole中修改蓝图
这是重载函数Can Start的蓝图
为我们的怒气值创建一个新的Widget,然后蓝图如下连接