斯坦福CS193U-虚幻4C++课程学习笔记(16) Gameplay Tags

Gameplay Tags

FGameplayTag

  • 将FName TagName封装在其中
  • 可代替bool, enum, FName使用
  • 可以在项目设置中创建
  • 可以在虚幻编辑器中选择
  • 被GAS广泛使用

FGameplayTagContainer

  • 封装了TArray GameplayTags
  • HasTag函数判断容器中是否存在该Tag
    • Exact Match选项
      • 为True时要求准确匹配tag
      • 为False时,可使用非叶子级的Tag作为条件进行判断

优点

  • 不用进行任何类型转换,只需要获取Tag管理组件,通过容器对Tag进行条件判断即可使用
  • 减少组件和各种类之间的依赖,解耦
  • 更好的支持多人游戏

Tips

  • 在类中用到具体类而非指针作为成为变量时,需要#include而不是Forward Declaration
  • 除了声明GameplayTag作为成员变量来使用,还可以hardcode成如下形式(强烈不建议,后续难以维护),如果使用建议声明为静态的
static FGameplayTag Tag = FGameplayTag::RequestGameplayTag("TagName");

项目实战

GitHub: https://github.com/yufeige4/ActionRoguelike
Note: 在ActionComp中而不是Action进行IsRunning判断,能够使子类在继承和重载StartAction或StopAction时不用每次进行IsRunning的判断,减少可能出现BUG的可能性

  • Action,ActionComponet引入Tags机制
  • 将之前实现的几种Action使用Tag进行互斥处理
  • 使用GameplayTag作为判断触发的条件
  • 使用GameplayTag实现反弹BUFF
  • 给主角添加ActionComponent
  • 给AI怪物添加ActionComponent

image.png

// GAction.h
UCLASS(Blueprintable)
class ACTIONROGUELIKE_API UGAction : public UObject
{
	GENERATED_BODY()

public:
	// nickname of an action
	UPROPERTY(EditDefaultsOnly, Category = "Action")
	FName ActionName;
	
protected:
	// Tags added to owning actor
	UPROPERTY(EditDefaultsOnly, Category = "Tags")
	FGameplayTagContainer GrantedTags;
	// Actions can only start without having these tags
	UPROPERTY(EditDefaultsOnly, Category = "Tags")
	FGameplayTagContainer BlockedTags;
	// internal bool to check whether it is running
	bool bIsRunning;
	
public:
	// functions
	UFUNCTION(BlueprintNativeEvent, Category = "Action")
	bool CanStart(AActor* Instigator);
	
	UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Action")
	void StartAction(AActor* Instigator);

	UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Action")
	void StopAction(AActor* Instigator);

	virtual UWorld* GetWorld() const override;

	UFUNCTION(BlueprintCallable, Category = "Action")
	bool IsRunning() const;

protected:
	
	UFUNCTION(BlueprintCallable, Category = "Action")
	UGActionComponent* GetOwningActionComp() const;
};
// GAction.cpp
bool UGAction::CanStart_Implementation(AActor* Instigator)
{
	if(IsRunning())
	{
		return false;
	}
	UGActionComponent* ActionComp = GetOwningActionComp();
	if(ActionComp && ActionComp->ActiveGameplayTags.HasAny(BlockedTags))
	{
		return false;
	}
	bIsRunning = true;
	return true;
}

void UGAction::StartAction_Implementation(AActor* Instigator)
{
	UE_LOG(LogTemp,Log,TEXT("StartAction: %s"),*GetNameSafe(this));

	ensureAlwaysMsgf(bIsRunning,TEXT("Trying to stop action that was not started!"));
	
	UGActionComponent* ActionComp = GetOwningActionComp();
	if(ActionComp)
	{
		ActionComp->ActiveGameplayTags.AppendTags(GrantedTags);
	}
}

void UGAction::StopAction_Implementation(AActor* Instigator)
{
	UE_LOG(LogTemp,Log,TEXT("StopAction: %s"),*GetNameSafe(this));
	
	UGActionComponent* ActionComp = GetOwningActionComp();
	if(ActionComp)
	{
		ActionComp->ActiveGameplayTags.RemoveTags(GrantedTags);
	}
	bIsRunning = false;
}

UGActionComponent* UGAction::GetOwningActionComp() const
{
	return Cast<UGActionComponent>(GetOuter());
}

UWorld* UGAction::GetWorld() const
{
	UGActionComponent* ActionComp = Cast<UGActionComponent>(GetOuter());
	if(ActionComp)
	{
		return ActionComp->GetWorld();
	}
	return nullptr;
}

bool UGAction::IsRunning() const
{
	return bIsRunning;
}
// GActionComponent.h
class UGAction;

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class ACTIONROGUELIKE_API UGActionComponent : public UActorComponent
{
	GENERATED_BODY()

public:
	
	UGActionComponent();

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Tags")
	FGameplayTagContainer ActiveGameplayTags;

protected:
	
	UPROPERTY()
	TArray<UGAction*> Actions;

	UPROPERTY(EditAnywhere, Category = "Actions")
	TArray<TSubclassOf<UGAction>> DefaultActions;

public:
	
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

	UFUNCTION(BlueprintCallable, Category = "Actions")
	void AddAction(TSubclassOf<UGAction> ActionClass);

	UFUNCTION(BlueprintCallable, Category = "Actions")
	bool StartActionByName(AActor* Instigator, FName ActionName);

	UFUNCTION(BlueprintCallable, Category = "Actions")
	bool StopActionByName(AActor* Instigator, FName ActionName);

	UFUNCTION(BlueprintCallable, Category = "Actions")
	static UGActionComponent* GetActionComponent(AActor* Actor);

protected:
	
	virtual void BeginPlay() override;
};
// GActionComponent.cpp
UGActionComponent::UGActionComponent()
{
	PrimaryComponentTick.bCanEverTick = true;
}

void UGActionComponent::BeginPlay()
{
	Super::BeginPlay();
	for(TSubclassOf<UGAction> Action : DefaultActions)
	{
		AddAction(Action);
	}
}

void UGActionComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	if(DebugActionTag.GetValueOnGameThread())
	{
		if(GEngine)
		{
			FString DebugMsg = GetNameSafe(GetOwner()) + " : " + ActiveGameplayTags.ToStringSimple();
			GEngine->AddOnScreenDebugMessage(-1,0.0,FColor::White,DebugMsg);
		}
	}
}

void UGActionComponent::AddAction(TSubclassOf<UGAction> ActionClass)
{
	if(!ensure(ActionClass))
	{
		return;
	}
	// UE创建新的UObject的方式
	UGAction* NewAction =  NewObject<UGAction>(this,ActionClass);
	if(ensure(NewAction))
	{
		Actions.Add(NewAction);
	}
}

bool UGActionComponent::StartActionByName(AActor* Instigator, FName ActionName)
{
	for(UGAction* Action : Actions)
	{
		if(Action && Action->ActionName==ActionName)
		{
			if(!Action->CanStart(Instigator))
			{
				return false;
			}
			Action->StartAction(Instigator);
			return true;
		}
	}
	return false;
}

bool UGActionComponent::StopActionByName(AActor* Instigator, FName ActionName)
{
	for(UGAction* Action : Actions)
	{
		if(Action && Action->ActionName==ActionName)
		{
			if(Action->IsRunning())
			{
				Action->StopAction(Instigator);
				return true;
			}
		}
	}
	return false;
}

UGActionComponent* UGActionComponent::GetActionComponent(AActor* Actor)
{
	return Cast<UGActionComponent>(Actor->GetComponentByClass(UGActionComponent::StaticClass()));
}
// GMagicProjectile.h
UCLASS()
class ACTIONROGUELIKE_API AGMagicProjectile : public AGProjectileBase
{
	GENERATED_BODY()

	// ...

protected:

	UPROPERTY(EditDefaultsOnly, Category = "Damage")
	FGameplayTag ParryTag;

};
// GMagicProjectile.cpp\
// ...
void AGMagicProjectile::OnActorOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
                                       UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	if(OtherActor && OtherActor!=GetInstigator())
	{
		UGActionComponent* ActionComp = UGActionComponent::GetActionComponent(OtherActor);
		if(ActionComp && ActionComp->ActiveGameplayTags.HasTag(ParryTag))
		{
			// 识别到反弹BUFF
			MoveComp->Velocity = -MoveComp->Velocity;
			// 改变魔法导弹的所有权
			SetInstigator(Cast<APawn>(OtherActor));
			return;
		}
		
		if(IgnoreActors.Find(OtherActor)!=INDEX_NONE)
		{
			return;
		}
		IgnoreActors.Push(OtherActor);
		if(UGGameplayFunctionLibrary::ApplyDirectionalDamage(GetInstigator(),OtherActor,-DamageAmount,SweepResult))
		{
			Explode();
		}
	}
}

image.png
image.png

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值